Each per-feature AGENTS.md now reflects the post-Plan-9 layout: entity/error paths, public-API split (./ui), use-case schemas, presenter pattern, feature-scoped tRPC error map, and feature-specific errors-to-codes table. core-testing/AGENTS.md gains a Plan 9 test-patterns section documenting R25 (output validation), R26 (router error mapping), R27/R28 (presenter shape) test obligations. auth: documents real PayloadUsersRepository + AuthenticationService and the deferred session methods. media: documents the full Clean Architecture scaffold introduced in Plan 8. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
93 lines
4.0 KiB
Markdown
93 lines
4.0 KiB
Markdown
# @repo/core-testing
|
|
|
|
Shared testing utilities. Tag: `tooling`. May be depended on by any package as a devDependency.
|
|
|
|
## Subpath exports
|
|
|
|
- `@repo/core-testing/factory` — `defineFactory<T>(builder)` for test data factories
|
|
- `@repo/core-testing/contract` — `defineContractSuite<T>(name, suite)` for cross-impl contract tests
|
|
- `@repo/core-testing/react` — `renderWithProviders`, `createMockTrpcClient`
|
|
- `renderWithProviders` does NOT include a tRPC provider. Consumers needing tRPC should wire their own TRPCProvider (from their app's tRPC client setup) and use `createMockTrpcClient` as the client. This constraint exists because tooling packages cannot import `AppRouter` from `@repo/core-api`.
|
|
- `@repo/core-testing/payload` — `stubPayloadConfig`, `mockPayloadModule`
|
|
- `@repo/core-testing/setup/jsdom` — vitest setupFile (jest-dom + cleanup)
|
|
- `@repo/core-testing/setup/node` — vitest setupFile (no-op placeholder)
|
|
|
|
## Adding a factory
|
|
|
|
```typescript
|
|
import { defineFactory } from "@repo/core-testing/factory";
|
|
|
|
export const articleFactory = defineFactory<Article>(({ sequence }) => ({
|
|
id: `article-${sequence}`,
|
|
title: `Article ${sequence}`,
|
|
// stable defaults — overrides drive variation
|
|
}));
|
|
```
|
|
|
|
## Using createMockTrpcClient
|
|
|
|
For component tests that consume tRPC procedures, mock the responses by procedure path (dot-separated):
|
|
|
|
```typescript
|
|
import { createMockTrpcClient } from "@repo/core-testing/react";
|
|
import type { AppRouter } from "@repo/core-api"; // import in your app/feature, not in core-testing
|
|
|
|
const trpcClient = createMockTrpcClient<AppRouter>({
|
|
"blog.articleBySlug": { id: "1", title: "Hello", slug: "hello" },
|
|
"blog.listArticles": [],
|
|
});
|
|
```
|
|
|
|
Combine with your app's TRPCProvider for components that need a tRPC client in the render tree.
|
|
|
|
## Adding a contract suite
|
|
|
|
See `docs/guides/tdd-workflow.md` §"Contract suite usage".
|
|
|
|
## Plan 9 test patterns
|
|
|
|
These test obligations apply to every feature package. The examples below show the minimal shape — adapt to the feature's actual types.
|
|
|
|
### R25 — Output validation (use case)
|
|
|
|
Every non-void use case must have a test that injects a mock returning malformed data and asserts the use case rejects with a `ZodError`. This proves `xOutputSchema.parse(result)` is actually called.
|
|
|
|
```typescript
|
|
it("throws ZodError when repository returns malformed data (R25)", async () => {
|
|
const badRepo = { getArticleBySlug: async () => ({ id: 1 }) }; // id should be string
|
|
await expect(getArticleBySlugUseCase(badRepo as any)({ slug: "x" }))
|
|
.rejects.toBeInstanceOf(ZodError);
|
|
});
|
|
```
|
|
|
|
Void use cases (`signOut`, `deleteMedia`) are exempt — they have no `xOutputSchema`.
|
|
|
|
### R26 — Router error mapping (tRPC)
|
|
|
|
Each feature's `router.test.ts` must assert the correct `TRPCError.code` for at least one mapped domain error, using `xRouter.createCaller({})`.
|
|
|
|
```typescript
|
|
it("returns NOT_FOUND when article is missing (R26)", async () => {
|
|
const caller = blogRouter.createCaller({});
|
|
const error = await caller.articleBySlug({ slug: "missing" }).catch((e) => e);
|
|
expect(error).toBeInstanceOf(TRPCError);
|
|
expect(error.code).toBe("NOT_FOUND");
|
|
});
|
|
```
|
|
|
|
Also assert `BAD_REQUEST` for at least one invalid-input call (exercises the `strict()` schema boundary).
|
|
|
|
### R27/R28 — Presenter shape (controller tests)
|
|
|
|
When a controller's presenter reshapes the use-case output (e.g., `signInController` extracts `cookie` from `{ session, cookie }`), the controller test must assert against the **view shape**, not the use-case output shape.
|
|
|
|
```typescript
|
|
// signInController: presenter returns value.cookie (a Cookie object)
|
|
const result = await signInController(mockUseCase)({ username: "u", password: "p" });
|
|
expect(result.name).toBe(SESSION_COOKIE); // Cookie.name
|
|
expect(result.value).toBeDefined(); // Cookie.value
|
|
// NOT: expect(result.session).toBeDefined() — that's the use-case output, not the view
|
|
```
|
|
|
|
Identity presenters (`return value;`) skip this obligation — the view shape equals the use-case output shape.
|