/
githubmirror
/
angular
Обзор
Документация
Войти
/
githubmirror
/
angular
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
packages/service-worker/test/provider_spec.ts
495 строк
17 KB
SkyZeroZx
feat(service-worker): Adds for type in provideServiceWorker (#62831)
06 авг 2025, 16:10
06 авг 2025, 16:10
5220b51
Код
Авторство
О чём код?
/** * @license * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.dev/license */ import {ApplicationRef, PLATFORM_ID} from '@angular/core'; import {fakeAsync, flushMicrotasks, TestBed, tick} from '@angular/core/testing'; import {Subject} from 'rxjs'; import {filter, take} from 'rxjs/operators'; import {ServiceWorkerModule} from '../src/module'; import {provideServiceWorker, SwRegistrationOptions} from '../src/provider'; import {SwUpdate} from '../src/update'; const provideServiceWorkerApi = 'provideServiceWorker'; const serviceWorkerModuleApi = 'ServiceWorkerModule'; async function waitForReadyToRegister() { // `readyToRegister` is a microtask, so we wait for it to execute by // scheduling another microtask before running expectations. await Promise.resolve(); } [provideServiceWorkerApi, serviceWorkerModuleApi].forEach((apiFnName: string) => { describe(apiFnName, () => { // Skip environments that don't support the minimum APIs needed to run these SW tests. if (typeof navigator === 'undefined' || typeof navigator.serviceWorker === 'undefined') { // Jasmine will throw if there are no tests. it('should pass', () => {}); return; } let swRegisterSpy: jasmine.Spy; const untilStable = () => { return TestBed.inject(ApplicationRef).whenStable(); }; beforeEach( () => (swRegisterSpy = spyOn(navigator.serviceWorker, 'register').and.returnValue( Promise.resolve(null as any), )), ); describe('register', () => { const configTestBed = async (options: SwRegistrationOptions) => { if (apiFnName === provideServiceWorkerApi) { TestBed.configureTestingModule({ providers: [ provideServiceWorker('sw.js', options), {provide: PLATFORM_ID, useValue: 'browser'}, ], }); } else { TestBed.configureTestingModule({ imports: [ServiceWorkerModule.register('sw.js', options)], providers: [{provide: PLATFORM_ID, useValue: 'browser'}], }); } await untilStable(); await waitForReadyToRegister(); }; it('sets the registration options', async () => { await configTestBed({enabled: true, scope: 'foo', updateViaCache: 'all', type: 'classic'}); expect(TestBed.inject(SwRegistrationOptions)).toEqual({ enabled: true, scope: 'foo', updateViaCache: 'all', type: 'classic', }); expect(swRegisterSpy).toHaveBeenCalledWith('sw.js', { scope: 'foo', updateViaCache: 'all', type: 'classic', }); }); it('can disable the SW', async () => { await configTestBed({enabled: false}); expect(TestBed.inject(SwUpdate).isEnabled).toBe(false); expect(swRegisterSpy).not.toHaveBeenCalled(); }); it('can enable the SW', async () => { await configTestBed({enabled: true}); expect(TestBed.inject(SwUpdate).isEnabled).toBe(true); expect(swRegisterSpy).toHaveBeenCalledWith('sw.js', { scope: undefined, updateViaCache: undefined, type: undefined, }); }); it('can set updateViaCache', async () => { await configTestBed({enabled: true, updateViaCache: 'imports'}); expect(TestBed.inject(SwUpdate).isEnabled).toBe(true); expect(swRegisterSpy).toHaveBeenCalledWith('sw.js', { scope: undefined, updateViaCache: 'imports', type: undefined, }); }); it('can set type', async () => { await configTestBed({enabled: true, type: 'module'}); expect(TestBed.inject(SwUpdate).isEnabled).toBe(true); expect(swRegisterSpy).toHaveBeenCalledWith('sw.js', { scope: undefined, updateViaCache: undefined, type: 'module', }); }); it('defaults to enabling the SW', async () => { await configTestBed({}); expect(TestBed.inject(SwUpdate).isEnabled).toBe(true); expect(swRegisterSpy).toHaveBeenCalledWith('sw.js', { scope: undefined, updateViaCache: undefined, type: undefined, }); }); it('catches and logs registration errors', async () => { const consoleErrorSpy = spyOn(console, 'error'); swRegisterSpy.and.returnValue(Promise.reject('no reason')); await configTestBed({enabled: true, scope: 'foo'}); expect(consoleErrorSpy).toHaveBeenCalledWith( 'NG05604: Service worker registration failed with: no reason', ); }); }); describe('SwRegistrationOptions', () => { const configTestBed = ( providerOpts: SwRegistrationOptions, staticOpts?: SwRegistrationOptions, ) => { if (apiFnName === provideServiceWorkerApi) { TestBed.configureTestingModule({ providers: [ provideServiceWorker('sw.js', staticOpts || {scope: 'static', updateViaCache: 'all'}), {provide: PLATFORM_ID, useValue: 'browser'}, {provide: SwRegistrationOptions, useFactory: () => providerOpts}, ], }); } else { TestBed.configureTestingModule({ imports: [ ServiceWorkerModule.register( 'sw.js', staticOpts || {scope: 'static', updateViaCache: 'all'}, ), ], providers: [ {provide: PLATFORM_ID, useValue: 'browser'}, {provide: SwRegistrationOptions, useFactory: () => providerOpts}, ], }); } }; it('sets the registration options (and overwrites those set via `provideServiceWorker()`', async () => { configTestBed({ enabled: true, scope: 'provider', updateViaCache: 'imports', type: 'module', }); await untilStable(); expect(TestBed.inject(SwRegistrationOptions)).toEqual({ enabled: true, scope: 'provider', updateViaCache: 'imports', type: 'module', }); await waitForReadyToRegister(); expect(swRegisterSpy).toHaveBeenCalledWith('sw.js', { scope: 'provider', updateViaCache: 'imports', type: 'module', }); }); it('can disable the SW', async () => { configTestBed({enabled: false}, {enabled: true}); await untilStable(); expect(TestBed.inject(SwUpdate).isEnabled).toBe(false); expect(swRegisterSpy).not.toHaveBeenCalled(); }); it('can enable the SW', async () => { configTestBed({enabled: true}, {enabled: false}); await untilStable(); expect(TestBed.inject(SwUpdate).isEnabled).toBe(true); await waitForReadyToRegister(); expect(swRegisterSpy).toHaveBeenCalledWith('sw.js', { scope: undefined, updateViaCache: undefined, type: undefined, }); }); it('defaults to enabling the SW', async () => { configTestBed({}, {enabled: false}); await untilStable(); expect(TestBed.inject(SwUpdate).isEnabled).toBe(true); await waitForReadyToRegister(); expect(swRegisterSpy).toHaveBeenCalledWith('sw.js', { scope: undefined, updateViaCache: undefined, type: undefined, }); }); describe('registrationStrategy', () => { const configTestBedWithMockedStability = ( strategy?: SwRegistrationOptions['registrationStrategy'], ) => { const isStableSub = new Subject<boolean>(); if (apiFnName === provideServiceWorkerApi) { TestBed.configureTestingModule({ providers: [ provideServiceWorker('sw.js'), { provide: ApplicationRef, useValue: { isStable: isStableSub.asObservable(), whenStable: () => isStableSub.pipe(filter(Boolean), take(1)), afterTick: new Subject(), onDestroy: () => {}, }, }, {provide: PLATFORM_ID, useValue: 'browser'}, { provide: SwRegistrationOptions, useFactory: () => ({registrationStrategy: strategy}), }, ], }); } else { TestBed.configureTestingModule({ imports: [ServiceWorkerModule.register('sw.js')], providers: [ { provide: ApplicationRef, useValue: { isStable: isStableSub.asObservable(), whenStable: () => isStableSub.pipe(filter(Boolean), take(1)), afterTick: new Subject(), onDestroy: () => {}, }, }, {provide: PLATFORM_ID, useValue: 'browser'}, { provide: SwRegistrationOptions, useFactory: () => ({registrationStrategy: strategy}), }, ], }); } // Dummy `inject()` call to initialize the test "app". TestBed.inject(ApplicationRef); return isStableSub; }; it('defaults to registering the SW when the app stabilizes (under 30s)', fakeAsync(() => { const isStableSub = configTestBedWithMockedStability(); isStableSub.next(false); expect(swRegisterSpy).not.toHaveBeenCalled(); // tick(20000); // Calling `tick(20000)` drains the microtask queue, // which leads to `register` being called. isStableSub.next(true); tick(); expect(swRegisterSpy).toHaveBeenCalledWith('sw.js', { scope: undefined, updateViaCache: undefined, type: undefined, }); })); it('defaults to registering the SW after 30s if the app does not stabilize sooner', fakeAsync(() => { configTestBedWithMockedStability(); expect(swRegisterSpy).not.toHaveBeenCalled(); tick(30000); expect(swRegisterSpy).toHaveBeenCalledWith('sw.js', { scope: undefined, updateViaCache: undefined, type: undefined, }); })); it('registers the SW when the app stabilizes with `registerWhenStable:<timeout>`', fakeAsync(() => { const isStableSub = configTestBedWithMockedStability('registerWhenStable:1000'); isStableSub.next(false); isStableSub.next(false); expect(swRegisterSpy).not.toHaveBeenCalled(); isStableSub.next(true); tick(); expect(swRegisterSpy).toHaveBeenCalledWith('sw.js', { scope: undefined, updateViaCache: undefined, type: undefined, }); })); it('registers the SW after `timeout` if the app does not stabilize with `registerWhenStable:<timeout>`', fakeAsync(() => { configTestBedWithMockedStability('registerWhenStable:1000'); expect(swRegisterSpy).not.toHaveBeenCalled(); tick(1000); expect(swRegisterSpy).toHaveBeenCalledWith('sw.js', { scope: undefined, updateViaCache: undefined, type: undefined, }); })); it('registers the SW asap (asynchronously) before the app stabilizes with `registerWhenStable:0`', fakeAsync(() => { configTestBedWithMockedStability('registerWhenStable:0'); // Create a microtask. Promise.resolve(); expect(swRegisterSpy).not.toHaveBeenCalled(); tick(0); expect(swRegisterSpy).toHaveBeenCalledWith('sw.js', { scope: undefined, updateViaCache: undefined, type: undefined, }); })); it('registers the SW only when the app stabilizes with `registerWhenStable:`', fakeAsync(() => { const isStableSub = configTestBedWithMockedStability('registerWhenStable:'); isStableSub.next(false); isStableSub.next(false); expect(swRegisterSpy).not.toHaveBeenCalled(); isStableSub.next(true); tick(); expect(swRegisterSpy).toHaveBeenCalledWith('sw.js', { scope: undefined, updateViaCache: undefined, type: undefined, }); })); it('registers the SW only when the app stabilizes with `registerWhenStable`', fakeAsync(() => { const isStableSub = configTestBedWithMockedStability('registerWhenStable'); isStableSub.next(false); isStableSub.next(false); expect(swRegisterSpy).not.toHaveBeenCalled(); isStableSub.next(true); tick(); expect(swRegisterSpy).toHaveBeenCalledWith('sw.js', { scope: undefined, updateViaCache: undefined, type: undefined, }); })); it('registers the SW immediatelly (synchronously) with `registerImmediately`', async () => { configTestBedWithMockedStability('registerImmediately'); await waitForReadyToRegister(); expect(swRegisterSpy).toHaveBeenCalledWith('sw.js', { scope: undefined, updateViaCache: undefined, type: undefined, }); }); it('registers the SW after the specified delay with `registerWithDelay:<delay>`', fakeAsync(() => { configTestBedWithMockedStability('registerWithDelay:100000'); // tick(99999); // Calling `tick(99999)` drains the microtask queue, // which leads to `register` being called. expect(swRegisterSpy).not.toHaveBeenCalled(); tick(100000); expect(swRegisterSpy).toHaveBeenCalledWith('sw.js', { scope: undefined, updateViaCache: undefined, type: undefined, }); })); it('registers the SW asap (asynchronously) with `registerWithDelay:`', fakeAsync(() => { configTestBedWithMockedStability('registerWithDelay:'); // Create a microtask. Promise.resolve(); flushMicrotasks(); expect(swRegisterSpy).not.toHaveBeenCalled(); tick(0); expect(swRegisterSpy).toHaveBeenCalledWith('sw.js', { scope: undefined, updateViaCache: undefined, type: undefined, }); })); it('registers the SW asap (asynchronously) with `registerWithDelay`', fakeAsync(() => { configTestBedWithMockedStability('registerWithDelay'); // Create a microtask. Promise.resolve(); flushMicrotasks(); expect(swRegisterSpy).not.toHaveBeenCalled(); tick(0); expect(swRegisterSpy).toHaveBeenCalledWith('sw.js', { scope: undefined, updateViaCache: undefined, type: undefined, }); })); it('registers the SW on first emitted value with observable factory function', fakeAsync(() => { const registerSub = new Subject<void>(); const isStableSub = configTestBedWithMockedStability(() => registerSub.asObservable()); isStableSub.next(true); tick(); expect(swRegisterSpy).not.toHaveBeenCalled(); registerSub.next(); tick(); expect(swRegisterSpy).toHaveBeenCalledWith('sw.js', { scope: undefined, updateViaCache: undefined, type: undefined, }); })); it('throws an error with unknown strategy', () => { expect(() => configTestBedWithMockedStability('registerYesterday')).toThrowError( 'NG05600: Unknown ServiceWorker registration strategy: registerYesterday', ); expect(swRegisterSpy).not.toHaveBeenCalled(); }); it('should not register service worker if app was destroyed before it was ready to register', async () => { const registerSub = new Subject<void>(); configTestBedWithMockedStability(() => registerSub); expect(swRegisterSpy).not.toHaveBeenCalled(); // Given that the app is destroyed (e.g., by calling `ApplicationRef.destroy()`) // before the `readyToRegister` promise resolves and `serviceWorker.register(...)` is called. TestBed.resetTestingModule(); await waitForReadyToRegister(); expect(swRegisterSpy).not.toHaveBeenCalled(); }); }); }); }); });