mirror of
https://github.com/hanzoai/workbox.git
synced 2026-09-24 17:04:35 +00:00
1057 lines
34 KiB
JavaScript
1057 lines
34 KiB
JavaScript
/*
|
|
Copyright 2018 Google LLC
|
|
|
|
Use of this source code is governed by an MIT-style
|
|
license that can be found in the LICENSE file or at
|
|
https://opensource.org/licenses/MIT.
|
|
*/
|
|
|
|
const chai = require('chai');
|
|
const chaiAsPromised = require('chai-as-promised');
|
|
const fse = require('fs-extra');
|
|
const upath = require('upath');
|
|
const tempy = require('tempy');
|
|
|
|
const {errors} = require('../../../packages/workbox-build/build/lib/errors');
|
|
const {
|
|
generateSW,
|
|
} = require('../../../packages/workbox-build/build/generate-sw');
|
|
const {
|
|
WorkboxConfigError,
|
|
} = require('../../../packages/workbox-build/build/lib/validate-options');
|
|
const confirmDirectoryContains = require('../../../infra/testing/confirm-directory-contains');
|
|
const validateServiceWorkerRuntime = require('../../../infra/testing/validator/service-worker-runtime');
|
|
|
|
chai.use(chaiAsPromised);
|
|
const {expect} = chai;
|
|
|
|
describe(`[workbox-build] generate-sw.js (End to End)`, function () {
|
|
const GLOB_DIR = upath.join(__dirname, '..', 'static', 'example-project-1');
|
|
const BASE_OPTIONS = {
|
|
globDirectory: GLOB_DIR,
|
|
inlineWorkboxRuntime: false,
|
|
mode: 'development',
|
|
swDest: tempy.file({extension: 'js'}),
|
|
};
|
|
const REQUIRED_PARAMS = ['swDest'];
|
|
const SUPPORTED_PARAMS = [
|
|
'additionalManifestEntries',
|
|
'babelPresetEnvTargets',
|
|
'cacheId',
|
|
'clientsClaim',
|
|
'directoryIndex',
|
|
'disableDevLogs',
|
|
'dontCacheBustURLsMatching',
|
|
'globDirectory',
|
|
'globFollow',
|
|
'globIgnores',
|
|
'globPatterns',
|
|
'globStrict',
|
|
'ignoreURLParametersMatching',
|
|
'importScripts',
|
|
'inlineWorkboxRuntime',
|
|
'manifestTransforms',
|
|
'maximumFileSizeToCacheInBytes',
|
|
'mode',
|
|
'modifyURLPrefix',
|
|
'navigateFallback',
|
|
'navigateFallbackDenylist',
|
|
'navigateFallbackAllowlist',
|
|
'navigationPreload',
|
|
'offlineGoogleAnalytics',
|
|
'runtimeCaching',
|
|
'skipWaiting',
|
|
'sourcemap',
|
|
'templatedURLs',
|
|
].concat(REQUIRED_PARAMS);
|
|
const UNSUPPORTED_PARAMS = ['injectionPoint', 'swSrc'];
|
|
const PRECACHE_ORDER = [
|
|
{
|
|
url: 'webpackEntry.js',
|
|
revision: /^[0-9a-f]{32}$/,
|
|
},
|
|
{
|
|
url: 'page-2.html',
|
|
revision: /^[0-9a-f]{32}$/,
|
|
},
|
|
{
|
|
url: 'page-1.html',
|
|
revision: /^[0-9a-f]{32}$/,
|
|
},
|
|
{
|
|
url: 'index.html',
|
|
revision: /^[0-9a-f]{32}$/,
|
|
},
|
|
{
|
|
url: 'styles/stylesheet-2.css',
|
|
revision: /^[0-9a-f]{32}$/,
|
|
},
|
|
{
|
|
url: 'styles/stylesheet-1.css',
|
|
revision: /^[0-9a-f]{32}$/,
|
|
},
|
|
];
|
|
|
|
describe('[workbox-build] required parameters', function () {
|
|
for (const requiredParam of REQUIRED_PARAMS) {
|
|
it(`should fail validation when '${requiredParam}' is missing`, async function () {
|
|
const options = Object.assign({}, BASE_OPTIONS);
|
|
delete options[requiredParam];
|
|
|
|
await expect(generateSW(options)).to.eventually.be.rejectedWith(
|
|
WorkboxConfigError,
|
|
requiredParam,
|
|
);
|
|
});
|
|
}
|
|
});
|
|
|
|
describe('[workbox-build] unsupported parameters', function () {
|
|
for (const unsupportedParam of UNSUPPORTED_PARAMS) {
|
|
it(`should fail validation when '${unsupportedParam}' is present`, async function () {
|
|
const options = Object.assign({}, BASE_OPTIONS);
|
|
options[unsupportedParam] = unsupportedParam;
|
|
|
|
await expect(generateSW(options)).to.eventually.be.rejectedWith(
|
|
WorkboxConfigError,
|
|
unsupportedParam,
|
|
);
|
|
});
|
|
}
|
|
});
|
|
|
|
describe('[workbox-build] invalid parameter values', function () {
|
|
for (const param of SUPPORTED_PARAMS) {
|
|
it(`should fail validation when '${param}' is an unexpected value`, async function () {
|
|
const options = Object.assign({}, BASE_OPTIONS);
|
|
options[param] = () => {};
|
|
|
|
await expect(generateSW(options)).to.eventually.be.rejectedWith(
|
|
WorkboxConfigError,
|
|
param,
|
|
);
|
|
});
|
|
}
|
|
|
|
it(`should reject when there are no manifest entries or runtimeCaching`, async function () {
|
|
const options = Object.assign({}, BASE_OPTIONS);
|
|
// This temporary directory will be empty.
|
|
options.globDirectory = tempy.directory();
|
|
|
|
await expect(generateSW(options)).to.eventually.be.rejectedWith(
|
|
errors['no-manifest-entries-or-runtime-caching'],
|
|
);
|
|
});
|
|
});
|
|
|
|
describe(`[workbox-build] writing a service worker file`, function () {
|
|
it(`should use defaults when all the required parameters are present`, async function () {
|
|
const outputDir = tempy.directory();
|
|
const swDest = upath.join(outputDir, 'sw.js');
|
|
const options = Object.assign({}, BASE_OPTIONS, {swDest});
|
|
|
|
const {count, filePaths, size, warnings} = await generateSW(options);
|
|
expect(warnings).to.be.empty;
|
|
expect(count).to.eql(6);
|
|
// Line ending differences lead to different sizes on Windows.
|
|
expect(size).to.be.oneOf([2782, 2698]);
|
|
|
|
await confirmDirectoryContains(outputDir, filePaths);
|
|
|
|
await validateServiceWorkerRuntime({
|
|
swFile: swDest,
|
|
expectedMethodCalls: {
|
|
__WB_DISABLE_DEV_LOGS: undefined,
|
|
importScripts: [[/^\.\/workbox-[0-9a-f]{8}$/]],
|
|
precacheAndRoute: [[PRECACHE_ORDER, {}]],
|
|
},
|
|
});
|
|
});
|
|
|
|
it(`should include the versioning strings in the generated bundle`, async function () {
|
|
const outputDir = tempy.directory();
|
|
const swDest = upath.join(outputDir, 'sw.js');
|
|
const options = Object.assign({}, BASE_OPTIONS, {
|
|
swDest,
|
|
inlineWorkboxRuntime: true,
|
|
});
|
|
|
|
const {count, warnings} = await generateSW(options);
|
|
expect(warnings).to.be.empty;
|
|
expect(count).to.eql(6);
|
|
|
|
const contents = await fse.readFile(swDest, 'utf8');
|
|
// This isn't the exact string, but it's close enough.
|
|
expect(contents).to.include(`workbox:core:`);
|
|
});
|
|
|
|
it(`should disable logging when disableDevLogs is set to true`, async function () {
|
|
const outputDir = tempy.directory();
|
|
const swDest = upath.join(outputDir, 'sw.js');
|
|
const options = Object.assign({}, BASE_OPTIONS, {
|
|
disableDevLogs: true,
|
|
swDest,
|
|
});
|
|
|
|
const {count, filePaths, size, warnings} = await generateSW(options);
|
|
expect(warnings).to.be.empty;
|
|
expect(count).to.eql(6);
|
|
// Line ending differences lead to different sizes on Windows.
|
|
expect(size).to.be.oneOf([2782, 2698]);
|
|
|
|
await confirmDirectoryContains(outputDir, filePaths);
|
|
|
|
await validateServiceWorkerRuntime({
|
|
swFile: swDest,
|
|
expectedMethodCalls: {
|
|
__WB_DISABLE_DEV_LOGS: true,
|
|
importScripts: [[/^\.\/workbox-[0-9a-f]{8}$/]],
|
|
precacheAndRoute: [[PRECACHE_ORDER, {}]],
|
|
},
|
|
});
|
|
});
|
|
|
|
it(`should use defaults when all the required parameters are present, with additional importScripts`, async function () {
|
|
const outputDir = tempy.directory();
|
|
const swDest = upath.join(outputDir, 'sw.js');
|
|
const importScripts = ['manifest.js'];
|
|
const options = Object.assign({}, BASE_OPTIONS, {
|
|
importScripts,
|
|
swDest,
|
|
});
|
|
|
|
const {count, filePaths, size, warnings} = await generateSW(options);
|
|
expect(warnings).to.be.empty;
|
|
expect(count).to.eql(6);
|
|
// Line ending differences lead to different sizes on Windows.
|
|
expect(size).to.be.oneOf([2782, 2698]);
|
|
|
|
await confirmDirectoryContains(outputDir, filePaths);
|
|
|
|
await validateServiceWorkerRuntime({
|
|
swFile: swDest,
|
|
expectedMethodCalls: {
|
|
importScripts: [[/^\.\/workbox-[0-9a-f]{8}$/], [...importScripts]],
|
|
precacheAndRoute: [[PRECACHE_ORDER, {}]],
|
|
},
|
|
});
|
|
});
|
|
|
|
it(`should use defaults when all the required parameters are present, with additional configuration`, async function () {
|
|
const outputDir = tempy.directory();
|
|
const swDest = upath.join(outputDir, 'sw.js');
|
|
const directoryIndex = 'test.html';
|
|
const ignoreURLParametersMatching = [/test1/, /test2/];
|
|
const cacheId = 'test';
|
|
const additionalOptions = {
|
|
cacheId,
|
|
directoryIndex,
|
|
ignoreURLParametersMatching,
|
|
clientsClaim: true,
|
|
skipWaiting: true,
|
|
};
|
|
const options = Object.assign({}, BASE_OPTIONS, additionalOptions, {
|
|
swDest,
|
|
});
|
|
|
|
const {count, filePaths, size, warnings} = await generateSW(options);
|
|
expect(warnings).to.be.empty;
|
|
expect(count).to.eql(6);
|
|
// Line ending differences lead to different sizes on Windows.
|
|
expect(size).to.be.oneOf([2782, 2698]);
|
|
|
|
await confirmDirectoryContains(outputDir, filePaths);
|
|
|
|
await validateServiceWorkerRuntime({
|
|
swFile: swDest,
|
|
expectedMethodCalls: {
|
|
importScripts: [[/^\.\/workbox-[0-9a-f]{8}$/]],
|
|
clientsClaim: [[]],
|
|
skipWaiting: [[]],
|
|
setCacheNameDetails: [[{prefix: cacheId}]],
|
|
precacheAndRoute: [
|
|
[PRECACHE_ORDER, {directoryIndex, ignoreURLParametersMatching}],
|
|
],
|
|
},
|
|
addEventListenerValidation: (addEventListenerStub) => {
|
|
// When skipWaiting is true, the 'message' addEventListener shouldn't be called.
|
|
expect(addEventListenerStub.called).to.be.false;
|
|
},
|
|
});
|
|
});
|
|
|
|
it(`should use defaults when all the required parameters are present, with additionalManifestEntries`, async function () {
|
|
const outputDir = tempy.directory();
|
|
const swDest = upath.join(outputDir, 'sw.js');
|
|
const options = Object.assign({}, BASE_OPTIONS, {
|
|
additionalManifestEntries: [
|
|
'/one',
|
|
{url: '/two', revision: null},
|
|
{url: '/three', revision: '333'},
|
|
// See https://github.com/GoogleChrome/workbox/issues/2558
|
|
{url: '/four', revision: '123', integrity: '456'},
|
|
],
|
|
swDest,
|
|
});
|
|
|
|
const {count, filePaths, size, warnings} = await generateSW(options);
|
|
// The string additionalManifestEntries entry should lead to one warning.
|
|
expect(warnings).to.have.length(1);
|
|
expect(count).to.eql(10);
|
|
// Line ending differences lead to different sizes on Windows.
|
|
expect(size).to.be.oneOf([2782, 2698]);
|
|
|
|
await confirmDirectoryContains(outputDir, filePaths);
|
|
|
|
await validateServiceWorkerRuntime({
|
|
swFile: swDest,
|
|
expectedMethodCalls: {
|
|
importScripts: [[/^\.\/workbox-[0-9a-f]{8}$/]],
|
|
precacheAndRoute: [
|
|
[
|
|
[
|
|
...PRECACHE_ORDER,
|
|
{url: '/one', revision: null},
|
|
{url: '/two', revision: null},
|
|
{url: '/three', revision: '333'},
|
|
{url: '/four', revision: '123', integrity: '456'},
|
|
],
|
|
{},
|
|
],
|
|
],
|
|
},
|
|
});
|
|
});
|
|
|
|
it(`should add a 'message' event listener when 'skipWaiting: false'`, async function () {
|
|
const outputDir = tempy.directory();
|
|
const swDest = upath.join(outputDir, 'sw.js');
|
|
const additionalOptions = {
|
|
skipWaiting: false,
|
|
};
|
|
const options = Object.assign({}, BASE_OPTIONS, additionalOptions, {
|
|
swDest,
|
|
});
|
|
|
|
const {count, filePaths, size, warnings} = await generateSW(options);
|
|
expect(warnings).to.be.empty;
|
|
expect(count).to.eql(6);
|
|
// Line ending differences lead to different sizes on Windows.
|
|
expect(size).to.be.oneOf([2782, 2698]);
|
|
|
|
await confirmDirectoryContains(outputDir, filePaths);
|
|
|
|
await validateServiceWorkerRuntime({
|
|
swFile: swDest,
|
|
expectedMethodCalls: {
|
|
importScripts: [[/^\.\/workbox-[0-9a-f]{8}$/]],
|
|
precacheAndRoute: [[PRECACHE_ORDER, {}]],
|
|
},
|
|
addEventListenerValidation: (addEventListenerStub) => {
|
|
expect(addEventListenerStub.calledOnce).to.be.true;
|
|
expect(addEventListenerStub.firstCall.args[0]).to.eql('message');
|
|
// This isn't the *cleanest* possible way of testing the message event
|
|
// handler, but given the constraints of this node-based environment,
|
|
// it seems the most effective way to ensure the right code gets run.
|
|
expect(addEventListenerStub.firstCall.args[1].toString()).to.eql(
|
|
`event => {\n if (event.data && event.data.type === 'SKIP_WAITING') {\n self.skipWaiting();\n }\n }`,
|
|
);
|
|
},
|
|
});
|
|
});
|
|
|
|
it(`should use defaults when all the required parameters are present, with 'navigateFallback'`, async function () {
|
|
const outputDir = tempy.directory();
|
|
const swDest = upath.join(outputDir, 'sw.js');
|
|
const navigateFallback = 'test.html';
|
|
const navigateFallbackDenylist = [/test1/, /test2/];
|
|
const navigateFallbackAllowlist = [/test3/, /test4/];
|
|
const options = Object.assign({}, BASE_OPTIONS, {
|
|
navigateFallback,
|
|
navigateFallbackDenylist,
|
|
navigateFallbackAllowlist,
|
|
swDest,
|
|
});
|
|
|
|
const {count, filePaths, size, warnings} = await generateSW(options);
|
|
expect(warnings).to.be.empty;
|
|
expect(count).to.eql(6);
|
|
// Line ending differences lead to different sizes on Windows.
|
|
expect(size).to.be.oneOf([2782, 2698]);
|
|
|
|
await confirmDirectoryContains(outputDir, filePaths);
|
|
|
|
await validateServiceWorkerRuntime({
|
|
swFile: swDest,
|
|
expectedMethodCalls: {
|
|
createHandlerBoundToURL: [[navigateFallback]],
|
|
importScripts: [[/^\.\/workbox-[0-9a-f]{8}$/]],
|
|
precacheAndRoute: [[PRECACHE_ORDER, {}]],
|
|
registerRoute: [[{name: 'NavigationRoute'}]],
|
|
NavigationRoute: [
|
|
[
|
|
'/urlWithCacheKey',
|
|
{
|
|
denylist: navigateFallbackDenylist,
|
|
allowlist: navigateFallbackAllowlist,
|
|
},
|
|
],
|
|
],
|
|
},
|
|
});
|
|
});
|
|
|
|
it(`should use defaults when all the required parameters are present, with symlinks`, async function () {
|
|
const outputDir = tempy.directory();
|
|
const swDest = upath.join(outputDir, 'sw.js');
|
|
const globDirectory = tempy.directory();
|
|
|
|
await fse.ensureSymlink(GLOB_DIR, upath.join(globDirectory, 'link'));
|
|
|
|
const options = Object.assign({}, BASE_OPTIONS, {
|
|
globDirectory,
|
|
swDest,
|
|
});
|
|
|
|
const {count, filePaths, size, warnings} = await generateSW(options);
|
|
expect(warnings).to.be.empty;
|
|
expect(count).to.eql(6);
|
|
// Line ending differences lead to different sizes on Windows.
|
|
expect(size).to.be.oneOf([2782, 2698]);
|
|
|
|
await confirmDirectoryContains(outputDir, filePaths);
|
|
|
|
await validateServiceWorkerRuntime({
|
|
swFile: swDest,
|
|
expectedMethodCalls: {
|
|
importScripts: [[/^\.\/workbox-[0-9a-f]{8}$/]],
|
|
precacheAndRoute: [
|
|
[
|
|
[
|
|
{
|
|
url: 'link/webpackEntry.js',
|
|
revision: /^[0-9a-f]{32}$/,
|
|
},
|
|
{
|
|
url: 'link/page-2.html',
|
|
revision: /^[0-9a-f]{32}$/,
|
|
},
|
|
{
|
|
url: 'link/page-1.html',
|
|
revision: /^[0-9a-f]{32}$/,
|
|
},
|
|
{
|
|
url: 'link/index.html',
|
|
revision: /^[0-9a-f]{32}$/,
|
|
},
|
|
{
|
|
url: 'link/styles/stylesheet-2.css',
|
|
revision: /^[0-9a-f]{32}$/,
|
|
},
|
|
{
|
|
url: 'link/styles/stylesheet-1.css',
|
|
revision: /^[0-9a-f]{32}$/,
|
|
},
|
|
],
|
|
{},
|
|
],
|
|
],
|
|
},
|
|
});
|
|
});
|
|
|
|
it(`should use defaults when all the required parameters are present, with 'globFollow:true' and symlinks`, async function () {
|
|
// https://github.com/isaacs/node-glob/blob/main/changelog.md#90
|
|
// 10.3 exclude symbolic links to directories when follow and nodir are both set
|
|
|
|
const outputDir = tempy.directory();
|
|
const swDest = upath.join(outputDir, 'sw.js');
|
|
const globDirectory = tempy.directory();
|
|
|
|
await fse.ensureSymlink(GLOB_DIR, upath.join(globDirectory, 'link'));
|
|
|
|
const options = Object.assign({}, BASE_OPTIONS, {
|
|
globDirectory,
|
|
globFollow: true,
|
|
swDest,
|
|
});
|
|
|
|
const {count, filePaths, size, warnings} = await generateSW(options);
|
|
expect(warnings).to.be.empty;
|
|
expect(count).to.eql(6);
|
|
// Line ending differences lead to different sizes on Windows.
|
|
expect(size).to.be.oneOf([2782, 2698]);
|
|
|
|
await confirmDirectoryContains(outputDir, filePaths);
|
|
|
|
await validateServiceWorkerRuntime({
|
|
swFile: swDest,
|
|
expectedMethodCalls: {
|
|
importScripts: [[/^\.\/workbox-[0-9a-f]{8}$/]],
|
|
precacheAndRoute: [
|
|
[
|
|
[
|
|
{
|
|
url: 'link/webpackEntry.js',
|
|
revision: /^[0-9a-f]{32}$/,
|
|
},
|
|
{
|
|
url: 'link/page-2.html',
|
|
revision: /^[0-9a-f]{32}$/,
|
|
},
|
|
{
|
|
url: 'link/page-1.html',
|
|
revision: /^[0-9a-f]{32}$/,
|
|
},
|
|
{
|
|
url: 'link/index.html',
|
|
revision: /^[0-9a-f]{32}$/,
|
|
},
|
|
{
|
|
url: 'link/styles/stylesheet-2.css',
|
|
revision: /^[0-9a-f]{32}$/,
|
|
},
|
|
{
|
|
url: 'link/styles/stylesheet-1.css',
|
|
revision: /^[0-9a-f]{32}$/,
|
|
},
|
|
],
|
|
{},
|
|
],
|
|
],
|
|
},
|
|
});
|
|
});
|
|
|
|
it(`should use defaults when all the required parameters are present, with 'offlineGoogleAnalytics' set to true`, async function () {
|
|
const outputDir = tempy.directory();
|
|
const swDest = upath.join(outputDir, 'sw.js');
|
|
const options = Object.assign({}, BASE_OPTIONS, {
|
|
swDest,
|
|
offlineGoogleAnalytics: true,
|
|
});
|
|
|
|
const {count, filePaths, size, warnings} = await generateSW(options);
|
|
expect(warnings).to.be.empty;
|
|
expect(count).to.eql(6);
|
|
// Line ending differences lead to different sizes on Windows.
|
|
expect(size).to.be.oneOf([2782, 2698]);
|
|
|
|
await confirmDirectoryContains(outputDir, filePaths);
|
|
|
|
await validateServiceWorkerRuntime({
|
|
swFile: swDest,
|
|
expectedMethodCalls: {
|
|
importScripts: [[/^\.\/workbox-[0-9a-f]{8}$/]],
|
|
precacheAndRoute: [[PRECACHE_ORDER, {}]],
|
|
initialize: [[{}]],
|
|
},
|
|
});
|
|
});
|
|
|
|
it(`should use defaults when all the required parameters are present, with 'offlineGoogleAnalytics' set to a config`, async function () {
|
|
const outputDir = tempy.directory();
|
|
const swDest = upath.join(outputDir, 'sw.js');
|
|
const options = Object.assign({}, BASE_OPTIONS, {
|
|
swDest,
|
|
offlineGoogleAnalytics: {
|
|
parameterOverrides: {
|
|
cd1: 'offline',
|
|
},
|
|
},
|
|
});
|
|
|
|
const {count, filePaths, size, warnings} = await generateSW(options);
|
|
expect(warnings).to.be.empty;
|
|
expect(count).to.eql(6);
|
|
// Line ending differences lead to different sizes on Windows.
|
|
expect(size).to.be.oneOf([2782, 2698]);
|
|
|
|
await confirmDirectoryContains(outputDir, filePaths);
|
|
|
|
await validateServiceWorkerRuntime({
|
|
swFile: swDest,
|
|
expectedMethodCalls: {
|
|
importScripts: [[/^\.\/workbox-[0-9a-f]{8}$/]],
|
|
precacheAndRoute: [[PRECACHE_ORDER, {}]],
|
|
initialize: [
|
|
[
|
|
{
|
|
parameterOverrides: {
|
|
cd1: 'offline',
|
|
},
|
|
},
|
|
],
|
|
],
|
|
},
|
|
});
|
|
});
|
|
|
|
it(`should support using a swDest that includes a subdirectory`, async function () {
|
|
const outputDir = tempy.directory();
|
|
const swDest = upath.join(outputDir, 'sub', 'directory', 'sw.js');
|
|
const options = Object.assign({}, BASE_OPTIONS, {
|
|
swDest,
|
|
});
|
|
|
|
const {count, filePaths, size, warnings} = await generateSW(options);
|
|
expect(warnings).to.be.empty;
|
|
expect(count).to.eql(6);
|
|
// Line ending differences lead to different sizes on Windows.
|
|
expect(size).to.be.oneOf([2782, 2698]);
|
|
|
|
await confirmDirectoryContains(outputDir, filePaths);
|
|
});
|
|
|
|
it(`should inline the Workbox runtime when 'inlineWorkboxRuntime' is true`, async function () {
|
|
const outputDir = tempy.directory();
|
|
const swDest = upath.join(outputDir, 'sw.js');
|
|
const options = Object.assign({}, BASE_OPTIONS, {
|
|
swDest,
|
|
inlineWorkboxRuntime: true,
|
|
});
|
|
|
|
const {count, filePaths, size, warnings} = await generateSW(options);
|
|
expect(warnings).to.be.empty;
|
|
expect(count).to.eql(6);
|
|
// Line ending differences lead to different sizes on Windows.
|
|
expect(size).to.be.oneOf([2782, 2698]);
|
|
|
|
await confirmDirectoryContains(outputDir, filePaths);
|
|
// We can't validate the generated sw.js file, unfortunately.
|
|
});
|
|
});
|
|
|
|
describe(`[workbox-build] behavior with 'runtimeCaching'`, function () {
|
|
const DEFAULT_METHOD = 'GET';
|
|
const REGEXP_URL_PATTERN = /test/;
|
|
const STRING_URL_PATTERN = '/test';
|
|
const STRING_HANDLER = 'CacheFirst';
|
|
const FUNCTION_URL_PATTERN = (params) => true;
|
|
|
|
it(`should reject when 'urlPattern' is missing from 'runtimeCaching'`, async function () {
|
|
const handler = STRING_HANDLER;
|
|
const options = Object.assign({}, BASE_OPTIONS, {
|
|
runtimeCaching: [{handler}],
|
|
});
|
|
|
|
await expect(generateSW(options)).to.eventually.be.rejectedWith(
|
|
WorkboxConfigError,
|
|
'urlPattern',
|
|
);
|
|
});
|
|
|
|
it(`should reject when 'handler' is missing from 'runtimeCaching'`, async function () {
|
|
const urlPattern = REGEXP_URL_PATTERN;
|
|
const options = Object.assign({}, BASE_OPTIONS, {
|
|
runtimeCaching: [{urlPattern}],
|
|
});
|
|
|
|
await expect(generateSW(options)).to.eventually.be.rejectedWith(
|
|
WorkboxConfigError,
|
|
'handler',
|
|
);
|
|
});
|
|
|
|
it(`should reject when 'handler' is not a valid strategy name`, async function () {
|
|
const urlPattern = REGEXP_URL_PATTERN;
|
|
const options = Object.assign({}, BASE_OPTIONS, {
|
|
runtimeCaching: [
|
|
{
|
|
urlPattern,
|
|
handler: 'invalid',
|
|
},
|
|
],
|
|
});
|
|
|
|
await expect(generateSW(options)).to.eventually.be.rejectedWith(
|
|
WorkboxConfigError,
|
|
'handler',
|
|
);
|
|
});
|
|
|
|
// See https://github.com/GoogleChrome/workbox/issues/2078
|
|
it(`should not require using precaching`, async function () {
|
|
const swDest = tempy.file({extension: 'js'});
|
|
const runtimeCaching = [
|
|
{
|
|
urlPattern: STRING_URL_PATTERN,
|
|
handler: STRING_HANDLER,
|
|
},
|
|
];
|
|
const options = Object.assign({}, BASE_OPTIONS, {
|
|
runtimeCaching,
|
|
swDest,
|
|
});
|
|
delete options.globDirectory;
|
|
|
|
const {count, size, warnings} = await generateSW(options);
|
|
expect(warnings).to.be.empty;
|
|
expect(count).to.eql(0);
|
|
expect(size).to.eql(0);
|
|
await validateServiceWorkerRuntime({
|
|
swFile: swDest,
|
|
expectedMethodCalls: {
|
|
[STRING_HANDLER]: [[]],
|
|
importScripts: [[/^\.\/workbox-[0-9a-f]{8}$/]],
|
|
registerRoute: [
|
|
[STRING_URL_PATTERN, {name: STRING_HANDLER}, DEFAULT_METHOD],
|
|
],
|
|
},
|
|
});
|
|
});
|
|
|
|
it(`should support a single string 'urlPattern' and a string 'handler'`, async function () {
|
|
const swDest = tempy.file({extension: 'js'});
|
|
const runtimeCaching = [
|
|
{
|
|
urlPattern: STRING_URL_PATTERN,
|
|
handler: STRING_HANDLER,
|
|
},
|
|
];
|
|
const options = Object.assign({}, BASE_OPTIONS, {
|
|
runtimeCaching,
|
|
swDest,
|
|
});
|
|
|
|
const {count, size, warnings} = await generateSW(options);
|
|
expect(warnings).to.be.empty;
|
|
expect(count).to.eql(6);
|
|
// Line ending differences lead to different sizes on Windows.
|
|
expect(size).to.be.oneOf([2782, 2698]);
|
|
await validateServiceWorkerRuntime({
|
|
swFile: swDest,
|
|
expectedMethodCalls: {
|
|
[STRING_HANDLER]: [[]],
|
|
importScripts: [[/^\.\/workbox-[0-9a-f]{8}$/]],
|
|
precacheAndRoute: [[PRECACHE_ORDER, {}]],
|
|
registerRoute: [
|
|
[STRING_URL_PATTERN, {name: STRING_HANDLER}, DEFAULT_METHOD],
|
|
],
|
|
},
|
|
});
|
|
});
|
|
|
|
it(`should support a single function 'urlPattern' and a string 'handler'`, async function () {
|
|
const swDest = tempy.file({extension: 'js'});
|
|
const runtimeCaching = [
|
|
{
|
|
urlPattern: FUNCTION_URL_PATTERN,
|
|
handler: STRING_HANDLER,
|
|
},
|
|
];
|
|
const options = Object.assign({}, BASE_OPTIONS, {
|
|
runtimeCaching,
|
|
swDest,
|
|
});
|
|
|
|
const {count, size, warnings} = await generateSW(options);
|
|
expect(warnings).to.be.empty;
|
|
expect(count).to.eql(6);
|
|
// Line ending differences lead to different sizes on Windows.
|
|
expect(size).to.be.oneOf([2782, 2698]);
|
|
await validateServiceWorkerRuntime({
|
|
swFile: swDest,
|
|
expectedMethodCalls: {
|
|
[STRING_HANDLER]: [[]],
|
|
importScripts: [[/^\.\/workbox-[0-9a-f]{8}$/]],
|
|
precacheAndRoute: [[PRECACHE_ORDER, {}]],
|
|
// See https://github.com/chaijs/chai/issues/697
|
|
registerRoute: [
|
|
['params => true', {name: STRING_HANDLER}, DEFAULT_METHOD],
|
|
],
|
|
},
|
|
});
|
|
});
|
|
|
|
it(`should support setting individual 'options' each, for multiple 'runtimeCaching' entries`, async function () {
|
|
const swDest = tempy.file({extension: 'js'});
|
|
const firstRuntimeCachingOptions = {
|
|
cacheName: 'first-cache-name',
|
|
expiration: {
|
|
maxEntries: 1,
|
|
maxAgeSeconds: 1,
|
|
purgeOnQuotaError: false,
|
|
},
|
|
};
|
|
const secondRuntimeCachingOptions = {
|
|
cacheName: 'second-cache-name',
|
|
cacheableResponse: {
|
|
headers: {
|
|
'X-Test': 'test',
|
|
},
|
|
statuses: [0, 200],
|
|
},
|
|
precacheFallback: {
|
|
fallbackURL: '/test',
|
|
},
|
|
};
|
|
const runtimeCaching = [
|
|
{
|
|
urlPattern: REGEXP_URL_PATTERN,
|
|
handler: STRING_HANDLER,
|
|
options: firstRuntimeCachingOptions,
|
|
},
|
|
{
|
|
urlPattern: REGEXP_URL_PATTERN,
|
|
handler: STRING_HANDLER,
|
|
options: secondRuntimeCachingOptions,
|
|
},
|
|
];
|
|
const options = Object.assign({}, BASE_OPTIONS, {
|
|
runtimeCaching,
|
|
swDest,
|
|
});
|
|
|
|
const {count, size, warnings} = await generateSW(options);
|
|
|
|
expect(warnings).to.be.empty;
|
|
expect(count).to.eql(6);
|
|
// Line ending differences lead to different sizes on Windows.
|
|
expect(size).to.be.oneOf([2782, 2698]);
|
|
|
|
await validateServiceWorkerRuntime({
|
|
swFile: swDest,
|
|
expectedMethodCalls: {
|
|
[STRING_HANDLER]: [
|
|
[
|
|
{
|
|
cacheName: firstRuntimeCachingOptions.cacheName,
|
|
plugins: [{}],
|
|
},
|
|
],
|
|
[
|
|
{
|
|
cacheName: secondRuntimeCachingOptions.cacheName,
|
|
plugins: [{}, {}],
|
|
},
|
|
],
|
|
],
|
|
ExpirationPlugin: [[firstRuntimeCachingOptions.expiration]],
|
|
CacheableResponsePlugin: [
|
|
[secondRuntimeCachingOptions.cacheableResponse],
|
|
],
|
|
PrecacheFallbackPlugin: [
|
|
[secondRuntimeCachingOptions.precacheFallback],
|
|
],
|
|
importScripts: [[/^\.\/workbox-[0-9a-f]{8}$/]],
|
|
precacheAndRoute: [[PRECACHE_ORDER, {}]],
|
|
registerRoute: [
|
|
[REGEXP_URL_PATTERN, {name: STRING_HANDLER}, DEFAULT_METHOD],
|
|
[REGEXP_URL_PATTERN, {name: STRING_HANDLER}, DEFAULT_METHOD],
|
|
],
|
|
},
|
|
});
|
|
});
|
|
|
|
it(`should reject with a ValidationError when 'networkTimeoutSeconds' is used and handler is not 'NetworkFirst'`, async function () {
|
|
const swDest = tempy.file({extension: 'js'});
|
|
const runtimeCachingOptions = {
|
|
networkTimeoutSeconds: 1,
|
|
};
|
|
const runtimeCaching = [
|
|
{
|
|
urlPattern: REGEXP_URL_PATTERN,
|
|
handler: 'NetworkOnly',
|
|
options: runtimeCachingOptions,
|
|
},
|
|
];
|
|
const options = Object.assign({}, BASE_OPTIONS, {
|
|
runtimeCaching,
|
|
swDest,
|
|
});
|
|
|
|
await expect(generateSW(options)).to.eventually.be.rejectedWith(
|
|
errors['invalid-network-timeout-seconds'],
|
|
);
|
|
});
|
|
|
|
it(`should support passing in a function when allowed`, async function () {
|
|
const swDest = tempy.file({extension: 'js'});
|
|
const handler = () => {};
|
|
const urlPattern = () => {};
|
|
|
|
const runtimeCachingOptions = {
|
|
backgroundSync: {
|
|
name: 'test',
|
|
options: {
|
|
onSync: () => {},
|
|
},
|
|
},
|
|
plugins: [
|
|
{
|
|
cachedResponseWillBeUsed: () => {},
|
|
},
|
|
],
|
|
};
|
|
const runtimeCaching = [
|
|
{
|
|
handler,
|
|
urlPattern,
|
|
options: runtimeCachingOptions,
|
|
},
|
|
];
|
|
const options = Object.assign({}, BASE_OPTIONS, {
|
|
runtimeCaching,
|
|
swDest,
|
|
});
|
|
|
|
const {count, size, warnings} = await generateSW(options);
|
|
expect(warnings).to.be.empty;
|
|
expect(count).to.eql(6);
|
|
// Line ending differences lead to different sizes on Windows.
|
|
expect(size).to.be.oneOf([2782, 2698]);
|
|
await validateServiceWorkerRuntime({
|
|
swFile: swDest,
|
|
expectedMethodCalls: {
|
|
[handler]: [[runtimeCachingOptions]],
|
|
importScripts: [[/^\.\/workbox-[0-9a-f]{8}$/]],
|
|
precacheAndRoute: [[PRECACHE_ORDER, {}]],
|
|
registerRoute: [
|
|
[urlPattern.toString(), handler.toString(), DEFAULT_METHOD],
|
|
],
|
|
},
|
|
});
|
|
});
|
|
|
|
it(`should support 'networkTimeoutSeconds' when handler is 'NetworkFirst'`, async function () {
|
|
const swDest = tempy.file({extension: 'js'});
|
|
const networkTimeoutSeconds = 1;
|
|
const handler = 'NetworkFirst';
|
|
|
|
const runtimeCachingOptions = {
|
|
networkTimeoutSeconds,
|
|
plugins: [],
|
|
};
|
|
const runtimeCaching = [
|
|
{
|
|
urlPattern: REGEXP_URL_PATTERN,
|
|
handler,
|
|
options: runtimeCachingOptions,
|
|
},
|
|
];
|
|
const options = Object.assign({}, BASE_OPTIONS, {
|
|
runtimeCaching,
|
|
swDest,
|
|
});
|
|
|
|
const {count, size, warnings} = await generateSW(options);
|
|
expect(warnings).to.be.empty;
|
|
expect(count).to.eql(6);
|
|
// Line ending differences lead to different sizes on Windows.
|
|
expect(size).to.be.oneOf([2782, 2698]);
|
|
await validateServiceWorkerRuntime({
|
|
swFile: swDest,
|
|
expectedMethodCalls: {
|
|
[handler]: [[runtimeCachingOptions]],
|
|
importScripts: [[/^\.\/workbox-[0-9a-f]{8}$/]],
|
|
precacheAndRoute: [[PRECACHE_ORDER, {}]],
|
|
registerRoute: [
|
|
[REGEXP_URL_PATTERN, {name: handler}, DEFAULT_METHOD],
|
|
],
|
|
},
|
|
});
|
|
});
|
|
|
|
it(`should reject when 'options.expiration' is used without 'options.cacheName'`, async function () {
|
|
const urlPattern = REGEXP_URL_PATTERN;
|
|
const options = Object.assign({}, BASE_OPTIONS, {
|
|
runtimeCaching: [
|
|
{
|
|
urlPattern,
|
|
handler: 'NetworkFirst',
|
|
options: {
|
|
expiration: {
|
|
maxEntries: 5,
|
|
},
|
|
},
|
|
},
|
|
],
|
|
});
|
|
|
|
await expect(generateSW(options)).to.eventually.be.rejectedWith(
|
|
WorkboxConfigError,
|
|
errors['cache-name-required'],
|
|
);
|
|
});
|
|
|
|
it(`should ignore swDest and workbox-*.js when generating manifest entries`, async function () {
|
|
const tempDirectory = tempy.directory();
|
|
await fse.copy(BASE_OPTIONS.globDirectory, tempDirectory);
|
|
const swDest = upath.join(tempDirectory, 'service-worker.js');
|
|
await fse.createFile(swDest);
|
|
// See https://rollupjs.org/guide/en/#outputchunkfilenames
|
|
await fse.createFile(upath.join(tempDirectory, 'workbox-abcd1234.js'));
|
|
const options = Object.assign({}, BASE_OPTIONS, {
|
|
globDirectory: tempDirectory,
|
|
swDest,
|
|
});
|
|
|
|
const {count, size, warnings} = await generateSW(options);
|
|
expect(warnings).to.be.empty;
|
|
expect(count).to.eql(6);
|
|
// Line ending differences lead to different sizes on Windows.
|
|
expect(size).to.be.oneOf([2782, 2698]);
|
|
await validateServiceWorkerRuntime({
|
|
swFile: swDest,
|
|
expectedMethodCalls: {
|
|
importScripts: [[/^\.\/workbox-[0-9a-f]{8}$/]],
|
|
precacheAndRoute: [[PRECACHE_ORDER, {}]],
|
|
},
|
|
});
|
|
});
|
|
});
|
|
|
|
describe(`[workbox-build] behavior with 'navigationPreload'`, function () {
|
|
it(`should reject when 'navigationPreload' is true and 'runtimeCaching' is undefined`, async function () {
|
|
const options = Object.assign({}, BASE_OPTIONS, {
|
|
navigationPreload: true,
|
|
});
|
|
|
|
await expect(generateSW(options)).to.eventually.be.rejectedWith(
|
|
WorkboxConfigError,
|
|
errors['nav-preload-runtime-caching'],
|
|
);
|
|
});
|
|
|
|
it(`should reject when 'navigationPreload' is true and 'runtimeCaching' is undefined`, async function () {
|
|
const options = Object.assign({}, BASE_OPTIONS, {
|
|
runtimeCaching: undefined,
|
|
navigationPreload: true,
|
|
});
|
|
|
|
await expect(generateSW(options)).to.eventually.be.rejectedWith(
|
|
WorkboxConfigError,
|
|
errors['nav-preload-runtime-caching'],
|
|
);
|
|
});
|
|
|
|
it(`should generate when 'navigationPreload' is true and 'runtimeCaching' is valid`, async function () {
|
|
const swDest = tempy.file({extension: 'js'});
|
|
const urlPattern = /test/;
|
|
const handler = 'CacheFirst';
|
|
const options = Object.assign({}, BASE_OPTIONS, {
|
|
swDest,
|
|
runtimeCaching: [{urlPattern, handler}],
|
|
navigationPreload: true,
|
|
});
|
|
|
|
const {count, size, warnings} = await generateSW(options);
|
|
expect(warnings).to.be.empty;
|
|
expect(count).to.eql(6);
|
|
// Line ending differences lead to different sizes on Windows.
|
|
expect(size).to.be.oneOf([2782, 2698]);
|
|
await validateServiceWorkerRuntime({
|
|
swFile: swDest,
|
|
expectedMethodCalls: {
|
|
[handler]: [[]],
|
|
importScripts: [[/^\.\/workbox-[0-9a-f]{8}$/]],
|
|
precacheAndRoute: [[PRECACHE_ORDER, {}]],
|
|
enable: [[]],
|
|
registerRoute: [[urlPattern, {name: handler}, 'GET']],
|
|
},
|
|
});
|
|
});
|
|
});
|
|
});
|