/** * Type-safe activity mocking with call recording for testing. * * Provides an `ActivityMockRegistry` to register mock implementations * of activity functions, inspect call history, and configure per-call * overrides (one-shot return values, one-shot rejections). * * @module testing/mocks */ /** * A single recorded call on a mock activity. * * @example * ```ts * import { TestEngine, type MockCall } from '@lostgradient/weft/testing'; * * const engine = new TestEngine(); * async function sendEmail(input: unknown): Promise { return ''; } * const mockHandle = engine.mock(sendEmail, async (input: unknown) => 'sent'); * await engine.start('notify', { to: 'user@example.com' }); * const call: MockCall = mockHandle.calls[0]!; * console.log(call.input); // { to: 'user@example.com' } * ``` */ export interface MockCall { readonly input: TInput; readonly result: TResult | undefined; readonly error: Error | undefined; readonly timestamp: number; } export type MockActivityFunction = [TInput] extends [void] ? (input?: TInput) => TResult | Promise : (input: TInput) => TResult | Promise; /** * Handle returned by {@link ActivityMockRegistry.mock} that lets tests inspect * call history and configure one-shot overrides. * * Call `mockReturnValueOnce` or `mockRejectionOnce` to inject a specific * outcome for the next invocation, then check `calls` to assert what arguments * were passed. Use `restore()` to remove the mock and revert to the real * implementation. * * @example * ```ts * import { TestEngine, type MockHandle } from '@lostgradient/weft/testing'; * * const engine = new TestEngine(); * async function sendEmail(input: unknown): Promise { return 'real'; } * * const handle: MockHandle = * engine.mock(sendEmail, async (input: unknown) => 'mocked'); * * handle.mockReturnValueOnce('override'); * console.log(handle.callCount); // 0 * await engine.start('notify', { to: 'user@example.com' }); * ``` */ export interface MockHandle { readonly calls: ReadonlyArray>; readonly callCount: number; readonly lastCall: MockCall | undefined; /** The current base implementation (excludes one-time overrides). */ readonly currentImplementation: MockActivityFunction; mockImplementation(implementation: MockActivityFunction): void; mockReturnValueOnce(value: TResult): MockHandle; mockRejectionOnce(error: Error): MockHandle; resetCalls(): void; restore(): void; } /** * Internal record held by {@link ActivityMockRegistry} for each mocked * activity. * * Contains the current `implementation` function (which records calls and * applies one-time overrides) and the typed `handle` through which tests * inspect and configure the mock. Most consumers interact with * {@link MockHandle} instead of `MockedActivity` directly. * * @example * ```ts * import { ActivityMockRegistry, type MockedActivity } from '@lostgradient/weft/testing'; * * const registry = new ActivityMockRegistry(); * async function fetchUser(id: unknown): Promise { return String(id); } * * registry.mock(fetchUser, async (id: unknown) => 'user-mock'); * const mocked: MockedActivity | undefined = registry.get(fetchUser); * console.log(typeof mocked?.implementation); // 'function' * ``` */ export interface MockedActivity { implementation: (input?: unknown) => unknown; handle: MockHandle; } /** * Registry for mocking activity functions in tests. * * Call `mock(activityFn, implementation)` to replace an activity with a test * double and receive a {@link MockHandle} for inspection and configuration. * Use `restoreAll()` in `afterEach` to clear all mocks between test cases. * * @example * ```ts * import { ActivityMockRegistry } from '@lostgradient/weft/testing'; * * async function sendEmail(input: unknown): Promise { return 'sent'; } * * const registry = new ActivityMockRegistry(); * const handle = registry.mock(sendEmail, async (input: unknown) => 'mock-sent'); * * console.log(registry.has(sendEmail)); // true * await (registry.get(sendEmail)!.implementation)({ to: 'a@b.com' }); * console.log(handle.callCount); // 1 * registry.restoreAll(); * ``` */ export declare class ActivityMockRegistry { #private; constructor(); /** * Register a cleanup callback to run when `restore(activity)` or * `restoreAll()` removes the mock for `activity`. Used by {@link TestEngine} * to undo the surrogate activity registration it installs on the engine, so * `restoreAll()` does not leave stale registrations behind. The callback runs * at most once per registration and is then discarded. * * If a cleanup hook is already registered for `activity` (for example, when * the same activity is mocked twice without an intervening `restore()`), the * existing hook is kept and the new one is ignored. This preserves the * original-registration snapshot captured by the first mock, so re-mocking * never overwrites the restorer with one that points at a surrogate. */ onRestore(activity: Function, cleanup: () => void): void; /** Whether a cleanup hook is currently registered for `activity`. */ hasRestoreHook(activity: Function): boolean; mock(activity: () => Promise | TResult, implementation: () => TResult | Promise): MockHandle; mock(activity: (input: TInput) => Promise | TResult, implementation: (input: TInput) => TResult | Promise): MockHandle; has(activity: Function): boolean; get(activity: Function): MockedActivity | undefined; restore(activity: Function): void; restoreAll(): void; /** * Iterate all registered mock entries as `[activityFn, MockedActivity]` pairs. * Used internally by `TestEngine.runN` to propagate mocks to per-run engines. */ entries(): IterableIterator<[Function, MockedActivity]>; }