Skip to content

Commit

Permalink
feat: make init command more suitable for automation
Browse files Browse the repository at this point in the history
* feat: init no longer prompts when cli option is present

Prompts depend on the absence of cli options. The prompt answers and cli
options are then merged.

Provided cli options do not count as 'defaultAnswers'.

* fix: fix lint errors in lib/stencil-init.js

* test: updates integration and unit tests

* test: use better naming of methods and constants

* fix: reverts change to mock test answers

* fix: fix lint errors in lib/stencil-init.spec.js
  • Loading branch information
NickTolhurst26 authored Sep 11, 2020
1 parent fc62b19 commit ab9b919
Show file tree
Hide file tree
Showing 2 changed files with 84 additions and 43 deletions.
55 changes: 39 additions & 16 deletions lib/stencil-init.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ class StencilInit {
*/
async run (dotStencilFilePath, cliOptions = {}) {
const oldStencilConfig = this.readStencilConfig(dotStencilFilePath);
const defaultAnswers = this.getDefaultAnswers(oldStencilConfig, cliOptions);
const answers = await this.askQuestions(defaultAnswers);
const defaultAnswers = this.getDefaultAnswers(oldStencilConfig);
const answers = await this.askQuestions(defaultAnswers, cliOptions);
const updatedStencilConfig = this.applyAnswers(oldStencilConfig, answers);
this.saveStencilConfig(updatedStencilConfig, dotStencilFilePath);

Expand Down Expand Up @@ -70,38 +70,56 @@ class StencilInit {

/**
* @param {{port: (number), normalStoreUrl: (string), accessToken: (string)}} stencilConfig
* @param {{port: (number), url: (string), token: (string)}} cliOptions
* @returns {{port: (number), normalStoreUrl: (string), accessToken: (string)}}
*/
getDefaultAnswers (stencilConfig, cliOptions) {
getDefaultAnswers (stencilConfig) {
return {
normalStoreUrl: cliOptions.url || stencilConfig.normalStoreUrl,
accessToken: cliOptions.token || stencilConfig.accessToken,
port: cliOptions.port || stencilConfig.port || this.serverConfig.get('/server/port'),
normalStoreUrl: stencilConfig.normalStoreUrl,
accessToken: stencilConfig.accessToken,
port: stencilConfig.port || this.serverConfig.get('/server/port'),
};
}

/**
* @param {{port: (number), normalStoreUrl: (string), accessToken: (string)}} defaultAnswers
* @param {{port: (number), url: (string), token: (string)}} cliOptions
* @returns {Promise<object>}
*/
async askQuestions (defaultAnswers) {
return await this.inquirer.prompt([
{
async askQuestions (defaultAnswers, cliOptions) {
var prompts = [];
var cliAnswers = {};

if(cliOptions.url){
cliAnswers.normalStoreUrl = cliOptions.url;
}
else{
prompts.push({
type: 'input',
name: 'normalStoreUrl',
message: 'What is the URL of your store\'s home page?',
validate: val => /^https?:\/\//.test(val) || 'You must enter a URL',
default: defaultAnswers.normalStoreUrl,
},
{
});
}

if(cliOptions.token){
cliAnswers.accessToken = cliOptions.token;
}
else{
prompts.push({
type: 'input',
name: 'accessToken',
message: 'What is your Stencil OAuth Access Token?',
default: defaultAnswers.accessToken,
filter: val => val.trim(),
},
{
});
}

if(cliOptions.port){
cliAnswers.port = cliOptions.port;
}
else{
prompts.push({
type: 'input',
name: 'port',
message: 'What port would you like to run the server on?',
Expand All @@ -115,8 +133,13 @@ class StencilInit {
return true;
}
},
},
]);
});
}

return {
...await this.inquirer.prompt(prompts),
...cliAnswers,
};
}

/**
Expand Down
72 changes: 45 additions & 27 deletions lib/stencil-init.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ afterEach(() => jest.restoreAllMocks());

describe('StencilInit integration tests', () => {
describe('run', () => {
it('should perform all the actions, save the result and inform the user about the successful finish', async () => {
it('using cli prompts, should perform all the actions, save the result and inform the user about the successful finish', async () => {
const dotStencilFilePath = './test/_mocks/bin/dotStencilFile.json';
const answers = getAnswers();
const expectedResult = JSON.stringify({ customLayouts: DEFAULT_CUSTOM_LAYOUTS_CONFIG, ...answers }, null, 2);
Expand All @@ -58,7 +58,39 @@ describe('StencilInit integration tests', () => {
serverConfig,
logger: console,
});
await instance.run(dotStencilFilePath, getCliOptions());
await instance.run(dotStencilFilePath);

expect(fsWriteFileSyncStub).toHaveBeenCalledTimes(1);
expect(inquirerPromptStub).toHaveBeenCalledTimes(1);
expect(consoleErrorStub).toHaveBeenCalledTimes(0);
expect(consoleLogStub).toHaveBeenCalledTimes(1);

expect(fsWriteFileSyncStub).toHaveBeenCalledWith(dotStencilFilePath, expectedResult);
expect(consoleLogStub).toHaveBeenCalledWith('You are now ready to go! To start developing, run $ ' + 'stencil start'.cyan);
}),
it('using cli options, should perform all the actions, save the result and inform the user about the successful finish', async () => {
const dotStencilFilePath = './test/_mocks/bin/dotStencilFile.json';
const cliOptions = getCliOptions();
const cliOptionsAsAnswers = {
"normalStoreUrl": cliOptions.url,
"port": cliOptions.port,
"accessToken": cliOptions.token,
};
const expectedResult = JSON.stringify({ customLayouts: DEFAULT_CUSTOM_LAYOUTS_CONFIG, ...cliOptionsAsAnswers }, null, 2);
const fsWriteFileSyncStub = jest.spyOn(fs, "writeFileSync").mockImplementation(jest.fn());
const inquirerPromptStub = jest.spyOn(inquirer, 'prompt').mockReturnValue({});
const consoleErrorStub = jest.spyOn(console, 'error').mockImplementation(jest.fn());
const consoleLogStub = jest.spyOn(console, 'log').mockImplementation(jest.fn());

// Test with real entities, just some methods stubbed
const instance = new StencilInit({
inquirer,
jsonLint,
fs,
serverConfig,
logger: console,
});
await instance.run(dotStencilFilePath, cliOptions);

expect(fsWriteFileSyncStub).toHaveBeenCalledTimes(1);
expect(inquirerPromptStub).toHaveBeenCalledTimes(1);
Expand Down Expand Up @@ -186,59 +218,45 @@ describe('StencilInit unit tests', () => {
// eslint-disable-next-line jest/expect-expect
it('should not mutate the passed objects', async () => {
const stencilConfig = getStencilConfig();
const cliOptions = getCliOptions();
const instance = getStencilInitInstance();

await assertNoMutations(
[stencilConfig, cliOptions],
() => instance.getDefaultAnswers(stencilConfig, cliOptions),
[stencilConfig],
() => instance.getDefaultAnswers(stencilConfig),
);
});

it('should pick values from cliOptions first if present', async () => {
const stencilConfig = getStencilConfig();
const cliOptions = getCliOptions();
const instance = getStencilInitInstance();

const res = instance.getDefaultAnswers(stencilConfig, cliOptions);

expect(res.normalStoreUrl).toEqual(cliOptions.url);
expect(res.accessToken).toEqual(cliOptions.token);
expect(res.port).toEqual(cliOptions.port);
});

it('should pick values from stencilConfig if cliOptions are empty', async () => {
it('should pick values from stencilConfig if not empty', async () => {
const stencilConfig = getStencilConfig();
const cliOptions = {};
const instance = getStencilInitInstance();

const res = instance.getDefaultAnswers(stencilConfig, cliOptions);
const res = instance.getDefaultAnswers(stencilConfig);

expect(res.normalStoreUrl).toEqual(stencilConfig.normalStoreUrl);
expect(res.accessToken).toEqual(stencilConfig.accessToken);
expect(res.port).toEqual(stencilConfig.port);
});

it('should pick values from serverConfig if stencilConfig and cliOptions are empty', async () => {
const cliOptions = _.pick(getCliOptions(), ['url']);
const stencilConfig = _.pick(getStencilConfig(), ['accessToken']);
it('should pick values from serverConfig if stencilConfig are empty', async () => {
const stencilConfig = _.pick(getStencilConfig(), ['accessToken','url']);
const instance = getStencilInitInstance();

const res = instance.getDefaultAnswers(stencilConfig, cliOptions);
const res = instance.getDefaultAnswers(stencilConfig);

expect(res.port).toEqual(serverConfigPort);

expect(res.normalStoreUrl).toEqual(cliOptions.url);
expect(res.normalStoreUrl).toEqual(stencilConfig.url);
expect(res.accessToken).toEqual(stencilConfig.accessToken);
});
});

describe('askQuestions', () => {
it('should call inquirer.prompt with correct arguments', async () => {
it('should call inquirer.prompt with correct arguments when cliOptions are empty', async () => {
const defaultAnswers = getAnswers();
const cliOptions = {};
const instance = getStencilInitInstance();

await instance.askQuestions(defaultAnswers);
await instance.askQuestions(defaultAnswers, cliOptions);

expect(inquirerStub.prompt).toHaveBeenCalledTimes(1);
// We compare the serialized results because the objects contain functions which hinders direct comparison
Expand Down

0 comments on commit ab9b919

Please sign in to comment.