test(service-worker): allow `SwPush` tests to run on Node.js (#24162)
PR Close #24162
This commit is contained in:
parent
86bf5f3912
commit
4d55dfd9d9
|
@ -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); }
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(); }); });
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue