Curated, product-agnostic snapshot of the post-story-04 tree: demo content deleted, auth-only reference feature, web-next shell, all gates green. Product-specific docs, ADRs 027-029, PRDs/epics/archive, editor library traces, and product naming are curated out; generic template repairs (coverage provider devDeps, root test:coverage script, live lint fixes, root-only release-please) are kept. See TEMPLATE.md for provenance, curation list, and usage. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016j8z4VHjedXDTjEDNg7qHK
3.9 KiB
@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,createMockTrpcClientrenderWithProvidersdoes NOT include a tRPC provider. Consumers needing tRPC should wire their own TRPCProvider (from their app's tRPC client setup) and usecreateMockTrpcClientas the client. This constraint exists because tooling packages cannot importAppRouterfrom@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
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):
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.
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({}).
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.
// 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.