From b70cd0de966ba58f4d0f90cd7e26508888ba9ac8 Mon Sep 17 00:00:00 2001 From: Philip Walton Date: Mon, 1 Apr 2019 21:39:39 -0700 Subject: [PATCH] Update tests to run in a SW env --- .eslintrc.js | 1 + infra/testing/server/routes/sw-bundle.js | 7 ++ .../server/templates/test-sw-runner.js.njk | 72 +++++++++++-------- .../testing/server/templates/test-sw.html.njk | 38 +++++----- test/workbox-sw/integration/test-all.js | 17 ++++- .../sw/controllers/test-WorkboxSW.mjs | 61 +++++++--------- 6 files changed, 110 insertions(+), 86 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 3371c4b5f..54d8a59a4 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -43,6 +43,7 @@ module.exports = { globals: { expectError: false, waitUntil: false, + BROWSER_NAMESPACES: false, }, rules: { 'max-len': 0, diff --git a/infra/testing/server/routes/sw-bundle.js b/infra/testing/server/routes/sw-bundle.js index 35c627030..8ade19e82 100644 --- a/infra/testing/server/routes/sw-bundle.js +++ b/infra/testing/server/routes/sw-bundle.js @@ -11,8 +11,13 @@ const replace = require('rollup-plugin-replace'); const resolve = require('rollup-plugin-node-resolve'); const multiEntry = require('rollup-plugin-multi-entry'); const commonjs = require('rollup-plugin-commonjs'); +const {getPackages} = require('../../../../gulp-tasks/utils/get-packages'); +const BROWSER_NAMESPACES = getPackages({type: 'browser'}).map((pkg) => { + return pkg.workbox.browserNamespace; +}); + const match = '/test/:package/*/sw-bundle.js'; const caches = {}; @@ -35,6 +40,8 @@ async function handler(req, res) { }), replace({ 'process.env.NODE_ENV': JSON.stringify(env), + 'BROWSER_NAMESPACES': JSON.stringify(BROWSER_NAMESPACES), + 'WORKBOX_CDN_ROOT_URL': '/__WORKBOX/buildFile', }), ], cache: caches[env], diff --git a/infra/testing/server/templates/test-sw-runner.js.njk b/infra/testing/server/templates/test-sw-runner.js.njk index 4a76da0e4..4e9ea46aa 100644 --- a/infra/testing/server/templates/test-sw-runner.js.njk +++ b/infra/testing/server/templates/test-sw-runner.js.njk @@ -60,40 +60,52 @@ mocha.setup({ reporter: null, }); -addEventListener('message', (event) => { - if (event.data && event.data.type === 'RUN_TESTS') { - const testsComplete = new Promise((resolve) => { - const reports = []; - const runner = mocha.run(); - - runner.on('fail', (test, err) => { - const flattenTitles = (test) => { - const titles = [test.title]; - while (test.parent.title) { - titles.push(test.parent.title); - test = test.parent; - } - return titles.reverse().join(' '); - }; - - reports.push({ - name: flattenTitles(test), - result: false, - message: err.message, - stack: err.stack, - }); +addEventListener('install', (event) => { + const testsComplete = new Promise((resolve, reject) => { + const reports = []; + const runner = mocha.run(); + + runner.on('fail', (test, err) => { + const flattenTitles = (test) => { + const titles = [test.title]; + while (test.parent.title) { + titles.push(test.parent.title); + test = test.parent; + } + return titles.reverse().join(' '); + }; + + reports.push({ + name: flattenTitles(test), + result: false, + message: err.message, + stack: err.stack, }); + }); + + runner.on('end', async () => { + const results = runner.stats; + results.reports = reports; - runner.on('end', async () => { - const results = runner.stats; - results.reports = reports; - event.ports[0].postMessage(results); - resolve(results); + const windows = await clients.matchAll({ + type: 'window', + includeUncontrolled: true, }); + + for (const win of windows) { + win.postMessage(results); + } + + // Fail installation if the tests don't pass. + if (results.failures) { + reject(); + } else { + resolve(); + } }); + }); - event.waitUntil(testsComplete); - } -}); + event.waitUntil(testsComplete); +}, {once: true}); // Run once since `install` events are dispatched in tests. importScripts('sw-bundle.js'); diff --git a/infra/testing/server/templates/test-sw.html.njk b/infra/testing/server/templates/test-sw.html.njk index 1ba71a370..b9ad80a75 100644 --- a/infra/testing/server/templates/test-sw.html.njk +++ b/infra/testing/server/templates/test-sw.html.njk @@ -45,25 +45,29 @@ (async () => { // // Randomize the test URL so every test run forces a new SW install. const wb = new Workbox('test-sw-runner.js.njk?v={{ uniqueID() }}'); - await wb.register(); - self.mochaResults = await wb.messageSW({type: 'RUN_TESTS'}); - if (self.mochaResults.failures === 0) { - const styles = [ - `color: hsl(150, 100%, 40%)`, - `font-weight: bold`, - `font-size: 1.5em`, - `padding: .25em 0`, - ]; - console.log('%cAll TESTS PASS!', styles.join(';')); - document.getElementById('success').textContent = 'ALL TESTS PASS!'; - } else { - const errors = self.mochaResults.reports.map((report) => { - return `${report.name}\n${report.stack}`; - }).join('\n\n'); + wb.addEventListener('message', (event) => { + self.mochaResults = event.data; - document.getElementById('errors').textContent = errors - } + if (self.mochaResults.failures === 0) { + const styles = [ + `color: hsl(150, 100%, 40%)`, + `font-weight: bold`, + `font-size: 1.5em`, + `padding: .25em 0`, + ]; + console.log('%cAll TESTS PASS!', styles.join(';')); + document.getElementById('success').textContent = 'ALL TESTS PASS!'; + } else { + const errors = self.mochaResults.reports.map((report) => { + return `${report.name}\n${report.stack}`; + }).join('\n\n'); + + document.getElementById('errors').textContent = errors + } + }); + + wb.register(); })(); diff --git a/test/workbox-sw/integration/test-all.js b/test/workbox-sw/integration/test-all.js index c6eaee587..4af6f7932 100644 --- a/test/workbox-sw/integration/test-all.js +++ b/test/workbox-sw/integration/test-all.js @@ -7,10 +7,21 @@ */ const expect = require('chai').expect; +const {runUnitTests} = require('../../../infra/testing/webdriver/runUnitTests'); + + +// Store local references of these globals. +const {webdriver, server} = global.__workbox; + +describe(`[workbox-sw]`, function() { + it(`passes all SW unit tests`, async function() { + await runUnitTests('/test/workbox-sw/sw/'); + }); +}); describe(`WorkboxSW interface`, function() { const wasRegistrationSuccessful = (swFile) => { - return global.__workbox.webdriver.executeAsyncScript((swFile, cb) => { + return webdriver.executeAsyncScript((swFile, cb) => { // Invokes cb() with true when registration succeeds, and false otherwise. navigator.serviceWorker.register(swFile) .then(() => cb(true)) @@ -18,11 +29,11 @@ describe(`WorkboxSW interface`, function() { }, swFile); }; - const testServerAddress = global.__workbox.server.getAddress(); + const testServerAddress = server.getAddress(); const testPageURL = `${testServerAddress}/test/workbox-sw/static/integration/`; before(async function() { - await global.__workbox.webdriver.get(testPageURL); + await webdriver.get(testPageURL); }); it(`should fail to activate an invalid SW which loads non-existent modules`, async function() { diff --git a/test/workbox-sw/sw/controllers/test-WorkboxSW.mjs b/test/workbox-sw/sw/controllers/test-WorkboxSW.mjs index 2d2860a9f..3df9483ac 100644 --- a/test/workbox-sw/sw/controllers/test-WorkboxSW.mjs +++ b/test/workbox-sw/sw/controllers/test-WorkboxSW.mjs @@ -6,36 +6,16 @@ https://opensource.org/licenses/MIT. */ -import {expect} from 'chai'; -import sinon from 'sinon'; -import generateTestVariants from '../../../infra/testing/generate-variant-tests'; -import {WorkboxSW} from '../../../packages/workbox-sw/controllers/WorkboxSW.mjs'; -import {getPackages} from '../../../gulp-tasks/utils/get-packages'; -import {outputFilenameToPkgMap} from '../../../gulp-tasks/utils/output-filename-to-package-map'; +import {WorkboxSW} from 'workbox-sw/controllers/WorkboxSW.mjs'; +import generateTestVariants from '../../../../infra/testing/generate-variant-tests'; -describe(`[workbox-sw] WorkboxSW`, function() { +describe(`WorkboxSW`, function() { let sandbox = sinon.createSandbox(); beforeEach(function() { sandbox.restore(); delete self.workbox; - - sandbox.stub(self, 'importScripts').callsFake((url) => { - // This auto generates a value for self.workbox. - const match = /WORKBOX_CDN_ROOT_URL\/(.*)\.(?:dev|prod)\.js/.exec(url); - if (!match) { - return; - } - - const outputFilename = match[1]; - const pkg = outputFilenameToPkgMap[outputFilename]; - - const namespace = pkg.workbox.browserNamespace.split('.')[1]; - self.workbox[namespace] = { - injectedMsg: `Injected value for ${pkg.name}.`, - }; - }); }); after(function() { @@ -45,6 +25,8 @@ describe(`[workbox-sw] WorkboxSW`, function() { describe(`constructor`, function() { it(`should construct with expect defaults`, function() { + sandbox.stub(location, 'hostname').value('example.com'); + self.workbox = new WorkboxSW(); expect(self.workbox._options).to.deep.equal({ debug: false, @@ -54,9 +36,7 @@ describe(`[workbox-sw] WorkboxSW`, function() { }); it(`should construct debug true when on localhost`, function() { - sandbox.stub(self, 'location').value({ - hostname: 'localhost', - }); + sandbox.stub(location, 'hostname').value('localhost'); self.workbox = new WorkboxSW(); expect(self.workbox._options.debug).to.deep.equal(true); @@ -81,6 +61,8 @@ describe(`[workbox-sw] WorkboxSW`, function() { }); it(`should throw when invoking config after loading a module`, function() { + sandbox.stub(self, 'importScripts'); + self.workbox = new WorkboxSW(); expect(() => { @@ -92,11 +74,16 @@ describe(`[workbox-sw] WorkboxSW`, function() { // Accessing .core loads workbox-core. self.workbox.core; + expect(importScripts.callCount).to.equal(1); + expect(importScripts.args[0][0]).to.equal(`http://custom-cdn.example.com/workbox-modules/v1.0.0/workbox-core.dev.js`); + expect(() => { self.workbox.setConfig({ modulePathPrefix: 'http://custom-cdn.example.com/workbox-modules/v2.0.0/', }); }).to.throw(); + + expect(importScripts.callCount).to.equal(1); }); it(`should not throw on no config and environment should stay the same`, function() { @@ -113,12 +100,11 @@ describe(`[workbox-sw] WorkboxSW`, function() { describe(`get`, function() { it(`should print error message when importScripts fails`, function() { const errorMessage = 'Injected error.'; - self.importScripts.restore(); + sandbox.stub(self, 'importScripts').throws(new Error(errorMessage)); sandbox.stub(console, 'error').callsFake((errMsg) => { expect(errMsg.includes('workbox-core')).to.be.true; - expect(errMsg.includes( - 'WORKBOX_CDN_ROOT_URL/workbox-core.prod.js')).to.be.true; + expect(errMsg.includes('WORKBOX_CDN_ROOT_URL/workbox-core')).to.be.true; }); try { @@ -134,6 +120,8 @@ describe(`[workbox-sw] WorkboxSW`, function() { }); it(`should use modulePathCb to load modules if provided`, function() { + sandbox.stub(self, 'importScripts'); + const callbackSpy = sandbox.spy((moduleName, debug) => { return `/custom-path/${moduleName}/${debug}`; }); @@ -180,6 +168,8 @@ describe(`[workbox-sw] WorkboxSW`, function() { }, ]; generateTestVariants(`should import using modulePathPrefix`, modulePathVariations, async function(variant) { + sandbox.stub(self, 'importScripts'); + self.workbox = new WorkboxSW(); self.workbox.setConfig({ @@ -194,16 +184,15 @@ describe(`[workbox-sw] WorkboxSW`, function() { }); }); - getPackages({type: 'browser'}).forEach((pkg) => { + BROWSER_NAMESPACES.forEach((namespace) => { // Don't test workbox-sw, which exports the `workbox` namespace. - if (pkg.workbox.browserNamespace === 'workbox') return; + if (namespace === 'workbox') return; - describe(`get ${pkg.workbox.browserNamespace}`, function() { - it(`should return ${pkg.workbox.browserNamespace}`, function() { - const namespace = pkg.workbox.browserNamespace.split('.')[1]; + describe(`get ${namespace}`, function() { + it(`should return ${namespace}`, function() { + const getter = namespace.split('.')[1]; self.workbox = new WorkboxSW(); - expect(self.workbox[namespace]).to.exist; - expect(self.workbox[namespace].injectedMsg).to.exist; + expect(self.workbox[getter]).to.exist; }); }); });