import { x as SpyOptions, C as ClassType, m as ClassSpyConfiguration, q as OnlyMethodKeysOf, S as Spy, I as InstanceSpyConfiguration, o as DeepPartial, D as DeepMockProxy, F as Func, h as AddSpyMethodsByReturnTypes } from './types-BM3BcWj1.js'; export { A as AccessorKeysOf, a as AddAccessorsSpies, b as AddCalledWithAny, c as AddCalledWithObservable, d as AddCalledWithPromise, e as AddCalledWithSpyMethods, f as AddObservableSpyMethods, g as AddPromiseSpyMethods, i as AddThrowHelper, j as AddVoidReturnHelpers, k as AnyReturnHelpers, l as AutoSpyRxjsTypes, n as CompleteValueConfig, E as ErrorValueConfig, M as MethodReturns, p as Mutable, N as NextValueConfig, O as ObservableLike, r as OnlyObservablePropsOf, s as OnlyPropsOf, t as Overload, u as OverloadChoice, v as Overloads, P as PropStubValue, w as SpyDisposable, y as StrictSpyConfiguration, z as SubjectLike, B as SubjectOf, U as UnstubbedCall, G as UnstubbedCallHandler, H as UnstubbedRead, J as UnstubbedReadHandler, V as ValueConfig, K as ValueConfigPerCall, W as WithMockReturnValue } from './types-BM3BcWj1.js'; export { A as AutoMockConfiguration, a as AutoSpyDefaultEntry, C as CallbackSubscribable, E as EmissionObserver, b as EmissionOptions, c as EmissionSource, S as SubscribableLike, d as autoMocked, e as clearAutoSpyDefaults, f as createAutoMock, g as expectCompletion, h as expectEmission, i as expectEmissions, j as expectError, k as expectNoEmission, r as registerAutoSpyDefaults, s as setEmissionTimeout } from './expect-emission-S5asJaJP.js'; export { c as createFunctionSpy, d as describeDuplicateCopies, g as getPackageCopies } from './package-identity-Cn7yncmJ.js'; export { A as AccessorImplementations, O as OutsideHookReaction, R as RestoreProp, c as countMockedProps, m as mockAccessorsProp, a as mockReadonlyProp, b as mockReadonlyPropGetter, d as mockValueProp, r as reportPropsOutsideHooks, e as restoreMockedProps } from './prop-mock-C--a8rpU.js'; import { Mock, MockInstance } from 'vitest'; /** * Generate a fully-typed auto-spy from a class. * * @example * ```ts * const users: Spy = createSpyFromClass(UserService); * * users.getName.mockReturnValue('Ada'); * users.load.calledWith(1).resolveWith({ id: 1 }); * * // a callable that is an instance field, not on the prototype * createSpyFromClass(TaskStore, { instanceMethodsToSpyOn: ['reload'] }); * * // a generated API client, whose useful signature is the first of four * createSpyFromClass(VenuesService); * ``` * * @remarks * Discovery walks the **prototype chain**, so a callable assigned in the constructor is invisible to * it: an arrow-function property, an Angular `signal()` field, and every method of an ngrx * `signalStore()`, which live on the instance. Name those in `instanceMethodsToSpyOn` — or build the * double from the type instead, with `createAutoMock()`, which reads no prototype at all. */ declare function createSpyFromClass(ObjectClass: ClassType, methodsToSpyOnOrConfig?: ClassSpyConfiguration | OnlyMethodKeysOf[]): Spy; /** * Undo every {@link createSpyFromInstance} patch on `instance`, newest first. A no-op on an object * that was never spied, or that has already been restored. * * `restoreMockedProps()` (and therefore `setupAutoSpy()`) undoes the same patches as part of its * sweep — this is the targeted form, for an object that has to be real again inside the same test. * * @example * ```ts * restoreSpiedInstance(client); // client.send is the real method again * ``` */ declare function restoreSpiedInstance(instance: object): void; /** * Replace an existing object's methods with this library's spies, in place, and hand it back typed * as a double. * * @example * ```ts * const client = new PaymentsClient(config); // a real object the test already holds * const spy = createSpyFromInstance(client); * * spy.charge.calledWith(100).resolveWith({ ok: true }); * await service.pay(); // the code under test still holds `client`, and sees the spy * * restoreSpiedInstance(client); * ``` * * @remarks * Discovery takes the object's own function-valued fields *and* every prototype method up to but not * including `Object.prototype`, so an arrow-function property needs no `instanceMethodsToSpyOn` here * and `hasOwnProperty` is never replaced. The configuration is {@link createSpyFromClass}', minus * the two options that describe a double being built rather than an object being patched: * `lazySpies` (the members already exist, so there is nothing to defer) and `fillMissing` (an * instance is not an erased `abstract` declaration). A `registerAutoSpyDefaults` registration for * the instance's class applies here as it does to the class factory, the caller's own configuration * winning over it — while a bare object literal resolves no registration, the class its * `constructor` names being `Object`. * * `passthrough: true` keeps the object working: an unconfigured method runs the real one and is still * recorded, until the test configures it. Angular lifecycle hooks are then left unspied, since the * framework rather than the test calls them. * * The returned value **is** the argument. `using spy = createSpyFromInstance(client)` restores the * object at the end of the block rather than merely resetting it, which is the only sense `dispose` * can have for an object the consumer owns. */ declare function createSpyFromInstance(instance: T, methodsToSpyOnOrConfig?: InstanceSpyConfiguration | OnlyMethodKeysOf[]): Spy; /** * `createMock` — a typed stand-in built from the fields a test actually touches. * * It is one type assertion, written once, in a place a reviewer can find. That is the whole point: * a spec that needs an `ActivatedRouteSnapshot` with a single `data` key has to lie to the type * system somewhere, and the choice is between `{ data } as ActivatedRouteSnapshot` scattered * through the suite (which every `no-type-assertion` lint rule then has to be silenced for, one * `eslint-disable` at a time) and a named helper whose signature keeps the input checked. * Fields that do not exist on `T`, or exist with a different type, are still rejected — **at any * depth**, which is the half that matters after a model changes: a renamed or removed field is the * thing a spec fixture is least likely to notice and most likely to be lying about. * * Not a substitute for {@link createAutoMock}. The two answer different questions: * * | | `createMock()` | `createAutoMock()` | * | --- | --- | --- | * | Returns | `T` | `Spy` | * | Unseeded members | `undefined` | a lazily created, decorated function spy | * | Use it for | data shapes — DTOs, snapshots, config objects | collaborators whose calls you assert | * * Reach for `createAutoMock` whenever the double is something the code under test *calls*; reach * for `createMock` when it is something the code under test *reads*. */ /** * Build a `T` from the subset of its fields a test needs. * * ```ts * const route = createMock({ data: { title: 'Report' } }); * const config = createMock({ baseUrl: 'https://example.test' }); * ``` * * Nested objects may be partial too, so a tree the test reads one leaf of is one literal rather * than one call per level: * * ```ts * const config = createMock({ featureFlags: { core_retry_count: '3' } }); * ``` * * @param partial The fields to populate, checked against `T` at every depth. Everything else is * `undefined` at runtime while the static type stays `T` — so an assertion on an unseeded field is * a bug in the test, not in the helper. `createMock(undefined)` is the same call as `createMock()` * and answers `{}`, never `undefined`: a fixture that means "no value" passes `undefined` itself. */ declare function createMock(partial?: DeepPartial): T; /** Stamps out one fixture per call. What {@link createFixtureFactory} returns. */ type FixtureFactory = (overrides?: DeepPartial) => T; /** * Build one `T` from a complete default and the fields this test changes. * * ```ts * const article = createFixture
(ARTICLE_DEFAULTS, { header: { title: 'Draft' } }); * ``` * * `defaults` is a whole `T`, so a field the model removed is a compile error here rather than a * value nothing reads; `overrides` is checked at every depth against the same type. Sibling fields * of an overridden leaf are kept — `header.subtitle` above survives — while an overridden array * replaces the default one entirely. * * Reach for {@link createFixtureFactory} as soon as a second spec needs the same defaults; reach * for {@link createMock} when there are no meaningful defaults to begin with and the test reads two * fields of a large shape. * * @param defaults A complete `T`. Checked in full: missing and removed fields both fail here. * @param overrides The fields this fixture changes, checked against `T` at every depth. */ declare function createFixture(defaults: T, overrides?: DeepPartial): T; /** * Somewhere to put a fixture, so that the model is written out and checked exactly once. * * ```ts * // article.fixture.ts — one file, one checked literal * export const anArticle = createFixtureFactory
({ id: '1', header: { title: '', subtitle: '' }, tags: [], … }); * * // in a spec * const draft = anArticle({ header: { title: 'Draft' } }); * const tagged = anArticle({ tags: ['news'] }); * ``` * * The defaults are copied when the factory is built, so a later edit to the object that was passed * in cannot reach a fixture already handed out — and every call returns a fresh copy, so one test * mutating what it was given cannot decide what the next one sees. * * @param defaults A complete `T`, checked once, here. */ declare function createFixtureFactory(defaults: T): FixtureFactory; /** * Behaviour switches for {@link mockDeep}. * * There is deliberately **no `strict` / `onUnstubbedCall` here**, and the omission is the one worth * writing down, because a deep proxy answering every property read is exactly the "a typo never * fails" weakness this package holds against the proxy-per-property mocks. * * Strict mode cannot repair it. The guard fires on a *call* with nothing configured, and every hop * of a deep chain except the last is a property *read* — so `api.reop.user.find` still materialises * silently, and all a guard could change is what the final call returns. The half of the problem * that is worth solving is solved elsewhere and by a different mechanism: `createSpyFromClass` and * `createAutoMock()` know the member set (from the prototype, or from `T` at the call site), so * a name outside it is either absent or refused. `mockDeep` is the factory you reach for when you * have chosen not to enumerate the surface. * * And a guard would not be inert here. {@link resolveUnstubbedGuard} consults the suite-wide * default `setupAutoSpy({ strict: true })` installs, so wiring one in would make that single line * throw on every unconfigured call in every existing deep tree in the suite — including the ones * {@link selfReturning} exists to answer, where "unconfigured call" is defined to mean "hand the * node back" and the guard would run first. A suite-wide switch silently disabling a per-mock * option is not a trade worth making for a check that cannot see the reads anyway. * * {@link fallbackMockImplementation} is the per-mock answer to the same wish, and it is per-mock for * that reason: nothing suite-wide reaches a deep tree, so no option here is ever overridden by one. */ interface MockDeepOptions { /** * Make a **called** node hand itself back, so a fluent API chains through calls as well as * through property reads. * * Off by default, because it changes what an unconfigured call returns: `undefined` becomes the * node. Turn it on for the shape it exists for — a builder, a channel factory, a query chain: * * ```ts * const logger = mockDeep({}, { selfReturning: true }); * * logger.channel('app').info('started'); // used to be `undefined.info(...)` * expect(logger.channel('app').info).toHaveBeenCalledWith('started'); * ``` * * A node still answers with whatever it was told to answer with — `mockReturnValue`, * `calledWith(...).mockReturnValue(...)`, `resolveWith` all win — so this only fills the gap * where nothing was configured. The one case it gets wrong is a node deliberately configured to * return `undefined`; assert on the spy's calls rather than on its return value there. * * What a call hands back is typed as the *declared* return type, not as a spy — the object is a * node either way, so bridge it the same way an injected double is bridged when the helpers are * needed: * * ```ts * asSpy(query.where('id')).limit.mockReturnValue(query); * ``` * * **A called node answers itself, not its receiver**, which is the difference between a factory * and a `return this` builder. `editor.chain().focus().insertContent('x')` therefore records * `insertContent` on `chain.focus`, while the `chain` handle the spec holds has no calls at all — * the obvious assertion reports nothing although the chain ran. Assert down the path the chain * walked, or build the object with `createAutoMock(undefined, { selfReturning: ['focus', …] })`, * where the named methods answer one double, which is what an API of this shape does. */ selfReturning?: boolean; /** * What a call answers on a node **nobody configured** — every node of the tree, at any depth. * The usual one throws, so an unmocked query fails at its call instead of handing `undefined` on: * * ```ts * const db = mockDeep({}, { * fallbackMockImplementation: () => { * throw new Error('not mocked'); * }, * }); * ``` * * Precedence, first match wins: the node's own configuration (`mockReturnValue`, `resolveWith`, * `calledWith(...)`, `mustBeCalledWith(...)`), then this fallback, then `selfReturning` — which * still hands the node back when the fallback returned `undefined`, so the two compose. * * "Configured" is per node, not per call: a node with a `calledWith(1)` chain answers a call * with `2` with `undefined`, not with the fallback. Use `mustBeCalledWith` for "any other * arguments are a failure". */ fallbackMockImplementation?(...args: unknown[]): unknown; } /** * Create a recursively-mocked `T` from its type alone (no class). Nested object * access auto-creates chainable spies; seed concrete values via `overrides`. * * @example * ```ts * const api = mockDeep(); * * api.repo.user.find.calledWith(1).resolveWith({ id: 1 }); * await expect(api.repo.user.find(1)).resolves.toEqual({ id: 1 }); * ``` * * Note which hops are property reads and which are calls: the chain above works because * `repo` and `user` are *read*. A chain that goes through a **call** — `api.repo('users').find()` — * needs `{ selfReturning: true }`, otherwise the call returns `undefined` and the next hop throws. * See {@link MockDeepOptions.selfReturning}. * * A member read with a numeric key becomes a real array: `api.page.items[0].title = 'x'` makes * `api.page.items` an `Array` whose elements are deep nodes, with a real `length` and working * `map` / iteration. The handle read *before* the first index stays a node; read the member again. * * Every node carries `[Symbol.dispose]`, so `using api = mockDeep()` resets the whole tree — * children included — when the block ends, and the `afterEach` that existed only to reset one deep * mock can go. */ declare function mockDeep(overrides?: Partial, options?: MockDeepOptions): DeepMockProxy; /** A captor: matches any argument in its position, and keeps what it saw. */ interface ArgCaptor { /** * Every value this captor was offered, oldest first — **candidates**, not matches. * * The runner tries an assertion's expectation against each recorded call until one passes, and it * compares positions left to right, stopping at the first that disagrees. A captor matches * everything, so it never stops that walk: in `toHaveBeenCalledWith(captor, 3)` it is offered the * first argument of *every* call the runner tried, including the ones the `3` went on to reject. * A captor in the last position is offered only the calls that agreed on everything before it, * and a captor given a `where` filter records only what its filter accepts — which is the way to * make this list say "matched". */ readonly values: readonly T[]; /** The most recent captured value. Throws when nothing has been captured yet. */ readonly value: T; /** * Whether anything has been captured — for reading the list without triggering the throw. * * It says the captor was *offered* a value, which is not the same as the assertion having passed: * a failing `expect(spy).not.toHaveBeenCalledWith(captor, 99)` still offers it every call's first * argument. Assert on the expectation, not on this. */ readonly captured: boolean; /** Forget everything seen so far, so one captor can serve two phases of a test. */ reset(): void; /** The asymmetric-matcher hook. Records the value and always matches. */ asymmetricMatch(actual: unknown): boolean; /** What the runner prints for this captor inside a diff. */ toString(): string; /** What Vitest's pretty-format prints for it. */ toAsymmetricMatcher(): string; } /** How a captor may narrow what it matches — see {@link captureArg}. */ interface CaptureArgOptions { /** * Which values this captor accepts. Without one it matches everything, and is offered every call * the runner tries. */ where(value: unknown): boolean; } /** * Create a captor for one argument position. * * ```ts * const config = captureArg(); * * await service.save(payload); * * expect(fetchSpy).toHaveBeenCalledWith('/api/save', config); * expect(config.value.method).toBe('POST'); * expect(JSON.parse(String(config.value.body))).toEqual(payload); * ``` * * A captor matches **anything** in its position — that is the trade. It is the right tool when the * value is one the test could not have written down, and the wrong one when it could: prefer the * literal, or `expect.objectContaining`, whenever the assertion can state what it expects, because * a captor that matches everything moves the check from the expectation to the lines after it. * * Matching everything is also why `.values` is a list of candidates rather than of matches: the * runner offers the captor one argument per call it tries, and a captor to the left of a position * that rejects the call has already recorded it. A `where` filter is the way to say which calls * count, and the captor then records only those: * * ```ts * const post = captureArg({ where: (value) => (value as RequestInit).method === 'POST' }); * * expect(fetchSpy).toHaveBeenCalledWith('/api/save', post); * expect(post.values).toHaveLength(1); * ``` * * @param options `where` narrows what the captor matches; without it, it matches every value. * @typeParam T What the captured argument is. Unchecked at run time — a captor matches any value — * so this is the test author's claim about the position, exactly like a cast at `mock.calls`, but * made once and read everywhere the captor is used. */ declare function captureArg(options?: CaptureArgOptions): ArgCaptor; /** * Clear recorded calls on every spy inside `spy` (configured return values are kept). * * @example * ```ts * clearAutoSpy(users); // recorded calls dropped; calledWith / resolveWith config kept * ``` */ declare function clearAutoSpy(spy: object): void; /** * Reset every spy inside `spy`: clears recorded calls and reverts all `calledWith`/return-value configuration. * * @example * ```ts * resetAutoSpy(users); // calls AND config dropped — every method returns undefined again * ``` */ declare function resetAutoSpy(spy: object): void; /** * One registered config, addressable on its own. * * {@link ArgsMap.configured} renders every config as a string, which is all a failure message * needs. Attributing an actual call to *which* config it hit needs a handle instead — re-rendering * the text and comparing it is not one, because two chains can render the same list. * * Built on demand: nothing here is written by `set` or read by `get`, so the match path is * untouched by the existence of this surface. */ interface ConfiguredEntry { /** 1-based position in {@link ArgsMap.configuredEntries}, in the order a lookup consults them. */ readonly index: number; /** The config's argument list, rendered the way {@link ArgsMap.configured} renders it. */ readonly args: string; /** Whether an actual call's arguments hit this config. */ matches(actualArgs: unknown[]): boolean; } /** * Identity that survives being bundled twice. * * tsup inlines a copy of this class into every entry point that reaches it, so a double built by * `vitest-auto-spy` and read by `vitest-auto-spy/diagnostics` carries two different `ArgsMap` * constructors and `instanceof` answers false. That is how `explainSpy` reported `nothing * configured` for every configured double in the published package while every source-level spec * passed: the specs import one copy. A registry symbol is the same value in both. */ declare const ARGS_MAP_BRAND: unique symbol; declare class ArgsMap { #private; readonly [ARGS_MAP_BRAND] = true; set(key: unknown, value: unknown): void; get(key: unknown): unknown; /** * Every configured argument list, rendered the way a lookup key is — the *wanted* half of a * `mustBeCalledWith` failure. * * Nothing is rendered here: the exact configs are keyed by their own serialization, and an * asymmetric config carries the description built when it was registered. A failure message is * assembling text it already has. */ configured(): string[]; /** * Every configured argument list as a {@link ConfiguredEntry} — the same lists {@link configured} * returns, each carrying its own position and its own predicate. * * The order is the order a lookup consults them (exact configs first, then the asymmetric ones in * registration order), so the first entry whose `matches` holds is the config `get` would have * answered from. That is what lets a reader be told "call 3 hit config 2" instead of a list of * configs and a list of calls with nothing joining them. */ configuredEntries(): ConfiguredEntry[]; } /** * Error reporting for `mustBeCalledWith` — thrown when a spy configured with * required arguments is called with anything else. * * The failure prints **both sides**. It used to print only the actual arguments, which reads as an * accusation without an alternative: the spec author is told the call was wrong and left to scroll * back through the setup to find what "right" was. `td.explain` and sinon's `printf('%C')` both * print wanted next to actual for the same reason — the diagnosis is the comparison, not either * half of it. Every configured list is already serialized inside {@link ArgsMap}, so showing it * costs a lookup on a path that is about to throw anyway. */ declare const errorHandler: { /** * Report a call that no `mustBeCalledWith` config accepts. * * @param actualArgs The arguments the spy was called with. * @param functionName The spied method's name, used to render both sides as calls. * @param configured The `mustBeCalledWith` map, so the message can show what was wanted. */ throwArgumentsError: (actualArgs: unknown[], functionName: string, configured?: ArgsMap) => never; }; /** * View a spy as the class it stands for, for APIs typed against `T`. * * ```ts * const store = createSpyFromClass(CartStore); * renderShallow(CartComponent, { providers: [{ provide: CartStore, useValue: asInstance(store) }] }); * ``` * * **This is the fix for three compiler errors** that never mention the word "spy", so they are hard * to connect to this function — a spy assigned into a field, an object literal or a parameter typed * as the real class: * * ``` * TS2739: Type 'Spy' is missing the following properties from type 'PlayerLayerService': … * TS2740: Type 'Spy' is missing the following properties … * TS2345: Argument of type 'Spy' is not assignable to parameter of type 'RemoteInput'. * ``` * * Do not silence them with a double assertion: that also hides a genuine mismatch, and this * function is the narrow, reviewed version of the same step. * * **A `mockDeep` result goes through here too.** `DeepMockProxy` is a different mapped type — it * has no `accessorSpies` bag — so it did not fit the `Spy` parameter, and a deep mock had * nowhere to go: §2 sends you to `mockDeep` when the calls chain, and then the result could not be * handed to anything expecting `T`. The runtime story is identical to `createAutoMock`'s (a Proxy * that answers every member), so the bridge is the same one. */ declare function asInstance(spy: Spy): T; declare function asInstance(spy: DeepMockProxy): T; /** Each element of a tuple of spies, viewed as the class it stands for. */ type AsInstances = { -readonly [K in keyof Spies]: Spies[K] extends Spy ? T : Spies[K]; }; /** * {@link asInstance} for a whole argument list at once. * * ```ts * factory = webSsoAuthCheckFactory(...asInstances(account, authCheck, domainEvents, storage), document); * ``` * * The version with one wrapper per argument is not merely longer — it is *discovered* one argument * at a time. TypeScript stops checking a call at the first argument that does not fit, so a factory * taking five spies reports one `TS2345`, and the next one only after the previous is fixed and the * compiler is run again. Wrapping the list is a single edit against a single error. * * A non-spy in the list passes through unchanged, so a call that mixes spies with real values * (`document`, a config literal) does not have to be split. */ declare function asInstances(...spies: Spies): AsInstances; /** * View an instance as its spy surface — for a dependency that was provided as a spy but comes back * from an API typed against the real class (`TestBed.inject`, `injector.get`, a `viewChild`). * * @example * ```ts * asSpy(TestBed.inject(CartService)).checkout.resolveWith({ ok: true }); * ``` * * **This is the fix for `TS2352`**, the error the habitual `TestBed.inject(X) as Spy` produces — * a cast a `jest-auto-spies` suite has in every file, and which only starts failing once the specs * are compiled by the same toolchain as production code: * * ``` * TS2352: Conversion of type 'Router' to type 'Spy' may be a mistake because neither type * sufficiently overlaps with the other. Property 'accessorSpies' is missing in type 'Router'. * ``` * * For a **generic** class, pass the type argument explicitly. `TestBed.inject` infers * `Service` from the constructor rather than the declared default, and the `any` surfaces much * later as a mismatch between `AddPromiseSpyMethods` and `WithMockReturnValue<…>`: * * ```ts * const config = asSpy(TestBed.inject(FeatureFlagService)); * ``` * * Declare the variable as `Spy`, never as Vitest's `Mocked`: `Mocked` keeps `T`'s private * members, so it reports a spy as "missing the following properties: _modalOpened, body, …" — a * list of private field names that gives no hint the declaration is what is wrong. * * For a method with several **overloads**, `Parameters` / `ReturnType` — and therefore the spy's * helpers — read the *last* one, which on a generated API client is `observe: 'events'` rather than * the body-returning signature anybody calls. Ask for the first instead: * * ```ts * const cinemas = asSpy(TestBed.inject(VenuesService)); * ``` * * **Not for the object under test.** `asSpy(TestBed.inject(ServiceUnderTest))` is a habit carried * over from `jest-auto-spies`; the service a spec exercises is not a double, and the compiler * reports the mistake as `TS2740: … is missing the following properties: httpClient, platform, …`, * a list of that service's private fields with no hint that the call is what is wrong. Type it as * the class. */ declare function asSpy(instance: T): Spy; /** A constructor stand-in: `new SpyClass()` yields a fresh {@link Spy}, and every construction is recorded. */ interface ConstructorSpy { new (...args: unknown[]): Spy; /** Arguments of every `new` (and plain call), in order. */ calls: unknown[][]; /** The spy produced by each construction, in order. */ instances: Spy[]; } /** What {@link createSpyClass} can do beyond building the instances. */ interface SpyClassOptions { /** * Carry the class's **static** members onto the double. * * A constructor double usually replaces the real class where the code under test can see it — * `mockValueProp(globalThis, 'Worker', createSpyClass(Worker))` — and production code reads * statics off that name too: a `Worker.isSupported()` feature check, a `Client.create()` factory, * a `VERSION` constant. Without them the replacement is missing exactly the half `new` does not * cover, and the failure is `SpyClass.isSupported is not a function` inside production code. * * Off by default, because it *adds* members to the double: a static named like one of this * helper's own (`calls`, `instances`) would otherwise be shadowed by it. */ statics?: boolean; } /** * A spy that can be called with `new`. * * A runner mock (`vi.fn()`) rejects `new` as soon as it carries a `mockReturnValue`, so code under * test that does `new Foo()` — a `Worker`, an `IntersectionObserver`, a hand-rolled client — cannot * be served by one. This returns a real constructor function whose instances are full auto-spies. * * ```ts * const WorkerSpy = createSpyClass(BackgroundWorker); * mockValueProp(globalThis, 'BackgroundWorker', WorkerSpy); * * service.start(); * expect(WorkerSpy.calls[0]).toEqual(['./task.js']); * WorkerSpy.instances[0].postMessage.mockReturnValue(undefined); * ``` */ declare function createSpyClass(ObjectClass: ClassType, config?: ClassSpyConfiguration, options?: SpyClassOptions): ConstructorSpy; /** The branch of `T` that has `Key`, or `T` itself when the union has no such member to extract. */ type WithKey = [Extract>] extends [never] ? T : Extract>; /** The subscribable branch of `T`, or `T` itself when there is nothing to extract. */ type Subscribable = [Extract] extends [never] ? T : Extract; /** * The narrowing every optional read needs: neither `null` nor `undefined`, handed back. * * ```ts * const covers = narrow.defined(row.content?.covers); * * expect(covers.map((cover) => cover.code)).toEqual(['a', 'b']); * ``` * * `expect(value).toBeDefined()` and `assert.exists(value)` both assert this, and neither **returns** * it — so under a strict spec type-check each optional read costs two statements and a local that * exists for the narrowing alone (`const x = obj.member; assert.exists(x); … x …`). That shape * multiplies: one suite hit it fifteen times across four files, and twice it grew a helper function * per member of a stub. This returns the value, so the narrowing sits inside the expression that * needed it. * * It is not a replacement for `assert.exists` where the *assertion* is the point of the test. The * difference is which of the two the line is about: asserting that a value arrived, or reading a * value the test already knows arrived. */ declare function defined(value: T, label?: string): NonNullable; /** * The most common narrowing, without writing the guard: the branch that has this key. * * ```ts * const params = narrow.byKey(result.link, 'params').params; * ``` */ declare function byKey(value: T, key: Key): WithKey; /** * The subscribable branch of a `MaybeAsync` — an Angular guard or resolver return type. * * ```ts * const canMatch$ = narrow.observable(guard.canMatch(route, segments)); * ``` * * It exists here rather than as a call to rxjs's `isObservable` because that one narrows to * `Observable`, dropping the element type — so every call site adds a type argument back * by hand. The check is structural (`subscribe` is callable), so nothing in the core imports rxjs. */ declare function observable(value: T): Subscribable; /** * The callable shape of {@link narrow}, spelled out because the runtime value is a wrapped * function rather than a `function` declaration: `vi.defineHelper` returns a new function, so the * two overloads and the two attached helpers have to be declared rather than inferred. */ interface Narrow { (value: T, predicate: (candidate: T) => candidate is Narrowed, label?: string): Narrowed; (value: T, predicate: (candidate: T) => boolean, label?: string): T; /** {@link byKey} */ byKey: typeof byKey; /** {@link defined} */ defined: typeof defined; /** {@link observable} */ observable: typeof observable; } /** * Narrow a union to the branch a test knows it got. * * ```ts * const link = narrow(result.link, (candidate): candidate is DeeplinkWithParams => 'params' in candidate); * ``` * * @param value The union-typed value. * @param predicate A type guard — or a plain boolean check, which narrows nothing but still fails * in one place with a readable message. * @param label What was expected, quoted in the failure. Defaults to the predicate's source, which * for an arrow is usually the most accurate description available. */ declare const narrow: Narrow; /** * A call-order journal: one recorder every collaborator reports to, so the ORDER of calls across * many objects is itself the value a spec asserts. * * A spy answers whether its one method ran. Order across collaborators is a different question — * it lives *between* the spies, and the shapes available for it degrade quickly. One * `toHaveBeenCalled` per spy passes in any order: three green checks that would accept the * sequence backwards. `toHaveBeenCalledBefore` pins it only pairwise — a chain that grows with the * square of the collaborators, says nothing about a call the spec forgot to name, and fails with * "expected spy to be called before spy" rather than with the sequence that actually ran: * * ```ts * const dropCache = vi.fn(); * const flushTelemetry = vi.fn(); * const stopEngine = vi.fn(); * * shutdown(); * * expect(dropCache).toHaveBeenCalled(); // true in any of the six orders * expect(flushTelemetry).toHaveBeenCalled(); * expect(stopEngine).toHaveBeenCalled(); * ``` * * One journal the code under test writes into makes the sequence a single comparable value, and a * failure prints the real order as a diff: * * ```ts * expect(log.result()).toBe('drop-cache; flush-telemetry; stop-engine'); * ``` * * Ported from Angular's own `Log` (`packages/core/testing/src/logger.ts`), which Angular keeps * three copies of — core, router, forms — because it is the idiomatic answer wherever the subject * is a sequence: lifecycle hooks, guards, resolvers, teardown. The log is a collaborator, not a * spy on one — the production code records its own sequence into it, in Angular through DI: * * ```ts * const PANEL_LOG = new InjectionToken>('panel log'); * * @Component({ selector: 'panel', template: '' }) * class Panel { * private readonly log = inject(PANEL_LOG); * * ngOnInit(): void { this.log.add('init'); } * ngAfterViewInit(): void { this.log.add('ready'); } * ngOnDestroy(): void { this.log.add('destroy'); } * } * * const log = createLog<'init' | 'ready' | 'destroy'>(); * * TestBed.configureTestingModule({ providers: [{ provide: PANEL_LOG, useValue: log }] }); * TestBed.createComponent(Panel).destroy(); * * expect(log.result()).toBe('init; ready; destroy'); * ``` * * Nothing here knows Angular or any runner — the module imports nothing — so the same journal * works unchanged on Vitest, `bun test` and `node:test`. * * `T` is constrained to strings on purpose. The journal's worth in a failure is its rendering — * `result()` joins the entries with `'; '` — and what `fn()` labels a callback with is a name, a * word a human reads in that line. A literal union makes the vocabulary part of the type: * `createLog<'init' | 'ready' | 'destroy'>()` rejects a step the log never declared, where the * unconstrained log would record the typo and hand back a green test. */ /** The journal {@link createLog} hands back: the record, and the ways to write into it. */ interface CallLog { /** Append one entry — a collaborator's report of where it got to. */ add(value: T): void; /** * A callback that records `value` when it runs. For the places that want a handler rather than * a call — event listeners, lifecycle hooks, guard methods — `log.fn('save')` hands one over * already labelled. It is typed as taking nothing, so it slots wherever a handler fits and * ignores whatever the caller passes it. */ fn(value: T): () => void; /** Drop every entry: a fresh journal without a fresh identity, for reuse across tests. */ clear(): void; /** * The entries so far, in the order they were recorded. Each read is an independent copy — a * reference held before later calls stays the journal of that moment, and nothing a spec does * to a returned array rewrites the record. This is the one deliberate divergence from Angular's * `Log`, whose entries sit in a public, live, mutable array. */ readonly items: readonly T[]; /** The journal as one line — entries joined with `'; '`, `''` when nothing was recorded. */ result(): string; } /** * Start a call-order journal. * * ```ts * const log = createLog<'boot' | 'run' | 'halt'>(); * * log.add('boot'); * controller.onStart = log.fn('run'); * controller.stop(); // runs onStart * * expect(log.result()).toBe('boot; run'); * ``` */ declare function createLog(): CallLog; /** * A fixture built from a model instance, with some fields changed. * * Angular codebases model their API responses as classes with getters — `get isSubscribed()`, * `get isExpired()` — computed from the raw fields. A spec that needs "the same subscription, but * expired" then has two options, and both are broken in ways that are hard to see: * * - `{ ...subscription, isExpired: true }` drops every getter, because spread copies own * enumerable properties and a prototype accessor is neither. The literal still satisfies the * model's *type* only if the getters were optional; where it does compile, the component reads * `undefined` from a flag it should have got a value from. * - `Object.assign(new SubscriptionModel(), fields)` keeps the getters *live*, so each one runs * against a half-filled instance — and a getter written for real data throws on a fixture, from * inside the model, with a stack that names neither the spec nor the field it was missing. * * {@link withOverrides} takes the third option: read every accessor once, right now, while the * model is still whole, and hand back a plain object carrying the results as data. */ /** * Snapshot a model instance as plain data, then apply `overrides`. * * ```ts * const expired = withOverrides(SUBSCRIPTION, { isExpired: true }); * ``` * * A getter that throws contributes `undefined` rather than failing the snapshot: it is reading data * a fixture may legitimately not carry, and a spec that goes on to assert on that field will say so * far more clearly than a stack inside the model would. * * The result is a plain object, so its getters no longer recompute — which is the point. Build the * next variation from the original model, not from a snapshot. */ declare function withOverrides(model: T, overrides?: Partial): T; /** * A test double the code under test can call with `new`. * * This is the single most expensive mistake of a Jest → Vitest move, and it is invisible at the * line where it is made. Under Jest, `jest.fn().mockImplementation(() => instance)` served `new`, * so every suite old enough to have mocked a global constructor — `new Image()` for a tracking * pixel, `new Worker()`, `new WebSocket()`, a payment or player SDK published as a global class — * carries that shape. Vitest only forwards `new` to an implementation that is itself constructible, * and an arrow function is not: the call is recorded, the body never runs, and `new` hands back an * empty object. Vitest says so on stderr ("the mock did not use 'function' or 'class'"), but that * line is nowhere near the failure, which arrives later as `TypeError: (cb) => {…} is not a * constructor` with a stack pointing into production code — or as a green test for the wrong * reason, when the resulting `undefined` is swallowed by a `catch` the assertion is happy with. * * The helpers here cannot be written wrongly. {@link mockConstructor} builds the `function` * implementation for you and still returns a real runner mock, so `toHaveBeenCalledWith`, * `mockClear` and the rest keep working; {@link stubConstructor} additionally puts it on a global * (or any object) through {@link mockValueProp}, so `restoreMockedProps()` takes it off again. */ /** * A runner mock that is also a constructor. * * It is the runner's own mock object, so every matcher and every `mock*` method applies. The two * additions are the `new` signature — which is the whole point — and {@link instances}, the objects * the factory produced, in construction order. */ interface ConstructorMock extends Mock<(...args: TArgs) => T> { new (...args: TArgs): T; /** * Everything `new` handed back, in construction order. * * Owned by this helper rather than read off the runner, so it is *not* emptied by `mockClear()` — * clearing the call record and forgetting the objects a spec still holds assertions against are * different wishes, and the runner's own `mock.instances` is there for the first one. */ readonly instances: T[]; } /** * Build a constructible runner mock whose instances come from `factory`. * * ```ts * const Syslog = mockConstructor(() => ({ log: vi.fn(), close: vi.fn() })); * * mockValueProp(globalThis, 'Syslog', Syslog); * service.start(); * * expect(Syslog).toHaveBeenCalledWith({ host: 'logs.test' }); * expect(Syslog.instances[0].log).toHaveBeenCalledTimes(1); * ``` * * @param factory Produces the instance for one construction; it receives the `new` arguments and * must return an object (see {@link ConstructorMock}). * @param name Shown in assertion output and in this helper's own error messages. */ declare function mockConstructor(factory: (...args: TArgs) => T, name?: string): ConstructorMock; /** * Replace a constructor on a global (or on any object) with a {@link mockConstructor}. * * The generalisation of `stubIntersectionObserver` & friends to everything else the platform * publishes as a class and production code reaches for directly: `Image`, `Worker`, `WebSocket`, * `Audio`, `EventSource`, a vendor SDK on `window`. * * ```ts * const Image = stubConstructor(globalThis, 'Image', () => ({ src: '' })); * * tracker.ping(); * * expect(Image).toHaveBeenCalledTimes(1); * expect(Image.instances[0].src).toBe('https://tns.example/hit'); * ``` * * Installation goes through {@link mockValueProp}, so `restoreMockedProps()` — which * `setupAutoSpy()` already runs after every test — puts the real constructor back. That matters * under `isolate: false`, where a hand-assigned global survives into the next file and fails there. * * @param target The object owning the constructor — usually `globalThis`. * @param property Its key. * @param factory Produces the instance for one construction. */ declare function stubConstructor(target: object, property: PropertyKey, factory: (...args: TArgs) => T): ConstructorMock; /** * Give the runtime `turns` real event-loop turns, whatever the timers are doing. * * Reach for it when the thing being awaited crosses out of the zone / out of the test's own * promise chain: a dynamic `import()` triggered by production code, a native `async` function * inside a dependency, a stub that resolves a turn later. Not an Angular `httpResource()` / * `resource()` — those need a *tick*, which is `settleResource()`, not this. * * ```ts * component.openModal(); // production code does `await import('./modal')` * await flushEventLoop(); * expect(modal.open).toHaveBeenCalled(); * ``` * * It yields a *task* turn (a `postMessage` task), which is what module loading and native `async` * continuations need. It deliberately does not run pending `setTimeout` callbacks — those are a * different task source, and a helper that also fired timers would be `advanceTimersByTime` under * another name. * * @param turns How many turns to take. One is enough for a single hand-off; raise it when a chain * hands off more than once (a promise resolved from another promise's macrotask continuation). */ declare function flushEventLoop(turns?: number): Promise; /** Options for {@link flushEventLoopUntil}. */ interface FlushUntilOptions { /** How many real turns to spend before giving up. Default 20. */ turns?: number; /** What was being waited for, quoted in the failure — `'the resource to leave loading'`. */ label?: string; } /** * Take real event-loop turns until `isDone()` says so, then stop — or fail saying it never did. * * The shape behind every hand-rolled "settle" helper: a lazily-loaded chunk becoming reachable, an * SDK reporting itself ready, a queue draining. Written by hand it is a fixed number of turns, tuned * by trial until the suite goes green — which is both slower than it needs to be (it always waits * the maximum) and quietly fragile (one more hand-off in a dependency and the number is wrong * again). * * ```ts * client.warmUp(); * * await flushEventLoopUntil(() => client.isReady(), { label: 'the SDK handshake' }); * expect(client.session()).toBeDefined(); * ``` * * **Not for an Angular resource** — use `settleResource()` from `vitest-auto-spy/angular` for that. * This helper takes real event-loop turns and never *ticks*, and an `httpResource()` issues no * request at all until something does: measured, a resource awaited here finishes the whole budget * having made zero requests, then fails saying the condition was never met. The docstring used to * claim that use case and show it as the example; it never worked. * * The budget is what separates this from a `while (true)`: a condition that never becomes true is * the normal way for this to be used wrongly — the request was never made, the stub never resolved * — and a test that hangs until the runner's timeout reports the file, not the wait. * * @param isDone Checked before the first turn, then after every turn. * @param options Turn budget and the label used in the failure. */ declare function flushEventLoopUntil(isDone: () => boolean, options?: FlushUntilOptions): Promise; /** * Load a module the way the code under test does, then let its continuation run. * * Two situations, one mechanism. Production code that does `await import('./thing')` on a click * leaves the spec with no promise to await — awaiting the *same* specifier here resolves against * the same module instance, and the following real turns let the component's own continuation * drain. The second situation is a bundled Angular suite where a symbol re-exported through a * barrel reads as `undefined` until its chunk has been evaluated; awaiting the import is what * evaluates it. * * ```ts * fixture.debugElement.query(By.css('button')).nativeElement.click(); * await settleDynamicImport(() => import('./profile-select.modal')); * expect(dialog.open).toHaveBeenCalled(); * ``` * * `fakeAsync` / `tick()` / `flushMicrotasks()` cannot replace this: they drive Angular's zone * queues, and the module loader is not one of them. * * @param load The same `() => import(...)` the code under test performs. * @param turns Real event-loop turns to take after the module resolved. Default 1. * @returns The module namespace, so the spec can also read what it just made sure exists. */ declare function settleDynamicImport(load: () => Promise, turns?: number): Promise; /** Options for {@link assertMocked}. */ interface AssertMockedOptions { /** The specifier the spec passed to `vi.mock`, quoted back in the failure. */ specifier?: string; /** * Names that must be mocks, rather than "at least one export is". * * Worth naming when the factory stubs part of a module and re-exports the rest: without a list, * a factory that lost the one export the test drives still looks mocked. * * An **empty** list is rejected rather than accepted: `exports: []` — which is what * `Object.keys(stubs)` or a filtered constant produces when it comes out empty — used to take the * named-exports branch, find nothing to check and return, so the one call in the file whose job * is to prove the mock applied proved nothing. */ exports?: readonly string[]; } /** * Fail now, naming the module, if the `vi.mock()` this spec relies on did not take effect. * * ```ts * import * as engine from '@app/pricing-engine'; * * vi.mock('@app/pricing-engine'); * * beforeEach(() => { * assertMocked(engine, { specifier: '@app/pricing-engine', exports: ['createEngine'] }); * }); * ``` * * @param namespace The module namespace object the spec imported. * @param options The specifier to name in the message, and the exports that must be mocks. * @returns `namespace`, so the check can wrap the import at the point of use. */ declare const assertMocked: (namespace: T, options?: AssertMockedOptions) => T; /** Options for {@link moduleNamespace}. */ interface ModuleNamespaceOptions { /** * Read an export the factory did not define as `undefined` instead of throwing. Default `false`. * * Vitest guards a factory result and fails on an unknown key (`No "x" export is defined on the * mock`), which is the better default: it catches a factory that drifted from the module. Jest * did not, so a suite ported from it can be reaching for exports it never stubbed — and there the * guard fails inside production code, several frames from the assertion that would have said * what the test actually wanted. Turn it on to port first and tighten later. */ lenient?: boolean; /** * Turn every function export into a spy that runs the real function until the test configures it. * Default `false`. Pass the actual module — `moduleNamespace(await importOriginal(), { passthrough: true })` * — for Vitest's `{ spy: true }` with `calledWith` and `resolveWith` on top. Classes and values stay as they are. */ passthrough?: boolean; } /** * A module mock's exports, plus the `default` an interop probe looks for. * * A factory that spells out its own `default` keeps it: a module whose default export is the thing * under test (`dayjs`, a class from `shaka-player`) is the common case, and handing the interop probe * the namespace instead used to turn `default(…)` into `default is not a function` — the very failure * this helper exists to remove. */ type ModuleNamespace = T & { default: T extends { default: infer Default; } ? Default : T; __esModule: true; }; /** * Build the object a `vi.mock` factory should return, with `default` and `__esModule` in place. * * ```ts * vi.mock('shaka-player', () => moduleNamespace({ Player: mockConstructor(() => playerStub) })); * ``` * * The missing `default` is the failure this removes. Any dependency written to run as both CommonJS * and ESM probes itself with `mod.default ?? mod`, and a factory returning bare named exports makes * Vitest throw `No "default" export is defined on the mock` — from inside that dependency, with a * stack that names the library rather than the factory three lines up in the spec. * * @param given What the mocked module exposes. * @param options See {@link ModuleNamespaceOptions.lenient} and {@link ModuleNamespaceOptions.passthrough}. */ declare function moduleNamespace(given: T, options?: ModuleNamespaceOptions): ModuleNamespace; /** * `adoptMock`: the library's helpers on a runner mock something else built, most often a `vi.mock` * factory. Taken over in place, because the code under test already holds that mock. */ /** Options for {@link adoptMock}. */ interface AdoptMockOptions { /** The name this spy's messages use — a `mustBeCalledWith` miss names it. Default: the mock's own name. */ name?: string; } /** What {@link adoptMock} hands back: the same mock, typed as a function spy of the signature it mocks. */ type AdoptedMock = AddSpyMethodsByReturnTypes ? Signature : F extends { mock: unknown; } ? (...args: Parameters) => ReturnType : F>; /** * Give a runner mock the library's helpers without replacing it, keeping what it already recorded. * * ```ts * import { loadUser } from './api'; * * vi.mock('./api', () => ({ loadUser: vi.fn() })); * * it('shows the user', async () => { * adoptMock(loadUser).calledWith(7).resolveWith({ id: 7, name: 'Ada' }); * // … * }); * ``` * * A call nobody configured answers what the mock answered before adoption. Adopting a spy this * library built, or the same mock twice, hands it back unchanged. * * @param mock A `vi.fn()` (or `bun:test` `mock()`, `rstest.fn()`) — typed as the module's function or as the mock. * @param options See {@link AdoptMockOptions}. * @returns The same mock, typed as a function spy. */ declare const adoptMock: (mock: F, options?: AdoptMockOptions) => AdoptedMock; export { AddSpyMethodsByReturnTypes, type AdoptMockOptions, type AdoptedMock, type ArgCaptor, type AsInstances, type AssertMockedOptions, type CallLog, type CaptureArgOptions, ClassSpyConfiguration, ClassType, type ConstructorMock, type ConstructorSpy, DeepMockProxy, DeepPartial, type FixtureFactory, type FlushUntilOptions, Func, InstanceSpyConfiguration, type MockDeepOptions, type ModuleNamespace, type ModuleNamespaceOptions, OnlyMethodKeysOf, Spy, type SpyClassOptions, SpyOptions, adoptMock, asInstance, asInstances, asSpy, assertMocked, captureArg, clearAutoSpy, createFixture, createFixtureFactory, createLog, createMock, createSpyClass, createSpyFromClass, createSpyFromInstance, errorHandler, flushEventLoop, flushEventLoopUntil, mockConstructor, mockDeep, moduleNamespace, narrow, resetAutoSpy, restoreSpiedInstance, settleDynamicImport, stubConstructor, withOverrides };