diff --git a/packages/common/http/src/transfer_cache.ts b/packages/common/http/src/transfer_cache.ts index b71d25e3b7c..46c202080b3 100644 --- a/packages/common/http/src/transfer_cache.ts +++ b/packages/common/http/src/transfer_cache.ts @@ -21,7 +21,7 @@ import { ɵRuntimeError as RuntimeError, } from '@angular/core'; import {Observable, of} from 'rxjs'; -import {tap} from 'rxjs/operators'; +import {concatMap} from 'rxjs/operators'; import {RuntimeErrorCode} from './errors'; import {HttpHeaders} from './headers'; @@ -267,14 +267,24 @@ export function transferCacheInterceptorFn( if (typeof ngServerMode !== 'undefined' && ngServerMode) { // Request not found in cache. Make the request and cache it if on the server. return event$.pipe( - tap((event: HttpEvent) => { + concatMap(async (event: HttpEvent) => { // Only cache successful HTTP responses. if (event instanceof HttpResponse) { + let body = event.body; + if (req.responseType === 'blob') { + // Note: Blob is converted to ArrayBuffer because Uint8Array constructor + // doesn't accept Blob directly, which would result in an empty array. + // Type assertion is safe here: when responseType is 'blob', the body is guaranteed to be a Blob + const arrayBuffer = await (event.body as Blob).arrayBuffer(); + body = toBase64(arrayBuffer); + } else if (req.responseType === 'arraybuffer') { + // For arraybuffer, convert to base64; for other types (json, text), store as-is. + // Type assertion is safe here: when responseType is 'arraybuffer', the body is + // guaranteed to be an ArrayBuffer + body = toBase64(event.body as ArrayBufferLike); + } transferState.set(storeKey, { - [BODY]: - req.responseType === 'arraybuffer' || req.responseType === 'blob' - ? toBase64(event.body as ArrayBufferLike) - : event.body, + [BODY]: body, [HEADERS]: getFilteredHeaders(event.headers, headersToInclude), [STATUS]: event.status, [STATUS_TEXT]: event.statusText, @@ -282,6 +292,7 @@ export function transferCacheInterceptorFn( [RESPONSE_TYPE]: req.responseType, }); } + return event; }), ); } diff --git a/packages/common/http/test/transfer_cache_spec.ts b/packages/common/http/test/transfer_cache_spec.ts index 35af1c2624b..870633e8651 100644 --- a/packages/common/http/test/transfer_cache_spec.ts +++ b/packages/common/http/test/transfer_cache_spec.ts @@ -17,7 +17,7 @@ import { } from '@angular/core'; import {TestBed} from '@angular/core/testing'; import {useAutoTick, timeout, withBody} from '@angular/private/testing'; -import {BehaviorSubject} from 'rxjs'; +import {BehaviorSubject, firstValueFrom, lastValueFrom} from 'rxjs'; import {HttpClient, HttpResponse, provideHttpClient} from '../public_api'; import { @@ -39,6 +39,7 @@ interface RequestParams { transferCache?: {includeHeaders: string[]} | boolean; headers?: {[key: string]: string}; body?: RequestBody; + responseType?: 'arraybuffer' | 'blob' | 'json' | 'text'; } type RequestBody = @@ -63,35 +64,41 @@ describe('TransferCache', () => { describe('withHttpTransferCache', () => { let isStable: BehaviorSubject; - function makeRequestAndExpectOne( + async function makeRequestAndExpectOne( url: string, body: RequestBody, params?: RequestParams, - ): string; - function makeRequestAndExpectOne( + ): Promise; + async function makeRequestAndExpectOne( url: string, body: RequestBody, params?: RequestParams & {observe: 'response'}, - ): HttpResponse; - function makeRequestAndExpectOne(url: string, body: RequestBody, params?: RequestParams): any { - let response!: any; - TestBed.inject(HttpClient) - .request(params?.method ?? 'GET', url, params) - .subscribe((r) => (response = r)); + ): Promise>; + async function makeRequestAndExpectOne( + url: string, + body: RequestBody, + params?: RequestParams, + ): Promise | any> { + const response = lastValueFrom( + TestBed.inject(HttpClient).request(params?.method ?? 'GET', url, params), + ); + TestBed.inject(HttpTestingController).expectOne(url).flush(body, {headers: params?.headers}); + return response; } - function makeRequestAndExpectNone( + async function makeRequestAndExpectNone( url: string, method: string = 'GET', params?: RequestParams, - ): HttpResponse { - let response!: HttpResponse; - TestBed.inject(HttpClient) - .request(method, url, {observe: 'response', ...params}) - .subscribe((r) => (response = r)); + ): Promise> { + const response = lastValueFrom( + TestBed.inject(HttpClient).request(method, url, {observe: 'response', ...params}), + ); + TestBed.inject(HttpTestingController).expectNone(url); + return response; } @@ -132,53 +139,86 @@ describe('TransferCache', () => { }), ); - it('should store HTTP calls in cache when application is not stable', () => { - makeRequestAndExpectOne('/test', 'foo'); + it('should store HTTP calls in cache when application is not stable', async () => { + await makeRequestAndExpectOne('/test', 'foo'); const transferState = TestBed.inject(TransferState); const key = makeStateKey(Object.keys((transferState as any).store)[0]); expect(transferState.get(key, null)).toEqual(jasmine.objectContaining({[BODY]: 'foo'})); }); - it('should cache arraybuffer responses correctly', () => { - const testData = new Uint8Array([1, 2, 3, 4, 5]).buffer; - let response!: ArrayBuffer; - TestBed.inject(HttpClient) - .get('/test-arraybuffer', {responseType: 'arraybuffer'}) - .subscribe((r) => (response = r)); - TestBed.inject(HttpTestingController).expectOne('/test-arraybuffer').flush(testData); + it('should cache arraybuffer responses correctly', async () => { + const testData = new Uint8Array([1, 2, 3, 4, 5]); + const response = await makeRequestAndExpectOne( + '/test-arraybuffer', + testData.buffer, + { + responseType: 'arraybuffer', + }, + ); - expect(new Uint8Array(response)).toEqual(new Uint8Array([1, 2, 3, 4, 5])); + expect(response).toEqual(testData.buffer); - let cachedResponse!: ArrayBuffer; - TestBed.inject(HttpClient) - .get('/test-arraybuffer', {responseType: 'arraybuffer'}) - .subscribe((r) => (cachedResponse = r)); - TestBed.inject(HttpTestingController).expectNone('/test-arraybuffer'); + const cachedResponse = await makeRequestAndExpectNone( + '/test-arraybuffer', + 'GET', + {responseType: 'arraybuffer'}, + ); - expect(new Uint8Array(cachedResponse)).toEqual(new Uint8Array([1, 2, 3, 4, 5])); + expect(new Uint8Array(cachedResponse.body!)).toEqual(testData); }); - it('should cache blob responses correctly', () => { - const testData = new Uint8Array([10, 20, 30, 40, 50]).buffer; - let response!: Blob; - TestBed.inject(HttpClient) - .get('/test-blob', {responseType: 'blob'}) - .subscribe((r) => (response = r)); - TestBed.inject(HttpTestingController).expectOne('/test-blob').flush(testData); + it('should cache blob responses correctly (with ArrayBuffer)', async () => { + const testData = new Uint8Array([10, 20, 30, 40, 50]); + const response = await makeRequestAndExpectOne('/test-blob', testData.buffer, { + responseType: 'blob', + }); - expect(response instanceof Blob).toBeTrue(); + expect(response).toBeInstanceOf(Blob); expect(response.size).toBe(5); + + const cachedResponse = await makeRequestAndExpectNone('/test-blob', 'GET', { + responseType: 'blob', + }); + + expect(cachedResponse.body).toBeInstanceOf(Blob); + expect(cachedResponse.body!.size).toBe(5); + + const cachedArrayBuffer = await cachedResponse.body!.arrayBuffer(); + expect(new Uint8Array(cachedArrayBuffer)).toEqual(testData); + }); + + it('should cache blob responses correctly (with Blob)', async () => { + const data = new Uint8Array([65, 66, 67, 68, 69, 70]); + const blobData = new Blob([data], { + type: 'application/octet-stream', + }); + const response = await makeRequestAndExpectOne('/test-blob-direct', blobData, { + responseType: 'blob', + }); + + expect(response).toBeInstanceOf(Blob); + expect(response.size).toBe(6); + expect(response.type).toBe('application/octet-stream'); + + const cachedResponse = await makeRequestAndExpectNone('/test-blob-direct', 'GET', { + responseType: 'blob', + }); + + expect(cachedResponse.body).toBeInstanceOf(Blob); + expect(cachedResponse.body!.size).toBe(6); + const cachedArrayBuffer = await cachedResponse.body!.arrayBuffer(); + expect(new Uint8Array(cachedArrayBuffer)).toEqual(data); }); it('should stop storing HTTP calls in `TransferState` after application becomes stable', async () => { - makeRequestAndExpectOne('/test-1', 'foo'); - makeRequestAndExpectOne('/test-2', 'buzz'); + await makeRequestAndExpectOne('/test-1', 'foo'); + await makeRequestAndExpectOne('/test-2', 'buzz'); isStable.next(true); await timeout(); - makeRequestAndExpectOne('/test-3', 'bar'); + await makeRequestAndExpectOne('/test-3', 'bar'); const transferState = TestBed.inject(TransferState); expect(JSON.parse(transferState.toJson()) as Record).toEqual({ @@ -201,76 +241,79 @@ describe('TransferCache', () => { }); }); - it(`should use calls from cache when present and application is not stable`, () => { - makeRequestAndExpectOne('/test-1', 'foo'); + it(`should use calls from cache when present and application is not stable`, async () => { + await makeRequestAndExpectOne('/test-1', 'foo'); // Do the same call, this time it should served from cache. - makeRequestAndExpectNone('/test-1'); + await makeRequestAndExpectNone('/test-1'); }); it(`should not use calls from cache when present and application is stable`, async () => { - makeRequestAndExpectOne('/test-1', 'foo'); + await makeRequestAndExpectOne('/test-1', 'foo'); isStable.next(true); await timeout(); // Do the same call, this time it should go through as application is stable. - makeRequestAndExpectOne('/test-1', 'foo'); + await makeRequestAndExpectOne('/test-1', 'foo'); }); it(`should differentiate calls with different parameters`, async () => { // make calls with different parameters. All of which should be saved in the state. - makeRequestAndExpectOne('/test-1?foo=1', 'foo'); - makeRequestAndExpectOne('/test-1', 'foo'); - makeRequestAndExpectOne('/test-1?foo=2', 'buzz'); + await makeRequestAndExpectOne('/test-1?foo=1', 'foo'); + await makeRequestAndExpectOne('/test-1', 'foo'); + await makeRequestAndExpectOne('/test-1?foo=2', 'buzz'); - makeRequestAndExpectNone('/test-1?foo=1'); + await makeRequestAndExpectNone('/test-1?foo=1'); await expectAsync(TestBed.inject(HttpClient).get('/test-1?foo=1').toPromise()).toBeResolvedTo( 'foo', ); }); - it('should skip cache when specified', () => { - makeRequestAndExpectOne('/test-1?foo=1', 'foo', {transferCache: false}); + it('should skip cache when specified', async () => { + await makeRequestAndExpectOne('/test-1?foo=1', 'foo', {transferCache: false}); // The previous request wasn't cached so this one can't use the cache - makeRequestAndExpectOne('/test-1?foo=1', 'foo'); + await makeRequestAndExpectOne('/test-1?foo=1', 'foo'); // But this one will - makeRequestAndExpectNone('/test-1?foo=1'); + await makeRequestAndExpectNone('/test-1?foo=1'); }); - it('should not cache a POST even with filter true specified', () => { - makeRequestAndExpectOne('/test-1?foo=1', 'post-body', {method: 'POST'}); + it('should not cache a POST even with filter true specified', async () => { + await makeRequestAndExpectOne('/test-1?foo=1', 'post-body', {method: 'POST'}); // Previous POST request wasn't cached - makeRequestAndExpectOne('/test-1?foo=1', 'body2', {method: 'POST'}); + await makeRequestAndExpectOne('/test-1?foo=1', 'body2', {method: 'POST'}); // filter => true won't cache neither - makeRequestAndExpectOne('/test-1?foo=1', 'post-body', {method: 'POST', transferCache: true}); + await makeRequestAndExpectOne('/test-1?foo=1', 'post-body', { + method: 'POST', + transferCache: true, + }); - const response = makeRequestAndExpectOne('/test-1?foo=1', 'body2', {method: 'POST'}); + const response = await makeRequestAndExpectOne('/test-1?foo=1', 'body2', {method: 'POST'}); expect(response).toBe('body2'); }); it('should not cache headers', async () => { // HttpTransferCacheOptions: true = fallback to default = headers won't be cached - makeRequestAndExpectOne('/test-1?foo=1', 'foo', { + await makeRequestAndExpectOne('/test-1?foo=1', 'foo', { headers: {foo: 'foo', bar: 'bar'}, transferCache: true, }); // request returns the cache without any header. - const response2 = makeRequestAndExpectNone('/test-1?foo=1'); + const response2 = await makeRequestAndExpectNone('/test-1?foo=1'); expect(response2.headers.keys().length).toBe(0); }); it('should cache with headers', async () => { // headers are case not sensitive - makeRequestAndExpectOne('/test-1?foo=1', 'foo', { + await makeRequestAndExpectOne('/test-1?foo=1', 'foo', { headers: {foo: 'foo', bar: 'bar', 'BAZ': 'baz'}, transferCache: {includeHeaders: ['foo', 'baz']}, }); const consoleWarnSpy = spyOn(console, 'warn'); // request returns the cache with only 2 header entries. - const response = makeRequestAndExpectNone('/test-1?foo=1', 'GET', { + const response = await makeRequestAndExpectNone('/test-1?foo=1', 'GET', { transferCache: {includeHeaders: ['foo', 'baz']}, }); expect(response.headers.keys().length).toBe(2); @@ -299,69 +342,77 @@ describe('TransferCache', () => { expect(consoleWarnSpy.calls.count()).toBe(3); }); - it('should not cache POST by default', () => { - makeRequestAndExpectOne('/test-1?foo=1', 'foo', {method: 'POST'}); - makeRequestAndExpectOne('/test-1?foo=1', 'foo', {method: 'POST'}); + it('should not cache POST by default', async () => { + await makeRequestAndExpectOne('/test-1?foo=1', 'foo', {method: 'POST'}); + await makeRequestAndExpectOne('/test-1?foo=1', 'foo', {method: 'POST'}); }); - it('should cache POST with the transferCache option', () => { - makeRequestAndExpectOne('/test-1?foo=1', 'foo', {method: 'POST', transferCache: true}); - makeRequestAndExpectNone('/test-1?foo=1', 'POST', {transferCache: true}); + it('should cache POST with the transferCache option', async () => { + await makeRequestAndExpectOne('/test-1?foo=1', 'foo', {method: 'POST', transferCache: true}); + await makeRequestAndExpectNone('/test-1?foo=1', 'POST', {transferCache: true}); - makeRequestAndExpectOne('/test-2?foo=1', 'foo', { + await makeRequestAndExpectOne('/test-2?foo=1', 'foo', { method: 'POST', transferCache: {includeHeaders: []}, }); - makeRequestAndExpectNone('/test-2?foo=1', 'POST', {transferCache: true}); + await makeRequestAndExpectNone('/test-2?foo=1', 'POST', {transferCache: true}); }); it('should not cache request that requires authorization by default', async () => { - makeRequestAndExpectOne('/test-auth', 'foo', { + await makeRequestAndExpectOne('/test-auth', 'foo', { headers: {Authorization: 'Basic YWxhZGRpbjpvcGVuc2VzYW1l'}, }); - makeRequestAndExpectOne('/test-auth', 'foo'); + await makeRequestAndExpectOne('/test-auth', 'foo'); }); it('should not cache request that requires proxy authorization by default', async () => { - makeRequestAndExpectOne('/test-auth', 'foo', { + await makeRequestAndExpectOne('/test-auth', 'foo', { headers: {'Proxy-Authorization': 'Basic YWxhZGRpbjpvcGVuc2VzYW1l'}, }); - makeRequestAndExpectOne('/test-auth', 'foo'); + await makeRequestAndExpectOne('/test-auth', 'foo'); }); - it('should cache POST with the differing body in string form', () => { - makeRequestAndExpectOne('/test-1', null, {method: 'POST', transferCache: true, body: 'foo'}); - makeRequestAndExpectNone('/test-1', 'POST', {transferCache: true, body: 'foo'}); - makeRequestAndExpectOne('/test-1', null, {method: 'POST', transferCache: true, body: 'bar'}); + it('should cache POST with the differing body in string form', async () => { + await makeRequestAndExpectOne('/test-1', null, { + method: 'POST', + transferCache: true, + body: 'foo', + }); + await makeRequestAndExpectNone('/test-1', 'POST', {transferCache: true, body: 'foo'}); + await makeRequestAndExpectOne('/test-1', null, { + method: 'POST', + transferCache: true, + body: 'bar', + }); }); - it('should cache POST with the differing body in object form', () => { - makeRequestAndExpectOne('/test-1', null, { + it('should cache POST with the differing body in object form', async () => { + await makeRequestAndExpectOne('/test-1', null, { method: 'POST', transferCache: true, body: {foo: true}, }); - makeRequestAndExpectNone('/test-1', 'POST', {transferCache: true, body: {foo: true}}); - makeRequestAndExpectOne('/test-1', null, { + await makeRequestAndExpectNone('/test-1', 'POST', {transferCache: true, body: {foo: true}}); + await makeRequestAndExpectOne('/test-1', null, { method: 'POST', transferCache: true, body: {foo: false}, }); }); - it('should cache POST with the differing body in URLSearchParams form', () => { - makeRequestAndExpectOne('/test-1', null, { + it('should cache POST with the differing body in URLSearchParams form', async () => { + await makeRequestAndExpectOne('/test-1', null, { method: 'POST', transferCache: true, body: new URLSearchParams('foo=1'), }); - makeRequestAndExpectNone('/test-1', 'POST', { + await makeRequestAndExpectNone('/test-1', 'POST', { transferCache: true, body: new URLSearchParams('foo=1'), }); - makeRequestAndExpectOne('/test-1', null, { + await makeRequestAndExpectOne('/test-1', null, { method: 'POST', transferCache: true, body: new URLSearchParams('foo=2'), @@ -407,9 +458,9 @@ describe('TransferCache', () => { }), ); - it('should skip storing in transfer cache when platform is browser', () => { - makeRequestAndExpectOne('/test-1?foo=1', 'foo'); - makeRequestAndExpectOne('/test-1?foo=1', 'foo'); + it('should skip storing in transfer cache when platform is browser', async () => { + await makeRequestAndExpectOne('/test-1?foo=1', 'foo'); + await makeRequestAndExpectOne('/test-1?foo=1', 'foo'); }); }); @@ -457,58 +508,58 @@ describe('TransferCache', () => { }), ); - it('should cache because of global filter', () => { - makeRequestAndExpectOne('/include?foo=1', 'foo'); - makeRequestAndExpectNone('/include?foo=1'); + it('should cache because of global filter', async () => { + await makeRequestAndExpectOne('/include?foo=1', 'foo'); + await makeRequestAndExpectNone('/include?foo=1'); }); - it('should not cache because of global filter', () => { - makeRequestAndExpectOne('/exclude?foo=1', 'foo'); - makeRequestAndExpectOne('/exclude?foo=1', 'foo'); + it('should not cache because of global filter', async () => { + await makeRequestAndExpectOne('/exclude?foo=1', 'foo'); + await makeRequestAndExpectOne('/exclude?foo=1', 'foo'); }); it(`should cache request that requires authorization when 'includeRequestsWithAuthHeaders' is 'true'`, async () => { - makeRequestAndExpectOne('/test-auth', 'foo', { + await makeRequestAndExpectOne('/test-auth', 'foo', { headers: {Authorization: 'Basic YWxhZGRpbjpvcGVuc2VzYW1l'}, }); - makeRequestAndExpectNone('/test-auth'); + await makeRequestAndExpectNone('/test-auth'); }); it(`should cache request that requires proxy authorization when 'includeRequestsWithAuthHeaders' is 'true'`, async () => { - makeRequestAndExpectOne('/test-auth', 'foo', { + await makeRequestAndExpectOne('/test-auth', 'foo', { headers: {'Proxy-Authorization': 'Basic YWxhZGRpbjpvcGVuc2VzYW1l'}, }); - makeRequestAndExpectNone('/test-auth'); + await makeRequestAndExpectNone('/test-auth'); }); - it('should cache a POST request', () => { - makeRequestAndExpectOne('/include?foo=1', 'post-body', {method: 'POST'}); + it('should cache a POST request', async () => { + await makeRequestAndExpectOne('/include?foo=1', 'post-body', {method: 'POST'}); // Previous POST request wasn't cached - const response = makeRequestAndExpectNone('/include?foo=1', 'POST'); + const response = await makeRequestAndExpectNone('/include?foo=1', 'POST'); expect(response.body).toBe('post-body'); }); - it('should cache with headers', () => { + it('should cache with headers', async () => { // nothing specified, should use global options = callback => include + headers - makeRequestAndExpectOne('/include?foo=1', 'foo', {headers: {foo: 'foo', bar: 'bar'}}); + await makeRequestAndExpectOne('/include?foo=1', 'foo', {headers: {foo: 'foo', bar: 'bar'}}); // This one was cached with headers - const response = makeRequestAndExpectNone('/include?foo=1'); + const response = await makeRequestAndExpectNone('/include?foo=1'); expect(response.headers.keys().length).toBe(2); }); - it('should cache without headers because overridden', () => { + it('should cache without headers because overridden', async () => { // nothing specified, should use global options = callback => include + headers - makeRequestAndExpectOne('/include?foo=1', 'foo', { + await makeRequestAndExpectOne('/include?foo=1', 'foo', { headers: {foo: 'foo', bar: 'bar'}, transferCache: {includeHeaders: []}, }); // This one was cached with headers - const response = makeRequestAndExpectNone('/include?foo=1'); + const response = await makeRequestAndExpectNone('/include?foo=1'); expect(response.headers.keys().length).toBe(0); }); }); @@ -550,17 +601,17 @@ describe('TransferCache', () => { }), ); - it('should cache with public origin', () => { - makeRequestAndExpectOne('http://internal-domain.com:1234/test-1?foo=1', 'foo'); - const cachedRequest = makeRequestAndExpectNone( + it('should cache with public origin', async () => { + await makeRequestAndExpectOne('http://internal-domain.com:1234/test-1?foo=1', 'foo'); + const cachedRequest = await makeRequestAndExpectNone( 'https://external-domain.net:443/test-1?foo=1', ); expect(cachedRequest.url).toBe('https://external-domain.net:443/test-1?foo=1'); }); - it('should cache normally when there is no mapping defined for the origin', () => { - makeRequestAndExpectOne('https://other.internal-domain.com:1234/test-1?foo=1', 'foo'); - makeRequestAndExpectNone('https://other.internal-domain.com:1234/test-1?foo=1'); + it('should cache normally when there is no mapping defined for the origin', async () => { + await makeRequestAndExpectOne('https://other.internal-domain.com:1234/test-1?foo=1', 'foo'); + await makeRequestAndExpectNone('https://other.internal-domain.com:1234/test-1?foo=1'); }); describe('when the origin map is configured with extra paths', () => { @@ -617,7 +668,7 @@ describe('TransferCache', () => { describe('on the client', () => { beforeEach( - withBody('', () => { + withBody('', async () => { TestBed.resetTestingModule(); isStable = new BehaviorSubject(false); @@ -647,7 +698,7 @@ describe('TransferCache', () => { }); // Make a request on the server to fill the transfer state then reuse it in the browser - makeRequestAndExpectOne('http://internal-domain.com:1234/test-1?foo=1', 'foo'); + await makeRequestAndExpectOne('http://internal-domain.com:1234/test-1?foo=1', 'foo'); const transferState = TestBed.inject(TransferState); TestBed.resetTestingModule(); diff --git a/packages/platform-browser/test/hydration_spec.ts b/packages/platform-browser/test/hydration_spec.ts index b97e2f24d5c..7fbda9cf293 100644 --- a/packages/platform-browser/test/hydration_spec.ts +++ b/packages/platform-browser/test/hydration_spec.ts @@ -18,7 +18,7 @@ import { } from '@angular/core'; import {TestBed} from '@angular/core/testing'; import {withBody} from '@angular/private/testing'; -import {BehaviorSubject} from 'rxjs'; +import {BehaviorSubject, lastValueFrom} from 'rxjs'; import {provideClientHydration, withNoHttpTransferCache} from '../public_api'; import {withHttpTransferCacheOptions} from '../src/hydration'; @@ -31,21 +31,23 @@ describe('provideClientHydration', () => { }) class SomeComponent {} - function makeRequestAndExpectOne( + async function makeRequestAndExpectOne( url: string, body: string, options: HttpTransferCacheOptions | boolean = true, - ): void { - TestBed.inject(HttpClient).get(url, {transferCache: options}).subscribe(); + ): Promise { + const response = lastValueFrom(TestBed.inject(HttpClient).get(url, {transferCache: options})); TestBed.inject(HttpTestingController).expectOne(url).flush(body); + return response; } - function makeRequestAndExpectNone( + async function makeRequestAndExpectNone( url: string, options: HttpTransferCacheOptions | boolean = true, - ): void { - TestBed.inject(HttpClient).get(url, {transferCache: options}).subscribe(); + ): Promise { + const response = lastValueFrom(TestBed.inject(HttpClient).get(url, {transferCache: options})); TestBed.inject(HttpTestingController).expectNone(url); + return response; } @Injectable() @@ -88,10 +90,10 @@ describe('provideClientHydration', () => { ), ); - it(`should use cached HTTP calls`, () => { - makeRequestAndExpectOne('/test-1', 'foo'); + it(`should use cached HTTP calls`, async () => { + await makeRequestAndExpectOne('/test-1', 'foo'); // Do the same call, this time it should served from cache. - makeRequestAndExpectNone('/test-1'); + await makeRequestAndExpectNone('/test-1'); }); }); @@ -120,10 +122,10 @@ describe('provideClientHydration', () => { ), ); - it(`should not cache HTTP calls`, () => { - makeRequestAndExpectOne('/test-1', 'foo', false); + it(`should not cache HTTP calls`, async () => { + await makeRequestAndExpectOne('/test-1', 'foo', false); // Do the same call, this time should pass through as cache is disabled. - makeRequestAndExpectOne('/test-1', 'foo'); + await makeRequestAndExpectOne('/test-1', 'foo'); }); }); @@ -154,14 +156,11 @@ describe('provideClientHydration', () => { ), ); - it(`should cache HTTP POST calls`, () => { + it(`should cache HTTP POST calls`, async () => { const url = '/test-1'; const body = 'foo'; - TestBed.inject(HttpClient).post(url, body).subscribe(); - TestBed.inject(HttpTestingController).expectOne(url).flush(body); - - TestBed.inject(HttpClient).post(url, body).subscribe(); - TestBed.inject(HttpTestingController).expectNone(url); + await makeRequestAndExpectOne(url, body); + await makeRequestAndExpectNone(url); }); }); });