import { Type, Injector, FactoryProvider, Provider } from '@angular/core'; import { Params, Data, UrlSegment, ActivatedRoute, Route, ResolveData, Navigation, Router, Event } from '@angular/router'; import { h as AddSpyMethodsByReturnTypes } from './types-BM3BcWj1.js'; import { SpyLocation } from '@angular/common/testing'; import 'vitest'; /** * `provideActivatedRoute()` + `injectActivatedRoute()` — an `ActivatedRoute` whose streams and * snapshot cannot disagree. * * `ActivatedRoute` keeps `snapshot`, `params`, `queryParams`, `data` and `fragment` in instance * fields, so `provideAutoSpy(ActivatedRoute)` has none of them, and a hand-written `useValue` knows * the stream half or the snapshot half — whichever the spec's author read first. The component that * reads the other half gets `undefined`, and the spec that set `snapshot.params` and never updated * `params` tests a route no navigation can produce. * * So the double is not a look-alike. It is Angular's own `ActivatedRoute`, built over one state * record: every stream is a `BehaviorSubject` of one of its fields, the snapshot is Angular's own * `ActivatedRouteSnapshot` of the same record, and a change rebuilds the snapshot and then emits the * streams it moved — in the order, and with the equality, the router uses after a navigation. * * **Why this is its own entry.** It is the only file of the package that imports `@angular/router`, * which stays an optional peer paid for by the suites that import `vitest-auto-spy/angular-router`. */ /** What a navigation moves. Each field is a stream on the route and a field of its snapshot. */ interface ActivatedRouteChange { /** The route's own parameters — `params`, `paramMap`, `snapshot.params`, `snapshot.paramMap`. */ params?: Params; /** The query parameters — `queryParams`, `queryParamMap` and their snapshot twins. */ queryParams?: Params; /** Static and resolved data — `data` and `snapshot.data`. */ data?: Data; /** The route's title — `route.title` and `snapshot.title`. Angular carries it inside `data`; so does the double. */ title?: string; /** The URL fragment — `fragment` and `snapshot.fragment`. */ fragment?: string | null; /** The matched URL segments. A string is split on `/`; pass `UrlSegment`s for matrix parameters. */ url?: UrlSegment[] | string; } /** The route a spec starts from: what a navigation moves, plus what stays fixed for the route's life. */ interface ActivatedRouteInit extends ActivatedRouteChange { /** The outlet the route is rendered into. Default `'primary'`. */ outlet?: string; /** The routed component. Default `null`. */ component?: Type | null; /** The `Route` it matched — `routeConfig` and `snapshot.routeConfig`. Default `null`. */ routeConfig?: Route | null; /** The resolve record — the snapshot's own `_resolve`, kept apart from `data` as Angular keeps it. */ resolve?: ResolveData; } /** The handle a spec drives the route through. The route itself stays exactly Angular's shape. */ interface ActivatedRouteDouble { /** The `ActivatedRoute` every injector in the test hands out — a real instance of Angular's class. */ readonly route: ActivatedRoute; /** Move several parts in one navigation: one new snapshot, then each changed stream emits once. */ set(change: ActivatedRouteChange): void; /** Replace the params. Not a merge — spread `route.snapshot.params` to keep the old ones. */ setParams(params: Params): void; /** Replace the query params. Not a merge, for the same reason. */ setQueryParams(queryParams: Params): void; /** Replace the data. */ setData(data: Data): void; /** Replace the fragment. */ setFragment(fragment: string | null): void; /** Replace the URL segments. */ setUrl(url: UrlSegment[] | string): void; } /** * Build an `ActivatedRoute` double without a `TestBed` — for a class constructed with `new`, or a * functional guard or resolver that takes `route.snapshot` as an argument. * * ```ts * const { route, setParams } = createActivatedRoute({ params: { id: '7' } }); * const page = new ProductPage(route); * * setParams({ id: '8' }); * ``` */ declare function createActivatedRoute(init?: ActivatedRouteInit): ActivatedRouteDouble; /** * The `ActivatedRoute` provider, for `TestBed.configureTestingModule` or a component's `providers`. * * ```ts * TestBed.configureTestingModule({ * providers: [provideActivatedRoute({ params: { id: '7' }, queryParams: { tab: 'reviews' } })], * }); * ``` * * A factory, so every injector that builds it gets a route of its own: a provider list hoisted to a * module constant never carries one test's navigation into the next. List it after `provideRouter()` * when a spec has both — the later provider of a token wins. */ declare function provideActivatedRoute(init?: ActivatedRouteInit): FactoryProvider; /** * The handle of the route `provideActivatedRoute()` put in the test's injector. * * ```ts * const route = injectActivatedRoute(); * * route.setQueryParams({ tab: 'specs' }); // queryParams emits, snapshot.queryParams already agrees * ``` * * Reads the `TestBed` by default; pass `fixture.debugElement.injector` when the route is in a * component's own `providers`. */ declare function injectActivatedRoute(injector?: Injector): ActivatedRouteDouble; /** * `provideRouterDouble()` + `injectRouterDouble()` — a `Router` whose URL, `routerState` and * `events` cannot disagree, with `navigate()` and `navigateByUrl()` already spied. * * After `ActivatedRoute`, `Router` is the provider suites write by hand most often, and they all * write the same one: `createAutoMock({ events: of(), url: '/' }, { returns: { navigate: * Promise.resolve(true) } })`. Every part of that is a guess a component can fall through — `of()` * never emits a second time, `url` is a string nobody updates, `routerState` is missing, and * `serializeUrl` throws the moment a guard builds a redirect. * * So the double keeps one URL and derives the rest of it: `url` is that URL serialized the way the * real router serializes it, `routerState` is Angular's own `RouterState` over a root route holding * its query parameters and fragment, `events` is a `BehaviorSubject` that starts at the * `NavigationEnd` which put the router there, `currentNavigation()` is `null` because a router * standing at a URL is idle, and `createUrlTree` / `serializeUrl` / `parseUrl` are the router's real * URL work rather than stubs. * * Navigation itself stays a spy, because a navigation in an application is asynchronous, runs * guards and can be cancelled: a double that moved its own URL on `navigate()` would be testing the * double. `setUrl()` and `emitNavigation()` are how the spec moves it, and `collectRouterEvents()` * turns the events that follow into a one-line assertion. * * Lives behind `vitest-auto-spy/angular-router` with the `ActivatedRoute` double, for the same * reason: `@angular/router` is an optional peer, paid for by the suites that import this entry. */ /** What a spec names about the navigation in flight; every other field is derived from where the router stands. */ type NavigationInit = Partial; /** Where the router starts: the URL it stands at, and whether a navigation is running. */ interface RouterDoubleInit { /** The URL the router reports until the spec moves it. Serialized as the router would. Default `'/'`. */ url?: string; /** * The navigation `currentNavigation()` and `getCurrentNavigation()` answer with. Default `null` — * a router standing at a URL is idle, which is what the real one answers between navigations. * Named here rather than only on the handle because a component reads it in a field initializer. */ currentNavigation?: NavigationInit | null; } /** The handle a spec drives the router through. */ interface RouterDouble { /** The value every injector in the test hands out for `Router`. */ readonly router: Router; /** The spied `navigate()` — resolves `true` until the spec configures it otherwise. */ readonly navigate: AddSpyMethodsByReturnTypes; /** The spied `navigateByUrl()` — resolves `true` until the spec configures it otherwise. */ readonly navigateByUrl: AddSpyMethodsByReturnTypes; /** Put the router at a URL: `url`, `routerState` and the root route move together. */ setUrl(url: string): void; /** * Put a navigation in flight, or end it with `null`. What the spec names is kept; `id`, * `initialUrl` and `extractedUrl` come from where the router stands, `trigger` is `'imperative'` * and `extras` is empty. */ setCurrentNavigation(navigation?: NavigationInit | null): void; /** * Push an event through `router.events`. A `NavigationEnd`, or a URL, moves the URL with it. * * Resolves once the event has been delivered and a navigation it ended has been cleared — the * moment `navigate()` resolves in an application — so a spec that awaits it reads the settled * router. The work itself is synchronous: a caller that ignores the promise, the way every * caller of the fire-and-forget original did, sees the same state on the next line. */ emitNavigation(event?: Event | string): Promise; } /** * Build a `Router` double without a `TestBed` — for a class constructed with `new`, or a functional * guard that takes the router from its own injector in the spec. * * ```ts * const { router, navigate } = createRouterDouble({ url: '/products/7' }); * const guard = new AuthGuard(router); * * expect(navigate).toHaveBeenCalledWith(['/login']); * ``` */ declare function createRouterDouble(init?: RouterDoubleInit): RouterDouble; /** * The `Router` provider, for `TestBed.configureTestingModule` or a component's `providers`. * * ```ts * TestBed.configureTestingModule({ providers: [provideRouterDouble({ url: '/products/7' })] }); * ``` * * A factory, so every injector that builds it gets a router of its own: a provider list hoisted to * a module constant never carries one test's navigation into the next. It provides nothing but the * `Router` token — `provideRouter([])` is still the answer when the routing itself is under test. */ declare function provideRouterDouble(init?: RouterDoubleInit): FactoryProvider; /** * The handle of the router `provideRouterDouble()` put in the test's injector. * * ```ts * const router = injectRouterDouble(); * * router.emitNavigation('/products/8'); // events emits a NavigationEnd; router.url already agrees * expect(router.navigate).toHaveBeenCalledWith(['/checkout']); * ``` * * Reads the `TestBed` by default; pass `fixture.debugElement.injector` when the router is in a * component's own `providers`. */ declare function injectRouterDouble(injector?: Injector): RouterDouble; /** One event the spec expects: the class it is an instance of, and the URL it carries when the pair names one. */ type RouterEventPair = readonly [Type, string?]; /** What {@link collectRouterEvents} hands back: the recording, and the assertion over it. */ interface RouterEventsHandle { /** Every event emitted since the collection started, in order. */ readonly events: readonly Event[]; /** Assert the recording: one `[class, url?]` pair per event, in order, none left over. */ expect(pairs: readonly RouterEventPair[]): void; } /** * Record what a double's `events` emits, and assert it in one line, the way Angular's own router * specs do — a class and a URL per event, rather than a hand-rolled array of `instanceof` checks. * * ```ts * const events = collectRouterEvents(router.events); * * await router.emitNavigation(new NavigationStart(2, '/checkout')); * await router.emitNavigation(new NavigationEnd(2, '/checkout', '/checkout')); * * events.expect([ * [NavigationStart, '/checkout'], * [NavigationEnd, '/checkout'], * ]); * ``` * * The recording starts empty: the double's `events` is a `BehaviorSubject`, and the value it replays * on subscribe is where the router stands, not something it emitted. The subscription ends with the * test that started the recording. */ declare function collectRouterEvents(source: Router['events']): RouterEventsHandle; /** What `injectLocationDouble()` and `createLocationDouble()` hand back: Angular's own recording fake. */ type LocationDouble = SpyLocation; /** * The `Location` and `LocationStrategy` providers, for `TestBed.configureTestingModule` or a * component's `providers`. * * ```ts * TestBed.configureTestingModule({ providers: [provideLocationDouble()] }); * * const location = injectLocationDouble(); * * location.go('/reports/7'); // urlChanges records it; path() reads it back * location.simulateUrlPop('/'); // the popstate no method call can cause * ``` * * Returns the provider pair Angular ships (`SpyLocation` for `Location`, `MockLocationStrategy` for * `LocationStrategy`) — a new array every call, so a list hoisted to a module constant still hands * each injector a double of its own. */ declare function provideLocationDouble(): Provider[]; /** * The handle of the `SpyLocation` `provideLocationDouble()` put in the test's injector. * * Reads the `TestBed` by default; pass `fixture.debugElement.injector` when the double is in a * component's own `providers`. * * The failure it exists to prevent is the quiet one: `Location` is `providedIn: 'root'`, so a spec * that forgot the provider gets the platform's real `Location`, silently — every method answers, * `path()` returns `''`, and nothing a test does to it lands anywhere. */ declare function injectLocationDouble(injector?: Injector): LocationDouble; /** * A `SpyLocation` without a `TestBed` — for a class constructed with `new`, or a guard handed the * `Location` as an argument. * * ```ts * const location = createLocationDouble(); * * location.go('/away'); * expect(location.urlChanges).toEqual(['/away']); * ``` */ declare function createLocationDouble(): LocationDouble; export { type ActivatedRouteChange, type ActivatedRouteDouble, type ActivatedRouteInit, type LocationDouble, type NavigationInit, type RouterDouble, type RouterDoubleInit, type RouterEventPair, type RouterEventsHandle, collectRouterEvents, createActivatedRoute, createLocationDouble, createRouterDouble, injectActivatedRoute, injectLocationDouble, injectRouterDouble, provideActivatedRoute, provideLocationDouble, provideRouterDouble };