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-reactis an optional peer dep.createTestConfigdynamically imports it only when the caller requestsenvironment: "happy-dom"AND the peer is installed. Missing peer must NOT break non-React consumers.pool: "forks"everywhere.vmThreadsandthreadsboth fail in Bun on WSL2. Both the vitest config factory and any direct config consumers must forceforks.fileParallelism: falserequired 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.tsuses vitest'svidirectly. On the bun-test path, themocks.tsshim is replaced by bun-test equivalents. Callers see the same function names either way.