Files
suaveplan/openspec/archive/2026-04-19-enterprise-testing/surface.md
2026-05-27 23:29:53 +01:00

8.0 KiB

@suaveplan/testing — Surface Inventory

Preservation contract. Every name below is reproduced unchanged in shape by the genesis rewrite. Signatures gain the optional { clock?, rng?, signal? } options parameter where appropriate (purely additive — zero-config callers are unaffected).

Source: /mnt/s/web/suaveplan/packages/universal/testing/src/ at branch main head ebff1a14d.

Root barrel (@suaveplan/testing)

Runner primitives (via conditional exports)

Name Kind Notes
afterAll, afterEach, beforeAll, beforeEach function Lifecycle hooks
describe function Group tests
it, test function Declare a test
expect function Assertion entry
vi namespace Vitest mock namespace (vitest path only)
fn <TArgs extends unknown[], TReturn>(implementation?: (...args: TArgs) => TReturn) => Mock Runner-agnostic mock fn
mock (modulePath: string, factory?: () => unknown) => { restore(): void } Module mock (vitest path only)
spy <T, M extends keyof T>(obj: T, method: M & string) Method spy
stub <T, M extends keyof T>(obj: T, method: M & string, impl?: (...args: unknown[]) => unknown) Method stub with default implementation
fakeClock () => { advance(ms: number): void; restore(): void } Vitest fake-timer wrapper
restoreMocks () => void
restoreTimers () => void

Routing: package.json conditional exports pick vitest (default), bun-test (bun condition), or the vitest barrel (vitest condition) per the archived 2026-04-08-testing-libraries change.

Builders

Name Kind
RequestBuilder class
ResponseBuilder class
get(url: string): RequestBuilder function (preset)
post(url: string, body?: unknown): RequestBuilder function (preset)
put(url: string, body?: unknown): RequestBuilder function (preset)
patch(url: string, body?: unknown): RequestBuilder function (preset)
del(url: string): RequestBuilder function (preset, delete keyword avoidance)
ok(body?: unknown): ResponseBuilder function (preset 200)
created(body?: unknown): ResponseBuilder function (preset 201)
noContent(): ResponseBuilder function (preset 204)
badRequest(body?: unknown): ResponseBuilder function (preset 400)
unauthorized(body?: unknown): ResponseBuilder function (preset 401)
notFound(body?: unknown): ResponseBuilder function (preset 404)
serverError(body?: unknown): ResponseBuilder function (preset 500)

Clock

Name Kind
FakeClock class

Factories

Name Kind
defineFactory<T>(definition: FactoryDefinition<T>): Factory<T> function
Factory<T> class

Fixtures

Name Kind
FixtureManager class

Storage

Name Kind
MemoryStore class

Helpers

Name Kind
assertThrows(fn: () => void, matcher?: string | RegExp | ErrorConstructor): Error function
assertThrowsAsync(fn: () => Promise<unknown>, matcher?: string | RegExp | ErrorConstructor): Promise<Error> function
createCollector<T>(): { push(value: T): void; items(): readonly T[]; clear(): void } function
deferred<T>(): { promise: Promise<T>; resolve(value: T): void; reject(reason: unknown): void } function
measureTime<T>(fn: () => T | Promise<T>): Promise<{ result: T; elapsedMs: number }> function
resetTestIdCounter(): void function
sleep(ms: number): Promise<void> function
uniqueTestId(prefix?: string): string function
waitFor(predicate: () => boolean | Promise<boolean>, options?: WaitForOptions): Promise<void> function

Constants

Name Kind Source
DEFAULT_CLOCK_OPTIONS Required<FakeClockOptions> src/constants/
DEFAULT_HTTP_STATUS number (200)
DEFAULT_RESPONSE_LATENCY_MS number (0)
DEFAULT_STORE_OPTIONS Required<MemoryStoreOptions>
DEFAULT_WAIT_OPTIONS Required<WaitForOptions>
MAX_FIXTURES number (100)
MAX_STORE_ENTRIES number (1,000,000)
MAX_TIMERS number (10,000)

Public types

Name Kind Definition location
HttpMethod "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "HEAD" | "OPTIONS" src/types/testing.types.ts
HttpHeaders Record<string, string>
QueryParams Record<string, string | string[]>
TestRequest interface: { method, url, headers, query, body, params }
TestResponse interface: { status, headers, body, latencyMs }
FakeClockOptions interface: { startTime?, autoAdvance?, autoAdvanceMs? }
ScheduledTimer interface: { id, callback, fireAt, interval, type }
MemoryStoreOptions interface: { maxEntries?, defaultTtlMs? }
StoreEntry<T> interface: { value: T, createdAt, expiresAt }
FactoryDefinition<T> interface: { defaults, traits?, afterCreate? }
FactoryDefaults<T> mapped type
FactoryBuildOptions<T> interface: { overrides?, traits?, index? }
FixtureDefinition<T> interface: { name, setup, teardown?, dependencies? }
FixtureState<T> interface: { name, value, isSetup, setupCount, teardownCount }
WaitForOptions interface: { timeoutMs?, intervalMs? }
ClockSnapshot feature-local src/clock/fake-clock/fake-clock.types.ts
RequestBuilderPreset, RequestBuilderState feature-local src/builders/request-builder/
ResponseBuilderPreset, ResponseBuilderState feature-local src/builders/response-builder/
BatchBuildResult feature-local src/factories/factory/
SetupAllResult, TeardownAllResult feature-local src/fixtures/fixture-manager/
MemoryStoreEvent, MemoryStoreStats feature-local src/storage/memory-store/

Subpath ./config (@suaveplan/testing/config)

Name Kind
createTestConfig(options?: TestConfigOptions): Promise<VitestUserConfig | BunTestConfig> async factory
TestConfigOptions interface extending VitestConfigOverrides with runner?: TestRunner
TestRunner "vitest" | "bun:test"
TestEnvironment "node" | "happy-dom" | "jsdom"
VitestConfigOverrides interface (passes through to vitest's config)
BunTestConfig interface (minimal bun-test config shape)
BunTestConfigOverrides interface (partial of the above)

Subpath ./utils (@suaveplan/testing/utils)

Re-exports every non-runner symbol above for single-import convenience (import { FakeClock, Factory, … } from "@suaveplan/testing/utils").

Subpath ./polyfills (@suaveplan/testing/polyfills)

Side-effecting module (import "@suaveplan/testing/polyfills";) loaded by setupFiles in consumers' vitest configs. Installs happy-dom-needed globals (e.g. crypto.getRandomValues polyfill, whatever else surfaces as needed on WSL2).

Non-obvious constraints (from the archived 2026-04-08-testing-libraries change)

  • Dual-runner exports are load-bearing. Swapping a package between vitest and bun-test is a single condition change with no test-source rewrite. The vitest path and the bun-test path expose the same names.
  • @vitejs/plugin-react is an optional peer dep. createTestConfig dynamically imports it only when the caller requests environment: "happy-dom" AND the peer is installed. Missing peer must NOT break non-React consumers.
  • pool: "forks" everywhere. vmThreads and threads both fail in Bun on WSL2. Both the vitest config factory and any direct config consumers must force forks.
  • fileParallelism: false required for the happy-dom path (20+ concurrent forks time out on WSL2).
  • Polyfills import is a side-effect module, loaded via setupFiles: ["@suaveplan/testing/polyfills"]. Must export nothing; merely installing globals.
  • mocks.ts uses vitest's vi directly. On the bun-test path, the mocks.ts shim is replaced by bun-test equivalents. Callers see the same function names either way.