Skip to content
This repository has been archived by the owner on May 3, 2024. It is now read-only.

Commit

Permalink
feat(one-service-worker): integration with one-app
Browse files Browse the repository at this point in the history
Co-authored-by: Nick Oliver <Nickolas.Oliver@aexp.com>
Co-authored-by: Michael A Tomcal <michael.a.tomcal@aexp.com>
Co-authored-by: Jimmy King <jimmy.king@aexp.com>
Co-authored-by: Jonny Adshead <JAdshead@users.noreply.github.com>
  • Loading branch information
5 people authored May 6, 2020
1 parent 8553ebc commit 3a76625
Show file tree
Hide file tree
Showing 58 changed files with 9,871 additions and 15 deletions.
17 changes: 17 additions & 0 deletions __tests__/client/initClient.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ jest.mock('../../src/client/prerender', () => {
const prerender = require.requireActual('../../src/client/prerender');
prerender.loadPrerenderScripts = jest.fn(() => Promise.resolve());
prerender.moveHelmetScripts = jest.fn();
prerender.loadServiceWorker = jest.fn();
return prerender;
});

Expand Down Expand Up @@ -173,6 +174,22 @@ describe('initClient', () => {
expect(tree).toMatchSnapshot();
});

it('should load pwa script', async () => {
expect.assertions(2);
document.getElementById = jest.fn(() => ({ remove: jest.fn() }));

const { match } = require('@americanexpress/one-app-router');
match.mockImplementationOnce((config, cb) => cb(null, null, { testProp: 'test' }));

const { loadServiceWorker } = require('../../src/client/prerender');

const initClient = require('../../src/client/initClient').default;

await initClient();

expect(loadServiceWorker).toHaveBeenCalledTimes(1);
});

it('should remove the server rendered stylesheets', async () => {
expect.assertions(2);
const remove = jest.fn();
Expand Down
35 changes: 34 additions & 1 deletion __tests__/client/prerender.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,12 @@
*/

import { fromJS } from 'immutable';
import { addErrorToReport } from '@americanexpress/one-app-ducks';

import { initializeClientStore, loadPrerenderScripts, moveHelmetScripts } from '../../src/client/prerender';
import {
initializeClientStore, loadPrerenderScripts, moveHelmetScripts, loadServiceWorker,
} from '../../src/client/prerender';
import { initializeServiceWorker } from '../../src/client/service-worker';

jest.mock('@americanexpress/one-app-router', () => ({
browserHistory: 'browserHistory',
Expand All @@ -32,6 +36,7 @@ jest.mock('../../src/universal/reducers', () => 'reducers');

jest.mock('@americanexpress/one-app-ducks', () => ({
getLocalePack: jest.fn((locale) => Promise.resolve(locale)),
addErrorToReport: jest.fn(),
}));

jest.mock('../../src/universal/utils/createTimeoutFetch', () => jest.fn(
Expand All @@ -41,6 +46,7 @@ jest.mock('../../src/universal/utils/createTimeoutFetch', () => jest.fn(
return res;
})
));
jest.mock('../../src/client/service-worker', () => ({ initializeServiceWorker: jest.fn(() => Promise.resolve()) }));

describe('initializeClientStore', () => {
beforeAll(() => {
Expand Down Expand Up @@ -175,3 +181,30 @@ describe('moveHelmetScripts', () => {
expect(NodeList.prototype.forEach).not.toHaveBeenCalled();
});
});

describe('loadServiceWorker', () => {
const dispatch = jest.fn();

beforeEach(() => {
jest.clearAllMocks();
});

it('should call initializeServiceWorker and resolve', async () => {
expect.assertions(2);

await expect(loadServiceWorker({ dispatch, config: {} })).resolves.toBeUndefined();
expect(initializeServiceWorker).toHaveBeenCalledTimes(1);
});

it('should not crash the application on failure nor should loadServiceWorker reject', async () => {
expect.assertions(4);

const error = new Error('ooops');
initializeServiceWorker.mockImplementationOnce(() => Promise.reject(error));

await expect(loadServiceWorker({ dispatch, config: {} })).resolves.toBeUndefined();
expect(dispatch).toHaveBeenCalledTimes(1);
expect(initializeServiceWorker).toHaveBeenCalledTimes(1);
expect(addErrorToReport).toHaveBeenCalledWith(error);
});
});
42 changes: 42 additions & 0 deletions __tests__/client/service-worker/client.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright 2020 American Express Travel Related Services Company, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/

import { on, register } from '@americanexpress/one-service-worker';

import serviceWorkerClient from '../../../src/client/service-worker/client';

jest.mock('@americanexpress/one-service-worker', () => ({
messageContext: jest.fn(),
messenger: jest.fn(),
on: jest.fn(),
register: jest.fn(() => Promise.resolve()),
}));

beforeEach(() => {
jest.clearAllMocks();
});

describe('serviceWorkerClient', () => {
test('it calls register and listens for messages', async () => {
expect.assertions(4);
const scriptUrl = '/_/pwa/sw.js';
const scope = '/';
await expect(serviceWorkerClient({ scriptUrl, scope })).resolves.toBeUndefined();
expect(on).toHaveBeenCalledTimes(1);
expect(register).toHaveBeenCalledTimes(1);
expect(register).toHaveBeenCalledWith(scriptUrl, { scope });
});
});
41 changes: 41 additions & 0 deletions __tests__/client/service-worker/events/lifecycle.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Copyright 2020 American Express Travel Related Services Company, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/

import { skipWaiting, clientsClaim } from '@americanexpress/one-service-worker';

import {
createInstallMiddleware,
createActivateMiddleware,
} from '../../../../src/client/service-worker/events/lifecycle';

jest.mock('@americanexpress/one-service-worker', () => ({
skipWaiting: () => 'skip-waiting',
clientsClaim: () => 'clients-claim',
}));

beforeEach(() => {
jest.clearAllMocks();
});

describe('createLifecycleMiddleware', () => {
test('createInstallMiddleware uses skipWaiting', () => {
expect(createInstallMiddleware()).toEqual(skipWaiting());
});

test('createActivateMiddleware uses clientsClaim', () => {
expect(createActivateMiddleware()).toEqual(clientsClaim());
});
});
149 changes: 149 additions & 0 deletions __tests__/client/service-worker/index.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
/*
* Copyright 2020 American Express Travel Related Services Company, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/

import { importServiceWorkerClient, initializeServiceWorker } from '../../../src/client/service-worker';
import serviceWorkerClient from '../../../src/client/service-worker/client';

jest.mock('../../../src/client/service-worker/client', () => jest.fn(() => Promise.resolve('serviceWorkerClient')));

beforeEach(() => {
jest.clearAllMocks();
});

describe('importServiceWorkerClient', () => {
test('imports client and calls with config', async () => {
expect.assertions(3);

const pwaConfig = {};
// we should expect serviceWorkerClient to chain the return value of initializeServiceWorker
await expect(importServiceWorkerClient(pwaConfig)).resolves.toEqual('serviceWorkerClient');
expect(serviceWorkerClient).toHaveBeenCalledTimes(1);
expect(serviceWorkerClient).toHaveBeenCalledWith(pwaConfig);
});
});

describe('initializeServiceWorker', () => {
let registration;
let getRegistration;

const onError = jest.fn();
const scope = '/';
const scriptUrl = '/_/pwa/service-worker.js';

beforeEach(() => {
registration = {
update: jest.fn(() => Promise.resolve()),
unregister: jest.fn(() => Promise.resolve()),
};
getRegistration = jest.fn(() => Promise.resolve(registration));
navigator.serviceWorker = {
getRegistration,
};
serviceWorkerClient.mockImplementation(() => Promise.resolve(registration));
});

test('does not call in serviceWorkerClient if service worker is not supported', async () => {
expect.assertions(3);

delete navigator.serviceWorker;

await expect(initializeServiceWorker({
serviceWorker: true,
})).resolves.toBeUndefined();
expect(getRegistration).not.toHaveBeenCalled();
expect(serviceWorkerClient).not.toHaveBeenCalled();
});

test('when serviceWorker is disabled, returns null if registration is not present', async () => {
expect.assertions(2);

getRegistration.mockImplementationOnce(() => Promise.resolve());

await expect(initializeServiceWorker({ serviceWorker: false })).resolves.toBe(null);
expect(serviceWorkerClient).not.toHaveBeenCalled();
});

test('when serviceWorker is disabled, returns registration and unregisters the service worker', async () => {
expect.assertions(3);

await expect(initializeServiceWorker({ serviceWorker: false })).resolves.toBe(registration);
expect(registration.unregister).toHaveBeenCalledTimes(1);
expect(serviceWorkerClient).not.toHaveBeenCalled();
});

test('when recoveryMode is active, returns null if registration is not present', async () => {
expect.assertions(2);

getRegistration.mockImplementationOnce(() => Promise.resolve());

await expect(initializeServiceWorker({
serviceWorker: true,
recoveryMode: true,
})).resolves.toBe(null);
expect(serviceWorkerClient).not.toHaveBeenCalled();
});

test('when recoveryMode is active, returns registration and updates the service worker', async () => {
expect.assertions(3);

await expect(initializeServiceWorker({
serviceWorker: true,
recoveryMode: true,
})).resolves.toBe(registration);
expect(registration.update).toHaveBeenCalledTimes(1);
expect(serviceWorkerClient).not.toHaveBeenCalled();
});

test('calls serviceWorkerClient with settings if a service worker registration does not exist', async () => {
expect.assertions(4);

getRegistration.mockImplementationOnce(() => Promise.resolve());

await expect(initializeServiceWorker({
serviceWorker: true,
recoveryMode: false,
scriptUrl,
scope,
onError,
})).resolves.toBe(registration);
expect(getRegistration).toHaveBeenCalledTimes(1);
expect(serviceWorkerClient).toHaveBeenCalledTimes(1);
expect(serviceWorkerClient).toHaveBeenCalledWith({
scriptUrl,
scope,
onError,
});
});

test('calls serviceWorkerClient with settings if the registration is present', async () => {
expect.assertions(4);

await expect(initializeServiceWorker({
serviceWorker: true,
recoveryMode: false,
scriptUrl,
scope,
onError,
})).resolves.toBe(registration);
expect(getRegistration).toHaveBeenCalledTimes(1);
expect(serviceWorkerClient).toHaveBeenCalledTimes(1);
expect(serviceWorkerClient).toHaveBeenCalledWith({
scriptUrl,
scope,
onError,
});
});
});
66 changes: 66 additions & 0 deletions __tests__/client/service-worker/worker.noop.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* Copyright 2020 American Express Travel Related Services Company, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/

/* eslint-disable no-restricted-globals */

import EventTarget from 'service-worker-mock/models/EventTarget';
import createServiceWorkerMocks from 'service-worker-mock';

function loadServiceWorker() {
jest.isolateModules(() => {
require('../../../src/client/service-worker/worker.noop');
});
}

describe('worker noop', () => {
beforeEach(() => {
self.postMessage = jest.fn();
Object.assign(
global,
createServiceWorkerMocks(),
Object.assign(new EventTarget(), {
addEventListener(type, listener) {
if (this.listeners.has(type)) {
this.listeners.get(type).add(listener);
} else {
this.listeners.set(type, new Set([listener]));
}
},
})
);
});

test('adds listeners', () => {
expect.assertions(2);

loadServiceWorker();

expect(self.listeners.size).toEqual(1);
expect(self.listeners.get('install').size).toEqual(1);
});

test('calls skipWaiting on "install"', () => {
expect.assertions(1);

jest.spyOn(global, 'skipWaiting');

loadServiceWorker();

self.listeners.get('install').forEach((handler) => handler());

expect(self.skipWaiting).toHaveBeenCalledTimes(1);
});
});
Loading

0 comments on commit 3a76625

Please sign in to comment.