Files
agentic-dev/packages/core-testing/AGENTS.md
Danijel Martinek edc98f8f9a docs(agents): per-feature + core-testing AGENTS.md for Plan 8 + Plan 9 conventions
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>
2026-05-06 16:47:56 +02:00

4.0 KiB

@repo/core-testing

Shared testing utilities. Tag: tooling. May be depended on by any package as a devDependency.

Subpath exports

  • @repo/core-testing/factorydefineFactory<T>(builder) for test data factories
  • @repo/core-testing/contractdefineContractSuite<T>(name, suite) for cross-impl contract tests
  • @repo/core-testing/reactrenderWithProviders, 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/payloadstubPayloadConfig, 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".

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.

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({}).

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.

// 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.