Paul Gschwendtner 647d7bdd88 refactor: fix typescript strict flag failures in all tests (#30993)
Fixes all TypeScript failures caused by enabling the `--strict`
flag for test source files. We also want to enable the strict
options for tests as the strictness enforcement improves the
overall codehealth, unveiled common issues and additionally it
allows us to enable `strict` in the `tsconfig.json` that is picked
up by IDE's.

PR Close #30993
2019-07-18 14:21:26 -07:00

497 lines
18 KiB
TypeScript

/**
* @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 {PLATFORM_ID} from '@angular/core';
import {TestBed} from '@angular/core/testing';
import {NgswCommChannel} from '@angular/service-worker/src/low_level';
import {SwRegistrationOptions, ngswCommChannelFactory} from '@angular/service-worker/src/module';
import {SwPush} from '@angular/service-worker/src/push';
import {SwUpdate} from '@angular/service-worker/src/update';
import {MockPushManager, MockPushSubscription, MockServiceWorkerContainer, MockServiceWorkerRegistration, patchDecodeBase64} from '@angular/service-worker/testing/mock';
{
describe('ServiceWorker library', () => {
let mock: MockServiceWorkerContainer;
let comm: NgswCommChannel;
beforeEach(() => {
mock = new MockServiceWorkerContainer();
comm = new NgswCommChannel(mock as any);
});
describe('NgswCommsChannel', () => {
it('can access the registration when it comes before subscription', done => {
const mock = new MockServiceWorkerContainer();
const comm = new NgswCommChannel(mock as any);
const regPromise = mock.getRegistration() as any as MockServiceWorkerRegistration;
mock.setupSw();
(comm as any).registration.subscribe((reg: any) => { done(); });
});
it('can access the registration when it comes after subscription', done => {
const mock = new MockServiceWorkerContainer();
const comm = new NgswCommChannel(mock as any);
const regPromise = mock.getRegistration() as any as MockServiceWorkerRegistration;
(comm as any).registration.subscribe((reg: any) => { done(); });
mock.setupSw();
});
});
describe('ngswCommChannelFactory', () => {
it('gives disabled NgswCommChannel for platform-server', () => {
TestBed.configureTestingModule({
providers: [
{provide: PLATFORM_ID, useValue: 'server'},
{provide: SwRegistrationOptions, useValue: {enabled: true}}, {
provide: NgswCommChannel,
useFactory: ngswCommChannelFactory,
deps: [SwRegistrationOptions, PLATFORM_ID]
}
]
});
expect(TestBed.get(NgswCommChannel).isEnabled).toEqual(false);
});
it('gives disabled NgswCommChannel when \'enabled\' option is false', () => {
TestBed.configureTestingModule({
providers: [
{provide: PLATFORM_ID, useValue: 'browser'},
{provide: SwRegistrationOptions, useValue: {enabled: false}}, {
provide: NgswCommChannel,
useFactory: ngswCommChannelFactory,
deps: [SwRegistrationOptions, PLATFORM_ID]
}
]
});
expect(TestBed.get(NgswCommChannel).isEnabled).toEqual(false);
});
it('gives disabled NgswCommChannel when navigator.serviceWorker is undefined', () => {
TestBed.configureTestingModule({
providers: [
{provide: PLATFORM_ID, useValue: 'browser'},
{provide: SwRegistrationOptions, useValue: {enabled: true}},
{
provide: NgswCommChannel,
useFactory: ngswCommChannelFactory,
deps: [SwRegistrationOptions, PLATFORM_ID],
},
],
});
const context: any = global || window;
const originalDescriptor = Object.getOwnPropertyDescriptor(context, 'navigator');
const patchedDescriptor = {value: {serviceWorker: undefined}, configurable: true};
try {
// Set `navigator` to `{serviceWorker: undefined}`.
Object.defineProperty(context, 'navigator', patchedDescriptor);
expect(TestBed.get(NgswCommChannel).isEnabled).toBe(false);
} finally {
if (originalDescriptor) {
Object.defineProperty(context, 'navigator', originalDescriptor);
} else {
delete context.navigator;
}
}
});
it('gives enabled NgswCommChannel when browser supports SW and enabled option is true',
() => {
TestBed.configureTestingModule({
providers: [
{provide: PLATFORM_ID, useValue: 'browser'},
{provide: SwRegistrationOptions, useValue: {enabled: true}}, {
provide: NgswCommChannel,
useFactory: ngswCommChannelFactory,
deps: [SwRegistrationOptions, PLATFORM_ID]
}
]
});
const context: any = global || window;
const originalDescriptor = Object.getOwnPropertyDescriptor(context, 'navigator');
const patchedDescriptor = {value: {serviceWorker: mock}, configurable: true};
try {
// Set `navigator` to `{serviceWorker: mock}`.
Object.defineProperty(context, 'navigator', patchedDescriptor);
expect(TestBed.get(NgswCommChannel).isEnabled).toBe(true);
} finally {
if (originalDescriptor) {
Object.defineProperty(context, 'navigator', originalDescriptor);
} else {
delete context.navigator;
}
}
});
});
describe('SwPush', () => {
let unpatchDecodeBase64: () => void;
let push: SwPush;
// Patch `SwPush.decodeBase64()` in Node.js (where `atob` is not available).
beforeAll(() => unpatchDecodeBase64 = patchDecodeBase64(SwPush.prototype as any));
afterAll(() => unpatchDecodeBase64());
beforeEach(() => {
push = new SwPush(comm);
mock.setupSw();
});
it('is injectable', () => {
TestBed.configureTestingModule({
providers: [
SwPush,
{provide: NgswCommChannel, useValue: comm},
]
});
expect(() => TestBed.get(SwPush)).not.toThrow();
});
describe('requestSubscription()', () => {
it('returns a promise that resolves to the subscription', async() => {
const promise = push.requestSubscription({serverPublicKey: 'test'});
expect(promise).toEqual(jasmine.any(Promise));
const sub = await promise;
expect(sub).toEqual(jasmine.any(MockPushSubscription));
});
it('calls `PushManager.subscribe()` (with appropriate options)', async() => {
const decode = (charCodeArr: Uint8Array) =>
Array.from(charCodeArr).map(c => String.fromCharCode(c)).join('');
// atob('c3ViamVjdHM/') === 'subjects?'
const serverPublicKey = 'c3ViamVjdHM_';
const appServerKeyStr = 'subjects?';
const pmSubscribeSpy = spyOn(MockPushManager.prototype, 'subscribe').and.callThrough();
await push.requestSubscription({serverPublicKey});
expect(pmSubscribeSpy).toHaveBeenCalledTimes(1);
expect(pmSubscribeSpy).toHaveBeenCalledWith({
applicationServerKey: jasmine.any(Uint8Array),
userVisibleOnly: true,
});
const actualAppServerKey = pmSubscribeSpy.calls.first().args[0].applicationServerKey;
const actualAppServerKeyStr = decode(actualAppServerKey);
expect(actualAppServerKeyStr).toBe(appServerKeyStr);
});
it('emits the new `PushSubscription` on `SwPush.subscription`', async() => {
const subscriptionSpy = jasmine.createSpy('subscriptionSpy');
push.subscription.subscribe(subscriptionSpy);
const sub = await push.requestSubscription({serverPublicKey: 'test'});
expect(subscriptionSpy).toHaveBeenCalledWith(sub);
});
});
describe('unsubscribe()', () => {
let psUnsubscribeSpy: jasmine.Spy;
beforeEach(() => {
psUnsubscribeSpy = spyOn(MockPushSubscription.prototype, 'unsubscribe').and.callThrough();
});
it('rejects if currently not subscribed to push notifications', async() => {
try {
await push.unsubscribe();
throw new Error('`unsubscribe()` should fail');
} catch (err) {
expect(err.message).toBe('Not subscribed to push notifications.');
}
});
it('calls `PushSubscription.unsubscribe()`', async() => {
await push.requestSubscription({serverPublicKey: 'test'});
await push.unsubscribe();
expect(psUnsubscribeSpy).toHaveBeenCalledTimes(1);
});
it('rejects if `PushSubscription.unsubscribe()` fails', async() => {
psUnsubscribeSpy.and.callFake(() => { throw new Error('foo'); });
try {
await push.requestSubscription({serverPublicKey: 'test'});
await push.unsubscribe();
throw new Error('`unsubscribe()` should fail');
} catch (err) {
expect(err.message).toBe('foo');
}
});
it('rejects if `PushSubscription.unsubscribe()` returns false', async() => {
psUnsubscribeSpy.and.returnValue(Promise.resolve(false));
try {
await push.requestSubscription({serverPublicKey: 'test'});
await push.unsubscribe();
throw new Error('`unsubscribe()` should fail');
} catch (err) {
expect(err.message).toBe('Unsubscribe failed!');
}
});
it('emits `null` on `SwPush.subscription`', async() => {
const subscriptionSpy = jasmine.createSpy('subscriptionSpy');
push.subscription.subscribe(subscriptionSpy);
await push.requestSubscription({serverPublicKey: 'test'});
await push.unsubscribe();
expect(subscriptionSpy).toHaveBeenCalledWith(null);
});
it('does not emit on `SwPush.subscription` on failure', async() => {
const subscriptionSpy = jasmine.createSpy('subscriptionSpy');
const initialSubEmit = new Promise(resolve => subscriptionSpy.and.callFake(resolve));
push.subscription.subscribe(subscriptionSpy);
await initialSubEmit;
subscriptionSpy.calls.reset();
// Error due to no subscription.
await push.unsubscribe().catch(() => undefined);
expect(subscriptionSpy).not.toHaveBeenCalled();
// Subscribe.
await push.requestSubscription({serverPublicKey: 'test'});
subscriptionSpy.calls.reset();
// Error due to `PushSubscription.unsubscribe()` error.
psUnsubscribeSpy.and.callFake(() => { throw new Error('foo'); });
await push.unsubscribe().catch(() => undefined);
expect(subscriptionSpy).not.toHaveBeenCalled();
// Error due to `PushSubscription.unsubscribe()` failure.
psUnsubscribeSpy.and.returnValue(Promise.resolve(false));
await push.unsubscribe().catch(() => undefined);
expect(subscriptionSpy).not.toHaveBeenCalled();
});
});
describe('messages', () => {
it('receives push messages', () => {
const sendMessage = (type: string, message: string) =>
mock.sendMessage({type, data: {message}});
const receivedMessages: string[] = [];
push.messages.subscribe((msg: any) => receivedMessages.push(msg.message));
sendMessage('PUSH', 'this was a push message');
sendMessage('NOTPUSH', 'this was not a push message');
sendMessage('PUSH', 'this was a push message too');
sendMessage('HSUP', 'this was a HSUP message');
expect(receivedMessages).toEqual([
'this was a push message',
'this was a push message too',
]);
});
});
describe('notificationClicks', () => {
it('receives notification clicked messages', () => {
const sendMessage = (type: string, action: string) =>
mock.sendMessage({type, data: {action}});
const receivedMessages: string[] = [];
push.notificationClicks.subscribe(
(msg: {action: string}) => receivedMessages.push(msg.action));
sendMessage('NOTIFICATION_CLICK', 'this was a click');
sendMessage('NOT_IFICATION_CLICK', 'this was not a click');
sendMessage('NOTIFICATION_CLICK', 'this was a click too');
sendMessage('KCILC_NOITACIFITON', 'this was a KCILC_NOITACIFITON message');
expect(receivedMessages).toEqual([
'this was a click',
'this was a click too',
]);
});
});
describe('subscription', () => {
let nextSubEmitResolve: () => void;
let nextSubEmitPromise: Promise<void>;
let subscriptionSpy: jasmine.Spy;
beforeEach(() => {
nextSubEmitPromise = new Promise(resolve => nextSubEmitResolve = resolve);
subscriptionSpy = jasmine.createSpy('subscriptionSpy').and.callFake(() => {
nextSubEmitResolve();
nextSubEmitPromise = new Promise(resolve => nextSubEmitResolve = resolve);
});
push.subscription.subscribe(subscriptionSpy);
});
it('emits on worker-driven changes (i.e. when the controller changes)', async() => {
// Initial emit for the current `ServiceWorkerController`.
await nextSubEmitPromise;
expect(subscriptionSpy).toHaveBeenCalledTimes(1);
expect(subscriptionSpy).toHaveBeenCalledWith(null);
subscriptionSpy.calls.reset();
// Simulate a `ServiceWorkerController` change.
mock.setupSw();
await nextSubEmitPromise;
expect(subscriptionSpy).toHaveBeenCalledTimes(1);
expect(subscriptionSpy).toHaveBeenCalledWith(null);
});
it('emits on subscription changes (i.e. when subscribing/unsubscribing)', async() => {
await nextSubEmitPromise;
subscriptionSpy.calls.reset();
// Subscribe.
await push.requestSubscription({serverPublicKey: 'test'});
expect(subscriptionSpy).toHaveBeenCalledTimes(1);
expect(subscriptionSpy).toHaveBeenCalledWith(jasmine.any(MockPushSubscription));
subscriptionSpy.calls.reset();
// Subscribe again.
await push.requestSubscription({serverPublicKey: 'test'});
expect(subscriptionSpy).toHaveBeenCalledTimes(1);
expect(subscriptionSpy).toHaveBeenCalledWith(jasmine.any(MockPushSubscription));
subscriptionSpy.calls.reset();
// Unsubscribe.
await push.unsubscribe();
expect(subscriptionSpy).toHaveBeenCalledTimes(1);
expect(subscriptionSpy).toHaveBeenCalledWith(null);
});
});
describe('with no SW', () => {
beforeEach(() => {
comm = new NgswCommChannel(undefined);
push = new SwPush(comm);
});
it('does not crash on subscription to observables', () => {
push.messages.toPromise().catch(err => fail(err));
push.notificationClicks.toPromise().catch(err => fail(err));
push.subscription.toPromise().catch(err => fail(err));
});
it('gives an error when registering', done => {
push.requestSubscription({serverPublicKey: 'test'}).catch(err => { done(); });
});
it('gives an error when unsubscribing',
done => { push.unsubscribe().catch(err => { done(); }); });
});
});
describe('SwUpdate', () => {
let update: SwUpdate;
beforeEach(() => {
update = new SwUpdate(comm);
mock.setupSw();
});
it('processes update availability notifications when sent', done => {
update.available.subscribe(event => {
expect(event.current).toEqual({hash: 'A'});
expect(event.available).toEqual({hash: 'B'});
expect(event.type).toEqual('UPDATE_AVAILABLE');
done();
});
mock.sendMessage({
type: 'UPDATE_AVAILABLE',
current: {
hash: 'A',
},
available: {
hash: 'B',
},
});
});
it('processes update activation notifications when sent', done => {
update.activated.subscribe(event => {
expect(event.previous).toEqual({hash: 'A'});
expect(event.current).toEqual({hash: 'B'});
expect(event.type).toEqual('UPDATE_ACTIVATED');
done();
});
mock.sendMessage({
type: 'UPDATE_ACTIVATED',
previous: {
hash: 'A',
},
current: {
hash: 'B',
},
});
});
it('activates updates when requested', done => {
mock.messages.subscribe((msg: {action: string, statusNonce: number}) => {
expect(msg.action).toEqual('ACTIVATE_UPDATE');
mock.sendMessage({
type: 'STATUS',
nonce: msg.statusNonce,
status: true,
});
});
return update.activateUpdate().then(() => done()).catch(err => done.fail(err));
});
it('reports activation failure when requested', done => {
mock.messages.subscribe((msg: {action: string, statusNonce: number}) => {
expect(msg.action).toEqual('ACTIVATE_UPDATE');
mock.sendMessage({
type: 'STATUS',
nonce: msg.statusNonce,
status: false,
error: 'Failed to activate',
});
});
return update.activateUpdate()
.catch(err => { expect(err.message).toEqual('Failed to activate'); })
.then(() => done())
.catch(err => done.fail(err));
});
it('is injectable', () => {
TestBed.configureTestingModule({
providers: [
SwUpdate,
{provide: NgswCommChannel, useValue: comm},
]
});
expect(() => TestBed.get(SwUpdate)).not.toThrow();
});
describe('with no SW', () => {
beforeEach(() => { comm = new NgswCommChannel(undefined); });
it('can be instantiated', () => { update = new SwUpdate(comm); });
it('does not crash on subscription to observables', () => {
update = new SwUpdate(comm);
update.available.toPromise().catch(err => fail(err));
update.activated.toPromise().catch(err => fail(err));
});
it('gives an error when checking for updates', done => {
update = new SwUpdate(comm);
update.checkForUpdate().catch(err => { done(); });
});
it('gives an error when activating updates', done => {
update = new SwUpdate(comm);
update.activateUpdate().catch(err => { done(); });
});
});
});
});
}