feat(core-testing): scaffold shared testing utilities package
Adds @repo/core-testing (tag: tooling) with:
- factory/defineFactory: monotonic-sequence object factories with overrides
- contract/defineContractSuite: shared test suites runnable against multiple impls
- react/renderWithProviders + createMockTrpcClient: RTL helpers
- payload/stubPayloadConfig + mockPayloadModule: Payload mocking helpers
- setup/{jsdom,node}: vitest setup files
Spec: docs/superpowers/specs/2026-05-05-tdd-foundation-design.md §5
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2
packages/core-testing/src/react/index.ts
Normal file
2
packages/core-testing/src/react/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export { renderWithProviders, type RenderOptions } from "./render-with-providers.js";
|
||||
export { createMockTrpcClient } from "./mock-trpc.js";
|
||||
29
packages/core-testing/src/react/mock-trpc.ts
Normal file
29
packages/core-testing/src/react/mock-trpc.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { createTRPCClient, httpBatchLink } from "@trpc/client";
|
||||
import superjson from "superjson";
|
||||
import type { AnyTRPCRouter } from "@trpc/server";
|
||||
|
||||
// Returns a tRPC client whose fetch is a stub honouring the provided mocks.
|
||||
// Mocks are keyed by procedure path ("blog.articleBySlug") returning the
|
||||
// raw response body.
|
||||
export function createMockTrpcClient<TRouter extends AnyTRPCRouter>(
|
||||
mocks: Record<string, unknown> = {},
|
||||
) {
|
||||
const fetchStub: typeof fetch = async (input) => {
|
||||
const url = typeof input === "string" ? input : (input as Request).url;
|
||||
const path = new URL(url, "http://mock").pathname.replace(/^\/api\/trpc\//, "");
|
||||
const result = mocks[path];
|
||||
if (result === undefined) {
|
||||
return new Response(JSON.stringify([{ error: { code: -32603, message: `No mock for ${path}` } }]), { status: 200 });
|
||||
}
|
||||
return new Response(JSON.stringify([{ result: { data: superjson.serialize(result) } }]), { status: 200 });
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const link = httpBatchLink<any>({
|
||||
url: "http://mock/api/trpc",
|
||||
transformer: superjson,
|
||||
fetch: fetchStub,
|
||||
});
|
||||
|
||||
return createTRPCClient<TRouter>({ links: [link] });
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { screen } from "@testing-library/react";
|
||||
import { renderWithProviders } from "@/react/render-with-providers";
|
||||
|
||||
describe("renderWithProviders", () => {
|
||||
it("renders the child", () => {
|
||||
renderWithProviders(<div data-testid="x">hi</div>);
|
||||
expect(screen.getByTestId("x")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("returns the queryClient instance", () => {
|
||||
const { queryClient } = renderWithProviders(<div />);
|
||||
expect(queryClient).toBeDefined();
|
||||
});
|
||||
});
|
||||
29
packages/core-testing/src/react/render-with-providers.tsx
Normal file
29
packages/core-testing/src/react/render-with-providers.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import type { PropsWithChildren, ReactElement } from "react";
|
||||
import { render } from "@testing-library/react";
|
||||
import type { RenderResult } from "@testing-library/react";
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
|
||||
export interface RenderOptions {
|
||||
queryClient?: QueryClient;
|
||||
}
|
||||
|
||||
export function renderWithProviders(
|
||||
ui: ReactElement,
|
||||
options: RenderOptions = {},
|
||||
): RenderResult & { queryClient: QueryClient } {
|
||||
const queryClient =
|
||||
options.queryClient ??
|
||||
new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: { retry: false },
|
||||
mutations: { retry: false },
|
||||
},
|
||||
});
|
||||
|
||||
const Wrapper = ({ children }: PropsWithChildren) => (
|
||||
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
|
||||
);
|
||||
|
||||
const result = render(ui, { wrapper: Wrapper });
|
||||
return Object.assign(result, { queryClient });
|
||||
}
|
||||
Reference in New Issue
Block a user