create-lumen 2.0.0-beta.1 → 2.0.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/package.json +1 -1
  2. package/schema/lumen.config.v2.json +29 -0
  3. package/src/agent-context.js +71 -47
  4. package/src/cleanup.js +19 -1
  5. package/src/cli-args.js +26 -0
  6. package/src/dependencies.js +42 -15
  7. package/src/engine/capabilities.js +49 -0
  8. package/src/engine/composition.js +242 -17
  9. package/src/env.js +24 -6
  10. package/src/main.js +5 -5
  11. package/src/manifest/emit.js +13 -0
  12. package/src/manifest/schema.js +30 -0
  13. package/src/prompts.js +77 -4
  14. package/templates/conditional/database/prisma/db.js +14 -0
  15. package/templates/conditional/database/prisma/db.ts +16 -0
  16. package/templates/conditional/database/prisma/schema.prisma +9 -0
  17. package/templates/conditional/database/prisma/seed.js +16 -0
  18. package/templates/conditional/database/prisma/seed.ts +16 -0
  19. package/templates/conditional/docker/.dockerignore +10 -0
  20. package/templates/conditional/docker/Dockerfile +57 -0
  21. package/templates/conditional/docker/docker-compose.yml +35 -0
  22. package/templates/conditional/next/route-groups/js/src/app/(admin)/layout.jsx +23 -0
  23. package/templates/conditional/next/route-groups/js/src/app/(auth)/layout.jsx +17 -0
  24. package/templates/conditional/next/route-groups/js/src/app/(private)/layout.jsx +27 -0
  25. package/templates/conditional/next/route-groups/js/src/app/(public)/layout.jsx +28 -0
  26. package/templates/conditional/next/route-groups/js/src/app/(public)/page.jsx +38 -0
  27. package/templates/conditional/next/route-groups/js/src/middleware.js +48 -0
  28. package/templates/conditional/next/route-groups/ts/src/app/(admin)/layout.tsx +28 -0
  29. package/templates/conditional/next/route-groups/ts/src/app/(auth)/layout.tsx +22 -0
  30. package/templates/conditional/next/route-groups/ts/src/app/(private)/layout.tsx +32 -0
  31. package/templates/conditional/next/route-groups/ts/src/app/(public)/layout.tsx +33 -0
  32. package/templates/conditional/next/route-groups/ts/src/app/(public)/page.tsx +38 -0
  33. package/templates/conditional/next/route-groups/ts/src/middleware.ts +49 -0
  34. package/templates/conditional/testing/jest/next/app-router/src/__tests__/page.test.jsx +10 -0
  35. package/templates/conditional/testing/jest/next/app-router/src/__tests__/page.test.tsx +10 -0
  36. package/templates/conditional/testing/jest/next/jest.config.mjs +20 -0
  37. package/templates/conditional/testing/jest/next/jest.config.ts +20 -0
  38. package/templates/conditional/testing/jest/next/pages-router/src/__tests__/index.test.jsx +10 -0
  39. package/templates/conditional/testing/jest/next/pages-router/src/__tests__/index.test.tsx +10 -0
  40. package/templates/conditional/testing/vitest/next/app-router/src/__tests__/page.test.jsx +11 -0
  41. package/templates/conditional/testing/vitest/next/app-router/src/__tests__/page.test.tsx +11 -0
  42. package/templates/conditional/testing/vitest/next/pages-router/src/__tests__/index.test.jsx +11 -0
  43. package/templates/conditional/testing/vitest/next/pages-router/src/__tests__/index.test.tsx +11 -0
  44. package/templates/conditional/testing/vitest/next/vitest.config.js +16 -0
  45. package/templates/conditional/testing/vitest/next/vitest.config.ts +16 -0
@@ -0,0 +1,16 @@
1
+ const { PrismaClient } = require("@prisma/client");
2
+
3
+ const prisma = new PrismaClient();
4
+
5
+ async function main() {
6
+ console.log("Database seeded successfully");
7
+ }
8
+
9
+ main()
10
+ .catch((e) => {
11
+ console.error(e);
12
+ process.exit(1);
13
+ })
14
+ .finally(async () => {
15
+ await prisma.$disconnect();
16
+ });
@@ -0,0 +1,16 @@
1
+ import { PrismaClient } from "@prisma/client";
2
+
3
+ const prisma = new PrismaClient();
4
+
5
+ async function main() {
6
+ console.log("Database seeded successfully");
7
+ }
8
+
9
+ main()
10
+ .catch((e) => {
11
+ console.error(e);
12
+ process.exit(1);
13
+ })
14
+ .finally(async () => {
15
+ await prisma.$disconnect();
16
+ });
@@ -0,0 +1,10 @@
1
+ node_modules
2
+ .next
3
+ .git
4
+ .gitignore
5
+ .env*
6
+ npm-debug.log*
7
+ yarn-debug.log*
8
+ yarn-error.log*
9
+ pnpm-debug.log*
10
+ README.md
@@ -0,0 +1,57 @@
1
+ FROM node:22-alpine AS base
2
+
3
+ # Install dependencies only when needed
4
+ FROM base AS deps
5
+ # Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
6
+ RUN apk add --no-cache libc6-compat
7
+ WORKDIR /app
8
+
9
+ # Install dependencies based on the preferred package manager
10
+ COPY package.json package-lock.json* yarn.lock* pnpm-lock.yaml* bun.lockb* bun.lock* ./
11
+ RUN \
12
+ if [ -f yarn.lock ]; then yarn --frozen-lockfile; \
13
+ elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm i --frozen-lockfile; \
14
+ elif [ -f package-lock.json ]; then npm ci; \
15
+ elif [ -f bun.lockb ] || [ -f bun.lock ]; then corepack enable bun && bun install --frozen-lockfile; \
16
+ else npm i; \
17
+ fi
18
+
19
+ # Rebuild the source code only when needed
20
+ FROM base AS builder
21
+ WORKDIR /app
22
+ COPY --from=deps /app/node_modules ./node_modules
23
+ COPY . .
24
+
25
+ # Next.js collects completely anonymous telemetry data about general usage.
26
+ # Learn more here: https://nextjs.org/telemetry
27
+ # Uncomment the following line in case you want to disable telemetry during the build.
28
+ ENV NEXT_TELEMETRY_DISABLED=1
29
+
30
+ RUN npm run build
31
+
32
+ # Production image, copy all the files and run next
33
+ FROM base AS runner
34
+ WORKDIR /app
35
+
36
+ ENV NODE_ENV=production
37
+ # Uncomment the following line in case you want to disable telemetry during runtime.
38
+ ENV NEXT_TELEMETRY_DISABLED=1
39
+
40
+ RUN addgroup --system --gid 1001 nodejs
41
+ RUN adduser --system --uid 1001 nextjs
42
+
43
+ COPY --chown=nextjs:nodejs public ./public
44
+
45
+ # Automatically leverage output traces to reduce image size
46
+ # https://nextjs.org/docs/advanced-features/output-file-tracing
47
+ COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
48
+ COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
49
+
50
+ USER nextjs
51
+
52
+ EXPOSE 3000
53
+
54
+ ENV PORT=3000
55
+ ENV HOSTNAME="0.0.0.0"
56
+
57
+ CMD ["node", "server.js"]
@@ -0,0 +1,35 @@
1
+ services:
2
+ app:
3
+ build:
4
+ context: .
5
+ dockerfile: Dockerfile
6
+ restart: always
7
+ ports:
8
+ - "3000:3000"
9
+ environment:
10
+ - NODE_ENV=production
11
+ - PORT=3000
12
+ - DATABASE_URL=postgresql://postgres:postgres@db:5432/app?schema=public
13
+ depends_on:
14
+ db:
15
+ condition: service_healthy
16
+
17
+ db:
18
+ image: postgres:16-alpine
19
+ restart: always
20
+ environment:
21
+ POSTGRES_USER: postgres
22
+ POSTGRES_PASSWORD: postgres
23
+ POSTGRES_DB: app
24
+ ports:
25
+ - "5432:5432"
26
+ volumes:
27
+ - postgres_data:/var/lib/postgresql/data
28
+ healthcheck:
29
+ test: ["CMD-SHELL", "pg_isready -U postgres -d app"]
30
+ interval: 5s
31
+ timeout: 5s
32
+ retries: 5
33
+
34
+ volumes:
35
+ postgres_data:
@@ -0,0 +1,23 @@
1
+ import Link from "next/link";
2
+
3
+ export default function AdminLayout({ children }) {
4
+ return (
5
+ <div className="min-h-screen flex flex-col">
6
+ <header className="bg-red-700 text-white px-6 py-3 flex items-center justify-between text-sm">
7
+ <div className="font-semibold flex items-center gap-2">
8
+ <span className="inline-block w-2 h-2 rounded-full bg-white animate-pulse" />
9
+ Admin Panel (Restricted Access)
10
+ </div>
11
+ <nav className="flex gap-4">
12
+ <Link href="/admin" className="underline hover:no-underline">
13
+ Management
14
+ </Link>
15
+ <Link href="/dashboard" className="underline hover:no-underline">
16
+ Exit to Dashboard
17
+ </Link>
18
+ </nav>
19
+ </header>
20
+ <main className="flex-1 p-8 bg-gray-100 dark:bg-gray-900">{children}</main>
21
+ </div>
22
+ );
23
+ }
@@ -0,0 +1,17 @@
1
+ import Link from "next/link";
2
+
3
+ export default function AuthLayout({ children }) {
4
+ return (
5
+ <div className="min-h-screen flex flex-col justify-center items-center bg-gray-50 dark:bg-gray-950 p-4">
6
+ <div className="w-full max-w-md bg-white dark:bg-gray-900 shadow-md rounded-lg p-6 border border-gray-200 dark:border-gray-800">
7
+ <div className="mb-6 text-center">
8
+ <Link href="/" className="text-xl font-bold">
9
+ Lumen Authentication
10
+ </Link>
11
+ <p className="text-sm text-gray-500 mt-1">Sign in to your account</p>
12
+ </div>
13
+ {children}
14
+ </div>
15
+ </div>
16
+ );
17
+ }
@@ -0,0 +1,27 @@
1
+ import Link from "next/link";
2
+
3
+ export default function PrivateLayout({ children }) {
4
+ return (
5
+ <div className="min-h-screen flex">
6
+ <aside className="w-64 border-r bg-gray-50 dark:bg-gray-950 p-6 flex flex-col justify-between">
7
+ <div>
8
+ <div className="font-bold text-lg mb-6">
9
+ <Link href="/dashboard">Private Dashboard</Link>
10
+ </div>
11
+ <nav className="flex flex-col gap-3 text-sm">
12
+ <Link href="/dashboard" className="text-gray-700 dark:text-gray-200 hover:text-black">
13
+ Overview
14
+ </Link>
15
+ <Link href="/settings" className="text-gray-700 dark:text-gray-200 hover:text-black">
16
+ Settings
17
+ </Link>
18
+ </nav>
19
+ </div>
20
+ <div className="text-xs text-gray-400">
21
+ Protected Area &bull; Authenticated
22
+ </div>
23
+ </aside>
24
+ <main className="flex-1 p-8">{children}</main>
25
+ </div>
26
+ );
27
+ }
@@ -0,0 +1,28 @@
1
+ import Link from "next/link";
2
+
3
+ export default function PublicLayout({ children }) {
4
+ return (
5
+ <div className="min-h-screen flex flex-col">
6
+ <header className="border-b px-6 py-4 flex items-center justify-between">
7
+ <div className="font-bold text-lg">
8
+ <Link href="/">Lumen App</Link>
9
+ </div>
10
+ <nav className="flex gap-4 text-sm">
11
+ <Link href="/login" className="hover:underline">
12
+ Sign In
13
+ </Link>
14
+ <Link href="/dashboard" className="hover:underline">
15
+ Dashboard
16
+ </Link>
17
+ <Link href="/admin" className="hover:underline">
18
+ Admin
19
+ </Link>
20
+ </nav>
21
+ </header>
22
+ <main className="flex-1">{children}</main>
23
+ <footer className="border-t px-6 py-4 text-center text-sm text-gray-500">
24
+ Public Area &copy; {new Date().getFullYear()}
25
+ </footer>
26
+ </div>
27
+ );
28
+ }
@@ -0,0 +1,38 @@
1
+ import Link from "next/link";
2
+
3
+ export default function PublicPage() {
4
+ return (
5
+ <div className="max-w-4xl mx-auto py-12 px-6">
6
+ <h1 className="text-4xl font-extrabold tracking-tight mb-4">
7
+ Welcome to Route Groups Preset
8
+ </h1>
9
+ <p className="text-lg text-gray-600 mb-8">
10
+ This application is organized using Next.js App Router route groups:
11
+ (public), (auth), (private), and (admin).
12
+ </p>
13
+ <div className="grid grid-cols-1 md:grid-cols-3 gap-6">
14
+ <div className="border rounded-lg p-6 shadow-sm">
15
+ <h2 className="text-xl font-semibold mb-2">Auth Group</h2>
16
+ <p className="text-gray-500 mb-4">Authentication screens and login layouts.</p>
17
+ <Link href="/login" className="text-blue-600 hover:underline">
18
+ Go to Login &rarr;
19
+ </Link>
20
+ </div>
21
+ <div className="border rounded-lg p-6 shadow-sm">
22
+ <h2 className="text-xl font-semibold mb-2">Private Group</h2>
23
+ <p className="text-gray-500 mb-4">Protected routes guarded by auth proxy middleware.</p>
24
+ <Link href="/dashboard" className="text-blue-600 hover:underline">
25
+ Go to Dashboard &rarr;
26
+ </Link>
27
+ </div>
28
+ <div className="border rounded-lg p-6 shadow-sm">
29
+ <h2 className="text-xl font-semibold mb-2">Admin Group</h2>
30
+ <p className="text-gray-500 mb-4">Elevated management section requiring admin role.</p>
31
+ <Link href="/admin" className="text-blue-600 hover:underline">
32
+ Go to Admin &rarr;
33
+ </Link>
34
+ </div>
35
+ </div>
36
+ </div>
37
+ );
38
+ }
@@ -0,0 +1,48 @@
1
+ import { NextResponse } from "next/server";
2
+
3
+ /**
4
+ * Auth Proxy / Route Guard Middleware
5
+ *
6
+ * Enforces role-based access control and session verification:
7
+ * - Unauthenticated requests to private or admin routes redirect to /login
8
+ * - Authenticated requests lacking the "admin" role redirect away from /admin
9
+ */
10
+ export function middleware(request) {
11
+ const { pathname } = request.nextUrl;
12
+
13
+ const authToken = request.cookies.get("auth-token")?.value;
14
+ const userRole = request.cookies.get("user-role")?.value || "user";
15
+
16
+ const isAuthRoute = pathname.startsWith("/login") || pathname.startsWith("/register");
17
+ const isPrivateRoute = pathname.startsWith("/dashboard") || pathname.startsWith("/settings");
18
+ const isAdminRoute = pathname.startsWith("/admin");
19
+
20
+ // If unauthenticated user tries to access protected areas, redirect to /login
21
+ if ((isPrivateRoute || isAdminRoute) && !authToken) {
22
+ const loginUrl = new URL("/login", request.url);
23
+ loginUrl.searchParams.set("from", pathname);
24
+ return NextResponse.redirect(loginUrl);
25
+ }
26
+
27
+ // If authenticated user without admin role accesses admin area, forbid or redirect to dashboard
28
+ if (isAdminRoute && userRole !== "admin") {
29
+ return NextResponse.redirect(new URL("/dashboard", request.url));
30
+ }
31
+
32
+ // If already authenticated user visits auth routes, redirect to dashboard
33
+ if (isAuthRoute && authToken) {
34
+ return NextResponse.redirect(new URL("/dashboard", request.url));
35
+ }
36
+
37
+ return NextResponse.next();
38
+ }
39
+
40
+ export const config = {
41
+ matcher: [
42
+ "/dashboard/:path*",
43
+ "/settings/:path*",
44
+ "/admin/:path*",
45
+ "/login",
46
+ "/register",
47
+ ],
48
+ };
@@ -0,0 +1,28 @@
1
+ import type { ReactNode } from "react";
2
+ import Link from "next/link";
3
+
4
+ export default function AdminLayout({
5
+ children,
6
+ }: Readonly<{
7
+ children: ReactNode;
8
+ }>) {
9
+ return (
10
+ <div className="min-h-screen flex flex-col">
11
+ <header className="bg-red-700 text-white px-6 py-3 flex items-center justify-between text-sm">
12
+ <div className="font-semibold flex items-center gap-2">
13
+ <span className="inline-block w-2 h-2 rounded-full bg-white animate-pulse" />
14
+ Admin Panel (Restricted Access)
15
+ </div>
16
+ <nav className="flex gap-4">
17
+ <Link href="/admin" className="underline hover:no-underline">
18
+ Management
19
+ </Link>
20
+ <Link href="/dashboard" className="underline hover:no-underline">
21
+ Exit to Dashboard
22
+ </Link>
23
+ </nav>
24
+ </header>
25
+ <main className="flex-1 p-8 bg-gray-100 dark:bg-gray-900">{children}</main>
26
+ </div>
27
+ );
28
+ }
@@ -0,0 +1,22 @@
1
+ import type { ReactNode } from "react";
2
+ import Link from "next/link";
3
+
4
+ export default function AuthLayout({
5
+ children,
6
+ }: Readonly<{
7
+ children: ReactNode;
8
+ }>) {
9
+ return (
10
+ <div className="min-h-screen flex flex-col justify-center items-center bg-gray-50 dark:bg-gray-950 p-4">
11
+ <div className="w-full max-w-md bg-white dark:bg-gray-900 shadow-md rounded-lg p-6 border border-gray-200 dark:border-gray-800">
12
+ <div className="mb-6 text-center">
13
+ <Link href="/" className="text-xl font-bold">
14
+ Lumen Authentication
15
+ </Link>
16
+ <p className="text-sm text-gray-500 mt-1">Sign in to your account</p>
17
+ </div>
18
+ {children}
19
+ </div>
20
+ </div>
21
+ );
22
+ }
@@ -0,0 +1,32 @@
1
+ import type { ReactNode } from "react";
2
+ import Link from "next/link";
3
+
4
+ export default function PrivateLayout({
5
+ children,
6
+ }: Readonly<{
7
+ children: ReactNode;
8
+ }>) {
9
+ return (
10
+ <div className="min-h-screen flex">
11
+ <aside className="w-64 border-r bg-gray-50 dark:bg-gray-950 p-6 flex flex-col justify-between">
12
+ <div>
13
+ <div className="font-bold text-lg mb-6">
14
+ <Link href="/dashboard">Private Dashboard</Link>
15
+ </div>
16
+ <nav className="flex flex-col gap-3 text-sm">
17
+ <Link href="/dashboard" className="text-gray-700 dark:text-gray-200 hover:text-black">
18
+ Overview
19
+ </Link>
20
+ <Link href="/settings" className="text-gray-700 dark:text-gray-200 hover:text-black">
21
+ Settings
22
+ </Link>
23
+ </nav>
24
+ </div>
25
+ <div className="text-xs text-gray-400">
26
+ Protected Area &bull; Authenticated
27
+ </div>
28
+ </aside>
29
+ <main className="flex-1 p-8">{children}</main>
30
+ </div>
31
+ );
32
+ }
@@ -0,0 +1,33 @@
1
+ import type { ReactNode } from "react";
2
+ import Link from "next/link";
3
+
4
+ export default function PublicLayout({
5
+ children,
6
+ }: Readonly<{
7
+ children: ReactNode;
8
+ }>) {
9
+ return (
10
+ <div className="min-h-screen flex flex-col">
11
+ <header className="border-b px-6 py-4 flex items-center justify-between">
12
+ <div className="font-bold text-lg">
13
+ <Link href="/">Lumen App</Link>
14
+ </div>
15
+ <nav className="flex gap-4 text-sm">
16
+ <Link href="/login" className="hover:underline">
17
+ Sign In
18
+ </Link>
19
+ <Link href="/dashboard" className="hover:underline">
20
+ Dashboard
21
+ </Link>
22
+ <Link href="/admin" className="hover:underline">
23
+ Admin
24
+ </Link>
25
+ </nav>
26
+ </header>
27
+ <main className="flex-1">{children}</main>
28
+ <footer className="border-t px-6 py-4 text-center text-sm text-gray-500">
29
+ Public Area &copy; {new Date().getFullYear()}
30
+ </footer>
31
+ </div>
32
+ );
33
+ }
@@ -0,0 +1,38 @@
1
+ import Link from "next/link";
2
+
3
+ export default function PublicPage() {
4
+ return (
5
+ <div className="max-w-4xl mx-auto py-12 px-6">
6
+ <h1 className="text-4xl font-extrabold tracking-tight mb-4">
7
+ Welcome to Route Groups Preset
8
+ </h1>
9
+ <p className="text-lg text-gray-600 mb-8">
10
+ This application is organized using Next.js App Router route groups:
11
+ (public), (auth), (private), and (admin).
12
+ </p>
13
+ <div className="grid grid-cols-1 md:grid-cols-3 gap-6">
14
+ <div className="border rounded-lg p-6 shadow-sm">
15
+ <h2 className="text-xl font-semibold mb-2">Auth Group</h2>
16
+ <p className="text-gray-500 mb-4">Authentication screens and login layouts.</p>
17
+ <Link href="/login" className="text-blue-600 hover:underline">
18
+ Go to Login &rarr;
19
+ </Link>
20
+ </div>
21
+ <div className="border rounded-lg p-6 shadow-sm">
22
+ <h2 className="text-xl font-semibold mb-2">Private Group</h2>
23
+ <p className="text-gray-500 mb-4">Protected routes guarded by auth proxy middleware.</p>
24
+ <Link href="/dashboard" className="text-blue-600 hover:underline">
25
+ Go to Dashboard &rarr;
26
+ </Link>
27
+ </div>
28
+ <div className="border rounded-lg p-6 shadow-sm">
29
+ <h2 className="text-xl font-semibold mb-2">Admin Group</h2>
30
+ <p className="text-gray-500 mb-4">Elevated management section requiring admin role.</p>
31
+ <Link href="/admin" className="text-blue-600 hover:underline">
32
+ Go to Admin &rarr;
33
+ </Link>
34
+ </div>
35
+ </div>
36
+ </div>
37
+ );
38
+ }
@@ -0,0 +1,49 @@
1
+ import { NextResponse } from "next/server";
2
+ import type { NextRequest } from "next/server";
3
+
4
+ /**
5
+ * Auth Proxy / Route Guard Middleware
6
+ *
7
+ * Enforces role-based access control and session verification:
8
+ * - Unauthenticated requests to private or admin routes redirect to /login
9
+ * - Authenticated requests lacking the "admin" role redirect away from /admin
10
+ */
11
+ export function middleware(request: NextRequest) {
12
+ const { pathname } = request.nextUrl;
13
+
14
+ const authToken = request.cookies.get("auth-token")?.value;
15
+ const userRole = request.cookies.get("user-role")?.value || "user";
16
+
17
+ const isAuthRoute = pathname.startsWith("/login") || pathname.startsWith("/register");
18
+ const isPrivateRoute = pathname.startsWith("/dashboard") || pathname.startsWith("/settings");
19
+ const isAdminRoute = pathname.startsWith("/admin");
20
+
21
+ // If unauthenticated user tries to access protected areas, redirect to /login
22
+ if ((isPrivateRoute || isAdminRoute) && !authToken) {
23
+ const loginUrl = new URL("/login", request.url);
24
+ loginUrl.searchParams.set("from", pathname);
25
+ return NextResponse.redirect(loginUrl);
26
+ }
27
+
28
+ // If authenticated user without admin role accesses admin area, forbid or redirect to dashboard
29
+ if (isAdminRoute && userRole !== "admin") {
30
+ return NextResponse.redirect(new URL("/dashboard", request.url));
31
+ }
32
+
33
+ // If already authenticated user visits auth routes, redirect to dashboard
34
+ if (isAuthRoute && authToken) {
35
+ return NextResponse.redirect(new URL("/dashboard", request.url));
36
+ }
37
+
38
+ return NextResponse.next();
39
+ }
40
+
41
+ export const config = {
42
+ matcher: [
43
+ "/dashboard/:path*",
44
+ "/settings/:path*",
45
+ "/admin/:path*",
46
+ "/login",
47
+ "/register",
48
+ ],
49
+ };
@@ -0,0 +1,10 @@
1
+ import "@testing-library/jest-dom";
2
+ import { render, screen } from "@testing-library/react";
3
+ import Home from "@/app/page";
4
+
5
+ describe("Home", () => {
6
+ it("renders a heading", () => {
7
+ render(<Home />);
8
+ expect(screen.getByRole("heading", { level: 1 })).toBeInTheDocument();
9
+ });
10
+ });
@@ -0,0 +1,10 @@
1
+ import "@testing-library/jest-dom";
2
+ import { render, screen } from "@testing-library/react";
3
+ import Home from "@/app/page";
4
+
5
+ describe("Home", () => {
6
+ it("renders a heading", () => {
7
+ render(<Home />);
8
+ expect(screen.getByRole("heading", { level: 1 })).toBeInTheDocument();
9
+ });
10
+ });
@@ -0,0 +1,20 @@
1
+ import nextJest from "next/jest.js";
2
+
3
+ const createJestConfig = nextJest({
4
+ // Provide the path to your Next.js app to load next.config.js and .env files in your test environment
5
+ dir: "./",
6
+ });
7
+
8
+ // Add any custom config to be passed to Jest
9
+ /** @type {import('jest').Config} */
10
+ const config = {
11
+ coverageProvider: "v8",
12
+ testEnvironment: "jsdom",
13
+ setupFilesAfterEnv: ["<rootDir>/jest.setup.js"],
14
+ moduleNameMapper: {
15
+ "^@/(.*)$": "<rootDir>/src/$1",
16
+ },
17
+ };
18
+
19
+ // createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async
20
+ export default createJestConfig(config);
@@ -0,0 +1,20 @@
1
+ import type { Config } from "jest";
2
+ import nextJest from "next/jest.js";
3
+
4
+ const createJestConfig = nextJest({
5
+ // Provide the path to your Next.js app to load next.config.js and .env files in your test environment
6
+ dir: "./",
7
+ });
8
+
9
+ // Add any custom config to be passed to Jest
10
+ const config: Config = {
11
+ coverageProvider: "v8",
12
+ testEnvironment: "jsdom",
13
+ setupFilesAfterEnv: ["<rootDir>/jest.setup.ts"],
14
+ moduleNameMapper: {
15
+ "^@/(.*)$": "<rootDir>/src/$1",
16
+ },
17
+ };
18
+
19
+ // createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async
20
+ export default createJestConfig(config);
@@ -0,0 +1,10 @@
1
+ import "@testing-library/jest-dom";
2
+ import { render, screen } from "@testing-library/react";
3
+ import Home from "@/pages/index";
4
+
5
+ describe("Home", () => {
6
+ it("renders a heading", () => {
7
+ render(<Home />);
8
+ expect(screen.getByRole("heading", { level: 1 })).toBeInTheDocument();
9
+ });
10
+ });
@@ -0,0 +1,10 @@
1
+ import "@testing-library/jest-dom";
2
+ import { render, screen } from "@testing-library/react";
3
+ import Home from "@/pages/index";
4
+
5
+ describe("Home", () => {
6
+ it("renders a heading", () => {
7
+ render(<Home />);
8
+ expect(screen.getByRole("heading", { level: 1 })).toBeInTheDocument();
9
+ });
10
+ });
@@ -0,0 +1,11 @@
1
+ import "@testing-library/jest-dom/vitest";
2
+ import { render, screen } from "@testing-library/react";
3
+ import { describe, it, expect } from "vitest";
4
+ import Home from "@/app/page";
5
+
6
+ describe("Home", () => {
7
+ it("renders a heading", () => {
8
+ render(<Home />);
9
+ expect(screen.getByRole("heading", { level: 1 })).toBeInTheDocument();
10
+ });
11
+ });
@@ -0,0 +1,11 @@
1
+ import "@testing-library/jest-dom/vitest";
2
+ import { render, screen } from "@testing-library/react";
3
+ import { describe, it, expect } from "vitest";
4
+ import Home from "@/app/page";
5
+
6
+ describe("Home", () => {
7
+ it("renders a heading", () => {
8
+ render(<Home />);
9
+ expect(screen.getByRole("heading", { level: 1 })).toBeInTheDocument();
10
+ });
11
+ });
@@ -0,0 +1,11 @@
1
+ import "@testing-library/jest-dom/vitest";
2
+ import { render, screen } from "@testing-library/react";
3
+ import { describe, it, expect } from "vitest";
4
+ import Home from "@/pages/index";
5
+
6
+ describe("Home", () => {
7
+ it("renders a heading", () => {
8
+ render(<Home />);
9
+ expect(screen.getByRole("heading", { level: 1 })).toBeInTheDocument();
10
+ });
11
+ });