test(service-worker): allow `SwPush` tests to run on Node.js (#24162)

PR Close #24162
This commit is contained in:
George Kalpakas 2018-05-28 18:27:51 +03:00 committed by Miško Hevery
parent 86bf5f3912
commit 4d55dfd9d9
3 changed files with 36 additions and 21 deletions

View File

@ -13,7 +13,6 @@ import {map, switchMap, take} from 'rxjs/operators';
import {ERR_SW_NOT_SUPPORTED, NgswCommChannel} from './low_level'; import {ERR_SW_NOT_SUPPORTED, NgswCommChannel} from './low_level';
/** /**
* Subscribe and listen to push notifications from the Service Worker. * Subscribe and listen to push notifications from the Service Worker.
* *
@ -55,7 +54,7 @@ export class SwPush {
return Promise.reject(new Error(ERR_SW_NOT_SUPPORTED)); return Promise.reject(new Error(ERR_SW_NOT_SUPPORTED));
} }
const pushOptions: PushSubscriptionOptionsInit = {userVisibleOnly: true}; const pushOptions: PushSubscriptionOptionsInit = {userVisibleOnly: true};
let key = atob(options.serverPublicKey.replace(/_/g, '/').replace(/-/g, '+')); let key = this.decodeBase64(options.serverPublicKey.replace(/_/g, '/').replace(/-/g, '+'));
let applicationServerKey = new Uint8Array(new ArrayBuffer(key.length)); let applicationServerKey = new Uint8Array(new ArrayBuffer(key.length));
for (let i = 0; i < key.length; i++) { for (let i = 0; i < key.length; i++) {
applicationServerKey[i] = key.charCodeAt(i); applicationServerKey[i] = key.charCodeAt(i);
@ -90,4 +89,6 @@ export class SwPush {
})); }));
return unsubscribe.pipe(take(1)).toPromise(); return unsubscribe.pipe(take(1)).toPromise();
} }
private decodeBase64(input: string): string { return atob(input); }
} }

View File

@ -13,7 +13,7 @@ import {NgswCommChannel} from '../src/low_level';
import {RegistrationOptions, ngswCommChannelFactory} from '../src/module'; import {RegistrationOptions, ngswCommChannelFactory} from '../src/module';
import {SwPush} from '../src/push'; import {SwPush} from '../src/push';
import {SwUpdate} from '../src/update'; import {SwUpdate} from '../src/update';
import {MockPushManager, MockPushSubscription, MockServiceWorkerContainer, MockServiceWorkerRegistration} from '../testing/mock'; import {MockPushManager, MockPushSubscription, MockServiceWorkerContainer, MockServiceWorkerRegistration, patchDecodeBase64} from '../testing/mock';
import {async_fit, async_it} from './async'; import {async_fit, async_it} from './async';
{ {
@ -137,8 +137,13 @@ import {async_fit, async_it} from './async';
}); });
describe('SwPush', () => { describe('SwPush', () => {
let unpatchDecodeBase64: () => void;
let push: SwPush; let push: SwPush;
// Patch `SwPush.decodeBase64()` in Node.js (where `atob` is not available).
beforeAll(() => unpatchDecodeBase64 = patchDecodeBase64(SwPush.prototype as any));
afterAll(() => unpatchDecodeBase64());
beforeEach(() => { beforeEach(() => {
push = new SwPush(comm); push = new SwPush(comm);
mock.setupSw(); mock.setupSw();
@ -164,9 +169,12 @@ import {async_fit, async_it} from './async';
}); });
async_it('calls `PushManager.subscribe()` (with appropriate options)', async() => { async_it('calls `PushManager.subscribe()` (with appropriate options)', async() => {
const decode = (charCodeArr: Uint8Array) =>
Array.from(charCodeArr).map(c => String.fromCharCode(c)).join('');
// atob('c3ViamVjdHM/') === 'subjects?' // atob('c3ViamVjdHM/') === 'subjects?'
const serverPublicKey = 'c3ViamVjdHM_'; const serverPublicKey = 'c3ViamVjdHM_';
const appServerKeyStr ='subjects?'; const appServerKeyStr = 'subjects?';
const pmSubscribeSpy = spyOn(MockPushManager.prototype, 'subscribe').and.callThrough(); const pmSubscribeSpy = spyOn(MockPushManager.prototype, 'subscribe').and.callThrough();
await push.requestSubscription({serverPublicKey}); await push.requestSubscription({serverPublicKey});
@ -178,7 +186,7 @@ import {async_fit, async_it} from './async';
}); });
const actualAppServerKey = pmSubscribeSpy.calls.first().args[0].applicationServerKey; const actualAppServerKey = pmSubscribeSpy.calls.first().args[0].applicationServerKey;
const actualAppServerKeyStr = new TextDecoder('utf-8').decode(actualAppServerKey); const actualAppServerKeyStr = decode(actualAppServerKey);
expect(actualAppServerKeyStr).toBe(appServerKeyStr); expect(actualAppServerKeyStr).toBe(appServerKeyStr);
}); });
@ -279,7 +287,7 @@ import {async_fit, async_it} from './async';
describe('messages', () => { describe('messages', () => {
it('receives push messages', () => { it('receives push messages', () => {
const sendMessage = (type: string, message: string) => const sendMessage = (type: string, message: string) =>
mock.sendMessage({type, data: {message}}); mock.sendMessage({type, data: {message}});
const receivedMessages: string[] = []; const receivedMessages: string[] = [];
push.messages.subscribe((msg: {message: string}) => receivedMessages.push(msg.message)); push.messages.subscribe((msg: {message: string}) => receivedMessages.push(msg.message));
@ -331,24 +339,21 @@ import {async_fit, async_it} from './async';
subscriptionSpy.calls.reset(); subscriptionSpy.calls.reset();
// Subscribe. // Subscribe.
push.requestSubscription({serverPublicKey: 'test'}); await push.requestSubscription({serverPublicKey: 'test'});
await nextSubEmitPromise;
expect(subscriptionSpy).toHaveBeenCalledTimes(1); expect(subscriptionSpy).toHaveBeenCalledTimes(1);
expect(subscriptionSpy).toHaveBeenCalledWith(jasmine.any(MockPushSubscription)); expect(subscriptionSpy).toHaveBeenCalledWith(jasmine.any(MockPushSubscription));
subscriptionSpy.calls.reset(); subscriptionSpy.calls.reset();
// Subscribe again. // Subscribe again.
push.requestSubscription({serverPublicKey: 'test'}); await push.requestSubscription({serverPublicKey: 'test'});
await nextSubEmitPromise;
expect(subscriptionSpy).toHaveBeenCalledTimes(1); expect(subscriptionSpy).toHaveBeenCalledTimes(1);
expect(subscriptionSpy).toHaveBeenCalledWith(jasmine.any(MockPushSubscription)); expect(subscriptionSpy).toHaveBeenCalledWith(jasmine.any(MockPushSubscription));
subscriptionSpy.calls.reset(); subscriptionSpy.calls.reset();
// Unsubscribe. // Unsubscribe.
push.unsubscribe(); await push.unsubscribe();
await nextSubEmitPromise;
expect(subscriptionSpy).toHaveBeenCalledTimes(1); expect(subscriptionSpy).toHaveBeenCalledTimes(1);
expect(subscriptionSpy).toHaveBeenCalledWith(null); expect(subscriptionSpy).toHaveBeenCalledWith(null);
}); });
@ -369,9 +374,8 @@ import {async_fit, async_it} from './async';
push.requestSubscription({serverPublicKey: 'test'}).catch(err => { done(); }); push.requestSubscription({serverPublicKey: 'test'}).catch(err => { done(); });
}); });
it('gives an error when unsubscribing', done => { it('gives an error when unsubscribing',
push.unsubscribe().catch(err => { done(); }); done => { push.unsubscribe().catch(err => { done(); }); });
});
}); });
}); });

View File

@ -8,6 +8,20 @@
import {Subject} from 'rxjs'; import {Subject} from 'rxjs';
export const patchDecodeBase64 = (proto: {decodeBase64: typeof atob}) => {
let unpatch: () => void = () => undefined;
if ((typeof atob === 'undefined') && (typeof Buffer === 'function')) {
const oldDecodeBase64 = proto.decodeBase64;
const newDecodeBase64 = (input: string) => Buffer.from(input, 'base64').toString('binary');
proto.decodeBase64 = newDecodeBase64;
unpatch = () => { proto.decodeBase64 = oldDecodeBase64; };
}
return unpatch;
};
export class MockServiceWorkerContainer { export class MockServiceWorkerContainer {
private onControllerChange: Function[] = []; private onControllerChange: Function[] = [];
private onMessage: Function[] = []; private onMessage: Function[] = [];
@ -63,9 +77,7 @@ export class MockServiceWorkerRegistration {
export class MockPushManager { export class MockPushManager {
private subscription: PushSubscription|null = null; private subscription: PushSubscription|null = null;
getSubscription(): Promise<PushSubscription|null> { getSubscription(): Promise<PushSubscription|null> { return Promise.resolve(this.subscription); }
return Promise.resolve(this.subscription);
}
subscribe(options?: PushSubscriptionOptionsInit): Promise<PushSubscription> { subscribe(options?: PushSubscriptionOptionsInit): Promise<PushSubscription> {
this.subscription = new MockPushSubscription() as any; this.subscription = new MockPushSubscription() as any;
@ -74,7 +86,5 @@ export class MockPushManager {
} }
export class MockPushSubscription { export class MockPushSubscription {
unsubscribe(): Promise<boolean> { unsubscribe(): Promise<boolean> { return Promise.resolve(true); }
return Promise.resolve(true);
}
} }