import { HttpRequest } from '@angular/common/http'; import { TestRequest } from '@angular/common/http/testing'; import { EnvironmentProviders, Provider } from '@angular/core'; /** * `provideHttpTesting()` + `expectRequest()` — the `httpResource()` and `HttpClient` dance in two * lines. * * Answering one request from a zoneless spec takes six steps today, and the order of them is not * guessable: tick (an `httpResource()` issues *nothing* until something does), inject the * `HttpTestingController`, `expectOne`, `flush`, let one microtask run so the response reaches the * resource, tick again so what read it is up to date. Miss the first tick and `expectOne` finds no * request; miss the microtask and the assertion reads the resource's *default* value — a green test * that asserts nothing until the day the default changes. * * ```ts * TestBed.configureTestingModule({ providers: [...provideHttpTesting()] }); * * const products = TestBed.runInInjectionContext(() => httpResource(() => '/api/products')); * * await expectRequest('/api/products').flush([product]); * * expect(products.value()).toEqual([product]); // no tick, no microtask, no detectChanges * ``` * * **This module is why `vitest-auto-spy/angular-http` is its own entry.** It is the only file in * the package that imports `@angular/common`, and `vitest-auto-spy/angular` has to keep loading in * a project that has `@angular/core` and not `@angular/common` — the same invariant that keeps * rxjs behind `vitest-auto-spy/rxjs`. The optional peer is paid for by the suites that import this * entry and by nobody else. * * It cooperates with `enableAngularDiagnostics({ pendingRequests })` rather than competing: both * take the open requests with `match(() => true)`, which is one-shot, so whichever looks first * owns them and one unanswered request is never reported twice. */ /** How a request is named: a URL, a pattern, or a question asked of the request itself. */ type RequestMatcher = RegExp | string | ((request: HttpRequest) => boolean); /** Options for {@link expectRequest} and {@link expectNoRequest}. */ interface ExpectRequestOptions { /** Narrow to one verb, for a URL that is both read and written in the same test. Case-insensitive. */ method?: string; } /** Options for {@link provideHttpTesting}. */ interface HttpTestingOptions { /** * Fail a test that ends holding a request nothing answered. Default `true`. * * A request nobody flushed is a spec whose code under test is still waiting on a response — every * assertion after that call is about a state the test never reached. Left alone it also leaks: * the next test's `expectRequest` matches a request the previous one made. * * An object keeps the check on and carries its option to it: `{ ignoreCancelled: true }` is the * same opt-in as `verifyNoPendingRequests({ ignoreCancelled: true })` — a request the code under * test cancelled, by unsubscribing, no longer fails the test. */ verifyOnTeardown?: boolean | { ignoreCancelled?: boolean; }; } /** The body types `TestRequest#flush` takes, read off Angular rather than restated here. */ type ResponseBody = Parameters[0]; /** `headers` / `status` / `statusText`, exactly as `TestRequest#flush` takes them. */ type FlushOptions = NonNullable[1]>; /** `headers` / `statusText` for {@link RequestExpectation.error} — the status is its first argument. */ type RequestErrorOptions = Omit[1]>, 'status'>; /** The one request {@link expectRequest} found, and the two ways to answer it. */ interface RequestExpectation { /** The request as the code under test sent it — URL, method, headers, body. */ readonly request: HttpRequest; /** * Answer the request, then settle: on the next line the resource has its value and the view that * reads it has been ticked. */ flush(body: ResponseBody, options?: FlushOptions): Promise; /** Fail the request with an HTTP status, then settle the same way {@link RequestExpectation.flush} does. */ error(status: number, options?: RequestErrorOptions): Promise; } /** * Everything `TestBed.configureTestingModule` needs for HTTP testing, in one spread. * * ```ts * TestBed.configureTestingModule({ providers: [...provideHttpTesting(), provideAutoSpy(Analytics)] }); * ``` * * It is `provideHttpClient()` + `provideHttpClientTesting()`, plus — unless `verifyOnTeardown` is * `false` — the environment initializer that arms the end-of-test check: a suite whose interceptors * are the thing under test keeps its own `provideHttpClient(withInterceptors([…]))` and adds * `provideHttpClientTesting()` after it. */ declare function provideHttpTesting(options?: HttpTestingOptions): (EnvironmentProviders | Provider)[]; /** * Find the one request that matches, and hand back the two ways to answer it. * * ```ts * await expectRequest('/api/products').flush([product]); // by URL * await expectRequest('/api/products', { method: 'POST' }).flush({}); // by URL and verb * await expectRequest(/\/api\/products\?page=\d+/).flush([]); // by pattern * await expectRequest((request) => request.body?.id === 7).flush({}); // by anything else * ``` * * It ticks before it looks, because an `httpResource()` created in an injection context has issued * nothing until something does — that is the step whose absence makes `expectOne` report a request * that was never sent rather than one that was sent wrongly. * * @param matcher The URL, a pattern for it, or a predicate over the request. * @param options `{ method }`, for a URL that is both read and written in the same test. */ declare function expectRequest(matcher: RequestMatcher, options?: ExpectRequestOptions): RequestExpectation; /** * Assert that nothing was requested — of one endpoint, or at all. * * ```ts * component.filter.set('open'); * expectNoRequest('/api/products'); // the cache answered; nothing went out * ``` * * Ticks first, like {@link expectRequest}: a request that has not been issued *yet* is not the * same claim, and this helper would otherwise pass for the wrong reason. */ declare function expectNoRequest(matcher?: RequestMatcher, options?: ExpectRequestOptions): void; /** * Fail if the `HttpTestingController` is still holding requests, and take them either way. * * `provideHttpTesting()` runs this after every test on its own; it is exported because the same * question is worth asking mid-test — after the arrange step, before the assertions that depend on * it — and because a suite that turned `verifyOnTeardown` off still wants it in the two specs where * it matters. * * `ignoreCancelled: true` is the opt-in `HttpTestingController.verify({ ignoreCancelled })` names: * a request the code under test cancelled — an unsubscribed `httpResource()`, a `takeUntil` that * cut a call — is taken but no longer held against the test. Default `false`, like Angular's own. * * A no-op when the test configured no HTTP testing at all. */ declare function verifyNoPendingRequests(options?: { ignoreCancelled?: boolean; }): void; export { type ExpectRequestOptions, type FlushOptions, type HttpTestingOptions, type RequestErrorOptions, type RequestExpectation, type RequestMatcher, type ResponseBody, expectNoRequest, expectRequest, provideHttpTesting, verifyNoPendingRequests };