From 3cf4d739e316c34797638343f6c562e67a58b814 Mon Sep 17 00:00:00 2001 From: Justin Niessner Date: Wed, 25 Mar 2015 22:42:02 -0400 Subject: [PATCH 1/4] Add flux support including generators --- action/index.js | 17 +++++++++++++++++ app/index.js | 16 ++++++++++++++++ component/index.js | 4 +--- main/index.js | 6 ++++++ script-base.js | 16 ++++++++++++---- store/index.js | 18 ++++++++++++++++++ templates/common/_package.json | 5 ++++- templates/javascript/Action.js | 8 ++++++++ templates/javascript/Dispatcher.js | 3 +++ templates/javascript/Store.js | 20 ++++++++++++++++++++ 10 files changed, 105 insertions(+), 8 deletions(-) create mode 100644 action/index.js create mode 100644 store/index.js create mode 100644 templates/javascript/Action.js create mode 100644 templates/javascript/Dispatcher.js create mode 100644 templates/javascript/Store.js diff --git a/action/index.js b/action/index.js new file mode 100644 index 0000000..4801117 --- /dev/null +++ b/action/index.js @@ -0,0 +1,17 @@ +'use strict'; +var util = require('util'); +var ScriptBase = require('../script-base.js'); + +var ActionGenerator = module.exports = function ActionGenerator(args, options, config) { + ScriptBase.apply(this, arguments); +} + +util.inherits(ActionGenerator, ScriptBase); + +ActionGenerator.prototype.createActionFile = function createActionFile() { + this.option('es6'); + + this.es6 = this.options.es6; + + this.generateAction(); +} diff --git a/app/index.js b/app/index.js index 196a2b3..5a60587 100644 --- a/app/index.js +++ b/app/index.js @@ -13,6 +13,7 @@ var ReactWebpackGenerator = module.exports = function ReactWebpackGenerator(args this.appname = this._.camelize(this._.slugify(this._.humanize(this.appname))); this.scriptAppName = this._.capitalize(this.appname) + generalUtils.appName(this); + this.config.set('app-name', this.appname); args = ['main']; @@ -65,6 +66,20 @@ ReactWebpackGenerator.prototype.askForReactRouter = function () { }.bind(this)); }; +ReactWebpackGenerator.prototype.askForFlux = function() { + var done = this.async(); + this.prompt({ + type : 'confirm', + name : 'flux', + message : 'Would you like to include flux?', + default : false + }, function(props) { + this.env.options.flux = props.flux; + this.config.set('flux', props.flux); + done(); + }.bind(this)); +}; + ReactWebpackGenerator.prototype.askForStylesLanguage = function () { var done = this.async(); this.prompt({ @@ -98,6 +113,7 @@ ReactWebpackGenerator.prototype.createIndexHtml = function createIndexHtml() { ReactWebpackGenerator.prototype.packageFiles = function () { this.es6 = this.options.es6; this.reactRouter = this.env.options.reactRouter; + this.flux = this.env.options.flux; this.stylesLanguage = this.env.options.stylesLanguage; this.template('../../templates/common/_package.json', 'package.json'); this.template('../../templates/common/_webpack.config.js', 'webpack.config.js'); diff --git a/component/index.js b/component/index.js index 1d50772..7e13d34 100644 --- a/component/index.js +++ b/component/index.js @@ -10,11 +10,9 @@ var ComponentGenerator = module.exports = function ComponentGenerator(args, opti util.inherits(ComponentGenerator, ScriptBase); ComponentGenerator.prototype.createComponentFile = function createComponentFile() { - this.option('es6'); - + this.option('es6'); this.es6 = this.options.es6; - console.log('es6:', this.es6) this.generateSourceAndTest( 'Component', diff --git a/main/index.js b/main/index.js index f58b8b4..4fe14de 100644 --- a/main/index.js +++ b/main/index.js @@ -20,3 +20,9 @@ MainGenerator.prototype.createMainFile = function createMainFile() { this.appTemplate('main', 'components/main'); } }; + +MainGenerator.prototype.createDispatcher = function createDispatcher() { + if(this.env.options.flux) { + this.appTemplate('Dispatcher', 'dispatcher/' + this.scriptAppName + 'Dispatcher'); + } +}; diff --git a/script-base.js b/script-base.js index b998fb5..c80eab1 100644 --- a/script-base.js +++ b/script-base.js @@ -8,8 +8,8 @@ var Generator = module.exports = function Generator() { yeoman.generators.NamedBase.apply(this, arguments); // Add capitalize mixin - this._.mixin({ 'capitalize': generalUtils.capitalize }); - this._.mixin({ 'capitalizeFile': generalUtils.capitalizeFile }); + this._.mixin({ 'capitalize': generalUtils.capitalize }); + this._.mixin({ 'capitalizeFile': generalUtils.capitalizeFile }); this._.mixin({ 'capitalizeClass': generalUtils.capitalizeClass }); this._.mixin({ 'lowercase': generalUtils.lowercase }); @@ -18,8 +18,8 @@ var Generator = module.exports = function Generator() { this.appname = this._.slugify(this._.humanize(this.appname)); this.scriptAppName = this._.camelize(this._.capitalize(this.appname)) + generalUtils.appName(this); this.classedFileName = this._.capitalizeFile(this.name); - this.classedName = this._.capitalizeClass(this.name); - this.stylesLanguage = this.config.get('styles-language'); + this.classedName = this._.capitalizeClass(this.name); + this.stylesLanguage = this.config.get('styles-language'); if (typeof this.options.appPath === 'undefined') { this.options.appPath = this.options.appPath || 'src/scripts'; @@ -93,3 +93,11 @@ Generator.prototype.generateSourceAndTest = function (appTemplate, testTemplate, this.testTemplate(testTemplate, path.join(targetDirectory, this._.capitalizeFile(this.name))); this.stylesTemplate(stylesTemplate, path.join(this._.capitalizeFile(this.name))); }; + +Generator.prototype.generateAction = function() { + this.appTemplate('Action', path.join('actions', this._.capitalizeFile(this.name) + 'ActionCreators')); +} + +Generator.prototype.generateStore = function() { + this.appTemplate('Store', path.join('stores', this._.capitalizeFile(this.name) + 'Store')); +} diff --git a/store/index.js b/store/index.js new file mode 100644 index 0000000..21b00d5 --- /dev/null +++ b/store/index.js @@ -0,0 +1,18 @@ +'use strict'; +var util = require('util'); +var ScriptBase = require('../script-base.js'); + +var ActionGenerator = module.exports = function ActionGenerator(args, options, config) { + ScriptBase.apply(this, arguments); +} + +util.inherits(ActionGenerator, ScriptBase); + +ActionGenerator.prototype.createActionFile = function createActionFile() { + this.option('es6'); + + this.es6 = this.options.es6; + this.dispatcherName = this._.capitalizeFile(this.config.get('app-name')) + 'AppDispatcher'; + + this.generateStore(); +} diff --git a/templates/common/_package.json b/templates/common/_package.json index 4709ba7..88708f8 100644 --- a/templates/common/_package.json +++ b/templates/common/_package.json @@ -10,7 +10,10 @@ "mainInput": "<% if (reactRouter) { %>main<% } else { %><%= scriptAppName %><% } %>", "mainOutput": "main", "dependencies": {<% if (reactRouter) { %> - "react-router": "^0.11.6",<% } %> + "react-router": "^0.11.6",<% } if (flux) { %> + "flux": "^2.0.1", + "events": "^1.0.2", + "object-assign": "^2.0.0", <% } %> "react": "~0.12.2" }, "devDependencies": { diff --git a/templates/javascript/Action.js b/templates/javascript/Action.js new file mode 100644 index 0000000..5196153 --- /dev/null +++ b/templates/javascript/Action.js @@ -0,0 +1,8 @@ +'use strict'; + +var <% classedName + 'ActionCreators' %> = { + +} + +<% if (es6) { %> export default <%= classedName + 'ActionCreators' %>; <% } +else { %>module.exports = <%= classedName + 'ActionCreators' %>; <% } %> diff --git a/templates/javascript/Dispatcher.js b/templates/javascript/Dispatcher.js new file mode 100644 index 0000000..5231a4e --- /dev/null +++ b/templates/javascript/Dispatcher.js @@ -0,0 +1,3 @@ +var Dispatcher = require('flux').Dispatcher; + +module.exports = new Dispatcher(); diff --git a/templates/javascript/Store.js b/templates/javascript/Store.js new file mode 100644 index 0000000..9f59eaf --- /dev/null +++ b/templates/javascript/Store.js @@ -0,0 +1,20 @@ +'use strict'; + +var EventEmitter = require('events').EventEmitter; +var assign = require('object-assign'); +var <%= dispatcherName %> = require('../dispatcher/<%= dispatcherName %>'); + +var <%= classedName + 'Store' %> = assign({}, EventEmitter.prototype, { + +}); + +<%= classedName + 'Store' %>.dispatchToken = <%= dispatcherName %>.register(function(action) { + + switch(action.type) { + default: + } + +}); + +<% if (es6) { %> export default <%= classedName + 'Store' %>; <% } +else { %>module.exports = <%= classedName + 'Store' %>; <% } %> From 53b262ae3c06265e079a15e46cb148951260ad45 Mon Sep 17 00:00:00 2001 From: Justin Niessner Date: Thu, 26 Mar 2015 23:09:45 -0400 Subject: [PATCH 2/4] Switch the generation of everything so we generate tests and they can actually pass. --- action/index.js | 11 ++++++++++- component/index.js | 4 ++-- script-base.js | 12 ++---------- store/index.js | 9 ++++++++- templates/common/_webpack.config.js | 4 +++- templates/common/_webpack.dist.config.js | 4 +++- templates/common/karma.conf.js | 12 +++++++++--- templates/javascript/Action.js | 6 +++--- templates/javascript/Store.js | 8 ++++---- templates/spec/Action.js | 13 +++++++++++++ templates/spec/Store.js | 13 +++++++++++++ 11 files changed, 70 insertions(+), 26 deletions(-) create mode 100644 templates/spec/Action.js create mode 100644 templates/spec/Store.js diff --git a/action/index.js b/action/index.js index 4801117..8fadfdc 100644 --- a/action/index.js +++ b/action/index.js @@ -3,6 +3,7 @@ var util = require('util'); var ScriptBase = require('../script-base.js'); var ActionGenerator = module.exports = function ActionGenerator(args, options, config) { + args[0] += 'ActionCreators'; ScriptBase.apply(this, arguments); } @@ -13,5 +14,13 @@ ActionGenerator.prototype.createActionFile = function createActionFile() { this.es6 = this.options.es6; - this.generateAction(); + console.log(this.name); + + this.generateSourceAndTest( + 'Action', + 'spec/Action', + void(0), + 'actions', + false + ); } diff --git a/component/index.js b/component/index.js index 7e13d34..59cdc95 100644 --- a/component/index.js +++ b/component/index.js @@ -3,7 +3,6 @@ var util = require('util'); var ScriptBase = require('../script-base.js'); var ComponentGenerator = module.exports = function ComponentGenerator(args, options, config) { - ScriptBase.apply(this, arguments); }; @@ -18,6 +17,7 @@ ComponentGenerator.prototype.createComponentFile = function createComponentFile( 'Component', 'spec/Component', 'styles/Component', - 'components' + 'components', + true ); }; diff --git a/script-base.js b/script-base.js index c80eab1..383f03a 100644 --- a/script-base.js +++ b/script-base.js @@ -88,16 +88,8 @@ Generator.prototype.htmlTemplate = function (src, dest) { ]); }; -Generator.prototype.generateSourceAndTest = function (appTemplate, testTemplate, stylesTemplate, targetDirectory) { +Generator.prototype.generateSourceAndTest = function (appTemplate, testTemplate, stylesTemplate, targetDirectory, includeStyles) { this.appTemplate(appTemplate, path.join(targetDirectory, this._.capitalizeFile(this.name))); this.testTemplate(testTemplate, path.join(targetDirectory, this._.capitalizeFile(this.name))); - this.stylesTemplate(stylesTemplate, path.join(this._.capitalizeFile(this.name))); + if(includeStyles) this.stylesTemplate(stylesTemplate, path.join(this._.capitalizeFile(this.name))); }; - -Generator.prototype.generateAction = function() { - this.appTemplate('Action', path.join('actions', this._.capitalizeFile(this.name) + 'ActionCreators')); -} - -Generator.prototype.generateStore = function() { - this.appTemplate('Store', path.join('stores', this._.capitalizeFile(this.name) + 'Store')); -} diff --git a/store/index.js b/store/index.js index 21b00d5..324b32b 100644 --- a/store/index.js +++ b/store/index.js @@ -3,6 +3,7 @@ var util = require('util'); var ScriptBase = require('../script-base.js'); var ActionGenerator = module.exports = function ActionGenerator(args, options, config) { + args[0] += 'Store'; ScriptBase.apply(this, arguments); } @@ -14,5 +15,11 @@ ActionGenerator.prototype.createActionFile = function createActionFile() { this.es6 = this.options.es6; this.dispatcherName = this._.capitalizeFile(this.config.get('app-name')) + 'AppDispatcher'; - this.generateStore(); + this.generateSourceAndTest( + 'Store', + 'spec/Store', + void(0), + 'stores', + false + ); } diff --git a/templates/common/_webpack.config.js b/templates/common/_webpack.config.js index a008b76..fb0734c 100644 --- a/templates/common/_webpack.config.js +++ b/templates/common/_webpack.config.js @@ -31,7 +31,9 @@ module.exports = { extensions: ['', '.js'], alias: { 'styles': '../../../src/styles', - 'components': '../../../src/scripts/components/' + 'components': '../../../src/scripts/components/'<% if(flux) { %>, + 'stores': '../../../src/scripts/stores/', + 'actions': '../../../src/scripts/actions/'<% } %> } }, module: { diff --git a/templates/common/_webpack.dist.config.js b/templates/common/_webpack.dist.config.js index 35d2255..bcf602c 100644 --- a/templates/common/_webpack.dist.config.js +++ b/templates/common/_webpack.dist.config.js @@ -36,7 +36,9 @@ module.exports = { extensions: ['', '.js'], alias: { 'styles': '../../../src/styles', - 'components': '../../../src/scripts/components/' + 'components': '../../../src/scripts/components/'<% if(flux) { %>, + 'stores': '../../../src/scripts/stores/', + 'actions': '../../../src/scripts/actions/'<% } %> } }, diff --git a/templates/common/karma.conf.js b/templates/common/karma.conf.js index d1d9054..b44935c 100644 --- a/templates/common/karma.conf.js +++ b/templates/common/karma.conf.js @@ -8,10 +8,14 @@ module.exports = function (config) { frameworks: ['jasmine'], files: [ 'test/helpers/**/*.js', - 'test/spec/components/**/*.js' + 'test/spec/components/**/*.js'<% if(flux) { %>, + 'test/spec/stores/**/*.js', + 'test/spec/actions/**/*.js'<% } %> ], preprocessors: { - 'test/spec/components/**/*.js': ['webpack'] + 'test/spec/components/**/*.js': ['webpack']<% if(flux) { %>, + 'test/spec/stores/**/*.js': ['webpack'], + 'test/spec/actions/**/*.js': ['webpack']<% } %> }, webpack: { cache: true, @@ -48,7 +52,9 @@ module.exports = function (config) { resolve: { alias: { 'styles': path.join(process.cwd(), './src/styles/'), - 'components': path.join(process.cwd(), './src/scripts/components/') + 'components': path.join(process.cwd(), './src/scripts/components/')<% if(flux) { %>, + 'stores': '../../../src/scripts/stores/', + 'actions': '../../../src/scripts/actions/'<% } %> } } }, diff --git a/templates/javascript/Action.js b/templates/javascript/Action.js index 5196153..de0c5d3 100644 --- a/templates/javascript/Action.js +++ b/templates/javascript/Action.js @@ -1,8 +1,8 @@ 'use strict'; -var <% classedName + 'ActionCreators' %> = { +var <%= classedName %> = { } -<% if (es6) { %> export default <%= classedName + 'ActionCreators' %>; <% } -else { %>module.exports = <%= classedName + 'ActionCreators' %>; <% } %> +<% if (es6) { %> export default <%= classedName %>; <% } +else { %>module.exports = <%= classedName %>; <% } %> diff --git a/templates/javascript/Store.js b/templates/javascript/Store.js index 9f59eaf..98d0467 100644 --- a/templates/javascript/Store.js +++ b/templates/javascript/Store.js @@ -4,11 +4,11 @@ var EventEmitter = require('events').EventEmitter; var assign = require('object-assign'); var <%= dispatcherName %> = require('../dispatcher/<%= dispatcherName %>'); -var <%= classedName + 'Store' %> = assign({}, EventEmitter.prototype, { +var <%= classedName %> = assign({}, EventEmitter.prototype, { }); -<%= classedName + 'Store' %>.dispatchToken = <%= dispatcherName %>.register(function(action) { +<%= classedName %>.dispatchToken = <%= dispatcherName %>.register(function(action) { switch(action.type) { default: @@ -16,5 +16,5 @@ var <%= classedName + 'Store' %> = assign({}, EventEmitter.prototype, { }); -<% if (es6) { %> export default <%= classedName + 'Store' %>; <% } -else { %>module.exports = <%= classedName + 'Store' %>; <% } %> +<% if (es6) { %> export default <%= classedName %>; <% } +else { %>module.exports = <%= classedName %>; <% } %> diff --git a/templates/spec/Action.js b/templates/spec/Action.js new file mode 100644 index 0000000..c768f48 --- /dev/null +++ b/templates/spec/Action.js @@ -0,0 +1,13 @@ +'use strict'; + +describe('<%= classedName %>', function() { + var action; + + beforeEach(function() { + action = require('actions/<%= classedFileName %>.js'); + }); + + it('should be defined', function() { + expect(action).toBeDefined(); + }); +}); diff --git a/templates/spec/Store.js b/templates/spec/Store.js new file mode 100644 index 0000000..a945396 --- /dev/null +++ b/templates/spec/Store.js @@ -0,0 +1,13 @@ +'use strict'; + +describe('<%= classedName %>', function() { + var store; + + beforeEach(function() { + store = require('stores/<%= classedFileName %>.js'); + }); + + it('should be defined', function() { + expect(store).toBeDefined(); + }); +}); From 70c28528e3afa78b07f2c020a9c49751b42558ac Mon Sep 17 00:00:00 2001 From: Justin Niessner Date: Fri, 27 Mar 2015 09:32:11 -0400 Subject: [PATCH 3/4] Add basic file check tests for Flux option during app generation --- test/test-creation.js | 64 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/test/test-creation.js b/test/test-creation.js index 55b40a2..43d0a53 100644 --- a/test/test-creation.js +++ b/test/test-creation.js @@ -125,6 +125,22 @@ describe('react-webpack generator', function() { }); }); + it('should not have any flux assets configured', function(done) { + react.run({}, function() { + assert.noFileContent([ + ['package.json', /flux/], + ['package.json', /events/], + ['package.json', /object-assign/], + ['karma.conf.js', /resolve[\S\s]+alias[\S\s]+stores/m], + ['webpack.config.js', /resolve[\S\s]+alias[\S\s]+stores/m], + ['webpack.dist.config.js', /resolve[\S\s]+alias[\S\s]+stores/m] + ]); + + assert.noFile('src/scripts/dispatcher/TempTestAppDispatcher.js'); + + done(); + }); + }); }); describe('Generator', function () { @@ -171,6 +187,54 @@ describe('react-webpack generator', function() { }); + describe('When using Flux', function() { + + beforeEach(function(done) { + helpers.mockPrompt(react, { + flux: true + }); + + react.run({}, function() { + done(); + }) + }); + + it('should add flux, events, and object-assign packages', function(done) { + assert.fileContent([ + ['package.json', /flux/], + ['package.json', /events/], + ['package.json', /object-assign/] + ]); + + done(); + }); + + it('should add stores and actions alias to karma config', function(done) { + assert.fileContent([ + ['karma.conf.js', /resolve[\S\s]+alias[\S\s]+stores/m] + ]); + + done(); + }); + + it('should add stores and actions alias to webpack configs', function(done) { + assert.fileContent([ + ['webpack.config.js', /resolve[\S\s]+alias[\S\s]+stores/m], + ['webpack.dist.config.js', /resolve[\S\s]+alias[\S\s]+stores/m] + ]); + + done(); + }); + + it('should have a Dispatcher generated', function(done) { + setTimeout(function(){ + assert.file('src/scripts/dispatcher/TempTestAppDispatcher.js'); + + done(); + }); + }) + }); + describe('Subgenerators', function() { var generatorTest = function(name, generatorType, specType, targetDirectory, scriptNameFn, specNameFn, suffix, done) { From 267a632e1155aa41b98f160a5656f6d96877f881 Mon Sep 17 00:00:00 2001 From: Justin Niessner Date: Fri, 27 Mar 2015 14:39:41 -0400 Subject: [PATCH 4/4] Add generator tests for actions and stores --- test/test-creation.js | 71 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 70 insertions(+), 1 deletion(-) diff --git a/test/test-creation.js b/test/test-creation.js index 43d0a53..cef1039 100644 --- a/test/test-creation.js +++ b/test/test-creation.js @@ -235,7 +235,7 @@ describe('react-webpack generator', function() { }) }); - describe('Subgenerators', function() { + describe('When generating a Component', function() { var generatorTest = function(name, generatorType, specType, targetDirectory, scriptNameFn, specNameFn, suffix, done) { var deps = [path.join('../..', generatorType)]; @@ -274,4 +274,73 @@ describe('react-webpack generator', function() { }); + describe('When generating an Action', function() { + + beforeEach(function(done){ + helpers.mockPrompt(react, { + flux: true + }); + + react.run({}, function() { + var generator = + helpers.createGenerator( + 'react-webpack:action', + [path.join('../../action')], + ['Test'], + { appPath: 'src/scripts' } + ); + + react.run([], function() { + generator.run([], function() { + done(); + }) + }); + }); + }); + + it('should generate a new action with tests', function(done) { + assert.fileContent([ + ['src/scripts/actions/TestActionCreators.js', /var TestActionCreators/g], + ['test/spec/actions/TestActionCreators.js', /require\('actions\/TestActionCreators.js'\)/g], + ['test/spec/actions/TestActionCreators.js', /describe\('TestActionCreators'/g] + ]); + + done(); + }); + }); + + describe('When generating a Store', function() { + + beforeEach(function(done) { + helpers.mockPrompt(react, { + flux: true + }); + + react.run({}, function() { + var generator = + helpers.createGenerator( + 'react-webpack:store', + [path.join('../../store')], + ['Test'], + { appPath: 'src/scripts' } + ); + + react.run([], function() { + generator.run([], function() { + done(); + }); + }); + }); + }); + + it('should generate a new store with tests', function(done) { + assert.fileContent([ + ['src/scripts/stores/TestStore.js', /var TestStore/g], + ['test/spec/stores/TestStore.js', /require\('stores\/TestStore.js'\)/g], + ['test/spec/stores/TestStore.js', /describe\('TestStore'/g] + ]); + + done(); + }); + }); });