Skip to content
Published alpha · next channelRead as Markdown

Fixture consumers ​

Use the same builders in UI previews, HTTP mocks and test-database setup without adding a UI framework, server, database driver or network client to the core. These utilities consume explicit callbacks, never discover production resources or install global interceptors. Native Web APIs are used; TypeScript consumers need Web platform declarations such as lib: ["ES2022", "DOM"].

Component previews ​

fixtureLoader(key, source) implements the ordinary Storybook loader shape. It returns a new loaded[key] value on every invocation; it does not mutate story args or cache fixtures across stories. This structural integration does not require Storybook to be installed in the toolkit.

ts
const loadUser = fixtureLoader('user', (context: { id: string }) => {
  const session = provider.session(context.id);
  return users.buildValidated(session);
});
// In a story:
const story = {
  loaders: [loadUser],
  render: (args, { loaded: { user } }) => renderUser({ ...args, user }),
};

Portable fixture cloning is the default, protecting stories from mutable factories that return shared objects. For a native class, provide clone; an explicit identity function opts into sharing. Clone callbacks must be synchronous. Abort signals are checked before and after asynchronous loading. No attempt is made to interrupt a non-cooperating application callback.

HTTP mocking ​

jsonResponseResolver(source, options) produces a fresh native Response for each explicitly handled Request. Connect it to MSW, a Fetch mock, or a test server in your application. The toolkit itself never replaces fetch or opens a listener.

ts
const resolveUser = jsonResponseResolver(
  (request) => {
    const id = new URL(request.url).searchParams.get('id') ?? 'one';
    return users.buildValidated(provider.session(id));
  },
  { headers: { 'x-fixture': 'true' }, maxBodyBytes: 100_000 }
);
// MSW integration in the consuming project:
// http.get('/user', ({ request }) => resolveUser(request));

Request aborts propagate; factory/validation/serialization errors are not changed into successful responses. Status defaults to 200, content type is JSON, and the UTF-8 response budget defaults to 1 MiB. For bodyless statuses, binary, streaming, multipart, or a prepared OpenAPI codec, explicitly construct a native Response from that codec instead. JSON limitations apply: BigInt, cycles and undefined roots are not invented wire encodings. The size check follows serialization and is not a sandbox or exact peak-memory limit for untrusted values/toJSON callbacks.

Persistence handoff ​

persistFixtureBatch(count, source, persist, context, options) generates and clones a complete bounded batch before calling the persistence callback once. Use a validated builder as source to catch fixture errors before database writes.

ts
await persistFixtureBatch(
  10,
  (index, { session }) => users.buildValidated(session.scope('user', index)),
  (values, { database }) => database.transaction((tx) => tx.insertUsers(values)),
  { session: provider.session(42), database: testDatabase },
  { maxItems: 100, signal: controller.signal }
);

Only the explicitly supplied sink can write. Transactions, rollback, connections, credentials and schema migrations belong to that sink; this utility does not imply that arbitrary callbacks are transactional. There are no automatic retries. The array passed to the sink is frozen; nested values remain ordinary fixture data. Generation is sequential. Cancellation before the sink prevents the handoff; cancellation during I/O is passed to the sink, not falsely reported as a rollback. An empty batch calls the sink once with an empty array. The default limit is 1,000.

The packed-consumer suite uses actual builders, native Requests/Responses and in-memory transactional test sinks. It does not claim a tested version matrix for every UI framework, mocking framework or database driver.

Mimlet · alpha · MIT licensed