Files
agentic-dev/packages/core-testing/AGENTS.md
Danijel Martinek 2bd882e0b0 feat(core-testing): git-serving fixture helpers
serveFixtureRepo(fixtureDir) copies a fixture into a temp dir, commits
it as a fresh single-commit repo, bare-clones it, and serves it over
`git daemon --export-all` on an ephemeral localhost port (default) or
as a file:// URL (fallback for daemon-less environments). Returns
{ cloneUrl, bareRepoPath, protocol, stop } with idempotent teardown.
Tests exercise a real `git clone` of fixtures/vite-kitchen over both
protocols. New subpath export @repo/core-testing/git — node-only, so
deliberately not on the jsdom root barrel. No new runtime deps: node
built-ins + system git (present in git's exec-path on macOS and the
ubuntu CI image).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016j8z4VHjedXDTjEDNg7qHK
2026-07-12 21:40:52 +02:00

98 lines
4.4 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/git``serveFixtureRepo(fixtureDir, { protocol? })` serves a fixture directory (e.g. `fixtures/vite-kitchen`) as a real git remote for integration tests: copies it to a temp dir, commits it, bare-clones it, and exposes it via `git daemon` on an ephemeral localhost port (default) or a `file://` URL (fallback). Returns `{ cloneUrl, bareRepoPath, protocol, stop }`; always `await stop()` in `afterEach`. Node-only (`node:child_process` + system git) — intentionally not re-exported from the root barrel
- `@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".
## Test patterns
These test obligations apply to every feature package. The examples below show the minimal shape — adapt to the feature's actual types.
### 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", 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`.
### 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", 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).
### 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.