From 5ea4c67f9358e5e160ec0f651be7d9e95c4976db Mon Sep 17 00:00:00 2001 From: Danijel Martinek Date: Tue, 5 May 2026 19:31:31 +0200 Subject: [PATCH] docs(adr): ADR-011 TDD foundation; update AGENTS.md per-feature MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Captures the decision to add @repo/core-testing, factories, contract suites, vitest safety defaults, coverage thresholds, Storybook test-runner, and CI as one cohesive TDD foundation. Per-feature AGENTS.md gains a Tests section pointing to factories, contract suite, and the canonical test commands. Spec: §7.4, §7.5 Co-Authored-By: Claude Sonnet 4.6 --- AGENTS.md | 5 +- docs/decisions/adr-011-tdd-foundation.md | 70 ++++++++++++++++++++++++ packages/auth/AGENTS.md | 14 +++++ packages/blog/AGENTS.md | 14 +++++ packages/marketing-pages/AGENTS.md | 14 +++++ packages/media/AGENTS.md | 12 ++++ packages/navigation/AGENTS.md | 14 +++++ 7 files changed, 141 insertions(+), 2 deletions(-) create mode 100644 docs/decisions/adr-011-tdd-foundation.md diff --git a/AGENTS.md b/AGENTS.md index b12315e..635e993 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -20,6 +20,7 @@ This is a **Turborepo + pnpm monorepo** organized by vertical features. Each fea | `@repo/navigation` | feature | Header global | | `@repo/core-eslint` | tooling | Shared ESLint 9 flat configs (base, next, react-internal, boundaries) | | `@repo/core-typescript` | tooling | Shared TypeScript base configs + Vitest base | +| `@repo/core-testing` | tooling | Shared test utilities (defineFactory, defineContractSuite, renderWithProviders, payload mocks) | --- @@ -31,7 +32,7 @@ This is a **Turborepo + pnpm monorepo** organized by vertical features. Each fea - **core-composition** (3 packages) — `packages/core-api`, `core-cms`, `core-trpc` - **core** (2 packages) — `packages/core-shared`, `core-ui` - **feature** (5 packages) — `packages/auth`, `blog`, `media`, `marketing-pages`, `navigation` -- **tooling** (2 packages) — `packages/core-eslint`, `core-typescript` +- **tooling** (3 packages) — `packages/core-eslint`, `core-typescript`, `core-testing` ### Allowed dependency directions @@ -212,5 +213,5 @@ Per-package documentation lives in each `AGENTS.md`: - `packages/core-api/AGENTS.md`, `core-cms/AGENTS.md`, `core-trpc/AGENTS.md` - `packages/core-ui/AGENTS.md` - `packages/auth/AGENTS.md`, `blog/AGENTS.md`, `media/AGENTS.md`, `marketing-pages/AGENTS.md`, `navigation/AGENTS.md` -- `packages/core-eslint/AGENTS.md`, `core-typescript/AGENTS.md` +- `packages/core-eslint/AGENTS.md`, `core-typescript/AGENTS.md`, `core-testing/AGENTS.md` - `apps/cms/AGENTS.md`, `web-next/AGENTS.md`, `web-tanstack/AGENTS.md`, `storybook/AGENTS.md` diff --git a/docs/decisions/adr-011-tdd-foundation.md b/docs/decisions/adr-011-tdd-foundation.md new file mode 100644 index 0000000..8d530a7 --- /dev/null +++ b/docs/decisions/adr-011-tdd-foundation.md @@ -0,0 +1,70 @@ +# ADR-011: TDD Foundation + +**Status:** Accepted +**Date:** 2026-05-05 +**Supersedes:** none +**Spec:** docs/superpowers/specs/2026-05-05-tdd-foundation-design.md + +## Context + +The vertical-feature monorepo refactor (ADRs 001-010) established +clean architecture with per-feature DI containers but did not enforce +TDD as the path of least resistance. Agentic workers were producing +code-first commits with tests added later, leading to test theatre and +mock/real drift. + +## Decision + +1. **New package `@repo/core-testing` (tag: tooling)** — shared test + utilities: defineFactory, defineContractSuite, renderWithProviders, + mock-payload helpers, jsdom setup file. Tagged `tooling` so any + package may depend on it as devDependency without boundary violation. + +2. **Vitest base configs split into node + jsdom** with safety defaults + (clearMocks, restoreMocks, mockReset, unstubGlobals, sequence.shuffle) + and coverage thresholds (80/75/80/80 baseline; 100% in entities + + use-cases + controllers). + +3. **Factories per feature** in `src/__factories__/` replace inline + fixtures. Stable date defaults (2026-01-01) so snapshot diffs reflect + SUT behavior only. + +4. **Contract suites per repository interface** in `src/__contracts__/` + run against every implementation (Mock + Payload). Eliminates the + class of bug where the mock and the real impl drift apart. + +5. **Tests in core-* packages and apps** — composition smoke tests + (appRouter, payloadConfig, bind-production, providers). + +6. **Storybook test-runner** — every story executed as a smoke test. + +7. **CI workflow** — typecheck + lint + boundaries + test + build + + e2e + storybook on every PR. Coverage uploaded as artifact. + +8. **Two new docs** — tdd-workflow.md (process) + restructured + adding-a-feature.md (interleaves tests with impl). + +## Alternatives considered + +- **Fixture files instead of factories** — rejected. Fixtures rot with + schema changes and require manual updates per test. +- **One shared test file per impl** — rejected. Contract suites give + the same coverage in fewer LOC and prevent drift. +- **Real Postgres in tests via testcontainers** — rejected for unit + tests (slow, complex). Repository contract suites + vi.mock('payload') + give equivalent confidence in milliseconds. +- **Stryker mutation testing** — deferred. Coverage thresholds + contract + suites get us most of the way; mutation testing is incremental. + +## Consequences + +- New package to maintain (small, mostly stable surface). +- Coverage thresholds may fail builds initially; we add tests to cross + threshold as part of Plan 7. +- Sequence shuffle may surface latent flakes; we fix as found. +- Templates for new features now require writing tests first; this is + by design. + +## Refines + +- ADR-006 (boundary tags) — adds @repo/core-testing as a tooling package. diff --git a/packages/auth/AGENTS.md b/packages/auth/AGENTS.md index f90835c..43fa677 100644 --- a/packages/auth/AGENTS.md +++ b/packages/auth/AGENTS.md @@ -75,6 +75,20 @@ beforeEach(() => { Covered areas: sign-in/up/out use cases, user validation, DI container binding. +## Tests + +- **Factories:** `src/__factories__/user.factory.ts`, `src/__factories__/session.factory.ts` — use `userFactory.build({ overrides })` to construct test data with stable defaults. +- **Contract suite:** `src/__contracts__/users-repository.contract.ts` — runs against every repository implementation (mock + payload). +- **Unit tests:** colocated as `*.test.ts` next to the source file. +- **Feature integration:** `tests/sign-in-flow.feature.test.ts` — full slice through tRPC router → controller → use case → mock repo. + +```bash +pnpm test --filter @repo/auth # all tests for this feature +pnpm test --filter @repo/auth -- --watch # watch mode +``` + +See `docs/guides/tdd-workflow.md` for the cycle. + ## Structure (minimal feature) ``` diff --git a/packages/blog/AGENTS.md b/packages/blog/AGENTS.md index 3739177..c8527a7 100644 --- a/packages/blog/AGENTS.md +++ b/packages/blog/AGENTS.md @@ -75,6 +75,20 @@ beforeEach(() => { Covered areas: article CRUD use cases, slug validation, publish/unpublish flows, DI container binding. +## Tests + +- **Factories:** `src/__factories__/article.factory.ts` — use `articleFactory.build({ overrides })` to construct test data with stable defaults. +- **Contract suite:** `src/__contracts__/articles-repository.contract.ts` — runs against every repository implementation (mock + payload). +- **Unit tests:** colocated as `*.test.ts` next to the source file. +- **Feature integration:** `tests/articles.feature.test.ts` — full slice through tRPC router → controller → use case → mock repo. + +```bash +pnpm test --filter @repo/blog # all tests for this feature +pnpm test --filter @repo/blog -- --watch # watch mode +``` + +See `docs/guides/tdd-workflow.md` for the cycle. + ## Structure ``` diff --git a/packages/marketing-pages/AGENTS.md b/packages/marketing-pages/AGENTS.md index 2cd58f9..af27df2 100644 --- a/packages/marketing-pages/AGENTS.md +++ b/packages/marketing-pages/AGENTS.md @@ -70,6 +70,20 @@ beforeEach(() => { - **Alias** — `@/` resolves to `src/` - **Run** — `pnpm test --filter @repo/marketing-pages` +## Tests + +- **Factories:** `src/__factories__/page.factory.ts`, `src/__factories__/site-settings.factory.ts` — use `pageFactory.build({ overrides })` to construct test data with stable defaults. +- **Contract suite:** `src/__contracts__/pages-repository.contract.ts` — runs against every repository implementation (mock + payload). +- **Unit tests:** colocated as `*.test.ts` next to the source file. +- **Feature integration:** `tests/page-by-slug.feature.test.ts` — full slice through tRPC router → controller → use case → mock repo. + +```bash +pnpm test --filter @repo/marketing-pages # all tests for this feature +pnpm test --filter @repo/marketing-pages -- --watch # watch mode +``` + +See `docs/guides/tdd-workflow.md` for the cycle. + ## Structure (minimal) ``` diff --git a/packages/media/AGENTS.md b/packages/media/AGENTS.md index 4c47c3c..d66bb09 100644 --- a/packages/media/AGENTS.md +++ b/packages/media/AGENTS.md @@ -69,6 +69,18 @@ beforeEach(() => { - **Alias** — `@/` resolves to `src/` - **Run** — `pnpm test --filter @repo/media` +## Tests + +- **Factories:** `src/__factories__/media.factory.ts` — use `mediaFactory.build({ overrides })` to construct test data with stable defaults. +- **Unit tests:** colocated as `*.test.ts` next to the source file. + +```bash +pnpm test --filter @repo/media # all tests for this feature +pnpm test --filter @repo/media -- --watch # watch mode +``` + +See `docs/guides/tdd-workflow.md` for the cycle. + ## Structure (minimal) ``` diff --git a/packages/navigation/AGENTS.md b/packages/navigation/AGENTS.md index baaa3ae..72c46a3 100644 --- a/packages/navigation/AGENTS.md +++ b/packages/navigation/AGENTS.md @@ -69,6 +69,20 @@ beforeEach(() => { - **Alias** — `@/` resolves to `src/` - **Run** — `pnpm test --filter @repo/navigation` +## Tests + +- **Factories:** `src/__factories__/header.factory.ts` — use `headerFactory.build({ overrides })` to construct test data with stable defaults. +- **Contract suite:** `src/__contracts__/navigation-repository.contract.ts` — runs against every repository implementation (mock + payload). +- **Unit tests:** colocated as `*.test.ts` next to the source file. +- **Feature integration:** `tests/get-header.feature.test.ts` — full slice through tRPC router → controller → use case → mock repo. + +```bash +pnpm test --filter @repo/navigation # all tests for this feature +pnpm test --filter @repo/navigation -- --watch # watch mode +``` + +See `docs/guides/tdd-workflow.md` for the cycle. + ## Structure (minimal) Per spec addendum v5 ("create folders only when needed"), this feature is small: