UNPKG

generator-liferay-theme

Version:

Yeoman generator for creating Liferay themes

997 lines (775 loc) 22.2 kB
/** * SPDX-FileCopyrightText: © 2017 Liferay, Inc. <https://liferay.com> * SPDX-License-Identifier: MIT */ const chai = require('chai'); const fs = require('fs'); const _ = require('lodash'); const path = require('path'); const sinon = require('sinon'); const stripAnsi = require('strip-ansi'); const assert = chai.assert; const sinonAssert = sinon.assert; describe('LayoutCreator', () => { let FORCE_COLOR; let LayoutCreator; let inquirer; let prototype; beforeEach(() => { FORCE_COLOR = process.env.FORCE_COLOR; process.env.FORCE_COLOR = 3; jest.resetModules(); LayoutCreator = require('../../lib/LayoutCreator'); inquirer = require('inquirer'); prototype = _.create(LayoutCreator.prototype); }); afterEach(() => { if (FORCE_COLOR !== undefined) { process.env.FORCE_COLOR = FORCE_COLOR; } else { delete process.env.FORCE_COLOR; } }); describe('constructor', () => { it('sets options as instance properties', () => { const init = LayoutCreator.prototype.init; LayoutCreator.prototype.init = sinon.spy(); const layoutCreator = new LayoutCreator({ className: 'class-name', }); assert.equal(layoutCreator.className, 'class-name'); LayoutCreator.prototype.init = init; }); }); describe('init', () => { it('sets rows to empty array and init prompting if rowData is undefined', () => { prototype.after = sinon.spy(); prototype._promptRow = sinon.spy(); prototype.init(); assert.deepEqual(prototype.rows, []); sinonAssert.calledOnce(prototype._promptRow); sinonAssert.calledWithMatch(prototype._promptRow, sinon.match.func); sinonAssert.notCalled(prototype.after); }); it('uses rowData if defined and skip prompting', () => { prototype._promptRow = sinon.spy(); prototype._renderLayoutTemplate = sinon.stub().returns('template'); prototype.after = sinon.spy(); prototype.className = 'class-name'; prototype.rowData = []; prototype.init(); sinonAssert.calledWith(prototype._renderLayoutTemplate.getCall(0), { className: 'class-name', rowData: [], }); sinonAssert.notCalled(prototype._promptRow); sinonAssert.calledOnce(prototype.after); sinonAssert.calledWith(prototype.after, 'template'); }); }); describe('prompt', () => { it('passes arguments to inquirer.prompt', () => { const prompt = inquirer.prompt; inquirer.prompt = sinon.spy(); const callback = sinon.spy(); prototype.prompt( [ { default: 'foobar', message: 'question1', name: 'thing', }, ], callback ); sinonAssert.calledWith( inquirer.prompt, [ { default: 'foobar', message: 'question1', name: 'thing', }, ], callback ); inquirer.prompt = prompt; }); }); describe('_addRow', () => { it('adds new row and print layout', () => { prototype._printLayoutPreview = sinon.spy(); prototype.rows = []; const rowData = { data: 'data', }; prototype._addRow(rowData); sinonAssert.calledOnce(prototype._printLayoutPreview); assert.deepEqual(prototype.rows[0], rowData); prototype.rowInsertIndex = 1; rowData.data = 123; prototype._addRow(rowData); sinonAssert.calledTwice(prototype._printLayoutPreview); assert.deepEqual(prototype.rows[1], rowData); }); }); describe('_addWhiteSpace', () => { it('adds whitespace', () => { const choices = []; prototype._addWhiteSpace(choices); const separator = new inquirer.Separator(' '); assert.equal(choices[0].line, separator.line); assert.equal(choices[0].type, separator.type); }); }); describe('_afterPrompt', () => { it('processes data returned from prompts and render template passing content to after property', () => { prototype.after = sinon.spy(); prototype.className = 'class-name'; prototype.rows = [ [ { columnNumber: 1, size: 6, }, { columnNumber: 2, size: 6, }, ], ]; prototype._afterPrompt(); sinonAssert.calledOnce(prototype.after); }); }); describe('_afterPromptColumnCount', () => { it('passes columnCount to cb function', () => { const answers = { columnCount: 3, }; const callback = sinon.spy(); prototype._afterPromptColumnCount(answers, callback); sinonAssert.calledOnce(callback); sinonAssert.calledWith(callback, null, 3); }); }); describe('_afterPromptColumnWidths', () => { it('pass columnCount to cb function', () => { prototype._addRow = sinon.spy(); prototype.rows = [1]; const answers = { 0: 6, 1: 6, }; const callback = sinon.spy(); prototype._afterPromptColumnWidths(answers, callback); sinonAssert.calledWith(callback, null, [1]); sinonAssert.calledWith(prototype._addRow, answers); }); }); describe('_afterPromptFinishRow', () => { it('follows correct workflow based on selection', () => { const cbSpy = sinon.spy(); prototype.rows = [1]; prototype._promptInsertRow = sinon.spy(); prototype._promptRemoveRow = sinon.spy(); prototype._promptRow = sinon.spy(); prototype._removeRow = sinon.spy(); prototype._afterPromptFinishRow( { finish: 'add', }, cbSpy ); sinonAssert.calledOnce(prototype._promptRow); sinonAssert.calledWith(prototype._promptRow, cbSpy); prototype._afterPromptFinishRow( { finish: 'insert', }, cbSpy ); sinonAssert.calledOnce(prototype._promptInsertRow); sinonAssert.calledWith(prototype._promptInsertRow, cbSpy); prototype._afterPromptFinishRow( { finish: 'finish', }, cbSpy ); sinonAssert.calledOnce(cbSpy); prototype._afterPromptFinishRow( { finish: 'remove', }, cbSpy ); sinonAssert.calledOnce(prototype._promptRemoveRow); sinonAssert.calledWith(prototype._promptRemoveRow, cbSpy); sinonAssert.calledOnce(cbSpy); sinonAssert.calledOnce(prototype._promptRow); }); it('does not call anything if invalid value', () => { const cbSpy = sinon.spy(); prototype.rows = [1]; prototype._promptInsertRow = sinon.spy(); prototype._promptRemoveRow = sinon.spy(); prototype._promptRow = sinon.spy(); prototype._afterPromptFinishRow( { finish: 'notsupported', }, cbSpy ); sinonAssert.notCalled(cbSpy); sinonAssert.notCalled(prototype._promptInsertRow); sinonAssert.notCalled(prototype._promptRemoveRow); sinonAssert.notCalled(prototype._promptRow); }); }); describe('_afterPromptInsertRow', () => { it('sets rowInserIndex based on answers and pass cb to _promptRow', () => { const callback = _.noop; prototype._promptRow = sinon.spy(); prototype._afterPromptInsertRow( { rowIndex: 2, }, callback ); assert.equal(prototype.rowInsertIndex, 2); sinonAssert.calledWith(prototype._promptRow, callback); }); }); describe('_afterPromptRemoveRow', () => { it('passes rowIndex answer to _removeRow and re-prompt finish row', () => { prototype._promptFinishRow = sinon.spy(); prototype._removeRow = sinon.spy(); prototype.rows = [2]; const callback = _.noop; prototype._afterPromptRemoveRow( { rowIndex: 3, }, callback ); sinonAssert.calledWith(prototype._removeRow, 3); sinonAssert.calledWith(prototype._promptFinishRow, [2], callback); }); }); describe('_formatInlineChoicePreview', () => { it('returns formatted preview', () => { let preview = prototype._formatInlineChoicePreview(2, 2); assert.match(preview, /\S+\s{2}\S+\s{2}\S+\s{8}/); preview = prototype._formatInlineChoicePreview(5, 5); assert.match(preview, /\S+\s{5}\S+\s{5}\S+\s{2}/); preview = prototype._formatInlineChoicePreview(0, 12); assert.match(preview, /\S+\s{12}\S+/); }); }); describe('_formatPercentageValue', () => { it('returns formatted label with column width percentage', () => { prototype._formatInlineChoicePreview = sinon .stub() .returns('preview'); const labels = [ '1/12 - 8.33%', '2/12 - 16.66%', '3/12 - 25%', '4/12 - 33.33%', '5/12 - 41.66%', '6/12 - 50%', '7/12 - 58.33%', '8/12 - 66.66%', '9/12 - 75%', '10/12 - 83.33%', '11/12 - 91.66%', '12/12 - 100%', ]; _.forEach(labels, (label, index) => { assert( _.startsWith( prototype._formatPercentageValue(index + 1), label ) ); }); sinonAssert.notCalled(prototype._formatInlineChoicePreview); _.forEach(labels, (label, index) => { const formattedLabel = prototype._formatPercentageValue( index + 1, 0, true ); assert( _.startsWith(formattedLabel, label), 'starts with label' ); assert( _.endsWith(formattedLabel, 'preview'), 'adds inline preview to choice label' ); }); sinonAssert.callCount(prototype._formatInlineChoicePreview, 12); }); }); describe('_getColumnClassNames', () => { it('returns appropriate column classes', () => { let classNames = prototype._getColumnClassNames(1, 1); assert.equal(classNames[0], 'portlet-column-only'); assert.equal(classNames[1], 'portlet-column-content-only'); classNames = prototype._getColumnClassNames(1, 2); assert.equal(classNames[0], 'portlet-column-first'); assert.equal(classNames[1], 'portlet-column-content-first'); classNames = prototype._getColumnClassNames(2, 2); assert.equal(classNames[0], 'portlet-column-last'); assert.equal(classNames[1], 'portlet-column-content-last'); classNames = prototype._getColumnClassNames(2, 3); assert(_.isUndefined(classNames)); }); }); describe('_getColumnWidthChoices', () => { it('returns limited width choices based on columnIndex, columnCount, and available row width', () => { let choices = prototype._getColumnWidthChoices(0, 1, {}); assert.equal(choices.length, 1); assert.equal(choices[0].value, 12); choices = prototype._getColumnWidthChoices(0, 2, {}); // assert.equal(_.last(choices).type, 'separator'); assert.equal(choices[choices.length - 1].value, 11); assert.equal(choices.length, 11); choices = prototype._getColumnWidthChoices(1, 2, { 0: 5, }); assert.equal(choices.length, 1); assert.equal(choices[0].value, 7); choices = prototype._getColumnWidthChoices(1, 4, { 0: 5, }); assert.equal(choices.length, 5); }); }); describe('_getFinishRowChoices', () => { it('only returns add option if rows property is empty', () => { let rows = []; let choices = prototype._getFinishRowChoices(rows); assert.equal(choices.length, 1); assert.deepEqual(choices[0], { name: 'Add row', value: 'add', }); rows = [1]; choices = prototype._getFinishRowChoices(rows); assert.equal(choices.length, 4); }); }); describe('_getInsertRowChoices', () => { it('returns compact layout preview where row borders are choices', () => { prototype.rows = [ { 0: 3, 1: 9, }, { 0: 3, 1: 9, }, ]; let choices = prototype._getInsertRowChoices(); let choiceValue = 0; _.forEach(choices, (choice, index) => { index = index + 1; if (index % 2 === 0) { assert.equal(choice.type, 'separator'); assert.equal( stripAnsi(choice.line), ' |3 |9 |' ); } else { assert.equal( choice.name, ' -------------------------------------' ); assert.equal(choice.value, choiceValue); choiceValue++; } }); assert(choices[choices.length - 1].type !== 'separator'); while (prototype.rows.length < 7) { prototype.rows.push({ 0: 3, 1: 9, }); } choices = prototype._getInsertRowChoices(); assert.equal( choices[0].name, ' -----------------TOP-----------------' ); assert.equal( choices[0].selectedName, ' =================TOP=================' ); }); }); describe('_getRemoveRowChoices', () => { it('returns compact layout preview where row bodies are choices', () => { prototype.rows = [ { 0: 3, 1: 9, }, { 0: 3, 1: 9, }, ]; let choices = prototype._getRemoveRowChoices(); let choiceValue = 0; _.forEach(choices, (choice, index) => { index = index + 1; if (index % 2 === 0) { assert.equal(choice.value, choiceValue); assert.equal( stripAnsi(choice.name), ' |3 |9 |' ); choiceValue++; } else { assert.equal( stripAnsi(choice.line), ' -------------------------------------' ); assert.equal(choice.type, 'separator'); } }); assert(choices[choices.length - 1].type === 'separator'); while (prototype.rows.length < 7) { prototype.rows.push({ 0: 3, 1: 9, }); } choices = prototype._getRemoveRowChoices(); assert.equal( stripAnsi(choices[0].line), ' -----------------TOP-----------------' ); }); }); describe('_getRowNumber', () => { it('returns next row number for labels and messages', () => { prototype.rows = [1, 2]; let rowNumber = prototype._getRowNumber(); assert.equal(rowNumber, 3); prototype.rowInsertIndex = 1; rowNumber = prototype._getRowNumber(); assert.equal(rowNumber, 2); }); }); describe('_preprocessLayoutTemplateData', () => { it('converts prompt data to data that template can easily process', () => { let rows = [ { 0: 2, 1: 10, }, { 0: 2, 1: 1, 2: 9, }, { 0: 12, }, ]; const rowDataFromObjects = prototype._preprocessLayoutTemplateData( rows ); rows = [[2, 10], [2, 1, 9], [12]]; const rowDataFromArray = prototype._preprocessLayoutTemplateData( rows ); assert(_.isArray(rowDataFromObjects), 'rowData is array'); assert.deepEqual( rowDataFromObjects, rowDataFromArray, 'that it returns the same data when passing in objects or arrays' ); let number = 0; _.forEach(rowDataFromObjects, (row) => { assert(_.isArray(row), 'each row is an array'); _.forEach(row, (column) => { assert(_.isObject(column), 'each row is an array'); number++; assert.equal( number, column.number, 'column number is indexed correctly' ); }); }); const json = JSON.parse( fs.readFileSync( path.join( __dirname, '__fixtures__', 'processed_template_data.json' ) ) ); assert.deepEqual(rowDataFromObjects, json); }); }); describe('_printLayoutPreview', () => { it('prints layout preview', () => { prototype.rows = [ [6, 6], [3, 3, 6], ]; prototype._stdoutWrite = sinon.spy(); prototype._printLayoutPreview(); const preview = '\n Here is what your layout looks like so far\n' + ' -------------------------------------\n' + ' |6 |6 |\n' + ' | | |\n' + ' -------------------------------------\n' + ' |3 |3 |6 |\n' + ' | | | |\n' + ' -------------------------------------\n\n'; const strippedPreview = stripAnsi( prototype._stdoutWrite.getCall(0).args[0] ); assert.equal(strippedPreview, preview); }); }); describe('_promptColumnCount', () => { it('prompts user for column count using correct row number in prompt message', () => { prototype.prompt = sinon.spy(); prototype.rows = []; prototype._promptColumnCount(_.noop); let question = prototype.prompt.args[0][0][0]; sinonAssert.calledWithMatch( prototype.prompt.getCall(0), sinon.match.array, sinon.match.func ); assert.equal(question.name, 'columnCount'); assert.equal(question.validate, prototype._validateColumnCount); assert.match(question.message, /row 1/); prototype.rows = [1, 2, 3]; prototype._promptColumnCount(_.noop); question = prototype.prompt.args[1][0][0]; sinonAssert.calledWithMatch( prototype.prompt.getCall(1), sinon.match.array, sinon.match.func ); assert.equal(question.name, 'columnCount'); assert.equal(question.validate, prototype._validateColumnCount); assert.match(question.message, /row 4/); }); }); describe('_promptColumnWidths', () => { it('prompts user for column widths', () => { prototype.prompt = sinon.spy(); prototype.rows = []; prototype._promptColumnWidths(2, _.noop); sinonAssert.calledOnce(prototype.prompt); const questions = prototype.prompt.args[0][0]; sinonAssert.calledWithMatch( prototype.prompt.getCall(0), sinon.match.array, sinon.match.func ); assert.equal( questions.length, 2, 'it creates a question for every column' ); _.forEach(questions, (question, index) => { const columnNumber = index + 1; assert.equal(index, question.name, 'name is index'); assert.match( question.message, new RegExp('column ' + columnNumber) ); assert(_.isFunction(question.choices), 'choices is function'); }); }); }); describe('_promptFinishRow', () => { it('prompts user for next action (add, insert, remove, finish)', () => { assertPromptFn(prototype, '_promptFinishRow', [[], _.noop], { message: 'What now?', name: 'finish', }); }); }); describe('_promptInsertRow', () => { it('prompts user for where they want to insert row', () => { assertPromptFn(prototype, '_promptInsertRow', [_.noop], { message: 'Where would you like to insert a new row?', name: 'rowIndex', }); }); }); describe('_promptRemoveRow', () => { it('prompts user for what row they want to remove', () => { assertPromptFn(prototype, '_promptRemoveRow', [_.noop], { message: 'What row would you like to remove?', name: 'rowIndex', }); }); }); describe('_promptRow', () => { it('removes last row and print layout', (done) => { const waterfallSpy = sinon.spy(); const getWaterfallFunction = function (name) { return function (data, callback) { if (!callback) { callback = data; } waterfallSpy(name, data); callback(null, name); }; }; prototype._promptColumnCount = getWaterfallFunction( '_promptColumnCount' ); prototype._promptColumnWidths = getWaterfallFunction( '_promptColumnWidths' ); prototype._promptFinishRow = getWaterfallFunction( '_promptFinishRow' ); prototype._promptRow(() => { sinonAssert.calledWith( waterfallSpy.getCall(0), '_promptColumnCount' ); sinonAssert.calledWith( waterfallSpy.getCall(1), '_promptColumnWidths', '_promptColumnCount' ); sinonAssert.calledWith( waterfallSpy.getCall(2), '_promptFinishRow', '_promptColumnWidths' ); done(); }); }); }); describe('_removeRow', () => { it('removes row by index and print layout', () => { prototype._printLayoutPreview = sinon.spy(); prototype.rows = [1, 2, 3]; prototype._removeRow(1); sinonAssert.calledOnce(prototype._printLayoutPreview); assert.deepEqual(prototype.rows, [1, 3], 'it removed middle row'); prototype._removeRow(1); sinonAssert.calledTwice(prototype._printLayoutPreview); assert.deepEqual(prototype.rows, [1], 'it removed last row'); }); }); describe('_renderPreviewLine', () => { it('renders preview line', () => { let line = prototype._renderPreviewLine({ 0: 4, 1: 8, }); line = stripAnsi(line); assert.equal(line, ' | | |'); line = prototype._renderPreviewLine( { 0: 4, 1: 4, 2: 2, 3: 2, }, { label: true, } ); line = stripAnsi(line); assert.equal(line, ' |4 |4 |2 |2 |'); }); }); describe('_renderLayoutTemplate', () => { it('compiles data into valid template', () => { const fileOptions = { encoding: 'utf8', }; const json = JSON.parse( fs.readFileSync( path.join( __dirname, '__fixtures__', 'processed_template_data.json' ), fileOptions ) ); prototype.liferayVersion = '7.0'; const tplContent = prototype._renderLayoutTemplate({ className: 'my-class-name', rowData: json, }); const tplFileContent = fs.readFileSync( path.join(__dirname, '__fixtures__', 'layout_template.tpl'), fileOptions ); assert.equal( tplContent, tplFileContent, 'correctly renders template' ); }); }); describe('_replaceAt', () => { it('replaces string character at index', () => { assert.equal(prototype._replaceAt('string', 0, 'x'), 'xtring'); assert.equal(prototype._replaceAt('string', 2, 'x'), 'stxing'); assert.equal(prototype._replaceAt('string', 6, 'x'), 'stringx'); }); }); describe('_stylePreviewLine', () => { it('passes', () => { expect(() => prototype._stylePreviewLine).not.toThrow(); }); }); describe('_validateColumnCount', () => { it('validates column count', () => { const errMessage = 'Please enter a number between 1 and 12!'; let retVal = prototype._validateColumnCount(1); assert(retVal, '1 is valid'); retVal = prototype._validateColumnCount(12); assert(retVal, '12 is valid'); retVal = prototype._validateColumnCount(0); assert.equal(retVal, errMessage, '0 is invalid'); retVal = prototype._validateColumnCount(13); assert(retVal, errMessage, '12 is invalid'); retVal = prototype._validateColumnCount('string'); assert(retVal, errMessage, 'string is invalid'); }); }); }); function assertPromptFn(prototype, fnName, args, assertionData) { prototype.prompt = sinon.spy(); prototype[fnName].apply(prototype, args); args = prototype.prompt.getCall(0).args; const question = args[0][0]; sinonAssert.calledWithMatch( prototype.prompt.getCall(0), sinon.match.array, sinon.match.func ); assert.equal(question.message, assertionData.message); assert.equal(question.name, assertionData.name); assert.equal(question.type, 'list'); }