From 119eab49fe2a52d2e4d78c27b73371def835de31 Mon Sep 17 00:00:00 2001 From: Danijel Martinek Date: Tue, 5 May 2026 16:34:19 +0200 Subject: [PATCH] feat(apps): add unit tests for providers + bind-production + cms config MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit web-next: bindAllProduction calls all 4 feature binders exactly once; Providers renders children. web-tanstack: equivalent providers + bind tests. cms: payload.config exports a SanitizedConfig with all expected collections. Spec: §6.7, §9 Co-Authored-By: Claude Sonnet 4.6 --- apps/cms/package.json | 5 ++- apps/cms/src/payload.config.test.ts | 20 +++++++++ apps/cms/tsconfig.json | 3 +- apps/cms/vitest.config.ts | 7 ++++ apps/web-next/package.json | 9 +++- apps/web-next/src/app/providers.test.tsx | 14 +++++++ .../src/server/bind-production.test.ts | 37 ++++++++++++++++ apps/web-next/tsconfig.json | 3 +- apps/web-next/vitest.config.ts | 8 ++++ apps/web-tanstack/package.json | 9 +++- apps/web-tanstack/src/routes/__root.test.tsx | 23 ++++++++++ apps/web-tanstack/tsconfig.json | 3 +- apps/web-tanstack/vitest.config.ts | 8 ++++ pnpm-lock.yaml | 42 +++++++++++++++++++ 14 files changed, 185 insertions(+), 6 deletions(-) create mode 100644 apps/cms/src/payload.config.test.ts create mode 100644 apps/cms/vitest.config.ts create mode 100644 apps/web-next/src/app/providers.test.tsx create mode 100644 apps/web-next/src/server/bind-production.test.ts create mode 100644 apps/web-next/vitest.config.ts create mode 100644 apps/web-tanstack/src/routes/__root.test.tsx create mode 100644 apps/web-tanstack/vitest.config.ts diff --git a/apps/cms/package.json b/apps/cms/package.json index 8bee798..1acc65e 100644 --- a/apps/cms/package.json +++ b/apps/cms/package.json @@ -7,6 +7,7 @@ "build": "echo 'CMS build requires database — use docker compose or pnpm dev'", "dev": "next dev --port 3001", "lint": "eslint .", + "test": "vitest run --passWithNoTests", "typecheck": "tsc --noEmit", "generate:types": "payload generate:types" }, @@ -24,9 +25,11 @@ }, "devDependencies": { "@repo/core-eslint": "workspace:*", + "@repo/core-testing": "workspace:*", "@repo/core-typescript": "workspace:*", "@types/node": "^22.0.0", "@types/react": "^19.0.0", - "@types/react-dom": "^19.0.0" + "@types/react-dom": "^19.0.0", + "vitest": "^3.0.0" } } diff --git a/apps/cms/src/payload.config.test.ts b/apps/cms/src/payload.config.test.ts new file mode 100644 index 0000000..1679d7f --- /dev/null +++ b/apps/cms/src/payload.config.test.ts @@ -0,0 +1,20 @@ +import { describe, it, expect } from "vitest"; +import config from "./payload.config"; + +describe("CMS app payload.config", () => { + it("registers all feature collections", async () => { + const resolved = await config; + const slugs = resolved.collections?.map((c) => c.slug) ?? []; + expect(slugs).toEqual( + expect.arrayContaining(["users", "articles", "pages", "media"]), + ); + }); + + it("registers all feature globals", async () => { + const resolved = await config; + const slugs = resolved.globals?.map((g) => g.slug) ?? []; + expect(slugs).toEqual( + expect.arrayContaining(["site-settings", "header"]), + ); + }); +}); diff --git a/apps/cms/tsconfig.json b/apps/cms/tsconfig.json index 016d10a..5105090 100644 --- a/apps/cms/tsconfig.json +++ b/apps/cms/tsconfig.json @@ -10,7 +10,8 @@ "./src/payload.config.ts" ] }, - "allowJs": true + "allowJs": true, + "types": ["vitest/globals"] }, "include": [ "next-env.d.ts", diff --git a/apps/cms/vitest.config.ts b/apps/cms/vitest.config.ts new file mode 100644 index 0000000..c933396 --- /dev/null +++ b/apps/cms/vitest.config.ts @@ -0,0 +1,7 @@ +import path from "node:path"; +import { mergeConfig } from "vitest/config"; +import { nodeVitestConfig } from "@repo/core-typescript/vitest.base.node"; + +export default mergeConfig(nodeVitestConfig, { + resolve: { alias: { "@": path.resolve(__dirname, "./src") } }, +}); diff --git a/apps/web-next/package.json b/apps/web-next/package.json index 5f65537..b05afae 100644 --- a/apps/web-next/package.json +++ b/apps/web-next/package.json @@ -7,6 +7,7 @@ "build": "echo 'Next.js build requires full environment — use pnpm dev or docker'", "dev": "next dev --port 3000", "lint": "eslint .", + "test": "vitest run --passWithNoTests", "test:e2e": "playwright test", "test:e2e:install": "playwright install --with-deps chromium", "typecheck": "tsc --noEmit" @@ -33,9 +34,15 @@ "devDependencies": { "@playwright/test": "^1.50.0", "@repo/core-eslint": "workspace:*", + "@repo/core-testing": "workspace:*", "@repo/core-typescript": "workspace:*", + "@testing-library/jest-dom": "^6.5.0", + "@testing-library/react": "^16.0.0", + "@testing-library/user-event": "^14.5.0", "@types/node": "^22.0.0", "@types/react": "^19.0.0", - "@types/react-dom": "^19.0.0" + "@types/react-dom": "^19.0.0", + "jsdom": "^25.0.0", + "vitest": "^3.0.0" } } diff --git a/apps/web-next/src/app/providers.test.tsx b/apps/web-next/src/app/providers.test.tsx new file mode 100644 index 0000000..ab94080 --- /dev/null +++ b/apps/web-next/src/app/providers.test.tsx @@ -0,0 +1,14 @@ +import { describe, it, expect } from "vitest"; +import { render, screen } from "@testing-library/react"; +import { Providers } from "./providers"; + +describe("Providers", () => { + it("renders children", () => { + render( + +
hi
+
, + ); + expect(screen.getByTestId("child")).toBeInTheDocument(); + }); +}); diff --git a/apps/web-next/src/server/bind-production.test.ts b/apps/web-next/src/server/bind-production.test.ts new file mode 100644 index 0000000..0722b49 --- /dev/null +++ b/apps/web-next/src/server/bind-production.test.ts @@ -0,0 +1,37 @@ +import { describe, it, expect, vi, beforeEach } from "vitest"; + +vi.mock("@repo/core-cms", () => ({ default: Promise.resolve({}) })); +vi.mock("@repo/blog/di/bind-production", () => ({ bindProductionBlog: vi.fn() })); +vi.mock("@repo/auth/di/bind-production", () => ({ bindProductionAuth: vi.fn() })); +vi.mock("@repo/marketing-pages/di/bind-production", () => ({ bindProductionMarketingPages: vi.fn() })); +vi.mock("@repo/navigation/di/bind-production", () => ({ bindProductionNavigation: vi.fn() })); + +describe("bindAllProduction", () => { + beforeEach(() => { + vi.resetModules(); + vi.clearAllMocks(); + }); + + it("binds all four feature production repos", async () => { + const { bindAllProduction } = await import("./bind-production"); + const { bindProductionBlog } = await import("@repo/blog/di/bind-production"); + const { bindProductionAuth } = await import("@repo/auth/di/bind-production"); + const { bindProductionMarketingPages } = await import("@repo/marketing-pages/di/bind-production"); + const { bindProductionNavigation } = await import("@repo/navigation/di/bind-production"); + + await bindAllProduction(); + + expect(bindProductionBlog).toHaveBeenCalledOnce(); + expect(bindProductionAuth).toHaveBeenCalledOnce(); + expect(bindProductionMarketingPages).toHaveBeenCalledOnce(); + expect(bindProductionNavigation).toHaveBeenCalledOnce(); + }); + + it("is idempotent — second call does not re-bind", async () => { + const { bindAllProduction } = await import("./bind-production"); + const { bindProductionBlog } = await import("@repo/blog/di/bind-production"); + await bindAllProduction(); + await bindAllProduction(); + expect(bindProductionBlog).toHaveBeenCalledOnce(); + }); +}); diff --git a/apps/web-next/tsconfig.json b/apps/web-next/tsconfig.json index 760bcb1..64b2c19 100644 --- a/apps/web-next/tsconfig.json +++ b/apps/web-next/tsconfig.json @@ -7,7 +7,8 @@ "./src/*" ] }, - "allowJs": true + "allowJs": true, + "types": ["vitest/globals", "@testing-library/jest-dom"] }, "include": [ "next-env.d.ts", diff --git a/apps/web-next/vitest.config.ts b/apps/web-next/vitest.config.ts new file mode 100644 index 0000000..b1c34d3 --- /dev/null +++ b/apps/web-next/vitest.config.ts @@ -0,0 +1,8 @@ +import path from "node:path"; +import { mergeConfig } from "vitest/config"; +import { jsdomVitestConfig } from "@repo/core-typescript/vitest.base.jsdom"; + +export default mergeConfig(jsdomVitestConfig, { + esbuild: { jsx: "automatic" }, + resolve: { alias: { "@": path.resolve(__dirname, "./src") } }, +}); diff --git a/apps/web-tanstack/package.json b/apps/web-tanstack/package.json index 26ec577..7e2866c 100644 --- a/apps/web-tanstack/package.json +++ b/apps/web-tanstack/package.json @@ -7,6 +7,7 @@ "build": "echo 'placeholder — TanStack Start build configured in later plan'", "dev": "echo 'placeholder'", "lint": "eslint .", + "test": "vitest run --passWithNoTests", "test:e2e": "playwright test", "typecheck": "tsc --noEmit" }, @@ -25,9 +26,15 @@ "devDependencies": { "@playwright/test": "^1.50.0", "@repo/core-eslint": "workspace:*", + "@repo/core-testing": "workspace:*", "@repo/core-typescript": "workspace:*", + "@testing-library/jest-dom": "^6.5.0", + "@testing-library/react": "^16.0.0", + "@testing-library/user-event": "^14.5.0", "@types/node": "^22.0.0", "@types/react": "^19.0.0", - "@types/react-dom": "^19.0.0" + "@types/react-dom": "^19.0.0", + "jsdom": "^25.0.0", + "vitest": "^3.0.0" } } diff --git a/apps/web-tanstack/src/routes/__root.test.tsx b/apps/web-tanstack/src/routes/__root.test.tsx new file mode 100644 index 0000000..d76c8b3 --- /dev/null +++ b/apps/web-tanstack/src/routes/__root.test.tsx @@ -0,0 +1,23 @@ +import { describe, it, expect, vi } from "vitest"; +import { render, screen } from "@testing-library/react"; +import "@testing-library/jest-dom"; + +// Mock @tanstack/react-router so we don't need a full router context +vi.mock("@tanstack/react-router", () => ({ + createRootRoute: vi.fn((opts: { component: React.ComponentType }) => ({ + options: { component: opts.component }, + })), + Outlet: () =>
, +})); + +describe("Root route", () => { + it("wraps Outlet with TanstackTrpcProvider (children render)", async () => { + const { Route } = await import("./__root"); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const RootComponent = (Route as any).options.component as React.ComponentType; + + render(); + + expect(screen.getByTestId("outlet")).toBeInTheDocument(); + }); +}); diff --git a/apps/web-tanstack/tsconfig.json b/apps/web-tanstack/tsconfig.json index af33277..c310251 100644 --- a/apps/web-tanstack/tsconfig.json +++ b/apps/web-tanstack/tsconfig.json @@ -6,7 +6,8 @@ "baseUrl": ".", "paths": { "@/*": ["./src/*"] - } + }, + "types": ["vitest/globals", "@testing-library/jest-dom"] }, "include": ["src/**/*.ts", "src/**/*.tsx"], "exclude": ["node_modules"] diff --git a/apps/web-tanstack/vitest.config.ts b/apps/web-tanstack/vitest.config.ts new file mode 100644 index 0000000..b1c34d3 --- /dev/null +++ b/apps/web-tanstack/vitest.config.ts @@ -0,0 +1,8 @@ +import path from "node:path"; +import { mergeConfig } from "vitest/config"; +import { jsdomVitestConfig } from "@repo/core-typescript/vitest.base.jsdom"; + +export default mergeConfig(jsdomVitestConfig, { + esbuild: { jsx: "automatic" }, + resolve: { alias: { "@": path.resolve(__dirname, "./src") } }, +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 92171f5..896b507 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -54,6 +54,9 @@ importers: '@repo/core-eslint': specifier: workspace:* version: link:../../packages/core-eslint + '@repo/core-testing': + specifier: workspace:* + version: link:../../packages/core-testing '@repo/core-typescript': specifier: workspace:* version: link:../../packages/core-typescript @@ -66,6 +69,9 @@ importers: '@types/react-dom': specifier: ^19.0.0 version: 19.2.3(@types/react@19.2.14) + vitest: + specifier: ^3.0.0 + version: 3.2.4(@types/debug@4.1.13)(@types/node@22.19.17)(happy-dom@20.8.9)(jiti@2.6.1)(jsdom@25.0.1)(lightningcss@1.32.0)(sass@1.99.0)(tsx@4.21.0) apps/storybook: dependencies: @@ -167,9 +173,21 @@ importers: '@repo/core-eslint': specifier: workspace:* version: link:../../packages/core-eslint + '@repo/core-testing': + specifier: workspace:* + version: link:../../packages/core-testing '@repo/core-typescript': specifier: workspace:* version: link:../../packages/core-typescript + '@testing-library/jest-dom': + specifier: ^6.5.0 + version: 6.9.1 + '@testing-library/react': + specifier: ^16.0.0 + version: 16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@testing-library/user-event': + specifier: ^14.5.0 + version: 14.6.1(@testing-library/dom@10.4.1) '@types/node': specifier: ^22.0.0 version: 22.19.17 @@ -179,6 +197,12 @@ importers: '@types/react-dom': specifier: ^19.0.0 version: 19.2.3(@types/react@19.2.14) + jsdom: + specifier: ^25.0.0 + version: 25.0.1 + vitest: + specifier: ^3.0.0 + version: 3.2.4(@types/debug@4.1.13)(@types/node@22.19.17)(happy-dom@20.8.9)(jiti@2.6.1)(jsdom@25.0.1)(lightningcss@1.32.0)(sass@1.99.0)(tsx@4.21.0) apps/web-tanstack: dependencies: @@ -219,9 +243,21 @@ importers: '@repo/core-eslint': specifier: workspace:* version: link:../../packages/core-eslint + '@repo/core-testing': + specifier: workspace:* + version: link:../../packages/core-testing '@repo/core-typescript': specifier: workspace:* version: link:../../packages/core-typescript + '@testing-library/jest-dom': + specifier: ^6.5.0 + version: 6.9.1 + '@testing-library/react': + specifier: ^16.0.0 + version: 16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@testing-library/user-event': + specifier: ^14.5.0 + version: 14.6.1(@testing-library/dom@10.4.1) '@types/node': specifier: ^22.0.0 version: 22.19.17 @@ -231,6 +267,12 @@ importers: '@types/react-dom': specifier: ^19.0.0 version: 19.2.3(@types/react@19.2.14) + jsdom: + specifier: ^25.0.0 + version: 25.0.1 + vitest: + specifier: ^3.0.0 + version: 3.2.4(@types/debug@4.1.13)(@types/node@22.19.17)(happy-dom@20.8.9)(jiti@2.6.1)(jsdom@25.0.1)(lightningcss@1.32.0)(sass@1.99.0)(tsx@4.21.0) packages/auth: dependencies: