2019-04-25 09:51:08 -04:00
|
|
|
/**
|
|
|
|
* @license
|
2020-05-19 15:08:49 -04:00
|
|
|
* Copyright Google LLC All Rights Reserved.
|
2019-04-25 09:51:08 -04:00
|
|
|
*
|
|
|
|
* 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
|
|
|
|
*/
|
|
|
|
|
2020-12-05 11:37:03 -05:00
|
|
|
import {ApplicationRef, ErrorHandler, PLATFORM_ID} from '@angular/core';
|
2020-03-30 11:22:25 -04:00
|
|
|
import {fakeAsync, flushMicrotasks, TestBed, tick} from '@angular/core/testing';
|
2018-11-07 15:46:22 -05:00
|
|
|
import {Subject} from 'rxjs';
|
2019-04-25 09:51:08 -04:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2020-12-05 11:37:03 -05:00
|
|
|
let errorHandlerSpy: jasmine.Spy;
|
2019-04-25 09:51:08 -04:00
|
|
|
let swRegisterSpy: jasmine.Spy;
|
|
|
|
|
2018-11-07 15:46:22 -05:00
|
|
|
const untilStable = () => {
|
2019-08-28 19:22:36 -04:00
|
|
|
const appRef: ApplicationRef = TestBed.inject(ApplicationRef);
|
2018-11-07 15:46:22 -05:00
|
|
|
return appRef.isStable.pipe(filter(Boolean), take(1)).toPromise();
|
|
|
|
};
|
|
|
|
|
2019-06-04 10:54:24 -04:00
|
|
|
beforeEach(
|
|
|
|
() => swRegisterSpy =
|
2020-01-03 00:28:06 -05:00
|
|
|
spyOn(navigator.serviceWorker, 'register').and.returnValue(Promise.resolve(null as any)));
|
2019-04-25 09:51:08 -04:00
|
|
|
|
|
|
|
describe('register()', () => {
|
2020-03-30 11:22:25 -04:00
|
|
|
const configTestBed = async (opts: SwRegistrationOptions) => {
|
2020-12-05 11:37:03 -05:00
|
|
|
const errorHandler = {handleError: () => {}};
|
|
|
|
errorHandlerSpy = spyOn(errorHandler, 'handleError');
|
2019-04-25 09:51:08 -04:00
|
|
|
TestBed.configureTestingModule({
|
|
|
|
imports: [ServiceWorkerModule.register('sw.js', opts)],
|
2020-12-05 11:37:03 -05:00
|
|
|
providers: [
|
|
|
|
{provide: ErrorHandler, useValue: errorHandler},
|
|
|
|
{provide: PLATFORM_ID, useValue: 'browser'},
|
|
|
|
],
|
2019-04-25 09:51:08 -04:00
|
|
|
});
|
|
|
|
|
2018-11-07 15:46:22 -05:00
|
|
|
await untilStable();
|
2019-04-25 09:51:08 -04:00
|
|
|
};
|
|
|
|
|
2020-03-30 11:22:25 -04:00
|
|
|
it('sets the registration options', async () => {
|
2019-04-25 09:51:08 -04:00
|
|
|
await configTestBed({enabled: true, scope: 'foo'});
|
|
|
|
|
2019-08-28 19:22:36 -04:00
|
|
|
expect(TestBed.inject(SwRegistrationOptions)).toEqual({enabled: true, scope: 'foo'});
|
2019-04-25 09:51:08 -04:00
|
|
|
expect(swRegisterSpy).toHaveBeenCalledWith('sw.js', {scope: 'foo'});
|
|
|
|
});
|
|
|
|
|
2020-03-30 11:22:25 -04:00
|
|
|
it('can disable the SW', async () => {
|
2019-04-25 09:51:08 -04:00
|
|
|
await configTestBed({enabled: false});
|
|
|
|
|
2019-08-28 19:22:36 -04:00
|
|
|
expect(TestBed.inject(SwUpdate).isEnabled).toBe(false);
|
2019-04-25 09:51:08 -04:00
|
|
|
expect(swRegisterSpy).not.toHaveBeenCalled();
|
|
|
|
});
|
|
|
|
|
2020-03-30 11:22:25 -04:00
|
|
|
it('can enable the SW', async () => {
|
2019-04-25 09:51:08 -04:00
|
|
|
await configTestBed({enabled: true});
|
|
|
|
|
2019-08-28 19:22:36 -04:00
|
|
|
expect(TestBed.inject(SwUpdate).isEnabled).toBe(true);
|
2019-04-25 09:51:08 -04:00
|
|
|
expect(swRegisterSpy).toHaveBeenCalledWith('sw.js', {scope: undefined});
|
|
|
|
});
|
|
|
|
|
2020-03-30 11:22:25 -04:00
|
|
|
it('defaults to enabling the SW', async () => {
|
2019-04-25 09:51:08 -04:00
|
|
|
await configTestBed({});
|
2018-11-07 15:46:22 -05:00
|
|
|
|
2019-08-28 19:22:36 -04:00
|
|
|
expect(TestBed.inject(SwUpdate).isEnabled).toBe(true);
|
2019-04-25 09:51:08 -04:00
|
|
|
expect(swRegisterSpy).toHaveBeenCalledWith('sw.js', {scope: undefined});
|
|
|
|
});
|
2019-06-04 10:54:24 -04:00
|
|
|
|
2020-03-30 11:22:25 -04:00
|
|
|
it('catches and a logs registration errors', async () => {
|
2019-06-04 10:54:24 -04:00
|
|
|
swRegisterSpy.and.returnValue(Promise.reject('no reason'));
|
|
|
|
|
|
|
|
await configTestBed({enabled: true, scope: 'foo'});
|
2020-12-05 11:37:03 -05:00
|
|
|
expect(errorHandlerSpy).toHaveBeenCalledWith('no reason');
|
2019-06-04 10:54:24 -04:00
|
|
|
});
|
2019-04-25 09:51:08 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
describe('SwRegistrationOptions', () => {
|
|
|
|
const configTestBed =
|
2018-11-07 15:46:22 -05:00
|
|
|
(providerOpts: SwRegistrationOptions, staticOpts?: SwRegistrationOptions) => {
|
|
|
|
TestBed.configureTestingModule({
|
|
|
|
imports: [ServiceWorkerModule.register('sw.js', staticOpts || {scope: 'static'})],
|
|
|
|
providers: [
|
|
|
|
{provide: PLATFORM_ID, useValue: 'browser'},
|
|
|
|
{provide: SwRegistrationOptions, useFactory: () => providerOpts},
|
|
|
|
],
|
|
|
|
});
|
|
|
|
};
|
2019-04-25 09:51:08 -04:00
|
|
|
|
2020-03-30 11:22:25 -04:00
|
|
|
it('sets the registration options (and overwrites those set via `.register()`', async () => {
|
2018-11-07 15:46:22 -05:00
|
|
|
configTestBed({enabled: true, scope: 'provider'});
|
|
|
|
await untilStable();
|
2019-04-25 09:51:08 -04:00
|
|
|
|
2019-08-28 19:22:36 -04:00
|
|
|
expect(TestBed.inject(SwRegistrationOptions)).toEqual({enabled: true, scope: 'provider'});
|
2019-04-25 09:51:08 -04:00
|
|
|
expect(swRegisterSpy).toHaveBeenCalledWith('sw.js', {scope: 'provider'});
|
|
|
|
});
|
|
|
|
|
2020-03-30 11:22:25 -04:00
|
|
|
it('can disable the SW', async () => {
|
2018-11-07 15:46:22 -05:00
|
|
|
configTestBed({enabled: false}, {enabled: true});
|
|
|
|
await untilStable();
|
2019-04-25 09:51:08 -04:00
|
|
|
|
2019-08-28 19:22:36 -04:00
|
|
|
expect(TestBed.inject(SwUpdate).isEnabled).toBe(false);
|
2019-04-25 09:51:08 -04:00
|
|
|
expect(swRegisterSpy).not.toHaveBeenCalled();
|
|
|
|
});
|
|
|
|
|
2020-03-30 11:22:25 -04:00
|
|
|
it('can enable the SW', async () => {
|
2018-11-07 15:46:22 -05:00
|
|
|
configTestBed({enabled: true}, {enabled: false});
|
|
|
|
await untilStable();
|
2019-04-25 09:51:08 -04:00
|
|
|
|
2019-08-28 19:22:36 -04:00
|
|
|
expect(TestBed.inject(SwUpdate).isEnabled).toBe(true);
|
2019-04-25 09:51:08 -04:00
|
|
|
expect(swRegisterSpy).toHaveBeenCalledWith('sw.js', {scope: undefined});
|
|
|
|
});
|
|
|
|
|
2020-03-30 11:22:25 -04:00
|
|
|
it('defaults to enabling the SW', async () => {
|
2018-11-07 15:46:22 -05:00
|
|
|
configTestBed({}, {enabled: false});
|
|
|
|
await untilStable();
|
2019-04-25 09:51:08 -04:00
|
|
|
|
2019-08-28 19:22:36 -04:00
|
|
|
expect(TestBed.inject(SwUpdate).isEnabled).toBe(true);
|
2019-04-25 09:51:08 -04:00
|
|
|
expect(swRegisterSpy).toHaveBeenCalledWith('sw.js', {scope: undefined});
|
|
|
|
});
|
2018-11-07 15:46:22 -05:00
|
|
|
|
|
|
|
describe('registrationStrategy', () => {
|
|
|
|
const configTestBedWithMockedStability =
|
|
|
|
(strategy?: SwRegistrationOptions['registrationStrategy']) => {
|
|
|
|
const isStableSub = new Subject<boolean>();
|
|
|
|
|
|
|
|
TestBed.configureTestingModule({
|
|
|
|
imports: [ServiceWorkerModule.register('sw.js')],
|
|
|
|
providers: [
|
|
|
|
{provide: ApplicationRef, useValue: {isStable: isStableSub.asObservable()}},
|
|
|
|
{provide: PLATFORM_ID, useValue: 'browser'},
|
|
|
|
{
|
|
|
|
provide: SwRegistrationOptions,
|
|
|
|
useFactory: () => ({registrationStrategy: strategy})
|
|
|
|
},
|
|
|
|
],
|
|
|
|
});
|
|
|
|
|
2020-03-24 16:54:51 -04:00
|
|
|
// Dummy `inject()` call to initialize the test "app".
|
2019-08-28 19:22:36 -04:00
|
|
|
TestBed.inject(ApplicationRef);
|
2018-11-07 15:46:22 -05:00
|
|
|
|
|
|
|
return isStableSub;
|
|
|
|
};
|
|
|
|
|
2020-03-24 16:54:53 -04:00
|
|
|
it('defaults to registering the SW when the app stabilizes (under 30s)', fakeAsync(() => {
|
2018-11-07 15:46:22 -05:00
|
|
|
const isStableSub = configTestBedWithMockedStability();
|
|
|
|
|
|
|
|
isStableSub.next(false);
|
|
|
|
isStableSub.next(false);
|
|
|
|
|
|
|
|
tick();
|
|
|
|
expect(swRegisterSpy).not.toHaveBeenCalled();
|
|
|
|
|
2020-03-24 16:54:53 -04:00
|
|
|
tick(20000);
|
2020-03-24 16:54:51 -04:00
|
|
|
expect(swRegisterSpy).not.toHaveBeenCalled();
|
|
|
|
|
|
|
|
isStableSub.next(true);
|
|
|
|
|
|
|
|
tick();
|
|
|
|
expect(swRegisterSpy).toHaveBeenCalledWith('sw.js', {scope: undefined});
|
|
|
|
}));
|
|
|
|
|
2020-03-24 16:54:53 -04:00
|
|
|
it('defaults to registering the SW after 30s if the app does not stabilize sooner',
|
|
|
|
fakeAsync(() => {
|
|
|
|
const isStableSub = configTestBedWithMockedStability();
|
|
|
|
|
|
|
|
tick(29999);
|
|
|
|
expect(swRegisterSpy).not.toHaveBeenCalled();
|
|
|
|
|
|
|
|
tick(1);
|
|
|
|
expect(swRegisterSpy).toHaveBeenCalledWith('sw.js', {scope: undefined});
|
|
|
|
}));
|
|
|
|
|
2020-03-24 16:54:51 -04:00
|
|
|
it('registers the SW when the app stabilizes with `registerWhenStable:<timeout>`',
|
|
|
|
fakeAsync(() => {
|
|
|
|
const isStableSub = configTestBedWithMockedStability('registerWhenStable:1000');
|
|
|
|
|
|
|
|
isStableSub.next(false);
|
|
|
|
isStableSub.next(false);
|
|
|
|
|
|
|
|
tick();
|
|
|
|
expect(swRegisterSpy).not.toHaveBeenCalled();
|
|
|
|
|
|
|
|
tick(500);
|
|
|
|
expect(swRegisterSpy).not.toHaveBeenCalled();
|
|
|
|
|
2018-11-07 15:46:22 -05:00
|
|
|
isStableSub.next(true);
|
|
|
|
|
|
|
|
tick();
|
|
|
|
expect(swRegisterSpy).toHaveBeenCalledWith('sw.js', {scope: undefined});
|
|
|
|
}));
|
|
|
|
|
2020-03-24 16:54:51 -04:00
|
|
|
it('registers the SW after `timeout` if the app does not stabilize with `registerWhenStable:<timeout>`',
|
|
|
|
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(() => {
|
2018-11-07 15:46:22 -05:00
|
|
|
const isStableSub = configTestBedWithMockedStability('registerWhenStable');
|
|
|
|
|
|
|
|
isStableSub.next(false);
|
|
|
|
isStableSub.next(false);
|
|
|
|
|
|
|
|
tick();
|
|
|
|
expect(swRegisterSpy).not.toHaveBeenCalled();
|
|
|
|
|
2020-03-24 16:54:51 -04:00
|
|
|
tick(60000);
|
|
|
|
expect(swRegisterSpy).not.toHaveBeenCalled();
|
|
|
|
|
2018-11-07 15:46:22 -05:00
|
|
|
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:<delay>`',
|
|
|
|
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<void>();
|
|
|
|
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();
|
|
|
|
});
|
|
|
|
});
|
2019-04-25 09:51:08 -04:00
|
|
|
});
|
|
|
|
});
|