import { AbstractType, Injector, InjectionToken, ValueProvider, FactoryProvider, ProviderToken } from '@angular/core'; import { h as AddSpyMethodsByReturnTypes } from './types-BM3BcWj1.js'; import 'vitest'; /** * `provideMatDialogData()` / `provideMatDialogRef()` — the Material dialog providers a suite writes * by hand, without `@angular/material` becoming a dependency of this package. * * 36 of them across two private suites are the same three shapes: `useValue: null` or a data object * on `MAT_DIALOG_DATA`, `{ close: vi.fn() }` on `MatDialogRef`, and a spy on `MatDialog`. The middle * one is where they break. `close` is the only member anybody wrote, so the component that * subscribes to `afterClosed()` fails with "is not a function", and the repair written next to it — * `afterClosed: () => of('saved')` — answers before anything closed the dialog: the spec then passes * whether or not the component ever called `close()`. * * **Material is not a dependency here and must not become one.** This package ships no runtime * dependencies at all, and the dialog is one component library's shape, not Angular's. So the token * and the ref class are arguments: nothing in this file imports `@angular/material`, while the data * is still typed against the token the spec passed and the result against the `close()` of the class * it passed. * * rxjs is imported here and, bar `/angular-router`, nowhere else in the Angular entry family — and * since the split moved this module to `vitest-auto-spy/angular/doubles` it no longer reaches * `vitest-auto-spy/angular` at all. Deliberately, in either place: a component * pipes `afterClosed()`, so the stream has to be a real `Observable` rather than something * subscribable. `@angular/core` has rxjs as a peer dependency of its own, so a suite that can import * this entry already has it. */ /** The half of Material's `MatDialogRef` a double stands in for: how the dialog closes. */ interface DialogRefLike { close(result?: never): void; } /** * The result of the ref class handed in — the `R` of Material's `MatDialogRef`. * * Read off `close()` rather than declared here, so a ref that names its result * (`class ConfirmRef extends MatDialogRef`) has `closedWith` * checked against it without this file ever seeing Material's own types. */ type DialogResult = Ref extends { close(result?: infer R): void; } ? R : never; /** * The dialog component the ref was opened for — the `T` of Material's `MatDialogRef`. * * Read off the class's own `componentInstance`, so a stand-in is checked against the component the * ref names while `@angular/material` stays unimported here. */ type DialogComponent = Ref extends { componentInstance: infer T; } ? NonNullable : never; /** Where the ref double starts. */ interface MatDialogRefInit { /** * The result `afterClosed()` answers with no component involved — for the ref a spied * `dialog.open()` hands back. A dismissal closes with `undefined`, which this field cannot say; * `emitClose()` is how a spec expresses that one. */ closedWith?: DialogResult; /** `ref.disableClose`, which components both read and write. Default `undefined`, as Material leaves it. */ disableClose?: boolean; /** * What `ref.componentInstance` answers — the members of the dialog component the opener drives it * through, a `save` emitter and an `isSaving` signal being the usual pair. Left out, reading it * throws by name rather than handing the opener `undefined`. */ componentInstance?: Partial>; } /** The handle a spec drives the dialog through. The ref itself keeps the class's own shape. */ interface MatDialogRefDouble { /** The value every injector hands out for the ref class — and what a spied `dialog.open()` answers. */ readonly ref: Ref; /** The spied `close()`, the very function the ref carries: the component's close is asserted through it. */ readonly close: AddSpyMethodsByReturnTypes<(result?: DialogResult) => void>; /** Close it from outside, the way the user does: the streams move and the spy records nothing. */ emitClose(result?: DialogResult): void; } /** * Build a `MatDialogRef` double without a `TestBed` — for a class built with `new`, and for the ref * a spied `MatDialog.open()` has to answer. * * ```ts * const dialog = injectSpy(MatDialog); * * dialog.open.mockReturnValue(createMatDialogRef(MatDialogRef, { closedWith: 'saved' }).ref); * ``` */ declare function createMatDialogRef(RefClass: AbstractType, init?: MatDialogRefInit): MatDialogRefDouble; /** * The `MatDialogRef` provider, for `TestBed.configureTestingModule` or a component's `providers`. * * ```ts * TestBed.configureTestingModule({ * providers: [provideMatDialogRef(MatDialogRef, { disableClose: false }), provideMatDialogData(MAT_DIALOG_DATA, { id: 7 })], * }); * ``` * * The class is an argument because `@angular/material` is not a dependency of this package — it is * the DI token as well as the shape the double is measured against, so the spec's own * `MatDialogRef` import is the only place Material is named. * * A factory, so every injector builds its own: a provider array hoisted to a module constant never * carries one test's closed dialog into the next. */ declare function provideMatDialogRef(RefClass: AbstractType, init?: MatDialogRefInit): FactoryProvider; /** * The dialog's data, under the token the application injects it from. * * ```ts * providers: [provideMatDialogData(MAT_DIALOG_DATA, { id: 7, name: 'Ada' })]; * ``` * * A typed `{ provide, useValue }` and nothing more, which is the whole of what the hand-written one * is too — except that Material declares `MAT_DIALOG_DATA` as `InjectionToken`, so the object * in `useValue: { id: 7 }` is checked against nothing at all and `useValue: null` compiles for a * component that reads `data.name`. Name the type argument and the data is checked against it; pass * an `InjectionToken` of your own and it is checked without naming anything. * * The value is handed out as it is, so a component that writes to the data writes to the object the * spec passed — build it per test rather than hoisting it to a module constant. */ declare function provideMatDialogData(token: InjectionToken, data: NoInfer): ValueProvider; /** * The handle of the ref `provideMatDialogRef()` put in the test's injector. * * ```ts * const dialog = injectMatDialogRef(MatDialogRef); * * fixture.componentInstance.save(); * * expect(dialog.close).toHaveBeenCalledWith('saved'); * await expect(expectEmission(dialog.ref.afterClosed())).resolves.toBe('saved'); * ``` * * Reads the `TestBed` by default; pass `fixture.debugElement.injector` when the ref is in a * component's own `providers`. */ declare function injectMatDialogRef(RefClass: AbstractType, injector?: Injector): MatDialogRefDouble; /** * `provideWindowDouble()` / `provideDocumentDouble()` — a `window` or `document` for DI that stays * the real one everywhere the spec did not say otherwise. * * These are the two most hand-rolled providers in an Angular suite: 95 `window` ones and 70 * `document` ones across two private codebases, written three ways — `useValue: window` (no * isolation at all: whatever the test writes stays there for the rest of the worker), a slice * (`{ screen: { width: 1280, height: 720 } }`), and a `mockDocument` with one hand-written * `querySelector`. The slice and the hand-written document share a failure: the component under * test reads `screen.colorDepth`, or calls `document.createElement`, and gets `undefined` — the * double only knows the members its author happened to think of, and the spec fails somewhere that * has nothing to do with what it was testing. * * So the double is not built from scratch. It is a **view over the real jsdom object**: every read * the overrides do not name falls through to the real `window` / `document`, and every write and * delete lands on the view rather than on the global, so nothing a test does survives it. That is * also why there is no teardown to call — `restoreMockedProps()` has nothing to put back, because * nothing was patched. * * **Why a `Proxy` and not `{ ...window, ...overrides }` or `Object.create(window)`.** Neither * works. A spread copies the own enumerable properties, and jsdom keeps `document.querySelector`, * `document.body` and most of `Screen` on the prototype, so the copy is nearly empty. A derived * object has them, but they are Web IDL methods: called with anything but the real instance as * `this` they throw `'querySelector' called on an object that is not a valid instance of Document`. * The proxy hands each method out bound to the real object, which is the only shape that answers * both — every method, but no constructor: `win.Date`, `win.Event` and `win.Promise` are handed out * untouched, because a bound function carries none of its target's own members. * * **Why the window helper takes a token and the document helper does not.** Angular ships * `DOCUMENT` — since v20 from `@angular/core` itself, which is what this entry imports, so * `@angular/common` stays out of it. There is no Angular `WINDOW` token and there never has been: * every application declares its own `InjectionToken`, so `provideWindowDouble` has to be * handed that one. It is generic over the token's type, so an app whose token is * `InjectionToken` gets its own members checked in the overrides too. */ /** How many levels of slicing the type follows. Beyond it a member takes its whole type. */ type Levels = [never, 0, 1, 2, 3]; /** * One member's override: the member's own type, or a slice of it wherever it is a plain-ish object. * * A method is matched whole and **first**: `Partial<(…) => …>` is a mapped type over a function, * which drops the call signature and leaves `{}` — a slice branch that accepts anything at all. An * array is matched whole too: slicing one by index describes nothing a spec means. */ type PlatformOverride = V extends (...args: never[]) => unknown ? V : V extends readonly unknown[] ? V : V extends object ? Depth extends 0 ? V : V | { [K in keyof V]?: PlatformOverride; } : V; /** * What a spec says about a platform object: a value per member, and a **slice** wherever the member * is itself an object — `{ screen: { width: 1920 } }` leaves `screen.colorDepth` real. * * The slice goes as deep as the merge does, three levels of it: the proxy re-merges every plain * object it hands out, so `{ document: { location: { href: '' } } }` works at run time, and a type * that stopped at the first level sent a spec that wrote it to `createMock()` or to a * cast for no reason anybody could state. Depth is bounded because `Window` is recursive — * `window.window` is a `Window` — and an unbounded mapped type over it is a bill every call site * pays. */ type PlatformOverrides = { [K in keyof T]?: PlatformOverride; }; /** * The `window` double without a `TestBed` — for a class built with `new`, or a plain function. * * ```ts * const win = createWindowDouble({ innerWidth: 375, screen: { width: 375 } }); * const layout = new LayoutProbe(win); * ``` */ declare function createWindowDouble(overrides?: PlatformOverrides): Window; /** For an application window type of its own: `createWindowDouble({ appBuildId: '…' })`. */ declare function createWindowDouble(overrides: PlatformOverrides): T; /** * The `document` double without a `TestBed`. * * ```ts * const doc = createDocumentDouble({ visibilityState: 'hidden' }); * ``` */ declare function createDocumentDouble(overrides?: PlatformOverrides): Document; /** * Provide a `window` under the application's own token, with the real one behind it. * * ```ts * TestBed.configureTestingModule({ * providers: [provideWindowDouble(WINDOW, { screen: { width: 1920, height: 1080 } })], * }); * ``` * * `screen.colorDepth`, `location.href`, `getComputedStyle`, `addEventListener` and everything else * the overrides did not name still answer the way jsdom answers them. Writes the code under test * makes land on the double rather than on the global, which is also how a spec moves a value * mid-test: `Object.assign(TestBed.inject(WINDOW), { scrollY: 40 })` — `Object.assign` rather than * an assignment because lib.dom declares most of `Window` `readonly`. There is no handle to learn. * * A factory, so every injector builds its own: a provider array hoisted to a module constant never * carries one test's writes into the next. */ declare function provideWindowDouble(token: ProviderToken, overrides?: PlatformOverrides): FactoryProvider; /** * Provide a `document` under Angular's `DOCUMENT`, with the real one behind it. * * ```ts * TestBed.configureTestingModule({ * providers: [provideDocumentDouble({ querySelector: vi.fn().mockReturnValue(anchor) })], * }); * ``` * * Pass a second argument for an application token that is not `DOCUMENT`. Note that overriding * `DOCUMENT` for the testing module hands the double to Angular's renderer as well, so replace * `createElement` or `body` only when the spec means to. */ declare function provideDocumentDouble(overrides?: PlatformOverrides, token?: ProviderToken): FactoryProvider; export { type DialogComponent, type DialogRefLike, type DialogResult, type MatDialogRefDouble, type MatDialogRefInit, type PlatformOverrides, createDocumentDouble, createMatDialogRef, createWindowDouble, injectMatDialogRef, provideDocumentDouble, provideMatDialogData, provideMatDialogRef, provideWindowDouble };