mirror of
https://gitlab.silvrtree.co.uk/martind2000/ft.git
synced 2025-01-11 08:45:09 +00:00
356 lines
10 KiB
JavaScript
356 lines
10 KiB
JavaScript
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 ReduxComponentGenerator {
|
|
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 redux component? (in hyphen format. ft- will be prefixed automatically)')
|
|
.then(name => {
|
|
let area = 'products';
|
|
this.ui.ensureAnswer(this.options.args[1], 'Which sub area? (blank sub area will add it at the area level)', 'portfolio')
|
|
.then(subarea => {
|
|
this.ui.ensureAnswer(this.options.args[2], 'Do you want a site specific data reducer? (Y/N)', 'Y')
|
|
.then(siteSpecificReducer => {
|
|
this.ui.ensureAnswer(this.options.args[3], 'Do you want an app state reducer? (Y/N)', 'N')
|
|
.then(appStateReducer => {
|
|
let fileName = this.project.makeFileName(name);
|
|
let className = this.project.makeClassName(name);
|
|
let funcName = this.project.makeFunctionName(name);
|
|
let makeSiteSpecificReducer = (siteSpecificReducer.toLowerCase() === 'y');
|
|
let makeAppStateReducer = (appStateReducer.toLowerCase() === 'y');
|
|
|
|
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, funcName)),
|
|
ProjectItem.text(`ft-${fileName}.html`, this.generateComponentHtmlSource(fileName)),
|
|
ProjectItem.text(`ft-${fileName}.md`, this.generateComponentMdSource('ft-' + fileName))
|
|
// ProjectItem.text(`_ft-${fileName}.scss`, this.generateComponentSassSource('ft-' + fileName, name))
|
|
);
|
|
|
|
if (makeSiteSpecificReducer) {
|
|
let reducerDir = dir + '/lib/en-us-retail';
|
|
mkdirp.sync(reducerDir);// quick fix incase parent folder doesn't exist.
|
|
this.project.locations.push(this.project[reducerDir] = ProjectItem.directory(reducerDir));
|
|
this.project[reducerDir].add(
|
|
ProjectItem.text(`${fileName}.reducer.js`, this.generateReducerJsSource(funcName, fileName)),
|
|
ProjectItem.text(`${fileName}.spec.js`, this.generateReducerTestJsSource(funcName, fileName))
|
|
);
|
|
}
|
|
|
|
if (makeAppStateReducer) {
|
|
let reducerDirAppState = dir + '/lib';
|
|
mkdirp.sync(reducerDirAppState);// quick fix incase parent folder doesn't exist.
|
|
this.project.locations.push(this.project[reducerDirAppState] = ProjectItem.directory(reducerDirAppState));
|
|
this.project[reducerDirAppState].add(
|
|
ProjectItem.text(`${fileName}.reducer.js`, this.generateAppStateReducerJsSource(funcName, fileName)),
|
|
ProjectItem.text(`${fileName}.spec.js`, this.generateAppStateReducerTestJsSource(funcName, 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 {inject} from 'aurelia-framework';
|
|
import {ReduxComponentBase} from '../../../../lib/redux-component-base';
|
|
//import {getFundState} from '../../lib/map-state-utils';
|
|
|
|
@inject('Store', 'Beans', Element)
|
|
export class Ft${className} extends ReduxComponentBase {
|
|
populated = false;
|
|
|
|
constructor(store, beans, element) {
|
|
// add logging and integration with the data store
|
|
super(store);
|
|
this.fundId = element.getAttribute('fund-id');
|
|
|
|
// This is a top level component so we need to initiate the population
|
|
this.dispatch({
|
|
type: 'POPULATE_CHANGE_ME_STATE',
|
|
// beans: beans..., // beans set in configuration for site e.g. configuration/en-us-retail/beans.js
|
|
fundId: this.fundId
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Called when the state changes.
|
|
*/
|
|
mapState(newState) {
|
|
try {
|
|
this.logger.debug('mapState()');
|
|
//let fundState = getFundState(newState, this.fundId);
|
|
// set component properties from state as appropriate
|
|
//this.label = newState.products.distributions.label;
|
|
//this.proximity = fundState.distributions.caveats.proximity;
|
|
|
|
// if we get to here without erroring, data has been populated successfully
|
|
this.populated = true;
|
|
} catch (e) {
|
|
// state not populated yet
|
|
this.populated = false;
|
|
}
|
|
}
|
|
}
|
|
`;
|
|
}
|
|
|
|
/**
|
|
* source for top level component js unit test
|
|
* @param className
|
|
* @param fileName
|
|
* @returns {string}
|
|
*/
|
|
generateComponentTestJsSource(className, fileName, relDir, funcName) {
|
|
return `import {StageComponent} from 'aurelia-testing';
|
|
import {bootstrap} from 'aurelia-bootstrapper';
|
|
import {wait} from '../../../../lib/test-utils.js';
|
|
|
|
describe('${className}', () => {
|
|
let delay = 10;
|
|
let component;
|
|
let mockStore = {
|
|
dispatch: function(action) {
|
|
expect(action.type).toBeDefined();
|
|
},
|
|
subscribe: function(callback) {
|
|
}
|
|
};
|
|
let mockBeans = { // see configuration/en-us-retail/beans.js for example
|
|
component: {
|
|
${funcName}: []
|
|
},
|
|
bean: {
|
|
}
|
|
};
|
|
let mockAttributes;
|
|
let mockState;
|
|
|
|
beforeEach(() => {
|
|
spyOn(mockStore, 'dispatch');
|
|
mockAttributes = {};
|
|
mockState = {};
|
|
component = StageComponent
|
|
.withResources('${relDir}/ft-${fileName}')
|
|
.inView(\`<ft-${fileName} id="test1"></ft-${fileName}>\`);
|
|
component.configure = (aurelia) => {
|
|
aurelia.container.registerInstance('Store', mockStore);
|
|
aurelia.container.registerInstance('Beans', mockBeans);
|
|
aurelia.use.standardConfiguration();
|
|
};
|
|
});
|
|
|
|
it('should render something', done => {
|
|
component.boundTo(mockAttributes);
|
|
component.create(bootstrap)
|
|
.then(() => {
|
|
if (component.viewModel.mapState) {
|
|
component.viewModel.mapState(mockState);
|
|
}
|
|
})
|
|
.then(wait(delay))
|
|
.then(() => {
|
|
const testElement = document.getElementById('test1');
|
|
expect(testElement.innerText.trim()).toBe('ft-${fileName}: replace me');
|
|
})
|
|
.then(done);
|
|
});
|
|
|
|
afterEach(() => {
|
|
component.dispose();
|
|
});
|
|
});
|
|
`;
|
|
}
|
|
|
|
/**
|
|
* source for top level component html
|
|
* @returns {string}
|
|
*/
|
|
generateComponentHtmlSource(fileName) {
|
|
return `<template>
|
|
<div>ft-${fileName}: replace me</div>
|
|
</template>
|
|
`;
|
|
}
|
|
|
|
/**
|
|
* 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"></${ fileName}>
|
|
${'```'}
|
|
*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 }"] {
|
|
|
|
// }
|
|
// `;
|
|
// }
|
|
}
|