/** * @license * Copyright Google Inc. 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.io/license */ import {ApplicationRef, PLATFORM_ID} from '@angular/core'; import {TestBed, fakeAsync, flushMicrotasks, tick} from '@angular/core/testing'; import {Subject} from 'rxjs'; import {filter, take} from 'rxjs/operators'; import {ServiceWorkerModule, SwRegistrationOptions} from '../src/module'; import {SwUpdate} from '../src/update'; describe('ServiceWorkerModule', () => { // Skip environments that don't support the minimum APIs needed to run these SW tests. if ((typeof navigator === 'undefined') || (typeof navigator.serviceWorker === 'undefined')) { return; } let swRegisterSpy: jasmine.Spy; const untilStable = () => { const appRef: ApplicationRef = TestBed.inject(ApplicationRef); return appRef.isStable.pipe(filter(Boolean), take(1)).toPromise(); }; beforeEach( () => swRegisterSpy = spyOn(navigator.serviceWorker, 'register').and.returnValue(Promise.resolve())); describe('register()', () => { const configTestBed = async(opts: SwRegistrationOptions) => { TestBed.configureTestingModule({ imports: [ServiceWorkerModule.register('sw.js', opts)], providers: [{provide: PLATFORM_ID, useValue: 'browser'}], }); await untilStable(); }; it('sets the registration options', async() => { await configTestBed({enabled: true, scope: 'foo'}); expect(TestBed.inject(SwRegistrationOptions)).toEqual({enabled: true, scope: 'foo'}); expect(swRegisterSpy).toHaveBeenCalledWith('sw.js', {scope: 'foo'}); }); 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}); }); it('defaults to enabling the SW', async() => { await configTestBed({}); expect(TestBed.inject(SwUpdate).isEnabled).toBe(true); expect(swRegisterSpy).toHaveBeenCalledWith('sw.js', {scope: undefined}); }); it('catches and a 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('Service worker registration failed with:', 'no reason'); }); }); describe('SwRegistrationOptions', () => { const configTestBed = (providerOpts: SwRegistrationOptions, staticOpts?: SwRegistrationOptions) => { TestBed.configureTestingModule({ imports: [ServiceWorkerModule.register('sw.js', staticOpts || {scope: 'static'})], providers: [ {provide: PLATFORM_ID, useValue: 'browser'}, {provide: SwRegistrationOptions, useFactory: () => providerOpts}, ], }); }; it('sets the registration options (and overwrites those set via `.register()`', async() => { configTestBed({enabled: true, scope: 'provider'}); await untilStable(); expect(TestBed.inject(SwRegistrationOptions)).toEqual({enabled: true, scope: 'provider'}); expect(swRegisterSpy).toHaveBeenCalledWith('sw.js', {scope: 'provider'}); }); 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); expect(swRegisterSpy).toHaveBeenCalledWith('sw.js', {scope: undefined}); }); it('defaults to enabling the SW', async() => { configTestBed({}, {enabled: false}); await untilStable(); expect(TestBed.inject(SwUpdate).isEnabled).toBe(true); expect(swRegisterSpy).toHaveBeenCalledWith('sw.js', {scope: undefined}); }); describe('registrationStrategy', () => { const configTestBedWithMockedStability = (strategy?: SwRegistrationOptions['registrationStrategy']) => { const isStableSub = new Subject(); TestBed.configureTestingModule({ imports: [ServiceWorkerModule.register('sw.js')], providers: [ {provide: ApplicationRef, useValue: {isStable: isStableSub.asObservable()}}, {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', fakeAsync(() => { const isStableSub = configTestBedWithMockedStability(); isStableSub.next(false); isStableSub.next(false); tick(); expect(swRegisterSpy).not.toHaveBeenCalled(); tick(60000); expect(swRegisterSpy).not.toHaveBeenCalled(); isStableSub.next(true); tick(); expect(swRegisterSpy).toHaveBeenCalledWith('sw.js', {scope: undefined}); })); it('registers the SW when the app stabilizes with `registerWhenStable:`', fakeAsync(() => { const isStableSub = configTestBedWithMockedStability('registerWhenStable:1000'); isStableSub.next(false); isStableSub.next(false); tick(); expect(swRegisterSpy).not.toHaveBeenCalled(); tick(500); expect(swRegisterSpy).not.toHaveBeenCalled(); isStableSub.next(true); tick(); expect(swRegisterSpy).toHaveBeenCalledWith('sw.js', {scope: undefined}); })); it('registers the SW after `timeout` if the app does not stabilize with `registerWhenStable:`', fakeAsync(() => { configTestBedWithMockedStability('registerWhenStable:1000'); tick(999); expect(swRegisterSpy).not.toHaveBeenCalled(); tick(1); expect(swRegisterSpy).toHaveBeenCalledWith('sw.js', {scope: undefined}); })); it('registers the SW asap (asynchronously) before the app stabilizes with `registerWhenStable:0`', fakeAsync(() => { const isStableSub = configTestBedWithMockedStability('registerWhenStable:0'); // Create a microtask. Promise.resolve(); flushMicrotasks(); expect(swRegisterSpy).not.toHaveBeenCalled(); tick(0); expect(swRegisterSpy).toHaveBeenCalledWith('sw.js', {scope: undefined}); })); it('registers the SW only when the app stabilizes with `registerWhenStable:`', fakeAsync(() => { const isStableSub = configTestBedWithMockedStability('registerWhenStable:'); isStableSub.next(false); isStableSub.next(false); tick(); expect(swRegisterSpy).not.toHaveBeenCalled(); tick(60000); expect(swRegisterSpy).not.toHaveBeenCalled(); isStableSub.next(true); tick(); expect(swRegisterSpy).toHaveBeenCalledWith('sw.js', {scope: undefined}); })); it('registers the SW only when the app stabilizes with `registerWhenStable`', fakeAsync(() => { const isStableSub = configTestBedWithMockedStability('registerWhenStable'); isStableSub.next(false); isStableSub.next(false); tick(); expect(swRegisterSpy).not.toHaveBeenCalled(); tick(60000); expect(swRegisterSpy).not.toHaveBeenCalled(); isStableSub.next(true); tick(); expect(swRegisterSpy).toHaveBeenCalledWith('sw.js', {scope: undefined}); })); it('registers the SW immediatelly (synchronously) with `registerImmediately`', () => { configTestBedWithMockedStability('registerImmediately'); expect(swRegisterSpy).toHaveBeenCalledWith('sw.js', {scope: undefined}); }); it('registers the SW after the specified delay with `registerWithDelay:`', fakeAsync(() => { configTestBedWithMockedStability('registerWithDelay:100000'); tick(99999); expect(swRegisterSpy).not.toHaveBeenCalled(); tick(1); expect(swRegisterSpy).toHaveBeenCalledWith('sw.js', {scope: 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}); })); 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}); })); it('registers the SW on first emitted value with observable factory function', fakeAsync(() => { const registerSub = new Subject(); const isStableSub = configTestBedWithMockedStability(() => registerSub.asObservable()); isStableSub.next(true); tick(); expect(swRegisterSpy).not.toHaveBeenCalled(); registerSub.next(); expect(swRegisterSpy).toHaveBeenCalledWith('sw.js', {scope: undefined}); })); it('throws an error with unknown strategy', () => { expect(() => configTestBedWithMockedStability('registerYesterday')) .toThrowError('Unknown ServiceWorker registration strategy: registerYesterday'); expect(swRegisterSpy).not.toHaveBeenCalled(); }); }); }); });