Skip to content

Commit

Permalink
Merge pull request #634 from NickTolhurst26/master
Browse files Browse the repository at this point in the history
feat: make init command more suitable for automation
  • Loading branch information
junedkazi authored Sep 25, 2020
2 parents af6204b + 0faf9de commit 4c4adc9
Show file tree
Hide file tree
Showing 3 changed files with 138 additions and 88 deletions.
7 changes: 6 additions & 1 deletion bin/stencil-init.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,9 @@ if (!versionCheck()) {

const cliOptions = program.opts();

new StencilInit().run(DOT_STENCIL_FILE_PATH, cliOptions);
new StencilInit().run(DOT_STENCIL_FILE_PATH,
{
normalStoreUrl: cliOptions.url,
accessToken: cliOptions.token,
port: cliOptions.port,
});
64 changes: 43 additions & 21 deletions lib/stencil-init.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,16 +34,18 @@ class StencilInit {
/**
* @param {string} dotStencilFilePath
* @param {object} cliOptions
* @param {string} cliOptions.url
* @param {string} cliOptions.token
* @param {string} cliOptions.normalStoreUrl
* @param {string} cliOptions.accessToken
* @param {number} cliOptions.port
* @returns {Promise<void>}
*/
async run (dotStencilFilePath, cliOptions = {}) {

const oldStencilConfig = this.readStencilConfig(dotStencilFilePath);
const defaultAnswers = this.getDefaultAnswers(oldStencilConfig, cliOptions);
const answers = await this.askQuestions(defaultAnswers);
const updatedStencilConfig = this.applyAnswers(oldStencilConfig, answers);
const defaultAnswers = this.getDefaultAnswers(oldStencilConfig);
const questions = this.getQuestions(defaultAnswers, cliOptions);
const answers = await this.askQuestions(questions);
const updatedStencilConfig = this.applyAnswers(oldStencilConfig, answers, cliOptions);
this.saveStencilConfig(updatedStencilConfig, dotStencilFilePath);

this.logger.log('You are now ready to go! To start developing, run $ ' + 'stencil start'.cyan);
Expand Down Expand Up @@ -73,38 +75,46 @@ 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
* @returns {Promise<object>}
* @param {{port: (number), normalStoreUrl: (string), accessToken: (string)}} cliOptions
* @returns {{object[]}}
*/
async askQuestions (defaultAnswers) {
return await this.inquirer.prompt([
{
getQuestions (defaultAnswers, cliOptions) {
const prompts = [];

if(!cliOptions.normalStoreUrl){
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.accessToken){
prompts.push({
type: 'input',
name: 'accessToken',
message: 'What is your Stencil OAuth Access Token?',
default: defaultAnswers.accessToken,
filter: val => val.trim(),
},
{
});
}

if(!cliOptions.port){
prompts.push({
type: 'input',
name: 'port',
message: 'What port would you like to run the server on?',
Expand All @@ -118,19 +128,31 @@ class StencilInit {
return true;
}
},
},
]);
});
}

return prompts;
}

/**
* @param {{object[]}} cliOptions
* @returns {Promise<object>}
*/
async askQuestions (questions) {
return questions.length ? this.inquirer.prompt(questions) : {};
}

/**
* @param {object} stencilConfig
* @param {object} answers
* @param {{port: (number), url: (string), token: (string)}} cliOptions
* @returns {object}
*/
applyAnswers (stencilConfig, answers) {
applyAnswers (stencilConfig, answers, cliOptions) {
return {
customLayouts: DEFAULT_CUSTOM_LAYOUTS_CONFIG,
...stencilConfig,
...cliOptions,
...answers,
};
}
Expand Down
155 changes: 89 additions & 66 deletions lib/stencil-init.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,47 @@ const getAnswers = () => ({
accessToken: "accessToken_from_answers",
});
const getCliOptions = () => ({
url: "https://url-from-cli-options.mybigcommerce.com",
normalStoreUrl: "https://url-from-cli-options.mybigcommerce.com",
port: 3002,
token: "accessToken_from_CLI_options",
accessToken: "accessToken_from_CLI_options",
});
const getQuestions = () => ([
{
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: 'https://url-from-answers.mybigcommerce.com',
},
{
type: 'input',
name: 'accessToken',
message: 'What is your Stencil OAuth Access Token?',
default: 'accessToken_from_answers',
filter: val => val.trim(),
},
{
type: 'input',
name: 'port',
message: 'What port would you like to run the server on?',
default: 3003,
validate: val => {
if (isNaN(val)) {
return 'You must enter an integer';
} else if (val < 1024 || val > 65535) {
return 'The port number must be between 1025 and 65535';
} else {
return true;
}
},
},
]);

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,13 +89,40 @@ 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 expectedResult = JSON.stringify({ customLayouts: DEFAULT_CUSTOM_LAYOUTS_CONFIG, ...cliOptions }, 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(0);
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);
});
Expand Down Expand Up @@ -186,97 +244,62 @@ 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 () => {
describe('getQuestions', () => {
it('should get all questions if no cli options were passed', async () => {
const defaultAnswers = getAnswers();
const cliConfig = {};
const instance = getStencilInitInstance();

await instance.askQuestions(defaultAnswers);
const res = instance.getQuestions(defaultAnswers, cliConfig);

expect(inquirerStub.prompt).toHaveBeenCalledTimes(1);
// We compare the serialized results because the objects contain functions which hinders direct comparison
expect(JSON.stringify(inquirerStub.prompt.mock.calls)).toEqual(JSON.stringify([[[
{
type: 'input',
name: 'normalStoreUrl',
message: 'What is the URL of your store\'s home page?',
validate(val) {
return /^https?:\/\//.test(val)
? true
: 'You must enter a URL';
},
default: defaultAnswers.normalStoreUrl,
},
{
type: 'input',
name: 'accessToken',
message: 'What is your Stencil OAuth Access Token?',
default: defaultAnswers.accessToken,
filter: val => val.trim(),
},
{
type: 'input',
name: 'port',
message: 'What port would you like to run the server on?',
default: defaultAnswers.port,
validate: val => {
if (isNaN(val)) {
return 'You must enter an integer';
} else if (val < 1024 || val > 65535) {
return 'The port number must be between 1025 and 65535';
} else {
return true;
}
},
},
]]]));
expect(JSON.stringify(res)).toEqual(JSON.stringify(getQuestions()));
});
});

describe('askQuestions', () => {
it('should call inquirer.prompt with correct arguments', async () => {
const instance = getStencilInitInstance();
const questions = getQuestions();

await instance.askQuestions(questions);

expect(inquirerStub.prompt).toHaveBeenCalledTimes(1);

// We compare the serialized results because the objects contain functions which hinders direct comparison
expect(JSON.stringify(inquirerStub.prompt.mock.calls)).toEqual(JSON.stringify([[questions]]));
});
});

Expand Down

0 comments on commit 4c4adc9

Please sign in to comment.