Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature] (workbox-cli) Prompt for 'ignoreURLParametersMatching' in workbox wizard #2763

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/workbox-cli/src/lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@ export const constants = {
ignoredFileExtensions: [
'map',
],
ignoreURLParametersMatching: [/^utm_/, /^fbclid$/],
};
4 changes: 4 additions & 0 deletions packages/workbox-cli/src/lib/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,8 @@ export const errors = {
'sw-src-missing-injection-point': ol`That is not a valid source service worker
file. Please try again with a file containing
'self.__WB_MANIFEST'.`,
'no-search-parameters-supplied': ol`Please provide the url search param(s)
you would like to ignore.`,
'invalid-search-parameters-supplied': ol`Please provide the valid URL search parameter(s)
without the leading '/' or '?' (i.e. source,version,language).`,
};
5 changes: 5 additions & 0 deletions packages/workbox-cli/src/lib/questions/ask-questions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,23 @@ import {askExtensionsToCache} from './ask-extensions-to-cache';
import {askRootOfWebApp} from './ask-root-of-web-app';
import {askSWDest} from './ask-sw-dest';
import {askSWSrc} from './ask-sw-src';
import {askQueryParametersInStartUrl} from './ask-start_url-query-params';

export async function askQuestions(options = {}) {
const globDirectory = await askRootOfWebApp();
const globPatterns = await askExtensionsToCache(globDirectory);
const swSrc = ("injectManifest" in options) ? await askSWSrc() : undefined;
const swDest = await askSWDest(globDirectory);
const configLocation = await askConfigLocation();
const ignoreURLParametersMatching = await askQueryParametersInStartUrl();
const config = {
globDirectory,
globPatterns,
swDest,
swSrc,
ignoreURLParametersMatching: ignoreURLParametersMatching.map(
regExp => regExp.toString()
),
};

return {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
Copyright 2021 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.
*/

import * as assert from 'assert';
import {prompt} from 'inquirer';
import {oneLine as ol} from 'common-tags';

import {errors} from '../errors';
import {constants} from '../constants';

const START_URL_QUERY_PARAMS_PROMPT = 'Please enter the search parameter(s) that you would like to ignore (separated by comma):';

// The keys used for the questions/answers.
const question_ignoreURLParametersMatching = 'ignoreURLParametersMatching';
const question_shouldAskForIgnoreURLParametersMatching = 'shouldAskForIgnoreURLParametersMatching';

/**
* @return {Promise<Object>} The answers from inquirer.
*/
async function askQuestion(): Promise<{ shouldAskForIgnoreURLParametersMatching: boolean; ignoreURLParametersMatching?: string }> {
return prompt([{
name: question_shouldAskForIgnoreURLParametersMatching,
message: ol`Does your web app manifest include search parameter(s)
in the 'start_url', other than 'utm_' or 'fbclid'
(like '?source=pwa')?`,
type: 'confirm',
default: false,
}, {
name: question_ignoreURLParametersMatching,
when: (answer: { shouldAskForIgnoreURLParametersMatching: boolean }) => answer.shouldAskForIgnoreURLParametersMatching,
message: START_URL_QUERY_PARAMS_PROMPT,
type: 'input',
}
]);
}

export async function askQueryParametersInStartUrl(
defaultIgnoredSearchParameters: RegExp[] = constants.ignoreURLParametersMatching
): Promise<RegExp[]> {
const { shouldAskForIgnoreURLParametersMatching, ignoreURLParametersMatching = '' } = await askQuestion();

if (!shouldAskForIgnoreURLParametersMatching) {
return defaultIgnoredSearchParameters;
}

assert(ignoreURLParametersMatching.length > 0, errors['no-search-parameters-supplied']);

const ignoreSearchParameters = ignoreURLParametersMatching.trim().split(',').filter(Boolean);

assert(ignoreSearchParameters.length > 0, errors['no-search-parameters-supplied']);
assert(ignoreSearchParameters.every(
param => !param.match(/^[^\w|-]/g)
), errors['invalid-search-parameters-supplied']);

return defaultIgnoredSearchParameters.concat(
ignoreSearchParameters
.map(searchParam => new RegExp(`^${searchParam}`))
);
}
36 changes: 21 additions & 15 deletions test/workbox-cli/node/lib/questions/ask-questions.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,29 +19,32 @@ describe(`[workbox-cli] lib/questions/ask-questions.js`, function() {
// and to verify that the stub's responses are used to create the overall
// response in the expected fashion.
let count = 0;
const stub = sinon.stub().callsFake(() => Promise.resolve(count++));
const stub = sinon.stub();

const {askQuestions} = proxyquire(MODULE_PATH, {
'./ask-root-of-web-app': {
askRootOfWebApp: stub,
askRootOfWebApp: stub.callsFake(() => Promise.resolve(count++)),
},
'./ask-extensions-to-cache': {
askExtensionsToCache: stub,
askExtensionsToCache: stub.callsFake(() => Promise.resolve(count++)),
},
'./ask-sw-dest': {
askSWDest: stub,
askSWDest: stub.callsFake(() => Promise.resolve(count++)),
},
'./ask-config-location': {
askConfigLocation: stub,
askConfigLocation: stub.callsFake(() => Promise.resolve(count++)),
},
'./ask-start_url-query-params': {
askQueryParametersInStartUrl: stub.onCall(4).callsFake(() => Promise.resolve([count++])),
},
});

const answer = await askQuestions();
expect(answer).to.eql({
config: {globDirectory: 0, globPatterns: 1, swDest: 2, swSrc: undefined},
config: {globDirectory: 0, globPatterns: 1, swDest: 2, swSrc: undefined, ignoreURLParametersMatching: ['4']},
configLocation: 3,
});
expect(stub.callCount).to.eql(4);
expect(stub.callCount).to.eql(5);
});

it(`should ask all the expected questions in the correct order, and return the expected result in injectManifest mode`, async function() {
Expand All @@ -50,32 +53,35 @@ describe(`[workbox-cli] lib/questions/ask-questions.js`, function() {
// and to verify that the stub's responses are used to create the overall
// response in the expected fashion.
let count = 0;
const stub = sinon.stub().callsFake(() => Promise.resolve(count++));
const stub = sinon.stub();

const {askQuestions} = proxyquire(MODULE_PATH, {
'./ask-root-of-web-app': {
askRootOfWebApp: stub,
askRootOfWebApp: stub.callsFake(() => Promise.resolve(count++)),
},
'./ask-extensions-to-cache': {
askExtensionsToCache: stub,
askExtensionsToCache: stub.callsFake(() => Promise.resolve(count++)),
},
'./ask-sw-src': {
askSWSrc: stub,
askSWSrc: stub.callsFake(() => Promise.resolve(count++)),
},
'./ask-sw-dest': {
askSWDest: stub,
askSWDest: stub.callsFake(() => Promise.resolve(count++)),
},
'./ask-config-location': {
askConfigLocation: stub,
askConfigLocation: stub.callsFake(() => Promise.resolve(count++)),
},
'./ask-start_url-query-params': {
askQueryParametersInStartUrl: stub.onCall(5).callsFake(() => Promise.resolve([count++])),
},
});

const answer = await askQuestions({injectManifest: true});
expect(answer).to.eql({
config: {globDirectory: 0, globPatterns: 1, swSrc: 2, swDest: 3},
config: {globDirectory: 0, globPatterns: 1, swSrc: 2, swDest: 3, ignoreURLParametersMatching: ['5']},
configLocation: 4,
});
expect(stub.callCount).to.eql(5);
expect(stub.callCount).to.eql(6);
});
});

134 changes: 134 additions & 0 deletions test/workbox-cli/node/lib/questions/ask-start_url-query-params.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
/*
Copyright 2021 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 expect = require('chai').expect;
const proxyquire = require('proxyquire');

const {errors} = require('../../../../../packages/workbox-cli/build/lib/errors');
const {constants} = require('../../../../../packages/workbox-cli/build/lib/constants');

const MODULE_PATH = '../../../../../packages/workbox-cli/build/lib/questions/ask-start_url-query-params';

// These are the hardcoded names of the question that are passed to inquirer.
// They are used as the keys to read the response from the users answers.
const question_ignoreURLParametersMatching = 'ignoreURLParametersMatching';
const question_shouldAskForIgnoreURLParametersMatching = 'shouldAskForIgnoreURLParametersMatching';

const DEFAULT_IGNORED_URL_PARAMETERS = constants.ignoreURLParametersMatching;

// Helper method for creating RegExp from dynamic values.
const toRegex = (searchParam) => new RegExp(`^${searchParam}`);

describe(`[workbox-cli] lib/questions/ask-start_url-query-params.js`, function() {
it(`should resolve with a default search parameters if answered no to the question`, async function() {
const shouldAskForIgnoreURLParametersMatching = false;
const {askQueryParametersInStartUrl} = proxyquire(MODULE_PATH, {
'inquirer': {
prompt: () => Promise.resolve({
[question_shouldAskForIgnoreURLParametersMatching]: shouldAskForIgnoreURLParametersMatching,
}),
},
});

const answer = await askQueryParametersInStartUrl();
expect(answer).to.eql(DEFAULT_IGNORED_URL_PARAMETERS);
});

it(`should throw 'no-search-parameters-supplied' if answered yes and no url search parameters are passed`, async function() {
const shouldAskForIgnoreURLParametersMatching = true;
const {askQueryParametersInStartUrl} = proxyquire(MODULE_PATH, {
'inquirer': {
prompt: () => Promise.resolve({
[question_shouldAskForIgnoreURLParametersMatching]: shouldAskForIgnoreURLParametersMatching,
}),
},
});

try {
await askQueryParametersInStartUrl();
throw new Error('Unexpected success.');
} catch (error) {
expect(error.message).to.eql(errors['no-search-parameters-supplied']);
}
});

it(`should throw 'invalid-search-parameters-supplied' if url search parameter passed is prefixed with '?' or '/'`, async function() {
const shouldAskForIgnoreURLParametersMatching = true;
const ignoreURLParametersMatching = '?source';
const {askQueryParametersInStartUrl} = proxyquire(MODULE_PATH, {
'inquirer': {
prompt: () => Promise.resolve({
[question_shouldAskForIgnoreURLParametersMatching]: shouldAskForIgnoreURLParametersMatching,
[question_ignoreURLParametersMatching]: ignoreURLParametersMatching,
}),
},
});

try {
await askQueryParametersInStartUrl();
throw new Error('Unexpected success.');
} catch (error) {
expect(error.message).to.eql(errors['invalid-search-parameters-supplied']);
}
});

it(`should throw 'invalid-search-parameters-supplied' if one of the provided url search parameters is prefixed with '?' or '/'`, async function() {
const shouldAskForIgnoreURLParametersMatching = true;
const ignoreURLParametersMatching = 'search,version,?language';
const {askQueryParametersInStartUrl} = proxyquire(MODULE_PATH, {
'inquirer': {
prompt: () => Promise.resolve({
[question_shouldAskForIgnoreURLParametersMatching]: shouldAskForIgnoreURLParametersMatching,
[question_ignoreURLParametersMatching]: ignoreURLParametersMatching,
}),
},
});

try {
await askQueryParametersInStartUrl();
throw new Error('Unexpected success.');
} catch (error) {
expect(error.message).to.eql(errors['invalid-search-parameters-supplied']);
}
});

it(`should resolve with a list of search parameters when a valid url search parameter is passed`, async function() {
const shouldAskForIgnoreURLParametersMatching = true;
const ignoreURLParametersMatching = 'search';
const expectedAnswer = DEFAULT_IGNORED_URL_PARAMETERS.concat(toRegex(ignoreURLParametersMatching));
const {askQueryParametersInStartUrl} = proxyquire(MODULE_PATH, {
'inquirer': {
prompt: () => Promise.resolve({
[question_shouldAskForIgnoreURLParametersMatching]: shouldAskForIgnoreURLParametersMatching,
[question_ignoreURLParametersMatching]: ignoreURLParametersMatching,
}),
},
});

const answer = await askQueryParametersInStartUrl();
expect(answer).to.eql(expectedAnswer);
});

it(`should resolve with a list of search parameters when a valid list of url search parameters is passed`, async function() {
const shouldAskForIgnoreURLParametersMatching = true;
const ignoreURLParametersMatching = 'search,version,language';
const expectedAnswer = DEFAULT_IGNORED_URL_PARAMETERS.concat(ignoreURLParametersMatching.split(',').map(toRegex));
const {askQueryParametersInStartUrl} = proxyquire(MODULE_PATH, {
'inquirer': {
prompt: () => Promise.resolve({
[question_shouldAskForIgnoreURLParametersMatching]: shouldAskForIgnoreURLParametersMatching,
[question_ignoreURLParametersMatching]: ignoreURLParametersMatching,
}),
},
});

const answer = await askQueryParametersInStartUrl();
expect(answer).to.eql(expectedAnswer);
});
});