├── .gitattributes ├── src ├── gen │ ├── component │ │ ├── templates │ │ │ ├── Helper.js │ │ │ ├── Scss.js │ │ │ ├── Fixtures.js │ │ │ ├── Component_test.js │ │ │ └── Component.js │ │ └── index.js │ ├── redux │ │ ├── templates │ │ │ ├── Constants.js │ │ │ ├── Selectors.js │ │ │ ├── Actions.js │ │ │ ├── Actions_test.js │ │ │ ├── Integration_test.js │ │ │ ├── Selectors_test.js │ │ │ ├── Reducer.js │ │ │ ├── Reducer_test.js │ │ │ └── index.js │ │ └── index.js │ └── base │ │ └── index.js ├── app │ ├── packages.js │ └── index.js ├── constants.js └── helpers.js ├── __tests__ ├── __mocks__ │ ├── Component.js │ └── helper.js ├── __snapshots__ │ └── helpers.test.js.snap ├── helpers.test.js └── app.test.js ├── .eslintignore ├── .gitignore ├── .travis.yml ├── .yo-rc.json ├── .eslintrc.js ├── .editorconfig ├── .babelrc ├── LICENSE ├── package.json └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /src/gen/component/templates/Helper.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/gen/component/templates/Scss.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /__tests__/__mocks__/Component.js: -------------------------------------------------------------------------------- 1 | // mock -------------------------------------------------------------------------------- /__tests__/__mocks__/helper.js: -------------------------------------------------------------------------------- 1 | // fixtures -------------------------------------------------------------------------------- /src/gen/component/templates/Fixtures.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | coverage 2 | **/templates 3 | generators 4 | src/components 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | lib 2 | coverage 3 | node_modules 4 | src/components 5 | .yo-rc.json 6 | .DS_Store -------------------------------------------------------------------------------- /src/app/packages.js: -------------------------------------------------------------------------------- 1 | const PACKAGES = ['react-redux-test-utils']; 2 | 3 | export default PACKAGES; 4 | -------------------------------------------------------------------------------- /src/gen/redux/templates/Constants.js: -------------------------------------------------------------------------------- 1 | export const <%= name_upper %>_CHANGE_BOOL = '<%= name_upper %>_CHANGE_BOOL'; 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - v10 4 | - v8 5 | after_script: cat ./coverage/lcov.info | coveralls 6 | -------------------------------------------------------------------------------- /src/gen/redux/templates/Selectors.js: -------------------------------------------------------------------------------- 1 | export const select<%= name %> = state => state.<%= name_lower %>; 2 | export const selectBool = state => select<%= name %>(state).bool; 3 | -------------------------------------------------------------------------------- /.yo-rc.json: -------------------------------------------------------------------------------- 1 | { 2 | "generator-react-domain": { 3 | "templatesPath": "overrides", 4 | "componentsPath": "src/components", 5 | "yarn": true, 6 | "depsInstalled": true 7 | } 8 | } -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "extends": ["airbnb", "prettier"], 3 | "env": { 4 | "jest": true 5 | }, 6 | "rules": { 7 | "linebreak-style": "off" 8 | } 9 | }; -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | 10 | [*.md] 11 | trim_trailing_whitespace = false 12 | -------------------------------------------------------------------------------- /src/gen/redux/templates/Actions.js: -------------------------------------------------------------------------------- 1 | import { <%= name_upper %>_CHANGE_BOOL } from './<%= name %>Constants'; 2 | 3 | export const changeBool = resource => dispatch => { 4 | dispatch({ 5 | type: <%= name_upper %>_CHANGE_BOOL, 6 | payload: resource, 7 | }); 8 | }; 9 | -------------------------------------------------------------------------------- /src/gen/redux/index.js: -------------------------------------------------------------------------------- 1 | import BaseGenerator from '../base'; 2 | import { REDUX_TPL } from '../../constants'; 3 | 4 | class ReduxGenerator extends BaseGenerator { 5 | writing() { 6 | this.copyTemplates(REDUX_TPL, this.options.name, this.options.path); 7 | } 8 | } 9 | 10 | module.exports = ReduxGenerator; 11 | -------------------------------------------------------------------------------- /src/gen/component/index.js: -------------------------------------------------------------------------------- 1 | import BaseGenerator from '../base'; 2 | import { REACT_TPL } from '../../constants'; 3 | 4 | class ComponentGenerator extends BaseGenerator { 5 | writing() { 6 | this.copyTemplates(REACT_TPL, this.options.name, this.options.path); 7 | } 8 | } 9 | 10 | module.exports = ComponentGenerator; 11 | -------------------------------------------------------------------------------- /src/gen/redux/templates/Actions_test.js: -------------------------------------------------------------------------------- 1 | import { testActionSnapshotWithFixtures } from 'react-redux-test-utils'; 2 | import { changeBool } from '../<%= name %>Actions'; 3 | 4 | const fixtures = { 5 | 'should changeBool': () => changeBool({ bool: true }), 6 | }; 7 | 8 | describe('<%= name %> actions', () => testActionSnapshotWithFixtures(fixtures)); 9 | -------------------------------------------------------------------------------- /src/constants.js: -------------------------------------------------------------------------------- 1 | export const REDUX_TPL = [ 2 | 'Actions', 3 | 'Constants', 4 | 'Reducer', 5 | 'Selectors', 6 | 'Actions_test', 7 | 'Reducer_test', 8 | 'Selectors_test', 9 | 'Integration_test', 10 | 'index' 11 | ]; 12 | 13 | export const REACT_TPL = [ 14 | 'Component', 15 | 'Scss', 16 | 'Fixtures', 17 | 'Helper', 18 | 'Component_test' 19 | ]; 20 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "env", 5 | { 6 | "targets": { 7 | "node": true 8 | } 9 | } 10 | ] 11 | ], 12 | "plugins": [ 13 | [ 14 | "transform-runtime", 15 | { 16 | "polyfill": true, 17 | "regenerator": true 18 | } 19 | ], 20 | ["transform-object-rest-spread"] 21 | ], 22 | "ignore": ["**/templates/*"] 23 | } 24 | -------------------------------------------------------------------------------- /src/gen/component/templates/Component_test.js: -------------------------------------------------------------------------------- 1 | import { testComponentSnapshotsWithFixtures } from 'react-redux-test-utils'; 2 | 3 | import <%= name %> from '../<%= name %>'; 4 | 5 | const fixtures = { 6 | 'render without Props': {}, 7 | /** fixtures, props for the component */ 8 | }; 9 | 10 | describe('<%= name %>', () => { 11 | describe('rendering', () => 12 | testComponentSnapshotsWithFixtures(<%= name %>, fixtures)); 13 | }); 14 | -------------------------------------------------------------------------------- /src/gen/redux/templates/Integration_test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { IntegrationTestHelper } from 'react-redux-test-utils'; 3 | 4 | import <%= name %>, { reducers } from '../index'; 5 | 6 | describe('<%= name %> integration test', () => { 7 | it('should flow', async () => { 8 | const integrationTestHelper = new IntegrationTestHelper(reducers); 9 | const component = integrationTestHelper.mount(<<%= name %> />); 10 | component.update(); 11 | /** Create a Flow test */ 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /src/gen/redux/templates/Selectors_test.js: -------------------------------------------------------------------------------- 1 | import { testSelectorsSnapshotWithFixtures } from 'react-redux-test-utils'; 2 | import { select<%= name %>, selectBool } from '../<%= name %>Selectors'; 3 | 4 | const state = { 5 | <%= name_lower %>: { 6 | bool: false, 7 | }, 8 | }; 9 | 10 | const fixtures = { 11 | 'should return <%= name %>': () => select<%= name %>(state), 12 | 'should return <%= name %> bool': () => selectBool(state), 13 | }; 14 | 15 | describe('<%= name %> selectors', () => testSelectorsSnapshotWithFixtures(fixtures)); 16 | -------------------------------------------------------------------------------- /src/gen/redux/templates/Reducer.js: -------------------------------------------------------------------------------- 1 | import Immutable from 'seamless-immutable'; 2 | 3 | import { <%= name_upper %>_CHANGE_BOOL } from './<%= name %>Constants'; 4 | 5 | const initialState = Immutable({ 6 | /** insert <%= name %> state here */ 7 | bool: false, 8 | }); 9 | 10 | export default (state = initialState, action) => { 11 | const { payload } = action; 12 | 13 | switch (action.type) { 14 | case <%= name_upper %>_CHANGE_BOOL: 15 | return state.set('bool', payload.bool); 16 | 17 | default: 18 | return state; 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /src/gen/component/templates/Component.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | class <%= name %> extends Component { 5 | componentDidMount() { 6 | // Do Something here 7 | } 8 | 9 | render() { 10 | const { children, className } = this.props; 11 | return
{children}
; 12 | } 13 | } 14 | 15 | <%= name %>.propTypes = { 16 | children: PropTypes.node, 17 | className: PropTypes.string, 18 | }; 19 | 20 | <%= name %>.defaultProps = { 21 | className: '', 22 | children: null, 23 | }; 24 | 25 | export default <%= name %>; 26 | -------------------------------------------------------------------------------- /src/gen/redux/templates/Reducer_test.js: -------------------------------------------------------------------------------- 1 | import { testReducerSnapshotWithFixtures } from 'react-redux-test-utils'; 2 | 3 | import { <%= name_upper %>_CHANGE_BOOL } from '../<%= name %>Constants'; 4 | import reducer from '../<%= name %>Reducer'; 5 | 6 | const fixtures = { 7 | 'should return the initial state': {}, 8 | 'should handle <%= name_upper %>_CHANGE_BOOL': { 9 | action: { 10 | type: <%= name_upper %>_CHANGE_BOOL, 11 | payload: { 12 | bool: true, 13 | }, 14 | }, 15 | }, 16 | }; 17 | 18 | describe('<%= name %> reducer', () => 19 | testReducerSnapshotWithFixtures(reducer, fixtures)); 20 | -------------------------------------------------------------------------------- /src/gen/base/index.js: -------------------------------------------------------------------------------- 1 | import Generator from 'yeoman-generator'; 2 | import { getPath, caseNames, getTemplatePath } from '../../helpers'; 3 | 4 | class BaseGenerator extends Generator { 5 | copyTemplates(templates, name, path) { 6 | // check if consumer has templates 7 | const consumerPath = this.config.get('templatesPath'); 8 | 9 | templates.forEach(file => 10 | this.fs.copyTpl( 11 | getTemplatePath(file, consumerPath, this.sourceRoot()), 12 | this.destinationPath(getPath(path, name, file)), 13 | caseNames(name) 14 | ) 15 | ); 16 | } 17 | } 18 | 19 | export default BaseGenerator; 20 | -------------------------------------------------------------------------------- /src/gen/redux/templates/index.js: -------------------------------------------------------------------------------- 1 | import { bindActionCreators } from 'redux'; 2 | import { connect } from 'react-redux'; 3 | 4 | import * as actions from './<%= name %>Actions'; 5 | import reducer from './<%= name %>Reducer'; 6 | import { selectBool } from './<%= name %>Selectors'; 7 | 8 | import <%= name %> from './<%= name %>'; 9 | 10 | // map state to props 11 | const mapStateToProps = state => ({ 12 | /** add state keys here */ 13 | bool: selectBool(state), 14 | }); 15 | 16 | // map action dispatchers to props 17 | const mapDispatchToProps = dispatch => bindActionCreators(actions, dispatch); 18 | 19 | // export reducers 20 | export const reducers = { <%= name_lower %>: reducer }; 21 | 22 | // export connected component 23 | export default connect( 24 | mapStateToProps, 25 | mapDispatchToProps 26 | )(<%= name %>); 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Gilad Lekner 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /__tests__/__snapshots__/helpers.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`helpers caseNames 1`] = ` 4 | Object { 5 | "name": "CoMpoNent", 6 | "name_lower": "coMpoNent", 7 | "name_upper": "COMPONENT", 8 | } 9 | `; 10 | 11 | exports[`helpers getPath 1`] = ` 12 | Object { 13 | "actionsPath": "some/path/ComponentName/ComponentNameActions.js", 14 | "componentTestPath": "some/path/ComponentName/__tests__/ComponentNameComponentTest.test.js", 15 | "fixturesPath": "some/path/ComponentName/ComponentNameFixtures.js", 16 | "indexPath": "some/path/ComponentName/index.js", 17 | "scssPath": "some/path/ComponentName/ComponentNameScss.js", 18 | "testPath": "some/path/ComponentName/__tests__/ComponentNameIndex.test.js", 19 | } 20 | `; 21 | 22 | exports[`helpers initializePrompts 1`] = ` 23 | Array [ 24 | Object { 25 | "default": "src/components", 26 | "message": "Enter your Components path", 27 | "name": "path", 28 | "type": "input", 29 | "validate": [Function], 30 | }, 31 | Object { 32 | "message": "Enter your Component name", 33 | "name": "name", 34 | "type": "input", 35 | "validate": [Function], 36 | }, 37 | ] 38 | `; 39 | -------------------------------------------------------------------------------- /__tests__/helpers.test.js: -------------------------------------------------------------------------------- 1 | import { 2 | initializePrompts, 3 | getPath, 4 | caseNames, 5 | validatePrompt, 6 | getTemplatePath 7 | } from '../src/helpers'; 8 | 9 | describe('helpers', () => { 10 | it('initializePrompts', () => { 11 | const allPrompts = initializePrompts({}, {}); 12 | expect(allPrompts).toMatchSnapshot(); 13 | 14 | // should return empty prompts 15 | const emptyPrompts = initializePrompts({ 16 | path: 'some/path', 17 | name: 'name', 18 | redux: true 19 | }); 20 | expect(emptyPrompts.length).toBe(0); 21 | 22 | // test validation 23 | const shouldFalse = validatePrompt('ss'); 24 | const shouldTrue = validatePrompt('sss'); 25 | 26 | expect(shouldFalse).toBeFalsy(); 27 | expect(shouldTrue).toBeTruthy(); 28 | }); 29 | 30 | it('getPath', () => { 31 | const paths = { 32 | indexPath: getPath('some/path', 'component name', 'index'), 33 | testPath: getPath('some/path', 'component name', 'index_test'), 34 | componentTestPath: getPath( 35 | 'some/path', 36 | 'component name', 37 | 'component.test' 38 | ), 39 | scssPath: getPath('some/path', 'component name', 'scss'), 40 | fixturesPath: getPath('some/path', 'component name', 'fixtures'), 41 | actionsPath: getPath('some/path', 'component name', 'Actions') 42 | }; 43 | expect(paths).toMatchSnapshot(); 44 | }); 45 | 46 | it('caseNames', () => { 47 | const name = caseNames('coMpoNent'); 48 | expect(name).toMatchSnapshot(); 49 | }); 50 | 51 | it('getTemplatePath w/lowercased Name', () => { 52 | const lowerNamePath = getTemplatePath('Helper', '', '__tests__'); 53 | expect(lowerNamePath).toBe('__tests__/helper.js'); 54 | }); 55 | }); 56 | -------------------------------------------------------------------------------- /src/app/index.js: -------------------------------------------------------------------------------- 1 | import Generator from 'yeoman-generator'; 2 | import chalk from 'chalk'; 3 | import { initializePrompts } from '../helpers'; 4 | import PACKAGES from './packages'; 5 | 6 | class InitialGenerator extends Generator { 7 | constructor(args, opts) { 8 | super(args, opts); 9 | 10 | this.argument('path', { type: String, required: false }); 11 | this.argument('name', { type: String, required: false }); 12 | this.option('redux'); 13 | } 14 | 15 | async prompting() { 16 | this.log(`\n${chalk.bold(chalk.blue('react-domain-generator'))}\n`); 17 | 18 | const prompts = initializePrompts(this.options, this.config.getAll()); 19 | this.answers = await this.prompt(prompts); 20 | this.results = { ...this.options, ...this.answers }; 21 | } 22 | 23 | writing() { 24 | const genProps = { 25 | name: this.results.name, 26 | path: this.results.path || this.config.get('componentsPath'), 27 | redux: this.results.redux || this.config.get('redux') 28 | }; 29 | 30 | this.composeWith(require.resolve('../gen/component'), genProps); 31 | if (genProps.redux) 32 | this.composeWith(require.resolve('../gen/redux'), genProps); 33 | } 34 | 35 | install() { 36 | if (!this.config.get('depsInstalled')) { 37 | this.log( 38 | `\n${chalk.bold(chalk.blueBright('Installing required packages...'))}\n` 39 | ); 40 | if (this.config.get('yarn')) { 41 | this.yarnInstall(PACKAGES, { dev: true }); 42 | } else { 43 | this.npmInstall(PACKAGES, { saveDev: true }); 44 | } 45 | this.config.set('depsInstalled', true); 46 | } 47 | } 48 | 49 | end() { 50 | this.log(`\n${chalk.bold(chalk.greenBright('done!'))}\n`); 51 | } 52 | } 53 | 54 | module.exports = InitialGenerator; 55 | -------------------------------------------------------------------------------- /src/helpers.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import toPascalCase from 'to-pascal-case'; 3 | import { camelCase } from 'lodash'; 4 | 5 | export const validatePrompt = value => value && value.length >= 3; 6 | 7 | export const initializePrompts = (args, config) => { 8 | const prompts = []; 9 | 10 | if (!args.path && !config.componentsPath) { 11 | prompts.push({ 12 | type: 'input', 13 | name: 'path', 14 | message: 'Enter your Components path', 15 | default: 'src/components', 16 | validate: validatePrompt 17 | }); 18 | } 19 | if (!args.name) { 20 | prompts.push({ 21 | type: 'input', 22 | name: 'name', 23 | message: 'Enter your Component name', 24 | validate: validatePrompt 25 | }); 26 | } 27 | return prompts; 28 | }; 29 | 30 | export const getPath = (path, name, type) => { 31 | const pascalName = toPascalCase(name); 32 | if (type.includes('test')) { 33 | if (type.includes('Component')) 34 | return `${path}/${pascalName}/__tests__/${pascalName}.test.js`; 35 | 36 | return `${path}/${pascalName}/__tests__/${toPascalCase(name) + 37 | toPascalCase(type.split('_')[0])}.test.js`; 38 | } 39 | 40 | switch (type) { 41 | case 'index': 42 | return `${path}/${pascalName}/index.js`; 43 | 44 | case 'Component': 45 | return `${path}/${pascalName}/${pascalName}.js`; 46 | 47 | case 'Fixtures': 48 | return `${path}/${pascalName}/${pascalName}.fixtures.js`; 49 | 50 | case 'Scss': 51 | return `${path}/${pascalName}/${camelCase(name)}.scss`; 52 | 53 | default: 54 | return `${path}/${pascalName}/${toPascalCase(name) + 55 | toPascalCase(type)}.js`; 56 | } 57 | }; 58 | 59 | export const caseNames = name => ({ 60 | name: toPascalCase(name), 61 | name_lower: camelCase(name), 62 | name_upper: name.replace(/\s+/g, '').toUpperCase() 63 | }); 64 | 65 | export const getTemplatePath = (file, consumerPath, sourceRoot) => { 66 | const lowerCasedName = file.toLowerCase(); 67 | 68 | if (consumerPath) { 69 | if (fs.existsSync(`${consumerPath}/${lowerCasedName}.js`)) 70 | return `${consumerPath}/${lowerCasedName}.js`; 71 | 72 | if (fs.existsSync(`${consumerPath}/${file}.js`)) 73 | return `${consumerPath}/${file}.js`; 74 | } 75 | 76 | if (fs.existsSync(`${sourceRoot}/${file}.js`)) 77 | return `${sourceRoot}/${file}.js`; 78 | 79 | return `${sourceRoot}/${lowerCasedName}.js`; 80 | }; 81 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "generator-react-domain", 3 | "version": "0.1.18", 4 | "description": "Generate React Components with Domain-Driven File Structuring", 5 | "homepage": "https://github.com/glekner/generator-react-domain#readme", 6 | "author": "Gilad Lekner", 7 | "files": [ 8 | "lib/generators" 9 | ], 10 | "main": "index.js", 11 | "keywords": [ 12 | "react", 13 | "generator", 14 | "domain", 15 | "component", 16 | "yeoman-generator", 17 | "yeoman-generator" 18 | ], 19 | "devDependencies": { 20 | "babel-cli": "^6.26.0", 21 | "babel-plugin-transform-object-rest-spread": "^6.26.0", 22 | "babel-plugin-transform-runtime": "^6.23.0", 23 | "babel-preset-env": "^1.7.0", 24 | "coveralls": "^3.0.2", 25 | "eslint": "^5.10.0", 26 | "eslint-config-airbnb": "^17.1.0", 27 | "eslint-config-prettier": "^3.3.0", 28 | "eslint-plugin-import": "^2.14.0", 29 | "eslint-plugin-jsx-a11y": "^6.1.2", 30 | "eslint-plugin-react": "^7.11.1", 31 | "husky": "^1.2.0", 32 | "jest": "^23.6.0", 33 | "lint-staged": "^13.1.0", 34 | "prettier": "^1.15.3", 35 | "yeoman-assert": "^3.1.0", 36 | "yeoman-test": "^1.7.0" 37 | }, 38 | "engines": { 39 | "npm": ">= 4.0.0" 40 | }, 41 | "scripts": { 42 | "build": "babel src -d lib/generators --copy-files", 43 | "test": "jest", 44 | "pretest": "eslint .", 45 | "start": "npm run build && npm link && yo react-domain" 46 | }, 47 | "repository": { 48 | "type": "git", 49 | "url": "git+https://github.com/glekner/generator-react-domain.git" 50 | }, 51 | "license": "MIT", 52 | "bugs": { 53 | "url": "https://github.com/glekner/generator-react-domain/issues" 54 | }, 55 | "dependencies": { 56 | "babel-runtime": "^6.26.0", 57 | "chalk": "^2.4.1", 58 | "lodash": "^4.17.11", 59 | "to-pascal-case": "^1.0.0", 60 | "yeoman-generator": "^2.0.1" 61 | }, 62 | "jest": { 63 | "testEnvironment": "node", 64 | "testMatch": [ 65 | "**/__tests__/*.test.js" 66 | ], 67 | "collectCoverage": true, 68 | "collectCoverageFrom": [ 69 | "src/**/*.js", 70 | "!src/**/templates/**/*" 71 | ], 72 | "coverageReporters": [ 73 | "lcov" 74 | ] 75 | }, 76 | "lint-staged": { 77 | "*.js": [ 78 | "eslint --fix", 79 | "git add" 80 | ], 81 | "*.json": [ 82 | "prettier --write", 83 | "git add" 84 | ] 85 | }, 86 | "eslintConfig": { 87 | "extends": [ 88 | "xo", 89 | "prettier" 90 | ], 91 | "env": { 92 | "jest": true, 93 | "node": true 94 | }, 95 | "rules": { 96 | "prettier/prettier": "error" 97 | }, 98 | "plugins": [ 99 | "prettier" 100 | ] 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # generator-react-domain 2 | [![Coverage Status](https://coveralls.io/repos/github/glekner/generator-react-domain/badge.svg?branch=master)](https://coveralls.io/github/glekner/generator-react-domain?branch=master) 3 | [![Build Status](https://travis-ci.org/glekner/generator-react-domain.svg?branch=master)](https://travis-ci.org/glekner/generator-react-domain) 4 | 5 | This Generator helps you create connected React components with the Domain file structure :snowflake: 6 | 7 | ```sh 8 | $ tree 9 | . 10 | ├── Component.fixtures.js 11 | ├── Component.js 12 | ├── ComponentActions.js 13 | ├── ComponentConstants.js 14 | ├── ComponentHelper.js 15 | ├── ComponentReducer.js 16 | ├── ComponentSelectors.js 17 | ├── __tests__ 18 | │ ├── Component.test.js 19 | │ ├── ComponentActions.test.js 20 | │ ├── ComponentIntegration.test.js 21 | │ ├── ComponentReducer.test.js 22 | │ └── ComponentSelectors.test.js 23 | ├── component.scss 24 | └── index.js 25 | ``` 26 | 27 | ## Usage 28 | 29 | ```sh 30 | # install 31 | npm install --global yo generator-react-domain 32 | 33 | # run it # destination # name 34 | yo react-domain src/components ComponentName 35 | ``` 36 | 37 | ## Options 38 | 39 | - `--redux` - Create Redux files. 40 | 41 | ## Tests 42 | This generator is using an external package called [react-redux-test-utils](https://github.com/sharvit/react-redux-test-utils) to create light and readable test templates for your components. The package uses `enzyme` at its core. 43 | 44 | ## Config 45 | 46 | create a `.yo-rc.json` file in your project's root folder and fill it: 47 | 48 | ```sh 49 | { 50 | "generator-react-domain": { 51 | "templatesPath": "path to your templates folder", 52 | "componentsPath": "path to your components folder", 53 | "redux": true # create redux files, 54 | "yarn": true # use yarn instead of npm 55 | } 56 | } 57 | ``` 58 | 59 | ## Replacing Templates 60 | 61 | *To learn how to create Templates, refer to [ejs.co](https://ejs.co/)* 62 | 63 | 64 | 1) fill your `.yo-rc.json` file in your project's root folder with `templatesPath` as seen above. 65 | 66 | 2. Put supported files in your templates folder, make sure to be case-sensitive. 67 | 68 | | File | Description | Has Template 69 | | ------------- | ------------- | ------------- | 70 | | Component.js | Component | :white_check_mark: 71 | | Component_test.js | Component Test | :white_check_mark: 72 | | Actions.js | Redux Actions | :white_check_mark: 73 | | Actions_test.js | Actions Test | :white_check_mark: 74 | | Reducer.js | Redux Reducer | :white_check_mark: 75 | | Reducer_test.js | Reducer Test | :white_check_mark: 76 | | Selectors.js | Redux Selectors | :white_check_mark: 77 | | Selectors_test.js | Selectors Test | :white_check_mark: 78 | | index.js | Index file | :white_check_mark: 79 | | Integration_test.js | Redux Flow Test | :white_check_mark: 80 | | Constants.js | Constants | :white_check_mark: 81 | | Helper.js | Helper methods | 82 | | Scss.js | SCSS File | 83 | | Fixtures.js | Fixtures/Mocks | 84 | 85 | 86 | All Templates receive the following props 87 | 88 | 89 | ```sh 90 | <%= name %> # Pascal case name 91 | <%= name_upper %> # Uppercased name 92 | <%= name_lower %> # Camel case name 93 | ``` 94 | ## License 95 | 96 | [MIT](https://github.com/glekner/generator-react-domain/blob/master/LICENSE) 97 | -------------------------------------------------------------------------------- /__tests__/app.test.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import path from 'path'; 3 | import assert from 'yeoman-assert'; 4 | import helpers from 'yeoman-test'; 5 | 6 | describe('generator-react-domain:app', () => { 7 | const generatorPath = path.join(__dirname, '../src/app'); 8 | 9 | it('react-domain flow', async () => { 10 | await helpers 11 | .run(generatorPath) 12 | .inTmpDir(dir => { 13 | fs.copyFileSync( 14 | path.join(__dirname, '__mocks__/Component.js'), 15 | path.join(dir, 'Component.js') 16 | ); 17 | }) 18 | .withPrompts({ name: 'component', redux: false }) 19 | .withLocalConfig({ componentsPath: 'src/components', "test-utils-installed": true }) 20 | .then(dir => { 21 | assert.file(`${dir}/Component.js`); 22 | }); 23 | }); 24 | 25 | it('react-domain flow w/redux and yarn install', async () => { 26 | await helpers 27 | .run(generatorPath) 28 | .inTmpDir(dir => { 29 | fs.copyFileSync( 30 | path.join(__dirname, '../src/gen/component/templates/Component.js'), 31 | path.join(dir, 'Component.js') 32 | ); 33 | }) 34 | .withPrompts({ name: 'component' }) 35 | .withLocalConfig({ componentsPath: 'src/components', redux: true, yarn: true }) 36 | .then(dir => { 37 | assert.file(`${dir}/Component.js`); 38 | }); 39 | }); 40 | 41 | it('react-domain flow w/redux and no package installations', async () => { 42 | await helpers 43 | .run(generatorPath) 44 | .inTmpDir(dir => { 45 | fs.copyFileSync( 46 | path.join(__dirname, '../src/gen/component/templates/Component.js'), 47 | path.join(dir, 'Component.js') 48 | ); 49 | }) 50 | .withPrompts({ name: 'component' }) 51 | .withLocalConfig({ componentsPath: 'src/components', redux: true, depsInstalled: true }) 52 | .then(dir => { 53 | assert.file(`${dir}/Component.js`); 54 | }); 55 | }); 56 | 57 | it('base generator flow override template', async () => { 58 | await helpers 59 | .run(path.join(__dirname, '../src/gen/component')) 60 | .inTmpDir(dir => { 61 | fs.mkdirSync(`${dir}/templates`); 62 | fs.copyFileSync( 63 | path.join(__dirname, '__mocks__/Component.js'), 64 | path.join(`${dir}/templates`, 'Component.js') 65 | ); 66 | }) 67 | .withOptions({ name: 'component', path: 'src/components' }) 68 | .withLocalConfig({ 69 | templatesPath: 'templates' 70 | }) 71 | .then(dir => { 72 | assert.file(`${dir}/src/components/Component/Component.js`); 73 | }); 74 | }); 75 | 76 | it('base generator flow override template w/lowercased File', async () => { 77 | await helpers 78 | .run(path.join(__dirname, '../src/gen/component')) 79 | .inTmpDir(dir => { 80 | fs.mkdirSync(`${dir}/templates`); 81 | fs.copyFileSync( 82 | path.join(__dirname, '__mocks__/helper.js'), 83 | path.join(`${dir}/templates`, 'helper.js') 84 | ); 85 | }) 86 | .withOptions({ name: 'component', path: 'src/components' }) 87 | .withLocalConfig({ 88 | templatesPath: 'templates' 89 | }) 90 | .then(dir => { 91 | assert.file(`${dir}/src/components/Component/ComponentHelper.js`); 92 | }); 93 | }); 94 | }); 95 | --------------------------------------------------------------------------------