From ab9b9190bb2cf8c9c225c98a55079fcaf7b202b9 Mon Sep 17 00:00:00 2001 From: twentysixnick <60437651+NickTolhurst26@users.noreply.github.com> Date: Fri, 11 Sep 2020 16:04:02 +0100 Subject: [PATCH] feat: make init command more suitable for automation * 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 --- lib/stencil-init.js | 55 +++++++++++++++++++++--------- lib/stencil-init.spec.js | 72 +++++++++++++++++++++++++--------------- 2 files changed, 84 insertions(+), 43 deletions(-) diff --git a/lib/stencil-init.js b/lib/stencil-init.js index face3ad5..4edc60e4 100644 --- a/lib/stencil-init.js +++ b/lib/stencil-init.js @@ -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); @@ -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} */ - 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?', @@ -115,8 +133,13 @@ class StencilInit { return true; } }, - }, - ]); + }); + } + + return { + ...await this.inquirer.prompt(prompts), + ...cliAnswers, + }; } /** diff --git a/lib/stencil-init.spec.js b/lib/stencil-init.spec.js index 6c2d42c6..cf9de099 100644 --- a/lib/stencil-init.spec.js +++ b/lib/stencil-init.spec.js @@ -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); @@ -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); @@ -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