import {inject} from 'aurelia-dependency-injection'; import {Project, ProjectItem, CLIOptions, UI} from 'aurelia-cli'; import mkdirp from 'mkdirp'; @inject(Project, CLIOptions, UI) export default class ElementGenerator { constructor(project, options, ui) { this.project = project; this.options = options; this.ui = ui; } execute() { return this.ui .ensureAnswer(this.options.args[0], 'What is the name of the element? (in hyphen format. ft- will be prefixed automatically)') .then(name => { let area = 'common/'; let subarea = 'elements/'; let fileName = this.project.makeFileName(name); let className = this.project.makeClassName(name); // let funcName = this.project.makeFunctionName(name); let subDir = (subarea) ? area + '/' + subarea : area; let relDir = 'components/' + subDir + '/ft-' + fileName; let dir = 'src/' + relDir; mkdirp.sync(dir);// quick fix incase parent folder doesn't exist. this.project.locations.push(this.project[dir] = ProjectItem.directory(dir)); this.project[dir].add( ProjectItem.text(`ft-${fileName}.js`, this.generateComponentJsSource(className)), ProjectItem.text(`ft-${fileName}.spec.js`, this.generateComponentTestJsSource(className, fileName, relDir)), ProjectItem.text(`ft-${fileName}.html`, this.generateComponentHtmlSource(fileName)), ProjectItem.text(`ft-${fileName}.md`, this.generateComponentMdSource('ft-' + fileName)), ); return this.project.commitChanges() .then(() => this.ui.log(`Created ${fileName}.`)); }); } /** * source for top level component js * @param className * @returns {string} */ generateComponentJsSource(className) { return `import {bindable, inject} from 'aurelia-framework'; @inject(Element) export class Ft${className} { @bindable nameThisBindable; constructor(element) { this.element = element; } callBackFunction() { this.element.innerHTML = this.nameThisBindable; } }`; } /** * source for top level component js unit test * @param className * @param fileName * @returns {string} */ generateComponentTestJsSource(className, fileName, relDir) { return `import {StageComponent} from 'aurelia-testing'; import {LogManager} from 'aurelia-framework'; import {bootstrap} from 'aurelia-bootstrapper'; const logger = LogManager.getLogger('${fileName}'); describe('${className}', () => { let component; let mockStore = { dispatch: function(action) { expect(action.type).toBeDefined(); logger.info('actions should have a type: ' + action.type); }, subscribe: function(callback) { logger.info('subscription request to the store'); } }; let beans = { // see configuration/en-us-retail/beans.js for example component: { ${className}: [] }, bean: { } }; beforeEach(() => { component = StageComponent .withResources('${relDir}/ft-${fileName}') .inView(''); component.configure = (aurelia) => { aurelia.container.registerInstance('Store', mockStore); aurelia.container.registerInstance('Beans', beans); aurelia.use.standardConfiguration() .feature('components/common') .feature('components/products/common'); }; }); it('should render something', done => { component.create(bootstrap).then(() => { const testElement = document.getElementById('test1'); expect(testElement.innerText).toMatch(/replace me/); done(); }); }); afterEach(() => { component.dispose(); }); }); `; } /** * source for top level component html * @returns {string} */ generateComponentHtmlSource(fileName) { return ` `; } /** * source for reducer * @param funcName, fileName */ generateReducerJsSource(funcName, fileName) { return `/** * Data Reducer for ${funcName} * Takes site specific json data and creates model for components * after applying business and presentation logic and data mapping */ import {LogManager} from 'aurelia-framework'; const logger = LogManager.getLogger('${fileName}'); export function ${funcName}(state, action) { switch (action.type) { case 'CHANGE_ME_TO_PROPER_ACTION_NAME_SUCCESS': logger.debug('Reducing: CHANGE_ME_TO_PROPER_ACTION_NAME_SUCCESS'); return Object.assign({}, state, { ${funcName}: action.data }); default: return state; } } `; } /** * source for reducer unit test * @param funcName */ generateReducerTestJsSource(funcName, fileName) { return `import {${funcName}} from './${fileName}.reducer'; describe('${funcName}', () => { it('should return unchanged state if action does not apply', done => { let action = { type: 'ANOTHER_ACTION' }; let oldState = {}; let newState = ${funcName}(oldState, action, {}); expect(newState).toBe(oldState); done(); }); it('should return some stuff', done => { let action = { type: 'CHANGE_ME_TO_PROPER_ACTION_NAME_SUCCESS', data: { stuff: 'some stuff' } }; let newState = ${funcName}({}, action, {}); expect(newState.${funcName}.stuff).toBe('some stuff'); done(); }); }); `; } /** * source for reducer * @param funcName, fileName */ generateAppStateReducerJsSource(funcName, fileName) { return `/** * App State Reducer for ${funcName} * Takes application state data and creates model for components */ import {LogManager} from 'aurelia-framework'; const logger = LogManager.getLogger('${fileName}'); export function ${funcName}(state, action) { switch (action.type) { case 'SOME_ACTION': logger.debug('Reducing: SOME_ACTION'); return Object.assign({}, state, { ${funcName}: action.data }); default: return state; } } `; } /** * source for reducer unit test * @param funcName */ generateAppStateReducerTestJsSource(funcName, fileName) { return `import {${funcName}} from './${fileName}.reducer'; describe('${funcName}', () => { it('should return unchanged state if action does not apply', done => { let action = { type: 'ANOTHER_ACTION' }; let oldState = {}; let newState = ${funcName}(oldState, action, {}); expect(newState).toBe(oldState); done(); }); it('should return some stuff', done => { let action = { type: 'SOME_ACTION', data: { stuff: 'some stuff' } }; let newState = ${funcName}({}, action, {}); expect(newState.${funcName}.stuff).toBe('some stuff'); done(); }); }); `; } /** * generate markdown stub * @param fileName * @returns {string} */ generateComponentMdSource(fileName) { return `# ${ fileName } ## Usage ${'```'}html <${ fileName} fund-id="817" cid="uniqueId"> ${'```'} *The cid is guaranteed to be unique to this page even if multiple instances of the component are added to the same page.* ## Developer notes `; } /** * generate sass partial stub * @param fileName * @returns {string} */ generateComponentSassSource(fileName, name) { return ` // CSS specific to the ${ fileName } component goes here [data-fti-component="${ name }"] { } `; } }