import { Locator, LocatorSelectors, PrettyDOMOptions } from "vitest/browser"; import { HttpInterceptorFn } from "@angular/common/http"; import { HttpTestingController } from "@angular/common/http/testing"; import { EnvironmentProviders, InputSignalWithTransform, OutputEmitterRef, Provider, ProviderToken, SchemaMetadata, Type, WritableSignal } from "@angular/core"; import { ComponentFixture, DeferBlockBehavior, DeferBlockState } from "@angular/core/testing"; import { Router, Routes } from "@angular/router"; import { RouterTestingHarness } from "@angular/router/testing"; //#region src/types/utils.d.ts type Prettify = { [K in keyof T]: T[K]; } & {}; //#endregion //#region src/types/render.d.ts /** * Configuration options for rendering components with Angular Router support. * * @example * ```typescript * // Basic routing with route params * await render(UserComponent, { * withRouting: { * routes: [{ path: 'user/:id', component: UserComponent }], * initialRoute: '/user/42', * }, * }); * * // Passing inputs via route data (uses withComponentInputBinding) * await render(ProfileComponent, { * withRouting: { * routes: [ * { * path: 'profile', * component: ProfileComponent, * data: { name: 'John', age: 30 }, * }, * ], * initialRoute: '/profile', * }, * }); * ```; */ interface RoutingConfig { /** The route configuration to use. These routes are passed to `provideRouter()`. */ routes: Routes; /** * The initial route to navigate to after setting up the router. This triggers navigation and * activates the matching route's component. * * @example * '/user/42' or '/profile?tab=settings' */ initialRoute?: string; /** * When `true`, disables Angular's `withComponentInputBinding()` feature. * * By default, `withComponentInputBinding()` is enabled, which automatically binds route params, * query params, and route data to matching component inputs. * * Set this to `true` if you want to manually handle route data via `ActivatedRoute`. * * @default false */ disableInputBinding?: boolean; } interface HttpConfig { interceptors?: Array; } /** * Configures the initial state of a single `@defer` block by index. */ interface DeferBlockStateConfig { /** The state to render the defer block in. */ deferBlockState: DeferBlockState; /** The index of the defer block to target, matching `fixture.getDeferBlocks()` order. */ deferBlockIndex: number; } type InputValueOrSignal = T extends InputSignalWithTransform ? WriteT | WritableSignal : never; type Inputs> = Partial<{ [PROP in keyof InstanceType as InstanceType[PROP] extends InputSignalWithTransform ? PROP : never]: InputValueOrSignal[PROP]>; }> & Record; type OutputKeys> = { [PROP in keyof InstanceType]: InstanceType[PROP] extends OutputEmitterRef ? PROP : never; }[keyof InstanceType]; type Outputs> = Partial<{ [K in OutputKeys]: InstanceType[K] extends OutputEmitterRef ? (value: T) => void : never; }> & Record; /** Base options for rendering a component with `render()`. */ interface BaseRenderOptions = Type> { /** The base element to render into. * @default document.body * */ baseElement?: HTMLElement; /** * When `true`, automatically infers the component's selector and adds it to the template. * Only works when `withRouting` is not enabled. */ inferTagName?: boolean; /** * Input values to pass to the component. * * Note: When using `withRouting`, inputs cannot be passed directly. Use route `data` instead. */ inputs?: Inputs; /** * Output emitters to pass to the component. * * Note: When using `withRouting`, outputs cannot be passed directly. */ outputs?: Outputs; /** * Enable Angular Router support for the component. * * - When `true`: Creates a wildcard route for the component and navigates to `/`. * - When `RoutingConfig`: Uses the provided routes and initial route. * * By default, `withComponentInputBinding()` is enabled, allowing you to pass inputs via route * `data`, route params, or query params. * * @example * ```typescript * // Simple routing (component matches any route) * await render(MyComponent, { withRouting: true }); * * // Full routing configuration * await render(UserComponent, { * withRouting: { * routes: [{ path: 'user/:id', component: UserComponent, data: { role: 'admin' } }], * initialRoute: '/user/42', * }, * }); * ```; */ withRouting?: RoutingConfig | boolean; /** Enable Angular HttpClient testing support for the component. When enabled, `httpTesting` is * available in the render result. * * - When `true`: Enables HttpClient testing with default configuration. * - When `HttpConfig`: Enables HttpClient testing with the provided configuration. * * @example * ```typescript * // Basic HttpClient testing * const { httpTesting } = await render(MyComponent, { withHttp: true }); * * // With custom interceptors * const { httpTesting } = await render(MyComponent, { * withHttp: { interceptors: [myInterceptor] }, * }); * ```; */ withHttp?: HttpConfig | boolean; /** Additional providers to configure in the testing module. */ providers?: Array; /** Additional imports for the testing module. */ imports?: Array; /** * The schema metadata for the component. * * In case the component uses custom elements or other non-standard Angular elements, you can provide the appropriate schema metadata here. * @example CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA * @see https://angular.dev/api/core/NO_ERRORS_SCHEMA * @see https://angular.dev/api/core/CUSTOM_ELEMENTS_SCHEMA */ schema?: SchemaMetadata | Array; /** * When `true`, removes Angular-specific attributes (`ng-version`) * from the rendered DOM after render. Useful for cleaner snapshots * and inline assertions on `container.innerHTML`. * * @default false */ removeAngularAttributes?: boolean; /** * Sets the behavior of `@defer` blocks in the rendered component. * * Defaults to `DeferBlockBehavior.Manual`, which keeps defer blocks in a * paused state so `renderDeferBlock`/`deferBlockStates` can control them * deterministically. Set to `DeferBlockBehavior.Playthrough` to let blocks * play through like they would in a real browser. * * @default DeferBlockBehavior.Manual * @see https://angular.dev/api/core/testing/DeferBlockBehavior */ deferBlockBehavior?: DeferBlockBehavior; /** * Sets the initial state of the `@defer` blocks right after render. * * Pass a single `DeferBlockState` to apply to every defer block, or an array * of `{ deferBlockState, deferBlockIndex }` to target specific blocks. * * @example * ```typescript * // All blocks in the Complete state * await render(MyComponent, { deferBlockStates: DeferBlockState.Complete }); * * // Only the first block in the Loading state * await render(MyComponent, { * deferBlockStates: [{ deferBlockState: DeferBlockState.Loading, deferBlockIndex: 0 }], * }); * ``` */ deferBlockStates?: DeferBlockState | Array; /** * When provided, overrides the component's `imports` with the specified imports. * Useful for mocking child components/directives/pipes used in the component's template. * * @example * ```typescript * await render(MyComponent, { * overrideImportsComponent: [ * { replace: ChildComponent, with: MockComponent }, * ], * }); * ```; */ overrideImportsComponent?: Array<{ replace: Type; with: Type; }>; /** * Replaces providers declared on the component itself with alternative providers. * Useful for mocking a service that the component declares in its `providers` array. * * `replace` must match the exact provider shape declared by the component: * the same object shape (same `provide`/`useClass`/`useValue`/`useFactory` keys) * or the bare class when the component uses the shorthand `providers: [Foo]`. * `with` is a full provider that typically provides the same token as `replace`, * e.g. `{ provide: Foo, useClass: MockFoo }`. * * * @example * ```typescript * await render(MyComponent, { * overrideProvidersComponent: [ * { replace: GreetingService, with: { provide: GreetingService, useClass: MockGreetingService } }, * ], * }); * ```; */ overrideProvidersComponent?: Array<{ replace: Provider; with: Provider; }>; } /** * Options for `render()` when routing is **not** enabled (`withRouting` is `false` or omitted). * * `inputs` and `outputs` are available. */ type ComponentRenderOptions = Type> = Prettify, 'withRouting'> & { withRouting?: false; }>; /** * Options for `render()` when routing is enabled (`withRouting` is `true` or a `RoutingConfig`). * * `inputs` and `outputs` are not allowed — use route `data`/params instead. */ type RoutedRenderOptions = Type> = Prettify, 'withRouting'> & { withRouting: true | RoutingConfig; inputs?: never; outputs?: never; }>; /** * Fallback options for `render()` when the routing state cannot be statically determined * (e.g. `withRouting` is a generic `boolean`). * * `inputs` and `outputs` are not allowed because the component might be routed. */ type RoutedFallbackRenderOptions = Type> = Prettify, 'withRouting' | 'inputs' | 'outputs'> & { withRouting?: boolean; inputs?: never; outputs?: never; }>; /** * Base fields shared by every `render()` result, independent of whether routing is enabled. */ interface BaseRenderResult extends LocatorSelectors { baseElement: HTMLElement; container: HTMLElement; debug(el?: HTMLElement | HTMLElement[] | Locator | Locator[], maxLength?: number, options?: PrettyDOMOptions): void; /** Vitest browser locator scoped to the rendered component's container. */ locator: Locator; /** The instance of the rendered component's class. */ componentClassInstance: T; /** * The Angular TestBed's HttpTestingController instance, if `withHttp` was enabled. */ httpTesting?: HttpTestingController; /** * Injects a dependency based on the component injector. * @param token - The token to inject. * @returns The instance of the requested dependency. */ inject: (token: ProviderToken) => T; /** * Sets the state of one (or all) `@defer` blocks of the rendered component. * * With no `deferBlockIndex`, every defer block is rendered in the given * state. Pass an index to target a specific block. * * @example * ```typescript * const { renderDeferBlock } = await render(MyComponent); * await renderDeferBlock(DeferBlockState.Complete); * await renderDeferBlock(DeferBlockState.Loading, 0); * ``` */ renderDeferBlock: (deferBlockState: DeferBlockState, deferBlockIndex?: number) => Promise; } /** * Result of `render()` when routing is **not** enabled. * * The `fixture` is the `ComponentFixture` of the rendered component. */ interface RenderResult extends BaseRenderResult { /** * The ComponentFixture for the rendered component. */ fixture: ComponentFixture; /** * Rerenders the component with new input values. * * @param newInputs - An object containing the new input values keyed by input name. * @returns A promise that resolves once the component has been updated and stabilized. */ rerender: (newInputs: Inputs>) => Promise; } /** * Result of `render()` when `withRouting` is enabled. * * The `fixture` is the `RouterTestingHarness`'s internal fixture (typed as * `ComponentFixture`), and `router` / `routerHarness` are always defined. */ interface RoutedRenderResult extends BaseRenderResult { /** * The RouterTestingHarness's internal fixture. Not a fixture of `T` directly. */ fixture: ComponentFixture; /** * The RouterTestingHarness instance. * * **Preferred for navigation in tests.** Use `navigateByUrl()` which: * * - Waits for all redirects to complete * - Automatically runs change detection * - Returns the activated component instance * - Handles guard rejections gracefully * * @example * ```typescript * // Navigate and get the activated component * const userComponent = await routerHarness.navigateByUrl('/user/42', UserComponent); * * // Simple navigation * await routerHarness.navigateByUrl('/about'); */ routerHarness: RouterTestingHarness; /** * The Angular Router instance. * * Useful for inspecting router state. For navigation, prefer `routerHarness.navigateByUrl()`. * * @example * expect(router.url).toBe('/user/42'); */ router: Router; } interface RenderFn { (component: Type, options?: ComponentRenderOptions>): Promise>; (component: Type, options: RoutedRenderOptions>): Promise>; (component: Type, options: RoutedFallbackRenderOptions>): Promise | RoutedRenderResult>; } type DirectiveRenderOptions = Prettify & { /** Template to render the directive in. Must include the directive selector. */ template: string; /** Host component input values to pass and make reactive. */ hostProps?: Record; /** Additional imports for the wrapper component. */ imports?: Array>; /** Change detection strategy for the host component. Defaults to 'onPush'. */ changeDetection?: 'eager' | 'onPush'; /** When provided, overrides the directive `imports` with the specified imports. */ overrideImportsDirective?: Array<{ replace: Type; with: Type; }>; /** Replaces providers declared on the directive itself with alternative providers. */ overrideProvidersDirective?: Array<{ replace: Provider; with: Provider; }>; }>; interface DirectiveRenderResult extends LocatorSelectors { container: HTMLElement; baseElement: HTMLElement; /** * The host component's fixture. */ hostFixture: ComponentFixture; /** * Instance of the tested directive. */ directiveInstance: T; /** * Locator scoped to the host element where the directive is applied. */ locator: Locator; /** * Debug function for the directive's element. */ debug(el?: HTMLElement | HTMLElement[] | Locator | Locator[], maxLength?: number, options?: PrettyDOMOptions): void; /** * Injects a dependency based on the directive's injector. * @param token - The token to inject. * @returns The instance of the requested dependency. */ inject: (token: ProviderToken) => T; /** * The Angular TestBed's HttpTestingController instance, if `withHttp` was enabled. */ httpTesting?: HttpTestingController; /** * Sets the state of one (or all) `@defer` blocks of the host component. * * With no `deferBlockIndex`, every defer block is rendered in the given * state. Pass an index to target a specific block. */ renderDeferBlock: (deferBlockState: DeferBlockState, deferBlockIndex?: number) => Promise; } //#endregion //#region src/pure.d.ts /** * Renders an Angular component for testing with Vitest Browser Mode. * * @example * ```typescript * // Basic render * const { locator } = await render(MyComponent); * await expect.element(locator.getByText('Hello')).toBeVisible(); * * // With inputs * const { componentClassInstance } = await render(UserComponent, { * inputs: { name: 'John', age: 30 }, * }); * * // With routing and route data as inputs * const { router } = await render(ProfileComponent, { * withRouting: { * routes: [{ path: 'profile', component: ProfileComponent, data: { userId: '42' } }], * initialRoute: '/profile', * }, * }); *``` * @param componentClass - The component class to render * @param options - Configuration options for rendering * @returns A promise that resolves to the render result with locators and component access */ declare function render(componentClass: Type, options?: ComponentRenderOptions>): Promise>; declare function render(componentClass: Type, options: RoutedRenderOptions>): Promise>; declare function render(componentClass: Type, options: RoutedFallbackRenderOptions>): Promise | RoutedRenderResult>; /** * Renders a directive for testing with Vitest Browser Mode. * * @param directiveClass - The directive class to test * @param options - Configuration including the template where the directive is applied * @returns A render result with fixture, directive instance, and query methods * * @example * ```typescript * // Basic directive test * const { directiveInstance, locator } = await renderDirective(HighlightDirective, { * template: `
Test
`, * }); * * // With host input binding * const { locator } = await renderDirective(HighlightDirective, { * template: `
Test
`, * hostProps: { color: 'red', onClick: vi.fn() }, * imports: [JsonPipe], // extra imports for template * }); * ``` */ declare function renderDirective(directiveClass: Type, options: DirectiveRenderOptions): Promise>; declare function cleanup(shouldTeardown?: boolean): void; //#endregion export { DeferBlockStateConfig as a, Inputs as c, RenderResult as d, RoutedFallbackRenderOptions as f, ComponentRenderOptions as i, Outputs as l, RoutedRenderResult as m, render as n, DirectiveRenderOptions as o, RoutedRenderOptions as p, renderDirective as r, DirectiveRenderResult as s, cleanup as t, RenderFn as u };