export { o as AngularTokenProvider, A as AngularValueProvider, a as AutoSpiedInstance, C as ComponentInputs, b as CreateWithAutoSpiesOptions, R as RenderShallowOptions, c as ResourceStatusLike, d as RunCounter, S as SettleResourceOptions, e as ShallowRender, f as SpyRegistry, g as StableOptions, h as createWithAutoSpies, i as flushEffects, j as injectSpy, p as provideAutoSpy, q as provideAutoSpyForToken, r as renderShallow, k as runEffect, s as setInputs, l as settleResource, m as stable, t as trackEffectRuns, n as trackRecomputations } from './track-signal-runs-BSVObkds.js'; import { InjectionToken, Provider, Type, WritableSignal, Signal } from '@angular/core'; import { A as AutoMockConfiguration, D as DeepReadonly } from './expect-emission-S5asJaJP.js'; export { C as CallbackSubscribable, E as EmissionObserver, b as EmissionOptions, c as EmissionSource, S as SubscribableLike, g as expectCompletion, h as expectEmission, i as expectEmissions, j as expectError, k as expectNoEmission, s as setEmissionTimeout } from './expect-emission-S5asJaJP.js'; import { o as DeepPartial, C as ClassType, m as ClassSpyConfiguration, S as Spy, q as OnlyMethodKeysOf, h as AddSpyMethodsByReturnTypes } from './types-BM3BcWj1.js'; import { TestAPI } from 'vitest'; export { I as InjectionLog, T as TrackInjectionsOptions, a as TrackedProvider, t as trackInjections } from './track-injections-Dig5t28T.js'; export { A as AccessorImplementations, R as RestoreProp, c as countMockedProps, m as mockAccessorsProp, a as mockReadonlyProp, b as mockReadonlyPropGetter, d as mockValueProp, e as restoreMockedProps } from './prop-mock-C--a8rpU.js'; import '@angular/core/testing'; /** * `registerAutoSpyDefaults` as `vitest-auto-spy/angular` exports it: the core's overloads, plus the * key the core cannot name — an `InjectionToken`. * * The registry never cared what its key is: it is keyed by object identity — weakly, so a default * dies with its class — and a token is an object exactly as a class is. What kept tokens out was the signature, because the core entry is * framework-agnostic and may not mention `InjectionToken`. So the registration is the same call and * the same registry; only this entry, which already depends on Angular, can type it. * * A token's double is built from its type (`createAutoMock`), so what a registration may say is what * that factory takes — `returns`, `selfReturning`, `observablePropsToSpyOn`, `strict`, `name` — plus * `overrides`, which `provideAutoSpyForToken` otherwise takes as its second argument. The merge is the * class one: lists unioned, `returns` / `overrides` merged key by key with the call site winning, * scalars decided by the call site when it names them. */ /** What a token's registration holds: {@link AutoMockConfiguration} plus the seeds `provideAutoSpyForToken` takes second. */ interface AutoSpyTokenDefaults extends AutoMockConfiguration { /** Values the double answers with rather than spies on — merged key by key under the call site's own seeds. */ overrides?: DeepPartial; } /** * The many-at-once form's constraint, with a second kind of row: a class row checked against its * class, a token row against the type the token carries. The same placement as the core's — in the * constraint, where it is instantiated after the overload is chosen (see `spy-defaults.ts`). */ type AngularAutoSpyDefaultEntries = { [Row in keyof Entries]: Entries[Row] extends readonly [infer Key, unknown] ? Key extends ClassType ? readonly [ClassType, DeepReadonly>] : Key extends InjectionToken ? readonly [InjectionToken, DeepReadonly>] : never : never; }; /** * Register the configuration every double of a class — or of an `InjectionToken` — should start from. * * The core's `registerAutoSpyDefaults`, over the same registry, with one more key: a token, read back * by `provideAutoSpyForToken(TOKEN)` the way `provideAutoSpy(Class)` reads a class's registration. * The per-class form and the table form are unchanged, and a table may mix class rows and token rows. * * @example * ```ts * registerAutoSpyDefaults(LOGGER, { returns: { info: undefined, err: undefined }, selfReturning: ['channel'] }); * * providers: [provideAutoSpyForToken(LOGGER)]; // starts from the registration * ``` * * `T` of a class key comes from the class alone, as in `provideAutoSpy` — a generic class keeps its * declared default next to an accessor list and `returns`. A token key needs no such help: `T` * inferred from the `InjectionToken` reference outranks anything the configuration offers. */ declare function registerAutoSpyDefaults(ObjectClass: ClassType, config: NoInfer>): void; declare function registerAutoSpyDefaults(token: InjectionToken, config: AutoSpyTokenDefaults): void; declare function registerAutoSpyDefaults & readonly (readonly [unknown, unknown])[]>(entries: Entries): void; /** Drop one class's or one token's registration, or every one of them. */ declare function clearAutoSpyDefaults(key?: ClassType | InjectionToken): void; /** * `extendWithAutoSpies` — the `TestBed` half of a spec, moved into Vitest fixtures. * * The shape it replaces is in every Angular suite ever migrated: * * ```ts * let cart: Spy; * let api: Spy; * * beforeEach(() => { * TestBed.configureTestingModule({ providers: [provideAutoSpy(CartService), provideAutoSpy(ApiService)] }); * cart = injectSpy(CartService); * api = injectSpy(ApiService); * }); * ``` * * Five lines of ceremony per dependency, a `let` that is `undefined` between tests and a * declaration whose type has to be repeated by hand. Vitest 4.1's builder form of `test.extend` * infers the type from the factory, so the whole block becomes one statement — and a test that does * not destructure `api` never builds it. * * **Why one call rather than a chain of `.extend`s.** The obvious composing form — * `base.extend('cart', autoSpy(CartService)).extend('api', autoSpy(ApiService))` — cannot work, and * the reason is `TestBed`, not typing. Fixtures resolve lazily and independently, so `cart` would * configure the testing module and inject, which *instantiates* it; `api` would then reach * `configureTestingModule` after instantiation and fail with Angular's own * "Cannot configure the test module when the test module has already been instantiated". Every * provider has to be known before the first injection, so the helper has to see them all at once. * * A `beforeEach` that configures the module further still composes, because it runs before any * fixture resolves and `configureTestingModule` may be called repeatedly right up until the first * injection. A `beforeEach` that *injects* does not — and that is the one case this cannot paper * over, since by then Angular has already made the decision. */ /** * One entry of the map: a class, a class with the configuration {@link provideAutoSpy} takes, or an * `InjectionToken` whose type argument is what the double is built from. */ type AutoSpyFixture = ClassType | InjectionToken | readonly [ClassType, ...unknown[]]; /** What each entry of the map becomes in the test's context. */ type SpiedFixtures = { [Name in keyof Spec]: Spec[Name] extends InjectionToken ? Spy : Spec[Name] extends readonly [ClassType, ...unknown[]] ? Spy : Spec[Name] extends ClassType ? Spy : never; }; /** Options for {@link extendWithAutoSpies}. */ interface ExtendWithAutoSpiesOptions { /** * Providers registered alongside the spies, in the same `configureTestingModule` call — the * component under test, a real service the spec deliberately keeps, `provideHttpClient()`. * * They go in *after* the generated ones, so a provider named here wins over the auto-spy that * would otherwise be made for the same token — Angular resolves duplicate providers last-one-wins. */ providers?: Provider[]; } /** * Turn a map of dependencies into typed `TestBed` fixtures. * * ```ts * import { test as base } from 'vitest'; * import { extendWithAutoSpies } from 'vitest-auto-spy/angular'; * * const test = extendWithAutoSpies(base, { * cart: CartService, * api: [ApiService, { methodsToSpyOn: ['get'] }], * passcode: PASSCODE_TOKEN, * }); * * test('checks out', async ({ cart }) => { * cart.checkout.resolveWith(true); * * await expect(cart.checkout(1)).resolves.toBe(true); * }); * ``` * * The testing module is configured **once per test**, the first time any of the fixtures is * touched, and the flag that remembers it is released on cleanup — a fixture object outlives the * test that used it, and a module configured for the previous one is the shape that makes an * Angular suite fail depending on order. * * Needs **Vitest 4.1 or newer**: the builder form of `test.extend` it is written against is what * infers a fixture's type from its factory, and older versions have only the object form. * * @param base The `test` to extend — Vitest's own, or one already carrying fixtures of yours. * @param spec Fixture name to dependency. A bare class, `[Class, config]` with what * {@link provideAutoSpy} accepts, or an `InjectionToken`. */ declare function extendWithAutoSpies>(base: TestAPI, spec: Spec, { providers }?: ExtendWithAutoSpiesOptions): TestAPI>; /** * Overriding what a *component* provides, and the diagnostic for the bundle that makes it necessary. * * `provideAutoSpy` registers a provider on the testing module, and a testing-module provider loses * to one the component declares in its own `@Component.providers` — route-scoped services, * per-component stores, `provideX()` helpers. Nothing reports the loss: the spec configures a spy, * the component keeps the real service, and the assertion fails somewhere else entirely. * * The documented answer is `TestBed.overrideProvider(Token, { useValue: spy })`, and it has two * traps of its own. * * The first is a silent no-op. `overrideProvider(Service, provideAutoSpy(Service))` passes a * *provider* where `{ useValue }` is expected; Angular neither throws nor warns, and the test runs * against the real service. {@link overrideAutoSpy} exists so that the value handed to * `overrideProvider` cannot be the wrong shape. * * The second is that `overrideProvider` only reaches a component the TestBed compiler knows about. * A standalone component instantiated through a parent's template is not in the testing module's * `imports`, so the override never applies to it. {@link overrideComponentProvider} queues the * component as well — which is also what keeps people away from `overrideComponent`, whose JIT * recompilation blanks the component's whole dependency scope under an AOT bundle (see * {@link assertNgModuleScopes}). * * Queuing the component removes the *usual* cause of a silent no-op; it does not prove the override * landed. So {@link overrideComponentProvider} also checks, on the next `TestBed.createComponent`, * that the component's own injector really answers with the spy — see {@link verifyOnNextCreate} * for why that check is always on rather than an opt-in diagnostic. * * The same AOT bundle is behind the two assertions at the end of the file. * {@link assertNgModuleScopes} covers the module whose scope the bundler stripped; * {@link assertComponentDefIntact} covers the component whose own definition was built while the * chunk holding one of its providers had not run yet. Neither fixes a build — both replace a stack * inside `@angular/core` with a line naming the thing that is missing. */ /** The `{ useValue }` shape `TestBed.overrideProvider` expects, carrying an auto-spy. */ interface AutoSpyOverride { useValue: Spy; } /** * An auto-spy wrapped as a `TestBed.overrideProvider` value. * * ```ts * const payments = overrideAutoSpy(PaymentMethodService); * * TestBed.configureTestingModule({ imports: [CheckoutComponent] }).overrideProvider(PaymentMethodService, payments); * payments.useValue.charge.resolveWith({ ok: true }); * ``` * * Use it — not `provideAutoSpy` — whenever the dependency is declared in a component's own * `providers`, because a module-level provider does not win there. * * `T` comes from the class alone, as in `provideAutoSpy` — a generic class keeps its default. */ declare function overrideAutoSpy(ObjectClass: ClassType, methodsToSpyOnOrConfig?: NoInfer | OnlyMethodKeysOf[]>): AutoSpyOverride; /** * Replace a dependency a component declares in its own `providers`, and make sure the override can * reach it. * * Queues `component` with the TestBed compiler — as an import when it is standalone, as a * declaration otherwise — because `overrideProvider` is applied while a component is compiled, and * a component the testing module never mentions is never compiled by it. * * ```ts * const menu = overrideComponentProvider(CatalogPageComponent, NavigationBuilderService); * * menu.build.mockReturnValue([]); // the component's own provider is now the spy * const fixture = TestBed.createComponent(HostComponent); * ``` * * Do not reach for `TestBed.overrideComponent` here: it forces a JIT recompilation of the * component, and in an AOT test bundle that recompilation resolves its directives and pipes from a * runtime scope the bundler has stripped — leaving the component with none of them. */ declare function overrideComponentProvider(component: Type, ObjectClass: ClassType, methodsToSpyOnOrConfig?: NoInfer | OnlyMethodKeysOf[]>): Spy; /** * Fail before rendering when a component's own definition has holes in it. * * Providers are **baked into `ɵcmp` when the component's module executes**, not read at * `createComponent` time. So when a bundler splits a barrel into a chunk that has not run yet, the * definition is built with `undefined` where a provider or a scope dependency should be, and Angular * discovers it much later, from inside itself: * * ``` * TypeError: Cannot read properties of undefined (reading 'provide') * ❯ resolveProvider render3/di_setup.ts:95 * ``` * * The stack names neither the barrel, nor the symbol, nor the component — and the spec it breaks is * usually one nobody touched, because chunk boundaries move with file *contents*: editing a type in * a neighbouring file is enough. Both documented cures fail, too, and for the same reason: an * `await import()` in `beforeEach` is already too late, and a static import at the top of the spec * does not fix the order this bundler emits. * * ```ts * assertComponentDefIntact(HoverMenuComponent); * const fixture = TestBed.createComponent(HoverMenuComponent); * ``` * * The same call answers the related `Cannot read properties of undefined (reading 'ɵcmp')` from * `imports: [Cmp]`, where the class reference itself is the thing that never arrived. * * This does not fix the build — that is a bundler configuration question — but it turns a * half-hour investigation into one line, and points it away from the spec. * * @param components The component (or directive) classes a spec is about to render or import. */ declare function assertComponentDefIntact(...components: unknown[]): void; /** * Fail early when an NgModule imported into the TestBed contributes nothing at runtime. * * An AOT test bundle — which is what `@angular/build:unit-test` produces, and what a Jest suite * moving to the native builder starts getting — drops `ɵɵsetNgModuleScope`, the call that records a * module's `declarations` and `exports` for the runtime. Nothing notices while AOT is in charge, * because the flat list of dependencies is already baked into each `ɵcmp`. The TestBed is the one * consumer that reads the scope at runtime, so `imports: [DirectivesModule]` silently contributes * zero directives, and the failure arrives as any of: * * ``` * NG0303: Can't bind to 'appTruncate' since it isn't a known property of 'div' * NG0301: Export of name 'focusable' not found! * NG0304: 'ui-smart-row' is not a known element * (nothing at all — an attribute directive simply never instantiates) * ``` * * None of them names the module. Call this with the modules a spec imports *for their declarations* * and the diagnosis becomes one line. * * ```ts * assertNgModuleScopes(DirectivesModule, PipesModule); * TestBed.configureTestingModule({ imports: [DirectivesModule, PipesModule] }); * ``` * * A module that genuinely declares nothing — a providers-only module — also has an empty scope, so * only pass modules you expect to bring directives, components or pipes. */ declare function assertNgModuleScopes(...modules: unknown[]): void; /** Which change-detection mode a spec file expects. */ type AngularTestEnvMode = 'zone' | 'zoneless'; /** What {@link setupAngularTestEnv} needs to know. */ interface AngularTestEnvOptions { /** * Whether the file at `testPath` runs zoneless. Called once per spec file — the path is the file, * and a file does not change its mind halfway through — so a `startsWith` / `includes` over the * path is all it should ever need to be. */ zoneless: (testPath: string) => boolean; /** Initialise the zone environment — `setupZoneTestEnv()`, or your own `initTestEnvironment` call. */ initZone: () => void; /** Initialise the zoneless environment. */ initZoneless: () => void; } /** * Install the Angular testing environment each spec file needs, switching platforms when it changes. * * Call it from the project's setup file, in place of the single `setupZoneTestEnv()` / * `setupZonelessTestEnv()` that a one-mode repository has. * * The mode is remembered per **worker**, not per file: under `isolate: false` the second and every * later file of a run in the same mode costs nothing at all. */ declare function setupAngularTestEnv(options: AngularTestEnvOptions): void; /** * A host component for testing a directive — correct for the compiler *and* for the TestBed. * * Testing an attribute directive means declaring a small host in the spec, and under the native * `@angular/build:unit-test` builder the obvious way to write one is wrong in a way nothing * reports. The two halves of Angular disagree about where `imports` is resolved: * * - `imports` on a **`@Component`** is resolved by the AOT compiler at build time, and the flat * list of dependencies is baked into `ɵcmp`. An `NgModule` there works. * - `imports` on **`TestBed.configureTestingModule`** is resolved by `TestBedCompiler` at runtime, * from `ɵmod` — and `ɵɵsetNgModuleScope` is not emitted into a test bundle, so every NgModule has * an empty runtime scope. The same line contributes nothing. * * So `imports: [DirectivesModule]` is alive in one place and dead in the other, and the failure — * `NG0303: Can't bind to 'appTruncate' since it isn't a known property of 'div'` — points at the * `@NgModule` where the directive is correctly declared. A host written `standalone: false` is worse * still: it is compiled outside any scope at all, so it has no `NgClass`, no `AsyncPipe`, nothing. * * This factory is that knowledge, applied: the host is always standalone, and `scope` becomes the * **component's** imports rather than the testing module's. */ /** What Angular accepts in a standalone component's `imports`: a class, or a nested array of them. */ type ScopeEntry = Type | readonly ScopeEntry[]; /** What the host needs to exist. */ interface DirectiveHostOptions { /** The host template — the place the directive under test is used. */ template: string; /** * What the template may use: the `NgModule` that declares the directive, a standalone directive * or component, a pipe. Becomes the host component's own `imports`. */ scope?: readonly ScopeEntry[]; /** Initial values for the host's inputs, and the type `componentInstance` is read through. */ props?: Props; /** Host element selector, when the template of a parent refers to it. Defaults to `auto-spy-host`. */ selector?: string; } /** * Build a standalone host component for a directive under test. * * ```ts * const Host = createDirectiveHost({ * template: `
`, * scope: [DirectivesModule], * props: { enabled: false, text: 'hello' }, * }); * * TestBed.configureTestingModule({ imports: [Host] }); * * const fixture = TestBed.createComponent(Host); * fixture.componentInstance.enabled = true; // typed from `props` * ``` * * `props` are copied onto each instance, so two fixtures never share them; a nested object is * copied by reference, as an object literal in a spec always is. */ declare function createDirectiveHost>(options: DirectiveHostOptions): Type; /** * `createComponentStub` — a stand-in for a child component, directive or pipe, built from the real * one's compiled definition so the two cannot drift apart. * * The hand-written stub is a class in the spec that restates the child's selector, inputs and * outputs. Nothing checks the copy: rename an input on the real child and the stub keeps the old * name, the parent's binding goes to a property nobody declared, and the spec either stays green * over a template that no longer binds, or fails with `NG0303` pointing at the stub. Reading the * definition — `ɵcmp`, `ɵdir`, `ɵpipe` — keeps the selector, the input and output names, the aliases * and `exportAs` in step with the class by construction. * * `renderShallow` is the other half, not a substitute: it drops a component's children, which is * right when nothing reads the template, and keeps none of their bindings. A stub is for the spec * that does read the template and wants the child's slot there without the child. */ /** Options for {@link createComponentStub}. */ interface ComponentStubOptions { /** * The stub component's template. Defaults to one `` per slot the real component * projects, so content the parent projects into the child still renders — and still answers a * query. Ignored for a directive or a pipe. */ template?: string; } /** * Build a standalone stand-in for a component, directive or pipe, from its compiled definition. * * ```ts * const ChartStub = createComponentStub(ChartComponent); * * TestBed.configureTestingModule({ imports: [DashboardComponent] }); * TestBed.overrideComponent(DashboardComponent, { remove: { imports: [ChartComponent] }, add: { imports: [ChartStub] } }); * * const fixture = TestBed.createComponent(DashboardComponent); * fixture.detectChanges(); * * const chart = fixture.debugElement.query(By.directive(ChartStub)).componentInstance; * expect(chart.series()).toEqual([1, 2, 3]); // a signal input stays a signal input * chart.pointSelected.emit(2); // an output the parent listens to * ``` * * What is copied: the selector, every input under its public name (a signal input or `model()` as * a signal, a decorator input as a property, its transform included), every output as an * `EventEmitter` (a model's change event is the model itself), `exportAs`, and for a pipe its name * and purity. Nothing else is: no template, no host bindings, no providers, no lifecycle hooks, no * queries — a stub renders only the content projected into it and answers nothing it was not given. * `hostDirectives` are not copied either, so an input the real child exposes through one is *not* on * the stub and a parent binding to it answers `NG0303` — that child is one to keep rather than stub. * * With `renderShallow`, keep the parent's template and name the stub as the child to keep: * `renderShallow(DashboardComponent, { keepTemplate: true, keepChildren: [ChartStub] })`. * * @param real The class to stand in for; it must carry a compiled definition. * @param overrides Members every stub instance starts with — a method the parent calls through a * `viewChild`, a pipe's `transform` (identity by default). Copied per instance, after the inputs * and outputs are created, so an override of one of those replaces it. * @param options {@link ComponentStubOptions}. */ declare function createComponentStub(real: Type, overrides?: Partial, options?: ComponentStubOptions): Type>; /** * Driving an Angular resource from a spec, without any HTTP at all. * * `httpResource()` and `resource()` are the primitives a modern Angular service exposes, and a spec * that wants to assert "the component shows the empty state while products are loading" has, until * now, had to produce that state the long way: configure `provideHttpClientTesting`, tick so the * request is issued, find it on the `HttpTestingController`, flush it, then settle. Six steps and a * real request, to arrive at a value the spec picked in advance. * * {@link settleResource} is the answer when the request is the point. This is the answer when it is * not — the shallow one, for a suite that tests business logic and never wanted a request in the * first place. The property is replaced by a hand-built double whose statuses the spec sets * directly, so nothing is ever in flight and there is nothing to wait for: no tick, no flush, no * budget, and no way for the test to pass against a resource's default value by accident. * * Reactivity is genuine, exactly as in {@link mockSignalProp}: the double is built out of real * `signal()`s from `@angular/core`, so a `computed()` reading `products.value()` recomputes and an * `effect()` watching `products.status()` runs. A plain object with the same keys would satisfy * every read and notify nothing. * * `@angular/core` stays an optional peer the same way the rest of this surface does: `ResourceRef` * is only ever a *type* here, and the value handed to the property is assembled from `signal()`. */ /** * The resource statuses Angular defines, as a string union. * * Declared here rather than imported so this module keeps `@angular/core` to a type-only * dependency in spirit as well as in fact — Angular moved this from an enum to a union in v20, and * a local union works against both without a version guard. */ type ResourceDoubleStatus = 'error' | 'idle' | 'loading' | 'local' | 'reloading' | 'resolved'; /** * Status and value as one object — Angular's `ResourceSnapshot`, which a template branches on * with `@switch (products.snapshot().status)` and which composes two resources without reading * four signals. */ type ResourceDoubleSnapshot = { readonly status: 'error'; readonly error: Error | undefined; } | { readonly status: Exclude; readonly value: TValue; }; /** * The double installed on the property — the whole of `ResourceRef`, in signals the spec owns. * * Structural on purpose: a component typed against `ResourceRef` never finds out that its * resource is a stand-in, so every member the real interface publishes is here, with the semantics * Angular gives it. An earlier version left `set`, `update`, `asReadonly`, `destroy` and `snapshot` * out on the theory that a consumer never calls them; a service exposing * `readonly products = this.#products.asReadonly()` calls one before the spec even starts, and an * optimistic write calls two more, each of them a `TypeError` at run time because the property is * typed as the real thing and the compiler has nothing to say. */ interface ResourceDouble { /** * The current value. Writable, and a write through it goes `'local'`, exactly as Angular's does. * * **Reading it in the `'error'` state throws**, also exactly as Angular's does: a real `value()` is * a computation that rethrows the failure instead of handing back the last good value. */ value: WritableSignal; /** `'resolved'` unless the spec moved it — see {@link MockedResource} and {@link MockResourceOptions}. */ status: Signal; /** The error behind an `'error'` status, `undefined` otherwise. */ error: Signal; /** `true` while the status is `'loading'` or `'reloading'`, matching Angular's own derivation. */ isLoading: Signal; /** Status and value together, the shape `@switch (products.snapshot().status)` reads. */ snapshot: Signal>; /** `true` unless the status is `'error'` or the value is `undefined` — Angular's rule since v20. */ hasValue(): boolean; /** A write from the code under test: status `'local'`, error cleared. */ set(value: TValue): void; /** {@link ResourceDouble.set} over the current value. */ update(updater: (value: TValue) => TValue): void; /** The readonly view Angular hands out — the same double, since a spec has nothing to hide from itself. */ asReadonly(): ResourceDouble; /** Back to `'idle'` at the initial value, after which a write from the code under test does nothing. */ destroy(): void; /** * Spied, and inert: a double has no request to re-issue, so the spec asserts the call instead. The * answer is Angular's — `false` while the resource is `'idle'` or `'loading'`, `true` otherwise. */ reload: AddSpyMethodsByReturnTypes<() => boolean>; } /** The spec's handle on a resource installed by {@link mockResourceProp}. */ interface MockedResource { /** Resolve the resource with a value — status `'resolved'`, error cleared. */ set(value: TValue): void; /** Fail the resource — status `'error'`, `error()` set, `hasValue()` false, `value()` throwing. */ fail(error: Error | string): void; /** Put the resource back in flight — status `'loading'`, the value left where it was. */ loading(): void; /** Park it before it ever ran — status `'idle'`, back at the initial value, error cleared. */ idle(): void; /** The spied `reload()`; `expect(products.reload).toHaveBeenCalled()`. */ reload: AddSpyMethodsByReturnTypes<() => boolean>; /** The double now behind the property, for asserting on it directly. */ resource: ResourceDouble; } /** How the double starts out, for the states a spec would otherwise arrange in its first two lines. */ interface MockResourceOptions { /** * The status the double is installed in. `'resolved'` by default — `'idle'` is the one a * `params`-driven resource sits in until the signal it reads is set. `'error'` is absent on * purpose: an error needs a reason, and that is {@link MockedResource.fail}. */ status?: Exclude; } /** * Replace a resource-valued property with a double the spec drives directly. * * ```ts * const service = injectSpy(ProductService); * const products = mockResourceProp(service, 'products', []); * * expect(component.emptyState()).toBe(true); * * products.set([product]); * await stable(fixture); * * expect(component.emptyState()).toBe(false); * * products.fail('offline'); * expect(component.errorMessage()).toBe('offline'); * ``` * * The resource starts `'resolved'` at `initialValue`, because that is the state a spec asserts * against most and the one it would otherwise have to arrange; `options.status` picks another one * up front. `loading()`, `idle()` and `fail()` are how the rest are reached, and each is a single * synchronous call — the point of this helper is that there is no asynchrony to get wrong. When a * spec *does* want the real request path, that is `settleResource` over a real `httpResource`, not * this. * * Undone by `restoreMockedProps()` like every other property patch, so a suite running * `setupAutoSpy()` needs no teardown of its own. * * @param object The spy (or real instance) whose property to replace. * @param property The resource-valued property. * @param initialValue The value the resource starts at, and the one `idle()` and `destroy()` return to. * @param options The status to start in. * @returns The handle driving that resource — `set` / `fail` / `loading` / `idle`, plus the spied `reload`. */ declare function mockResourceProp(object: T, property: K, initialValue: T[K] extends { value: Signal; } ? TValue : never, options?: MockResourceOptions): MockedResource; } ? TValue : never>; /** * Driving a service's signal from a spec. * * `createSpyFromClass` discovers methods by walking the prototype, and a `signal()` / `computed()` * field is not there — it is assigned on the instance. Listing it in `methodsToSpyOn` is not the * answer either: that would make it a function spy, and a function spy returns `undefined` until * configured, so a component reading `service.count()` gets `undefined` where it expects a value. * * What a spec actually wants is the signal to be real and writable, so the component reacts the way * it does in the application — a `computed()` downstream recomputes, an `effect()` runs, a template * binding updates. That is two lines every time: * * ```ts * const count = signal(0); * mockReadonlyProp(service, 'count', count); * ``` * * and the reason it is two is that the spec needs the writable handle while the service exposes a * readonly one. {@link mockSignalProp} is the same pair with the handle returned rather than * declared, which also removes the temptation to reach for `service.count` and call `.set` on it — * `Signal` has no `set`, so that only type-checks after an assertion. * * Reactivity is genuine: the signal comes from `@angular/core`, not from a stand-in. A stub with a * `set` method would satisfy `service.count()` and silently fail to notify anything downstream, * which is the failure this helper exists to avoid rather than cause. * * Which is also why it replaces as little as it can. Angular's graph is built out of links made at * **read time**, and a link points at the signal node, not at the property the node arrived through: * swap the property and every `computed()`, `effect()` and template binding that has already read * the member stays on the old node for the rest of the test, returning its cached value with nothing * to say about it. So a member with a node to write — `signal()`, `model()`, `linkedSignal()`, and * the `asReadonly()` view of one, which shares the very same node — is **written through** rather * than replaced, which keeps every edge of the graph valid, keeps a `model()`'s output half alive, * and leaves nothing to restore. The swap is kept for the two shapes that have no node to write: a * `computed()` the class declares, and a member a spy does not have yet. Those still have to be * patched before anything reads them, and the helper says so rather than letting the spec find out * three assertions later. */ /** * Put a value behind a signal-valued property, and hand back the writable handle. * * ```ts * const service = injectSpy(CounterService); * const count = mockSignalProp(service, 'count', 0); * * expect(component.label()).toBe('0 items'); * * count.set(42); * await fixture.whenStable(); * * expect(component.label()).toBe('42 items'); * ``` * * A member with a node behind it — a `signal()`, a `model()`, or the `asReadonly()` view a service * publishes — is written through, so the order does not matter and there is nothing to undo: the * value stays where the spec left it, which is the object's own business for anything that outlives * the test. A `computed()` or a member the spy does not have yet is replaced instead, and that patch * is undone by `restoreMockedProps()` like every other one. * * @param object The spy (or real instance) whose property to drive. * @param property The signal-valued property. * @param initialValue The value the signal starts at. * @returns The writable signal behind that property — `set()` and `update()` drive the test. * * @throws When the property is an `input()`, whose writes Angular routes around the property, or when * it is a `computed()` a live consumer has already read — both with the repair in the message. */ declare function mockSignalProp(object: T, property: K, initialValue: T[K] extends Signal ? TValue : never): WritableSignal ? TValue : never>; export { type AngularTestEnvMode, type AngularTestEnvOptions, type AutoSpyFixture, type AutoSpyOverride, type AutoSpyTokenDefaults, type ComponentStubOptions, type DirectiveHostOptions, type ExtendWithAutoSpiesOptions, type MockResourceOptions, type MockedResource, type ResourceDouble, type ResourceDoubleSnapshot, type ResourceDoubleStatus, type SpiedFixtures, assertComponentDefIntact, assertNgModuleScopes, clearAutoSpyDefaults, createComponentStub, createDirectiveHost, extendWithAutoSpies, mockResourceProp, mockSignalProp, overrideAutoSpy, overrideComponentProvider, registerAutoSpyDefaults, setupAngularTestEnv };