+ {project.title}
+ {project.description}
+ {project.href ? (
diff --git a/public/index.html b/public/index.html
deleted file mode 100644
index 89147f5..0000000
--- a/public/index.html
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
-
- Project Sparks
-
-
-
-
-
diff --git a/src/App.test.tsx b/src/App.test.tsx
index d618126..ebf9e97 100644
--- a/src/App.test.tsx
+++ b/src/App.test.tsx
@@ -1,43 +1,62 @@
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
-import { MemoryRouter } from "react-router-dom";
-import App from "./App";
-
-function renderWithRouter(initialPath = "/") {
- return render(
-
-
-
+import Layout from "./components/Layout";
+import HomePage from "../pages/index";
+import AboutPage from "../pages/about";
+import ProjectsPage from "../pages/projects";
+import NotFoundPage from "../pages/404";
+
+test("renders the home page content", () => {
+ render(
+
+
+
);
-}
-test("renders the home page", () => {
- renderWithRouter("/");
-
- expect(
- screen.getByRole("heading", { name: /projectsparks/i })
- ).toBeInTheDocument();
+ expect(screen.getByRole("heading", { name: /projectsparks/i })).toBeInTheDocument();
});
test("renders the nav links", () => {
- renderWithRouter("/");
+ render(
+
+
+
+ );
expect(screen.getByRole("link", { name: /home/i })).toBeInTheDocument();
expect(screen.getByRole("link", { name: /projects/i })).toBeInTheDocument();
expect(screen.getByRole("link", { name: /about/i })).toBeInTheDocument();
});
-test("navigates to About page", async () => {
- const user = userEvent.setup();
- renderWithRouter("/");
-
- await user.click(screen.getByRole("link", { name: /about/i }));
+test("about page renders", () => {
+ render(
+
+
+
+ );
expect(screen.getByRole("heading", { name: /about/i })).toBeInTheDocument();
});
-test("shows 404 for unknown routes", () => {
- renderWithRouter("/some-garbage-route");
+test("projects page renders", () => {
+ render(
+
+
+
+ );
+
+ expect(screen.getByRole("heading", { name: /projects/i })).toBeInTheDocument();
+});
+
+test("404 page shows go home link", async () => {
+ const user = userEvent.setup();
+
+ render(
+
+
+
+ );
expect(screen.getByRole("heading", { name: /404/i })).toBeInTheDocument();
+ await user.click(screen.getByRole("link", { name: /go home/i }));
});
diff --git a/src/App.tsx b/src/App.tsx
deleted file mode 100644
index 4e5c8fa..0000000
--- a/src/App.tsx
+++ /dev/null
@@ -1,22 +0,0 @@
-
-import { Route, Routes } from "react-router-dom";
-import Layout from "./components/Layout";
-import Home from "./pages/Home";
-import Projects from "./pages/Projects";
-import About from "./pages/About";
-import NotFound from "./pages/NotFound";
-
-export default function App() {
- return (
- <>
-
- }>
- } />
- } />
- } />
- } />
-
-
- >
- );
-}
diff --git a/src/components/Layout.tsx b/src/components/Layout.tsx
index 0170257..d61a016 100644
--- a/src/components/Layout.tsx
+++ b/src/components/Layout.tsx
@@ -1,32 +1,33 @@
-import { NavLink, Outlet } from "react-router-dom";
+import Link from "next/link";
-const linkStyle = ({ isActive }: { isActive: boolean }) => ({
+type LayoutProps = {
+ children: React.ReactNode;
+};
+
+const linkStyle = {
marginRight: 12,
textDecoration: "none",
- fontWeight: isActive ? 700 : 400,
-});
+};
-export default function Layout() {
+export default function Layout({ children }: LayoutProps) {
return (
-
-
-
+ {children}
);
}
diff --git a/src/globals.d.ts b/src/globals.d.ts
index e69de29..886cb08 100644
--- a/src/globals.d.ts
+++ b/src/globals.d.ts
@@ -0,0 +1,19 @@
+declare module "next/link" {
+ import type { AnchorHTMLAttributes, ReactNode } from "react";
+
+ type LinkProps = AnchorHTMLAttributes & {
+ href: string;
+ children?: ReactNode;
+ };
+
+ export default function Link(props: LinkProps): JSX.Element;
+}
+
+declare module "next/app" {
+ import type { ComponentType } from "react";
+
+ export type AppProps = {
+ Component: ComponentType;
+ pageProps: Record;
+ };
+}
diff --git a/src/index.tsx b/src/index.tsx
deleted file mode 100644
index 319884e..0000000
--- a/src/index.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-import "./styles/global.css";
-import { createRoot } from "react-dom/client";
-import { BrowserRouter } from "react-router-dom";
-import App from "./App";
-import { ErrorBoundary } from "./components/ErrorBoundary";
-
-const container = document.getElementById("root");
-if (!container) throw new Error("Root element #root not found");
-
-createRoot(container).render(
-
-
-
-
-
-);
diff --git a/src/pages/NotFound.tsx b/src/pages/NotFound.tsx
deleted file mode 100644
index 30e0fec..0000000
--- a/src/pages/NotFound.tsx
+++ /dev/null
@@ -1,11 +0,0 @@
-import { Link } from "react-router-dom";
-
-export default function NotFound() {
- return (
-
- 404
- That page doesn’t exist.
- Go home
-
- );
-}
diff --git a/src/test/mocks/next-app.ts b/src/test/mocks/next-app.ts
new file mode 100644
index 0000000..9479606
--- /dev/null
+++ b/src/test/mocks/next-app.ts
@@ -0,0 +1,6 @@
+import type { ComponentType } from "react";
+
+export type AppProps = {
+ Component: ComponentType;
+ pageProps: Record;
+};
diff --git a/src/test/mocks/next-link.tsx b/src/test/mocks/next-link.tsx
new file mode 100644
index 0000000..d9cd2b7
--- /dev/null
+++ b/src/test/mocks/next-link.tsx
@@ -0,0 +1,14 @@
+import type { AnchorHTMLAttributes, ReactNode } from "react";
+
+type MockLinkProps = AnchorHTMLAttributes & {
+ href: string;
+ children?: ReactNode;
+};
+
+export default function Link({ href, children, ...rest }: MockLinkProps) {
+ return (
+
+ {children}
+
+ );
+}
diff --git a/tsconfig.json b/tsconfig.json
index 26366cc..1394240 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,28 +1,28 @@
{
-
"compilerOptions": {
"baseUrl": ".",
"target": "ES2020",
"lib": ["DOM", "DOM.Iterable", "ES2020"],
-
- "module": "ESNext",
- "moduleResolution": "Bundler",
-
- "jsx": "react-jsx",
- "jsxImportSource": "react",
-
+ "allowJs": true,
+ "skipLibCheck": true,
"strict": true,
"noEmit": true,
-
"esModuleInterop": true,
- "allowSyntheticDefaultImports": true,
- "forceConsistentCasingInFileNames": true,
- "skipLibCheck": true,
-
- "types": ["react", "react-dom"],
+ "module": "ESNext",
+ "moduleResolution": "Bundler",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "jsx": "preserve",
+ "incremental": true,
+ "plugins": [
+ {
+ "name": "next"
+ }
+ ],
"paths": {
"@/*": ["src/*"]
- },
+ }
},
- "include": ["src/**/*"]
+ "include": ["next-env.d.ts", "pages/**/*", "src/**/*", ".next/types/**/*.ts"],
+ "exclude": ["node_modules"]
}
diff --git a/vitest.config.ts b/vitest.config.ts
index a6e5f7d..53ebd9f 100644
--- a/vitest.config.ts
+++ b/vitest.config.ts
@@ -1,6 +1,19 @@
+import path from "node:path";
+import { fileURLToPath } from "node:url";
import { defineConfig } from "vitest/config";
+const __dirname = path.dirname(fileURLToPath(import.meta.url));
+
export default defineConfig({
+ esbuild: {
+ jsx: "automatic",
+ },
+ resolve: {
+ alias: {
+ "next/link": path.resolve(__dirname, "src/test/mocks/next-link.tsx"),
+ "next/app": path.resolve(__dirname, "src/test/mocks/next-app.ts"),
+ },
+ },
test: {
globals: true,
environment: "jsdom",
diff --git a/webpack.config.cjs b/webpack.config.cjs
deleted file mode 100644
index 21508af..0000000
--- a/webpack.config.cjs
+++ /dev/null
@@ -1,66 +0,0 @@
-const path = require("path");
-const HtmlWebpackPlugin = require("html-webpack-plugin");
-const { BundleAnalyzerPlugin } = require("webpack-bundle-analyzer");
-
-module.exports = (env, argv) => {
- const isProd = argv.mode === "production";
-
- return {
- mode: isProd ? "production" : "development",
- entry: "./src/index.tsx",
-
- output: {
- filename: "bundle.[contenthash].js",
- path: path.resolve(__dirname, "dist"),
- publicPath: "/",
- clean: true,
-
- },
-
- module: {
- rules: [
- {
- test: /\.(ts|tsx)$/,
- exclude: /node_modules/,
- use: "babel-loader",
- },
- {
- test: /\.css$/i,
- use: ["style-loader", "css-loader"],
- },
- ],
- },
-
- resolve: {
- extensions: [".js", ".jsx", ".ts", ".tsx"],
- },
-
- devServer: isProd
- ? undefined
- : {
- host: "0.0.0.0",
- port: 8080,
- allowedHosts: "all",
- hot: true,
- historyApiFallback: true,
- static: {
- directory: path.join(__dirname, "public"),
- },
- client: {
- webSocketURL: "auto://0.0.0.0:0/ws",
- },
- watchFiles: ["src/**/*", "public/**/*"],
- },
- devtool: isProd ? "source-map" : "eval-cheap-module-source-map",
- optimization: isProd
- ? {
- splitChunks: { chunks: "all" },
- runtimeChunk: "single",
- }
- : undefined,
- plugins: [
- new HtmlWebpackPlugin({ template: path.resolve(__dirname, "public", "index.html") }),
- ...(process.env.ANALYZE === "true" ? [new BundleAnalyzerPlugin()] : []),
- ],
- };
-};