/src/test/?(*.)(spec|test).js?(x)"],
53 | "testEnvironment": "node",
54 | "testURL": "http://localhost",
55 | "transformIgnorePatterns": ["[/\\\\]node_modules[/\\\\].+\\.(js|jsx)$"]
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/apply-config-file.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const fs = require('fs');
4 | const path = require('path');
5 | const chalk = require('chalk');
6 |
7 | const { getRootPath } = require('./generator-utilities');
8 |
9 | const GRCC = 'grcc.json';
10 |
11 | function applyConfig(params, callback) {
12 | const configDirectory = path.join(getRootPath(), GRCC);
13 |
14 | if (fs.existsSync(configDirectory)) {
15 | fs.readFile(configDirectory, 'utf8', (err, data) => {
16 | if (err) throw err;
17 |
18 | let configData;
19 | try {
20 | configData = JSON.parse(data);
21 | } catch (error) {
22 | console.log(
23 | chalk.red('Error reading config file at'),
24 | chalk.gray(configDirectory),
25 | '\nMake sure your file has the correct format'
26 | );
27 | return callback(params);
28 | }
29 |
30 | params.native = Boolean(params.native || configData.native);
31 | params.redux = Boolean(params.redux || configData.redux);
32 | params.omitComments = Boolean(params.omitComments || configData.omitComments);
33 |
34 | console.log(
35 | chalk.bold.underline.cyan('Config Loaded:'),
36 | chalk.bold.magenta('\nnative:\t\t'),
37 | chalk.yellow(params.native),
38 | chalk.bold.magenta('\nredux:\t\t'),
39 | chalk.yellow(params.redux),
40 | chalk.bold.magenta('\nomitComments:\t'),
41 | chalk.yellow(params.omitComments),
42 | '\n'
43 | );
44 |
45 | return callback(params);
46 | });
47 | } else {
48 | return callback(params);
49 | }
50 | }
51 |
52 | module.exports = applyConfig;
53 |
--------------------------------------------------------------------------------
/src/create-all-templates.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const chalk = require('chalk');
4 | const _ = require('lodash');
5 |
6 | const {
7 | createTemplate,
8 | getAllDirectories,
9 | getAllPlaceholderNames
10 | } = require('./generator-utilities');
11 |
12 | function createAllTemplates(
13 | name,
14 | directory,
15 | native,
16 | redux,
17 | omitComments,
18 | reduxCore,
19 | reduxCoreDirectory
20 | ) {
21 | const directories = getAllDirectories(
22 | name,
23 | directory,
24 | native,
25 | redux,
26 | reduxCore,
27 | reduxCoreDirectory
28 | );
29 | const placeholderNames = getAllPlaceholderNames(name);
30 |
31 | _.forEach(directories, (directory, key) => {
32 | createTemplate(directory, placeholderNames, omitComments, () => {
33 | console.log(
34 | chalk.bold.blue(key),
35 | chalk.bold.green('file successfully created in'),
36 | chalk.bold.gray(directory.generated)
37 | );
38 | });
39 | });
40 | }
41 |
42 | module.exports = createAllTemplates;
43 |
--------------------------------------------------------------------------------
/src/generator-utilities.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const fs = require('fs');
4 | const shell = require('shelljs');
5 | const path = require('path');
6 | const prettier = require('prettier');
7 | const commander = require.resolve('commander');
8 | const _ = require('lodash');
9 |
10 | const NODE_MODULES_PATH = 'node_modules';
11 | const ROOT_PATH = getRootPath();
12 | const PRETTIER_CONFIG = {
13 | tabWidth: 4,
14 | singleQuote: true,
15 | printWidth: 100
16 | };
17 |
18 | function getAllDirectories(name, directory, native, redux, reduxCore, reduxCoreDirectory) {
19 | if (reduxCore) {
20 | return getReduxCoreDirs(reduxCoreDirectory);
21 | }
22 | return getReactComponentDirs(name, directory, native, redux);
23 | }
24 |
25 | function getReduxCoreDirs(reduxCoreDirectory) {
26 | const templateDirectory = path.join(__dirname, '..', 'templates', 'redux-core');
27 | const generatedDirectory = path.join(ROOT_PATH, reduxCoreDirectory);
28 |
29 | if (!fs.existsSync(generatedDirectory)) {
30 | shell.mkdir('-p', path.join(generatedDirectory, 'action-utilities', 'test'));
31 | }
32 |
33 | return {
34 | store: {
35 | template: path.join(templateDirectory, 'store.js'),
36 | generated: path.join(generatedDirectory, 'store.js')
37 | },
38 | rootReducer: {
39 | template: path.join(templateDirectory, 'root-reducer.js'),
40 | generated: path.join(generatedDirectory, 'root-reducer.js')
41 | },
42 | createAction: {
43 | template: path.join(templateDirectory, 'action-utilities', 'action-creator.js'),
44 | generated: path.join(generatedDirectory, 'action-utilities', 'action-creator.js')
45 | },
46 | createActionType: {
47 | template: path.join(templateDirectory, 'action-utilities', 'action-type-creator.js'),
48 | generated: path.join(generatedDirectory, 'action-utilities', 'action-type-creator.js')
49 | },
50 | createActionTest: {
51 | template: path.join(
52 | templateDirectory,
53 | 'action-utilities',
54 | 'test',
55 | 'action-creator.spec.js'
56 | ),
57 | generated: path.join(
58 | generatedDirectory,
59 | 'action-utilities',
60 | 'test',
61 | 'action-creator.spec.js'
62 | )
63 | },
64 | createActionTypeTest: {
65 | template: path.join(
66 | templateDirectory,
67 | 'action-utilities',
68 | 'test',
69 | 'action-type-creator.spec.js'
70 | ),
71 | generated: path.join(
72 | generatedDirectory,
73 | 'action-utilities',
74 | 'test',
75 | 'action-type-creator.spec.js'
76 | )
77 | }
78 | };
79 | }
80 |
81 | function getReactComponentDirs(name, directory, native, redux) {
82 | const subDir = native ? 'native' : 'web';
83 | const subSubDir = redux ? 'react-redux' : 'react';
84 |
85 | const templateDirectory = path.join(__dirname, '..', 'templates', subDir, subSubDir);
86 | const generatedDirectory = path.join(ROOT_PATH, directory, name);
87 |
88 | if (!fs.existsSync(generatedDirectory)) {
89 | shell.mkdir('-p', path.join(generatedDirectory, 'test'));
90 | }
91 |
92 | const reactDirs = {
93 | view: {
94 | template: path.join(templateDirectory, 'template.view.js'),
95 | generated: path.join(generatedDirectory, `${name}.view.js`)
96 | },
97 | viewTest: {
98 | template: path.join(templateDirectory, 'test', 'template.view.spec.js'),
99 | generated: path.join(generatedDirectory, 'test', `${name}.view.spec.js`)
100 | }
101 | };
102 |
103 | if (!native) {
104 | reactDirs.stylesheet = {
105 | template: path.join(templateDirectory, '_template.styles.scss'),
106 | generated: path.join(generatedDirectory, `_${name}.styles.scss`)
107 | };
108 | }
109 |
110 | if (!redux) {
111 | return reactDirs;
112 | }
113 |
114 | const reduxDirs = {
115 | container: {
116 | template: path.join(templateDirectory, 'template.container.js'),
117 | generated: path.join(generatedDirectory, `${name}.container.js`)
118 | },
119 | containerTest: {
120 | template: path.join(templateDirectory, 'test', 'template.container.spec.js'),
121 | generated: path.join(generatedDirectory, 'test', `${name}.container.spec.js`)
122 | },
123 | reducer: {
124 | template: path.join(templateDirectory, 'template.reducer.js'),
125 | generated: path.join(generatedDirectory, `${name}.reducer.js`)
126 | },
127 | reducerTest: {
128 | template: path.join(templateDirectory, 'test', 'template.reducer.spec.js'),
129 | generated: path.join(generatedDirectory, 'test', `${name}.reducer.spec.js`)
130 | }
131 | };
132 |
133 | return _.assign(reactDirs, reduxDirs);
134 | }
135 |
136 | function getAllPlaceholderNames(kebab) {
137 | const lowerCamel = _.camelCase(kebab);
138 | const upperCamel = _.upperFirst(lowerCamel);
139 | return { kebab, lowerCamel, upperCamel };
140 | }
141 |
142 | function removeComments(s) {
143 | return s.replace(/([\s\S]*?)\/\*[\s\S]*?\*\//g, '$1');
144 | }
145 |
146 | function createTemplate(directory, placeholderNames, omitComments, callback) {
147 | fs.readFile(directory.template, 'utf8', (err, data) => {
148 | if (err) throw err;
149 |
150 | data = _.replace(data, /TEMPLATE_KEBAB_CASE_NAME/g, placeholderNames.kebab);
151 | data = _.replace(data, /TEMPLATE_LOWER_CAMEL_CASE_NAME/g, placeholderNames.lowerCamel);
152 | data = _.replace(data, /TEMPLATE_UPPER_CAMEL_CASE_NAME/g, placeholderNames.upperCamel);
153 |
154 | if (omitComments) {
155 | data = removeComments(data);
156 | }
157 |
158 | const formattedCode = formatCodeWithPrettier(data, directory);
159 |
160 | fs.writeFile(directory.generated, formattedCode, err => {
161 | if (err) throw err;
162 | return callback();
163 | });
164 | });
165 | }
166 |
167 | function formatCodeWithPrettier(data, directory) {
168 | const parser = _.endsWith(directory.generated, '.scss') ? 'scss' : 'babylon';
169 | const config = { ...PRETTIER_CONFIG, parser };
170 |
171 | return prettier.format(data, config);
172 | }
173 |
174 | function getRootPath() {
175 | return commander.slice(0, commander.indexOf(NODE_MODULES_PATH.toLowerCase()));
176 | }
177 |
178 | module.exports = {
179 | createTemplate,
180 | getAllDirectories,
181 | getAllPlaceholderNames,
182 | getRootPath
183 | };
184 |
--------------------------------------------------------------------------------
/src/test/apply-config-file.spec.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const fs = require('fs');
4 | const path = require('path');
5 | const shell = require('shelljs');
6 |
7 | const { getRootPath } = require('../generator-utilities');
8 |
9 | const applyConfig = require('../apply-config-file');
10 |
11 | const ROOT_PATH = getRootPath();
12 |
13 | describe('Apply config file - Unit Test', () => {
14 | describe('applyConfig', () => {
15 | it('should apply config file parameters to passed in parameters', done => {
16 | const passedIn = {
17 | name: 'some-name'
18 | };
19 | const expected = {
20 | name: 'some-name',
21 | native: true,
22 | redux: true,
23 | omitComments: true
24 | };
25 | fs.writeFile(
26 | path.join(ROOT_PATH, 'grcc.json'),
27 | '{ "native": true, "redux": true, "omitComments": true }',
28 | () =>
29 | applyConfig(passedIn, params => {
30 | expect(params).toEqual(expected);
31 | shell.rm('-rf', path.join(ROOT_PATH, 'grcc.json'));
32 | done();
33 | })
34 | );
35 | });
36 |
37 | it('should not overwrite passed in parameters when a user specifies them', done => {
38 | const passedIn = {
39 | name: 'some-name',
40 | native: true,
41 | redux: true,
42 | omitComments: true
43 | };
44 | fs.writeFile(
45 | path.join(ROOT_PATH, 'grcc.json'),
46 | '{ "native": false, "redux": false, "omitComments": false }',
47 | () =>
48 | applyConfig(passedIn, params => {
49 | expect(params).toEqual(passedIn);
50 | shell.rm('-rf', path.join(ROOT_PATH, 'grcc.json'));
51 | done();
52 | })
53 | );
54 | });
55 |
56 | it('should return unaltered parameters when the config file does not exist', done => {
57 | const passedIn = {
58 | name: 'some-name',
59 | native: false,
60 | redux: false
61 | };
62 |
63 | applyConfig(passedIn, params => {
64 | expect(params).toEqual(passedIn);
65 | done();
66 | });
67 | });
68 |
69 | it('should return unaltered parameters when the config file exists but is not in the correct format', done => {
70 | const passedIn = {
71 | name: 'some-name',
72 | native: false
73 | };
74 | fs.writeFile(path.join(ROOT_PATH, 'grcc.json'), '{ oops it aint json }', () =>
75 | applyConfig(passedIn, params => {
76 | expect(params).toEqual(passedIn);
77 | shell.rm('-rf', path.join(ROOT_PATH, 'grcc.json'));
78 | done();
79 | })
80 | );
81 | });
82 | });
83 | });
84 |
--------------------------------------------------------------------------------
/src/test/create-all-templates.spec.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const fs = require('fs');
4 | const path = require('path');
5 | const shell = require('shelljs');
6 |
7 | const { getRootPath } = require('../generator-utilities');
8 | const createAllTemplates = require('../create-all-templates');
9 |
10 | const ROOT_PATH = getRootPath();
11 |
12 | describe('Create All Templates - Unit Test', () => {
13 | describe('createAllTemplates', () => {
14 | it('should create all template', done => {
15 | const name = 'some-complete-template';
16 | const directory = 'some-complete-template';
17 |
18 | createAllTemplates(name, directory);
19 |
20 | setTimeout(() => {
21 | expect(fs.existsSync(path.join(ROOT_PATH, directory))).toBeTruthy();
22 | expect(
23 | fs.existsSync(path.join(ROOT_PATH, directory, name, `${name}.view.js`))
24 | ).toBeTruthy();
25 | done();
26 | }, 500);
27 | });
28 | });
29 |
30 | afterAll(() => {
31 | shell.rm('-rf', path.join(ROOT_PATH, 'some-complete-template'));
32 | });
33 | });
34 |
--------------------------------------------------------------------------------
/src/test/generator-utilities.spec.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const path = require('path');
4 | const fs = require('fs');
5 | const shell = require('shelljs');
6 | const prettier = require('prettier');
7 |
8 | const resolvePath = require.resolve('commander');
9 |
10 | const {
11 | getRootPath,
12 | getAllDirectories,
13 | getAllPlaceholderNames,
14 | createTemplate
15 | } = require('../generator-utilities');
16 |
17 | const ROOT_PATH = getRootPath();
18 |
19 | describe('Generator Utilities - Unit Test', () => {
20 | describe('createTemplate', () => {
21 | it('should create template with comments', done => {
22 | const name = 'some-directory-create-template';
23 | const templatePath = path.join(ROOT_PATH, 'templates', 'web', 'react');
24 | const directory = path.join(ROOT_PATH, name);
25 |
26 | if (!fs.existsSync(directory)) {
27 | shell.mkdir('-p', path.join(directory));
28 | }
29 |
30 | const testTemplate = {
31 | template: path.join(templatePath, 'template.view.js'),
32 | generated: path.join(directory, `${name}.view.js`)
33 | };
34 |
35 | const testPlaceHolder = getAllPlaceholderNames(name);
36 |
37 | function assert() {
38 | expect(fs.existsSync(directory)).toBeTruthy();
39 | expect(fs.existsSync(testTemplate.generated)).toBeTruthy();
40 | expect(fs.readFileSync(testTemplate.generated, 'utf8')).toContain(
41 | 'Import all external modules here.'
42 | );
43 | done();
44 | }
45 |
46 | createTemplate(testTemplate, testPlaceHolder, false, assert);
47 | });
48 |
49 | it('should create template without comments', done => {
50 | const name = 'some-directory-create-template-without-comments';
51 | const templatePath = path.join(ROOT_PATH, 'templates', 'web', 'react');
52 | const directory = path.join(ROOT_PATH, name);
53 |
54 | if (!fs.existsSync(directory)) {
55 | shell.mkdir('-p', path.join(directory));
56 | }
57 |
58 | const testTemplate = {
59 | template: path.join(templatePath, 'template.view.js'),
60 | generated: path.join(directory, `${name}.view.js`)
61 | };
62 |
63 | const testPlaceHolder = getAllPlaceholderNames(name);
64 |
65 | function assert() {
66 | expect(fs.existsSync(directory)).toBeTruthy();
67 | expect(fs.existsSync(testTemplate.generated)).toBeTruthy();
68 | expect(fs.readFileSync(testTemplate.generated, 'utf8')).not.toContain(
69 | 'Import all external modules here.'
70 | );
71 | done();
72 | }
73 |
74 | createTemplate(testTemplate, testPlaceHolder, true, assert);
75 | });
76 |
77 | it('should format javascript code using prettier after it has been generated', done => {
78 | const name = 'some-directory-create-template';
79 | const templatePath = path.join(ROOT_PATH, 'templates', 'web', 'react');
80 | const directory = path.join(ROOT_PATH, name);
81 |
82 | if (!fs.existsSync(directory)) {
83 | shell.mkdir('-p', path.join(directory));
84 | }
85 |
86 | const testTemplate = {
87 | template: path.join(templatePath, 'template.view.js'),
88 | generated: path.join(directory, `${name}.view.js`)
89 | };
90 |
91 | const testPlaceHolder = getAllPlaceholderNames(name);
92 |
93 | const prettierSpy = jest.spyOn(prettier, 'format');
94 |
95 | function assert() {
96 | expect(prettierSpy).toHaveBeenCalled();
97 | prettierSpy.mockRestore();
98 | done();
99 | }
100 |
101 | createTemplate(testTemplate, testPlaceHolder, false, assert);
102 | });
103 |
104 | it('should format sass code using prettier after it has been generated', done => {
105 | const name = 'some-directory-create-template';
106 | const templatePath = path.join(ROOT_PATH, 'templates', 'web', 'react');
107 | const directory = path.join(ROOT_PATH, name);
108 |
109 | if (!fs.existsSync(directory)) {
110 | shell.mkdir('-p', path.join(directory));
111 | }
112 |
113 | const testTemplate = {
114 | template: path.join(templatePath, '_template.styles.scss'),
115 | generated: path.join(directory, `${name}.styles.scss`)
116 | };
117 |
118 | const testPlaceHolder = getAllPlaceholderNames(name);
119 |
120 | const prettierSpy = jest.spyOn(prettier, 'format');
121 |
122 | function assert() {
123 | expect(prettierSpy).toHaveBeenCalled();
124 | prettierSpy.mockRestore();
125 | done();
126 | }
127 |
128 | createTemplate(testTemplate, testPlaceHolder, false, assert);
129 | });
130 | });
131 |
132 | describe('getAllDirectories', () => {
133 | describe('getReactComponentDirs', () => {
134 | describe('for web', () => {
135 | describe('NOT redux', () => {
136 | it('should return all templates with directories set name and create folder name, with test', () => {
137 | const name = 'some-name';
138 | const directory = 'some-directory';
139 |
140 | const actual = getAllDirectories(name, directory);
141 |
142 | const templates = path.join(ROOT_PATH, 'templates', 'web', 'react');
143 | const folderName = name;
144 | const expected = {
145 | view: {
146 | template: path.join(templates, 'template.view.js'),
147 | generated: path.join(
148 | ROOT_PATH,
149 | directory,
150 | folderName,
151 | `${name}.view.js`
152 | )
153 | },
154 | viewTest: {
155 | template: path.join(templates, 'test', 'template.view.spec.js'),
156 | generated: path.join(
157 | ROOT_PATH,
158 | directory,
159 | folderName,
160 | 'test',
161 | `${name}.view.spec.js`
162 | )
163 | },
164 | stylesheet: {
165 | template: path.join(templates, '_template.styles.scss'),
166 | generated: path.join(
167 | ROOT_PATH,
168 | directory,
169 | folderName,
170 | `_${name}.styles.scss`
171 | )
172 | }
173 | };
174 |
175 | expect(actual).toEqual(expected);
176 | expect(
177 | fs.existsSync(path.join(ROOT_PATH, directory, folderName))
178 | ).toBeTruthy();
179 | });
180 | });
181 |
182 | describe('WITH redux', () => {
183 | it('should return all templates with directories set name and create folder name, with test', () => {
184 | const name = 'some-name-redux';
185 | const directory = 'some-directory-redux';
186 |
187 | const actual = getAllDirectories(name, directory, false, true);
188 |
189 | const templates = path.join(ROOT_PATH, 'templates', 'web', 'react-redux');
190 | const folderName = name;
191 | const expected = {
192 | view: {
193 | template: path.join(templates, 'template.view.js'),
194 | generated: path.join(
195 | ROOT_PATH,
196 | directory,
197 | folderName,
198 | `${name}.view.js`
199 | )
200 | },
201 | viewTest: {
202 | template: path.join(templates, 'test', 'template.view.spec.js'),
203 | generated: path.join(
204 | ROOT_PATH,
205 | directory,
206 | folderName,
207 | 'test',
208 | `${name}.view.spec.js`
209 | )
210 | },
211 | stylesheet: {
212 | template: path.join(templates, '_template.styles.scss'),
213 | generated: path.join(
214 | ROOT_PATH,
215 | directory,
216 | folderName,
217 | `_${name}.styles.scss`
218 | )
219 | },
220 | container: {
221 | template: path.join(templates, 'template.container.js'),
222 | generated: path.join(
223 | ROOT_PATH,
224 | directory,
225 | folderName,
226 | `${name}.container.js`
227 | )
228 | },
229 | containerTest: {
230 | template: path.join(
231 | templates,
232 | 'test',
233 | 'template.container.spec.js'
234 | ),
235 | generated: path.join(
236 | ROOT_PATH,
237 | directory,
238 | folderName,
239 | 'test',
240 | `${name}.container.spec.js`
241 | )
242 | },
243 |
244 | reducer: {
245 | template: path.join(templates, 'template.reducer.js'),
246 | generated: path.join(
247 | ROOT_PATH,
248 | directory,
249 | folderName,
250 | `${name}.reducer.js`
251 | )
252 | },
253 | reducerTest: {
254 | template: path.join(templates, 'test', 'template.reducer.spec.js'),
255 | generated: path.join(
256 | ROOT_PATH,
257 | directory,
258 | folderName,
259 | 'test',
260 | `${name}.reducer.spec.js`
261 | )
262 | }
263 | };
264 |
265 | expect(actual).toEqual(expected);
266 | expect(
267 | fs.existsSync(path.join(ROOT_PATH, directory, folderName))
268 | ).toBeTruthy();
269 | });
270 | });
271 | });
272 |
273 | describe('for native', () => {
274 | describe('NOT redux', () => {
275 | it('should return all templates with directories set name and create folder name, with test', () => {
276 | const name = 'some-name-native';
277 | const directory = 'some-directory-native';
278 |
279 | const actual = getAllDirectories(name, directory, true);
280 |
281 | const templates = path.join(ROOT_PATH, 'templates', 'native', 'react');
282 | const folderName = name;
283 | const expected = {
284 | view: {
285 | template: path.join(templates, 'template.view.js'),
286 | generated: path.join(
287 | ROOT_PATH,
288 | directory,
289 | folderName,
290 | `${name}.view.js`
291 | )
292 | },
293 | viewTest: {
294 | template: path.join(templates, 'test', 'template.view.spec.js'),
295 | generated: path.join(
296 | ROOT_PATH,
297 | directory,
298 | folderName,
299 | 'test',
300 | `${name}.view.spec.js`
301 | )
302 | }
303 | };
304 |
305 | expect(actual).toEqual(expected);
306 | expect(
307 | fs.existsSync(path.join(ROOT_PATH, directory, folderName))
308 | ).toBeTruthy();
309 | });
310 | });
311 |
312 | describe('WITH redux', () => {
313 | it('should return all templates with directories set name and create folder name, with test', () => {
314 | const name = 'some-name-redux';
315 | const directory = 'some-directory-redux';
316 |
317 | const actual = getAllDirectories(name, directory, true, true);
318 |
319 | const templates = path.join(
320 | ROOT_PATH,
321 | 'templates',
322 | 'native',
323 | 'react-redux'
324 | );
325 | const folderName = name;
326 | const expected = {
327 | view: {
328 | template: path.join(templates, 'template.view.js'),
329 | generated: path.join(
330 | ROOT_PATH,
331 | directory,
332 | folderName,
333 | `${name}.view.js`
334 | )
335 | },
336 | viewTest: {
337 | template: path.join(templates, 'test', 'template.view.spec.js'),
338 | generated: path.join(
339 | ROOT_PATH,
340 | directory,
341 | folderName,
342 | 'test',
343 | `${name}.view.spec.js`
344 | )
345 | },
346 | container: {
347 | template: path.join(templates, 'template.container.js'),
348 | generated: path.join(
349 | ROOT_PATH,
350 | directory,
351 | folderName,
352 | `${name}.container.js`
353 | )
354 | },
355 | containerTest: {
356 | template: path.join(
357 | templates,
358 | 'test',
359 | 'template.container.spec.js'
360 | ),
361 | generated: path.join(
362 | ROOT_PATH,
363 | directory,
364 | folderName,
365 | 'test',
366 | `${name}.container.spec.js`
367 | )
368 | },
369 |
370 | reducer: {
371 | template: path.join(templates, 'template.reducer.js'),
372 | generated: path.join(
373 | ROOT_PATH,
374 | directory,
375 | folderName,
376 | `${name}.reducer.js`
377 | )
378 | },
379 | reducerTest: {
380 | template: path.join(templates, 'test', 'template.reducer.spec.js'),
381 | generated: path.join(
382 | ROOT_PATH,
383 | directory,
384 | folderName,
385 | 'test',
386 | `${name}.reducer.spec.js`
387 | )
388 | }
389 | };
390 |
391 | expect(actual).toEqual(expected);
392 | expect(
393 | fs.existsSync(path.join(ROOT_PATH, directory, folderName))
394 | ).toBeTruthy();
395 | });
396 | });
397 | });
398 | });
399 |
400 | describe('getReduxCoreDirs', () => {
401 | it('should return all templates with directories, set with name and create folders for redux core', () => {
402 | const directory = 'some-directory-redux-core';
403 |
404 | let actual = getAllDirectories(null, null, null, null, true, directory);
405 |
406 | const templates = path.join(ROOT_PATH, 'templates', 'redux-core');
407 |
408 | const expected = {
409 | store: {
410 | template: path.join(templates, 'store.js'),
411 | generated: path.join(ROOT_PATH, directory, 'store.js')
412 | },
413 | rootReducer: {
414 | template: path.join(templates, 'root-reducer.js'),
415 | generated: path.join(ROOT_PATH, directory, 'root-reducer.js')
416 | },
417 | createAction: {
418 | template: path.join(templates, 'action-utilities', 'action-creator.js'),
419 | generated: path.join(
420 | ROOT_PATH,
421 | directory,
422 | 'action-utilities',
423 | 'action-creator.js'
424 | )
425 | },
426 | createActionType: {
427 | template: path.join(
428 | templates,
429 | 'action-utilities',
430 | 'action-type-creator.js'
431 | ),
432 | generated: path.join(
433 | ROOT_PATH,
434 | directory,
435 | 'action-utilities',
436 | 'action-type-creator.js'
437 | )
438 | },
439 | createActionTest: {
440 | template: path.join(
441 | templates,
442 | 'action-utilities',
443 | 'test',
444 | 'action-creator.spec.js'
445 | ),
446 | generated: path.join(
447 | ROOT_PATH,
448 | directory,
449 | 'action-utilities',
450 | 'test',
451 | 'action-creator.spec.js'
452 | )
453 | },
454 | createActionTypeTest: {
455 | template: path.join(
456 | templates,
457 | 'action-utilities',
458 | 'test',
459 | 'action-type-creator.spec.js'
460 | ),
461 | generated: path.join(
462 | ROOT_PATH,
463 | directory,
464 | 'action-utilities',
465 | 'test',
466 | 'action-type-creator.spec.js'
467 | )
468 | }
469 | };
470 |
471 | expect(actual).toEqual(expected);
472 | expect(fs.existsSync(path.join(ROOT_PATH, directory))).toBeTruthy();
473 | });
474 |
475 | it('should return all templates with directories, set with name and create folders for redux core even if the directory already exists', () => {
476 | const directory = 'some-directory-redux-core';
477 |
478 | let actual = getAllDirectories(null, null, null, null, true, directory);
479 | actual = getAllDirectories(null, null, null, null, true, directory);
480 |
481 | const templates = path.join(ROOT_PATH, 'templates', 'redux-core');
482 |
483 | const expected = {
484 | store: {
485 | template: path.join(templates, 'store.js'),
486 | generated: path.join(ROOT_PATH, directory, 'store.js')
487 | },
488 | rootReducer: {
489 | template: path.join(templates, 'root-reducer.js'),
490 | generated: path.join(ROOT_PATH, directory, 'root-reducer.js')
491 | },
492 | createAction: {
493 | template: path.join(templates, 'action-utilities', 'action-creator.js'),
494 | generated: path.join(
495 | ROOT_PATH,
496 | directory,
497 | 'action-utilities',
498 | 'action-creator.js'
499 | )
500 | },
501 | createActionType: {
502 | template: path.join(
503 | templates,
504 | 'action-utilities',
505 | 'action-type-creator.js'
506 | ),
507 | generated: path.join(
508 | ROOT_PATH,
509 | directory,
510 | 'action-utilities',
511 | 'action-type-creator.js'
512 | )
513 | },
514 | createActionTest: {
515 | template: path.join(
516 | templates,
517 | 'action-utilities',
518 | 'test',
519 | 'action-creator.spec.js'
520 | ),
521 | generated: path.join(
522 | ROOT_PATH,
523 | directory,
524 | 'action-utilities',
525 | 'test',
526 | 'action-creator.spec.js'
527 | )
528 | },
529 | createActionTypeTest: {
530 | template: path.join(
531 | templates,
532 | 'action-utilities',
533 | 'test',
534 | 'action-type-creator.spec.js'
535 | ),
536 | generated: path.join(
537 | ROOT_PATH,
538 | directory,
539 | 'action-utilities',
540 | 'test',
541 | 'action-type-creator.spec.js'
542 | )
543 | }
544 | };
545 |
546 | expect(actual).toEqual(expected);
547 | expect(fs.existsSync(path.join(ROOT_PATH, directory))).toBeTruthy();
548 | });
549 | });
550 | });
551 |
552 | describe('getAllPlaceholderNames', () => {
553 | it('should return kebab case name', () => {
554 | const name = 'some-kebab-case-name';
555 |
556 | const actual = getAllPlaceholderNames(name);
557 |
558 | const expected = {
559 | kebab: name,
560 | lowerCamel: 'someKebabCaseName',
561 | upperCamel: 'SomeKebabCaseName'
562 | };
563 |
564 | expect(actual).toEqual(expected);
565 | });
566 | });
567 |
568 | describe('getRootPath', () => {
569 | it('should return root path', () => {
570 | console.log('resolvePath:', resolvePath.slice(0, resolvePath.indexOf('node_modules')));
571 |
572 | const actual = getRootPath();
573 |
574 | const expected = resolvePath.slice(0, resolvePath.indexOf('node_modules'));
575 |
576 | expect(actual).toEqual(expected);
577 | });
578 | });
579 |
580 | afterAll(() => {
581 | shell.rm('-rf', path.join(ROOT_PATH, 'some-directory'));
582 | shell.rm('-rf', path.join(ROOT_PATH, 'some-directory-native'));
583 | shell.rm('-rf', path.join(ROOT_PATH, 'some-directory-redux'));
584 | shell.rm('-rf', path.join(ROOT_PATH, 'some-directory-redux-core'));
585 | shell.rm('-rf', path.join(ROOT_PATH, 'some-directory-create-template'));
586 | shell.rm('-rf', path.join(ROOT_PATH, 'some-directory-create-template-without-comments'));
587 | });
588 | });
589 |
--------------------------------------------------------------------------------
/templates/native/react-redux/template.container.js:
--------------------------------------------------------------------------------
1 | import { bindActionCreators } from 'redux';
2 | import { connect } from 'react-redux';
3 |
4 | import TEMPLATE_UPPER_CAMEL_CASE_NAMEView from './TEMPLATE_KEBAB_CASE_NAME.view';
5 | import {
6 | exampleAction,
7 | exampleAsyncAction
8 | /*
9 | Import all the actions you wish to expose to the view here.
10 | */
11 | } from './TEMPLATE_KEBAB_CASE_NAME.reducer';
12 |
13 | export function mapStateToProps({ TEMPLATE_LOWER_CAMEL_CASE_NAMEReducer }) {
14 | return {
15 | exampleVariable: TEMPLATE_LOWER_CAMEL_CASE_NAMEReducer.exampleVariable
16 | /*
17 | Add all the state variables you wish to expose to the view here.
18 | */
19 | };
20 | }
21 |
22 | export function mapDispatchToProps(dispatch) {
23 | return bindActionCreators(
24 | {
25 | exampleAction,
26 | exampleAsyncAction
27 | /*
28 | Add all the actions you wish to expose to the view here.
29 | */
30 | },
31 | dispatch
32 | );
33 | }
34 |
35 | export default connect(mapStateToProps, mapDispatchToProps)(TEMPLATE_UPPER_CAMEL_CASE_NAMEView);
36 |
--------------------------------------------------------------------------------
/templates/native/react-redux/template.reducer.js:
--------------------------------------------------------------------------------
1 | /*
2 | NOTE: Remember to add this reducer to the root reducer found in 'src/redux/root-reducer.js'.
3 | */
4 |
5 | /*
6 | NOTE: The CreateAction directory my change based on your module's desired location.
7 | */
8 | import CreateAction from '../../redux/action-utilities/action-creator';
9 | /*
10 | Import all local modules here.
11 | */
12 |
13 | const reducerName = 'TEMPLATE_KEBAB_CASE_NAME';
14 |
15 | /*
16 | Here you can create all your actions with the CreateAction action factory (See example below).
17 |
18 | NOTE: It's important to use this factory to create standard actions so that all actions will be
19 | consistent - this will also eliminate duplicate code.
20 | */
21 | const example = new CreateAction(reducerName, 'EXAMPLE_ACTION');
22 | export const exampleAction = example.action;
23 |
24 | /*
25 | Add all Asynchronous actions here. These are used when the action needs to access state or when multiple
26 | actions need to be dispatched (See example below).
27 |
28 | NOTE: It's important to note that 'getState()' returns a shallow copy of state - so if you mutate it,
29 | your state will change along with it. Be careful of this!
30 | */
31 | export function exampleAsyncAction() {
32 | return (dispatch, getState) => {
33 | const { TEMPLATE_LOWER_CAMEL_CASE_NAMEReducer } = getState();
34 | dispatch(exampleAction(!TEMPLATE_LOWER_CAMEL_CASE_NAMEReducer.exampleVariable));
35 | };
36 | }
37 |
38 | export const initialState = {
39 | exampleVariable: true
40 | /*
41 | Add your reducer's initial state here.
42 | */
43 | };
44 |
45 | export default function TEMPLATE_LOWER_CAMEL_CASE_NAMEReducer(state = initialState, action) {
46 | switch (action.type) {
47 | case example.actionType:
48 | return { ...state, exampleVariable: action.payload };
49 | default:
50 | return state;
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/templates/native/react-redux/template.view.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import PropTypes from 'prop-types';
3 | import { StyleSheet, View, Text /*Add other React Native components here*/ } from 'react-native';
4 | /*
5 | Import all external modules here.
6 | */
7 |
8 | /*
9 | Import all local modules here.
10 | */
11 |
12 | export default class TEMPLATE_UPPER_CAMEL_CASE_NAMEView extends Component {
13 | constructor(props) {
14 | super(props);
15 |
16 | /*
17 | Add all required function binding here. If no binding is required, the constructor may be omitted.
18 | */
19 | this.exampleHelper = this.exampleHelper.bind(this);
20 |
21 | /*
22 | NOTE: Do NOT use 'this.state'! State management is wat Redux is used for.
23 | */
24 | }
25 |
26 | componentDidMount() {
27 | /*
28 | If your screen needs to load data via the API immediately after rendering,
29 | call the load functions here in the `componentDidMount` function (See example below) -
30 | else, the `componentDidMount` function may be omitted.
31 | */
32 | this.props.exampleAsyncAction();
33 | }
34 |
35 | render() {
36 | return {this.exampleHelper()} ;
37 | }
38 |
39 | /*
40 | Add all helper functions here. Remember to 'bind' functions where 'this'
41 | needs to be used (See example below).
42 | */
43 | exampleHelper() {
44 | this.props.exampleAction(true);
45 | if (!this.props.exampleVariable) {
46 | return {'My template has been fiddled with'} ;
47 | }
48 | return {'Hello World! Welcome to my template'} ;
49 | }
50 | }
51 |
52 | /*
53 | Add all the props (variables and functions) being passe through to this component (by redux or inline).
54 | This will make code completion and IDE inspection possible, and will make it easy to see what data is relevant
55 | in this module.
56 | */
57 | TEMPLATE_UPPER_CAMEL_CASE_NAMEView.propTypes = {
58 | exampleVariable: PropTypes.bool,
59 |
60 | exampleAction: PropTypes.func,
61 | exampleAsyncAction: PropTypes.func
62 | };
63 |
64 | /*
65 | Add all the component styling here.
66 | */
67 | const styles = StyleSheet.create({
68 | text: {
69 | fontSize: 20,
70 | textAlign: 'center'
71 | }
72 | });
73 |
--------------------------------------------------------------------------------
/templates/native/react-redux/test/template.container.spec.js:
--------------------------------------------------------------------------------
1 | jest.mock('redux');
2 |
3 | import { bindActionCreators } from 'redux';
4 | import { mapStateToProps, mapDispatchToProps } from '../TEMPLATE_KEBAB_CASE_NAME.container';
5 | import {
6 | exampleAction,
7 | exampleAsyncAction,
8 | /*
9 | Import all the actions you wish to expose to the view here.
10 | */
11 | initialState
12 | } from '../TEMPLATE_KEBAB_CASE_NAME.reducer';
13 |
14 | describe('TEMPLATE_LOWER_CAMEL_CASE_NAMEContainer - Unit test', () => {
15 | function stateBefore() {
16 | return {
17 | TEMPLATE_LOWER_CAMEL_CASE_NAMEReducer: {
18 | ...initialState
19 | /*
20 | Setup your initial state for testing here.
21 | */
22 | }
23 | };
24 | }
25 |
26 | it('should map state to props', () => {
27 | const actual = mapStateToProps(stateBefore());
28 |
29 | const expected = {
30 | ...initialState
31 | /*
32 | Setup your initial state for verification here.
33 | */
34 | };
35 |
36 | expect(actual).toEqual(expected);
37 | });
38 |
39 | it('should map dispatch to props', () => {
40 | const dispatch = jest.fn();
41 |
42 | mapDispatchToProps(dispatch);
43 |
44 | expect(bindActionCreators).toHaveBeenCalledWith(
45 | {
46 | exampleAction,
47 | exampleAsyncAction
48 | /*
49 | Import all the actions you wish to expose to the view here.
50 | */
51 | },
52 | dispatch
53 | );
54 | });
55 | });
56 |
--------------------------------------------------------------------------------
/templates/native/react-redux/test/template.reducer.spec.js:
--------------------------------------------------------------------------------
1 | /*
2 | Add mocking setup here such as in the example:
3 |
4 | jest.mock('../../../service/some-api');
5 | import someApi from '../../../service/some-api';
6 | */
7 |
8 | import TEMPLATE_LOWER_CAMEL_CASE_NAMEReducer, {
9 | exampleAction,
10 | exampleAsyncAction,
11 | /*
12 | Add all the actions you wish to test here.
13 | */
14 | initialState
15 | } from '../TEMPLATE_KEBAB_CASE_NAME.reducer';
16 |
17 | describe('TEMPLATE_LOWER_CAMEL_CASE_NAMEReducer - Unit Test', () => {
18 | function stateBefore() {
19 | return {
20 | ...initialState
21 | /*
22 | Setup your initial state for testing here.
23 | */
24 | };
25 | }
26 |
27 | it('should return initial state when action is undefined', () => {
28 | const action = {};
29 |
30 | const actual = TEMPLATE_LOWER_CAMEL_CASE_NAMEReducer(undefined, action);
31 |
32 | const expected = {
33 | ...stateBefore()
34 | };
35 |
36 | expect(actual).toEqual(expected);
37 | });
38 |
39 | it('should return current state when unknown action is dispatched', () => {
40 | const action = { type: 'SOME_UNKNOWN_ACTION' };
41 |
42 | const currentState = {
43 | ...stateBefore()
44 | };
45 |
46 | const actual = TEMPLATE_LOWER_CAMEL_CASE_NAMEReducer(currentState, action);
47 |
48 | const expected = {
49 | ...stateBefore()
50 | };
51 |
52 | expect(actual).toEqual(expected);
53 | });
54 |
55 | /*
56 | Add all your action tests here. Add each action in its own 'describe' and test
57 | each of the action's scenarios in its own 'it'. Remember to test happy and sad cases.
58 | "Every bug is a test that wasn't written"
59 | */
60 | describe('exampleAction', () => {
61 | it('should send humans to Mars to recolonise', () => {
62 | const action = exampleAction(false);
63 |
64 | const actual = TEMPLATE_LOWER_CAMEL_CASE_NAMEReducer(stateBefore(), action);
65 |
66 | const expected = {
67 | ...stateBefore(),
68 | exampleVariable: false
69 | };
70 |
71 | expect(actual).toEqual(expected);
72 | });
73 | });
74 |
75 | /*
76 | Add all your asynchronous action tests here. Add each asynchronous action in its own 'describe' and test
77 | each of the asynchronous action's scenarios in its own 'it'. Remember to test happy and sad cases.
78 | If you have no asynchronous actions, this code may be omitted.
79 | */
80 | describe('exampleAsyncAction', () => {
81 | it('should dispatch exampleAction', () => {
82 | /*
83 | Mock dispatch and getState
84 | Note: If your Jest is on version 23 or higher you may use 'mockName' - this helps to
85 | indicate which mock function is being referenced. This can be used as shown in the example below:
86 |
87 | const getState = jest.fn().mockName('getState');
88 |
89 | const dispatch = jest.fn().mockName('dispatch');
90 |
91 | */
92 | const getState = jest.fn().mockImplementation(() => ({
93 | TEMPLATE_LOWER_CAMEL_CASE_NAMEReducer: { exampleVariable: false }
94 | }));
95 | const dispatch = jest.fn().mockImplementation();
96 |
97 | /*
98 | Test your async action using the mocked dispatch and getState
99 | */
100 | exampleAsyncAction()(dispatch, getState);
101 |
102 | /*
103 | Assert that dispatch has been called with example action
104 | Note: If your Jest is on version 23 or higher you may use 'toHaveBeenNthCalledWith' - this
105 | allows you to check the order in which your actions were called. This can be used as
106 | shown in the example below:
107 |
108 | expect(dispatch).toHaveBeenNthCalledWith(1, exampleAction(true));
109 | */
110 | expect(dispatch).toHaveBeenCalledWith(exampleAction(true));
111 | });
112 | });
113 | });
114 |
--------------------------------------------------------------------------------
/templates/native/react-redux/test/template.view.spec.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { shallow } from 'enzyme';
3 |
4 | import TEMPLATE_UPPER_CAMEL_CASE_NAMEView from '../TEMPLATE_KEBAB_CASE_NAME.view';
5 |
6 | describe('TEMPLATE_UPPER_CAMEL_CASE_NAMEView - DOES IT RENDER', () => {
7 | /*
8 | Here you can mock out any view function props not needed for rendering
9 | (i.e. state read/write functions from the reducer) so as to only test the view.
10 |
11 | These mocks may be omitted if no function props need to be mocked.
12 | */
13 | const exampleActionMock = jest.fn();
14 | const exampleAsyncActionMock = jest.fn();
15 |
16 | it('should render without crashing', () => {
17 | shallow(
18 |
22 | );
23 | });
24 | });
25 |
--------------------------------------------------------------------------------
/templates/native/react/template.view.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import PropTypes from 'prop-types';
3 | import { StyleSheet, View, Text /*Add other React Native components here*/ } from 'react-native';
4 | /*
5 | Import all external modules here.
6 | */
7 |
8 | /*
9 | Import all local modules here.
10 | */
11 |
12 | export default class TEMPLATE_UPPER_CAMEL_CASE_NAMEView extends Component {
13 | constructor(props) {
14 | super(props);
15 |
16 | /*
17 | Add all required function binding here. If no binding is required, the constructor may be omitted.
18 | */
19 | this.renderTextExample = this.renderTextExample.bind(this);
20 | }
21 |
22 | componentDidMount() {
23 | /*
24 | If your screen needs to load data via the API immediately after rendering,
25 | call the load functions here in the `componentDidMount` function (See example below) -
26 | else, the `componentDidMount` function may be omitted.
27 | */
28 | }
29 |
30 | render() {
31 | return {this.renderTextExample()} ;
32 | }
33 |
34 | /*
35 | Add all helper functions here. Remember to 'bind' functions where 'this'
36 | needs to be used (See example below).
37 | */
38 | renderTextExample() {
39 | const message = `Welcome to our template view ${this.props.exampleVariable || '( ͡° ͜ʖ ͡°)'}`;
40 | return {message} ;
41 | }
42 | }
43 |
44 | /*
45 | Add all the props (variables and functions) being passe through to this component (by redux or inline).
46 | This will make code completion and IDE inspection possible, and will make it easy to see what data is relevant
47 | in this module.
48 | */
49 | TEMPLATE_UPPER_CAMEL_CASE_NAMEView.propTypes = {
50 | exampleVariable: PropTypes.string
51 | };
52 |
53 | /*
54 | Add all the component styling here.
55 | */
56 | const styles = StyleSheet.create({
57 | text: {
58 | fontSize: 20,
59 | textAlign: 'center'
60 | }
61 | });
62 |
--------------------------------------------------------------------------------
/templates/native/react/test/template.view.spec.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { shallow } from 'enzyme';
3 |
4 | import TEMPLATE_UPPER_CAMEL_CASE_NAMEView from '../TEMPLATE_KEBAB_CASE_NAME.view';
5 |
6 | describe('TEMPLATE_UPPER_CAMEL_CASE_NAMEView - DOES IT RENDER', () => {
7 | /*
8 | Here you can mock out any view function props not needed for rendering
9 | (i.e. state read/write functions from the reducer) so as to only test the view.
10 |
11 | These mocks may be omitted if no function props need to be mocked.
12 | */
13 | const exampleVariableMock = 'some string';
14 |
15 | it('should render without crashing', () => {
16 | shallow( );
17 | });
18 | });
19 |
--------------------------------------------------------------------------------
/templates/redux-core/action-utilities/action-creator.js:
--------------------------------------------------------------------------------
1 | import createActionType from './action-type-creator';
2 |
3 | export default function CreateAction(reducerName, actionName) {
4 | if (!reducerName || !actionName) {
5 | throw 'Please provide a valid reducer and action name';
6 | }
7 | const actionType = createActionType(reducerName, actionName);
8 | return {
9 | actionType,
10 | action: payload => ({
11 | type: actionType,
12 | payload
13 | })
14 | };
15 | }
16 |
--------------------------------------------------------------------------------
/templates/redux-core/action-utilities/action-type-creator.js:
--------------------------------------------------------------------------------
1 | /*
2 | Add your app's name here.
3 | */
4 | export const appName = 'YourReduxApp';
5 |
6 | export default function createActionType(reducerName = '', actionName = '') {
7 | reducerName = reducerName.toString().trim();
8 | if (!reducerName) {
9 | throw new Error('Reducer name cannot be blank');
10 | }
11 | actionName = actionName.toString().trim();
12 | if (!actionName) {
13 | throw new Error('Action name cannot be blank');
14 | }
15 | return `${appName}/${reducerName}/${actionName}`;
16 | }
17 |
--------------------------------------------------------------------------------
/templates/redux-core/action-utilities/test/action-creator.spec.js:
--------------------------------------------------------------------------------
1 | import CreateAction from '../action-creator';
2 | import { appName } from '../action-type-creator';
3 |
4 | describe('Create Action - Unit Test', () => {
5 | it('should create an action for reducer', () => {
6 | const reducerName = 'some_reducer';
7 | const actionName = 'SOME_ACTION';
8 |
9 | const actual = new CreateAction(reducerName, actionName);
10 |
11 | const expected = {
12 | actionType: `${appName}/some_reducer/SOME_ACTION`,
13 | action: {
14 | type: `${appName}/some_reducer/SOME_ACTION`,
15 | payload: {
16 | name: 'field name',
17 | value: 'field value'
18 | }
19 | }
20 | };
21 |
22 | expect(actual.actionType).toEqual(expected.actionType);
23 | expect(
24 | actual.action({
25 | name: 'field name',
26 | value: 'field value'
27 | })
28 | ).toEqual(expected.action);
29 | });
30 |
31 | it('should fail if no reducer name is provided', () => {
32 | expect(CreateAction).toThrow('Please provide a valid reducer and action name');
33 | });
34 |
35 | it('should fail if no action name is provided', () => {
36 | expect(() => CreateAction('some_reducer')).toThrow(
37 | 'Please provide a valid reducer and action name'
38 | );
39 | });
40 | });
41 |
--------------------------------------------------------------------------------
/templates/redux-core/action-utilities/test/action-type-creator.spec.js:
--------------------------------------------------------------------------------
1 | import createActionType, { appName } from '../action-type-creator';
2 |
3 | describe('Build Action Name - Unit Test', () => {
4 | it('should return an action name', () => {
5 | const actual = createActionType('someReducer', 'SOME_ACTION');
6 | const expected = `${appName}/someReducer/SOME_ACTION`;
7 | expect(actual).toEqual(expected);
8 | });
9 |
10 | it('should throw error when reducer name not given', () => {
11 | expect(createActionType).toThrow('Reducer name cannot be blank');
12 | });
13 |
14 | it('should throw error when action name not given', () => {
15 | expect(() => createActionType('someReducer')).toThrow('Action name cannot be blank');
16 | });
17 | });
18 |
--------------------------------------------------------------------------------
/templates/redux-core/root-reducer.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 |
3 | /*
4 | Import all your reducers here.
5 | */
6 |
7 | export default combineReducers({
8 | /* Add all your reducers here*/
9 | });
10 |
--------------------------------------------------------------------------------
/templates/redux-core/store.js:
--------------------------------------------------------------------------------
1 | import { applyMiddleware, createStore } from 'redux';
2 | import ReduxThunk from 'redux-thunk';
3 |
4 | import rootReducer from './root-reducer';
5 |
6 | /*
7 | Use this as the store for your application (usually added in the `index.js` by using Redux's
8 | Provide to render you app).
9 | */
10 | export default createStore(rootReducer, applyMiddleware(ReduxThunk));
11 |
--------------------------------------------------------------------------------
/templates/web/react-redux/_template.styles.scss:
--------------------------------------------------------------------------------
1 | /*
2 | NOTE: Remember to add this style sheet o the main style sheet found in 'src/index.scss'.
3 | */
4 |
5 | /*
6 | Add all the component styling and nested styling here.
7 | */
8 |
9 | .TEMPLATE_KEBAB_CASE_NAME-wrapper {
10 | background-color: purple;
11 | text-align: center;
12 | padding: 10px;
13 |
14 | h1 {
15 | color: orange;
16 | font-size: 40px;
17 | }
18 |
19 | .button {
20 | color: black;
21 | font-size: 26px;
22 | border-radius: 5px;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/templates/web/react-redux/template.container.js:
--------------------------------------------------------------------------------
1 | import { bindActionCreators } from 'redux';
2 | import { connect } from 'react-redux';
3 |
4 | import TEMPLATE_UPPER_CAMEL_CASE_NAMEView from './TEMPLATE_KEBAB_CASE_NAME.view';
5 | import {
6 | exampleAction,
7 | exampleAsyncAction
8 | /*
9 | Import all the actions you wish to expose to the view here.
10 | */
11 | } from './TEMPLATE_KEBAB_CASE_NAME.reducer';
12 |
13 | export function mapStateToProps({ TEMPLATE_LOWER_CAMEL_CASE_NAMEReducer }) {
14 | return {
15 | exampleVariable: TEMPLATE_LOWER_CAMEL_CASE_NAMEReducer.exampleVariable
16 | /*
17 | Add all the state variables you wish to expose to the view here.
18 | */
19 | };
20 | }
21 |
22 | export function mapDispatchToProps(dispatch) {
23 | return bindActionCreators(
24 | {
25 | exampleAction,
26 | exampleAsyncAction
27 | /*
28 | Add all the actions you wish to expose to the view here.
29 | */
30 | },
31 | dispatch
32 | );
33 | }
34 |
35 | export default connect(mapStateToProps, mapDispatchToProps)(TEMPLATE_UPPER_CAMEL_CASE_NAMEView);
36 |
--------------------------------------------------------------------------------
/templates/web/react-redux/template.reducer.js:
--------------------------------------------------------------------------------
1 | /*
2 | NOTE: Remember to add this reducer to the root reducer found in 'src/redux/root-reducer.js'.
3 | */
4 |
5 | /*
6 | NOTE: The CreateAction directory my change based on your module's desired location.
7 | */
8 | import CreateAction from '../../redux/action-utilities/action-creator';
9 | /*
10 | Import all local modules here.
11 | */
12 |
13 | const reducerName = 'TEMPLATE_KEBAB_CASE_NAME';
14 |
15 | /*
16 | Here you can create all your actions with the CreateAction action factory (See example below).
17 |
18 | NOTE: It's important to use this factory to create standard actions so that all actions will be
19 | consistent - this will also eliminate duplicate code.
20 | */
21 | const example = new CreateAction(reducerName, 'EXAMPLE_ACTION');
22 | export const exampleAction = example.action;
23 |
24 | /*
25 | Add all Asynchronous actions here. These are used when the action needs to access state or when multiple
26 | actions need to be dispatched (See example below).
27 |
28 | NOTE: It's important to note that 'getState()' returns a shallow copy of state - so if you mutate it,
29 | your state will change along with it. Be careful of this!
30 | */
31 | export function exampleAsyncAction() {
32 | return (dispatch, getState) => {
33 | const { TEMPLATE_LOWER_CAMEL_CASE_NAMEReducer } = getState();
34 | dispatch(exampleAction(!TEMPLATE_LOWER_CAMEL_CASE_NAMEReducer.exampleVariable));
35 | };
36 | }
37 |
38 | export const initialState = {
39 | exampleVariable: false
40 | /*
41 | Add your reducer's initial state here.
42 | */
43 | };
44 |
45 | export default function TEMPLATE_LOWER_CAMEL_CASE_NAMEReducer(state = initialState, action) {
46 | switch (action.type) {
47 | case example.actionType:
48 | return { ...state, exampleVariable: action.payload };
49 | default:
50 | return state;
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/templates/web/react-redux/template.view.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import PropTypes from 'prop-types';
3 | /*
4 | Import all external modules here.
5 | */
6 |
7 | /*
8 | Import all local modules here.
9 | */
10 |
11 | export default class TEMPLATE_UPPER_CAMEL_CASE_NAMEView extends Component {
12 | constructor(props) {
13 | super(props);
14 |
15 | /*
16 | Add all required function binding here. If no binding is required, the constructor may be omitted.
17 | */
18 | this.renderHeaderExample = this.renderHeaderExample.bind(this);
19 | this.renderButtonExample = this.renderButtonExample.bind(this);
20 |
21 | /*
22 | NOTE: Do NOT use 'this.state'! State management is wat Redux is used for.
23 | */
24 | }
25 |
26 | componentDidMount() {
27 | /*
28 | If your screen needs to load data via the API immediately after rendering,
29 | call the load functions here in the `componentDidMount` function (See example below) -
30 | else, the `componentDidMount` function may be omitted.
31 | */
32 | this.props.exampleAsyncAction();
33 | }
34 |
35 | render() {
36 | return (
37 |
38 | {this.renderHeaderExample()}
39 | {this.renderButtonExample()}
40 |
41 | );
42 | }
43 |
44 | /*
45 | Add all helper functions here. Remember to 'bind' functions where 'this'
46 | needs to be used (See examples below). If 'this' does not need to be accessed,
47 | the function may be made static.
48 | */
49 | renderHeaderExample() {
50 | if (!this.props.exampleVariable) {
51 | return {"You've now fiddled with the template ( ͡° ͜ʖ ͡°)"} ;
52 | }
53 | return {'Welcome to our template (ง •̀_•́)ง'} ;
54 | }
55 |
56 | renderButtonExample() {
57 | return (
58 |
59 | {'Click Me'}
60 |
61 | );
62 | }
63 | }
64 |
65 | /*
66 | Add all the props (variables and functions) being passe through to this component (by redux or inline).
67 | This will make code completion and IDE inspection possible, and will make it easy to see what data is relevant
68 | in this module.
69 | */
70 | TEMPLATE_UPPER_CAMEL_CASE_NAMEView.propTypes = {
71 | exampleVariable: PropTypes.bool,
72 |
73 | exampleAction: PropTypes.func,
74 | exampleAsyncAction: PropTypes.func
75 | };
76 |
--------------------------------------------------------------------------------
/templates/web/react-redux/test/template.container.spec.js:
--------------------------------------------------------------------------------
1 | jest.mock('redux');
2 |
3 | import { bindActionCreators } from 'redux';
4 | import { mapStateToProps, mapDispatchToProps } from '../TEMPLATE_KEBAB_CASE_NAME.container';
5 | import {
6 | exampleAction,
7 | exampleAsyncAction,
8 | /*
9 | Import all the actions you wish to expose to the view here.
10 | */
11 | initialState
12 | } from '../TEMPLATE_KEBAB_CASE_NAME.reducer';
13 |
14 | describe('TEMPLATE_LOWER_CAMEL_CASE_NAMEContainer - Unit test', () => {
15 | function stateBefore() {
16 | return {
17 | TEMPLATE_LOWER_CAMEL_CASE_NAMEReducer: {
18 | ...initialState
19 | /*
20 | Setup your initial state for testing here.
21 | */
22 | }
23 | };
24 | }
25 |
26 | it('should map state to props', () => {
27 | const actual = mapStateToProps(stateBefore());
28 |
29 | const expected = {
30 | ...initialState
31 | /*
32 | Setup your initial state for verification here.
33 | */
34 | };
35 |
36 | expect(actual).toEqual(expected);
37 | });
38 |
39 | it('should map dispatch to props', () => {
40 | const dispatch = jest.fn();
41 |
42 | mapDispatchToProps(dispatch);
43 |
44 | expect(bindActionCreators).toHaveBeenCalledWith(
45 | {
46 | exampleAction,
47 | exampleAsyncAction
48 | /*
49 | Import all the actions you wish to expose to the view here.
50 | */
51 | },
52 | dispatch
53 | );
54 | });
55 | });
56 |
--------------------------------------------------------------------------------
/templates/web/react-redux/test/template.reducer.spec.js:
--------------------------------------------------------------------------------
1 | /*
2 | Add mocking setup here such as in the example:
3 |
4 | jest.mock('../../../service/some-api');
5 | import SomeApi from '../../../service/some-api';
6 | */
7 |
8 | import TEMPLATE_LOWER_CAMEL_CASE_NAMEReducer, {
9 | exampleAction,
10 | exampleAsyncAction,
11 | /*
12 | Add all the actions you wish to test here.
13 | */
14 | initialState
15 | } from '../TEMPLATE_KEBAB_CASE_NAME.reducer';
16 |
17 | describe('TEMPLATE_LOWER_CAMEL_CASE_NAMEReducer - Unit Test', () => {
18 | function stateBefore() {
19 | return {
20 | ...initialState
21 | /*
22 | Setup your initial state for testing here.
23 | */
24 | };
25 | }
26 |
27 | it('should return initial state when action is undefined', () => {
28 | const action = {};
29 |
30 | const actual = TEMPLATE_LOWER_CAMEL_CASE_NAMEReducer(undefined, action);
31 |
32 | const expected = {
33 | ...stateBefore()
34 | };
35 |
36 | expect(actual).toEqual(expected);
37 | });
38 |
39 | it('should return current state when unknown action is dispatched', () => {
40 | const action = { type: 'SOME_UNKNOWN_ACTION' };
41 |
42 | const currentState = {
43 | ...stateBefore()
44 | };
45 |
46 | const actual = TEMPLATE_LOWER_CAMEL_CASE_NAMEReducer(currentState, action);
47 |
48 | const expected = {
49 | ...stateBefore()
50 | };
51 |
52 | expect(actual).toEqual(expected);
53 | });
54 |
55 | /*
56 | Add all your action tests here. Add each action in its own 'describe' and test
57 | each of the action's scenarios in its own 'it'. Remember to test happy and sad cases.
58 | "Every bug is a test that wasn't written"
59 | */
60 | describe('exampleAction', () => {
61 | it('should send humans to Mars to recolonise', () => {
62 | const action = exampleAction(false);
63 |
64 | const actual = TEMPLATE_LOWER_CAMEL_CASE_NAMEReducer(stateBefore(), action);
65 |
66 | const expected = {
67 | ...stateBefore(),
68 | exampleVariable: false
69 | };
70 |
71 | expect(actual).toEqual(expected);
72 | });
73 | });
74 |
75 | /*
76 | Add all your asynchronous action tests here. Add each asynchronous action in its own 'describe' and test
77 | each of the asynchronous action's scenarios in its own 'it'. Remember to test happy and sad cases.
78 | If you have no asynchronous actions, this code may be omitted.
79 | */
80 | describe('exampleAsyncAction', () => {
81 | it('should dispatch exampleAction', () => {
82 | /*
83 | Mock dispatch and getState
84 | Note: If your Jest is on version 23 or higher you may use 'mockName' - this helps to
85 | indicate which mock function is being referenced. This can be used as shown in the example below:
86 |
87 | const getState = jest.fn().mockName('getState');
88 |
89 | const dispatch = jest.fn().mockName('dispatch');
90 |
91 | */
92 | const getState = jest.fn().mockImplementation(() => ({
93 | TEMPLATE_LOWER_CAMEL_CASE_NAMEReducer: { exampleVariable: false }
94 | }));
95 | const dispatch = jest.fn().mockImplementation();
96 |
97 | /*
98 | Test your async action using the mocked dispatch and getState
99 | */
100 | exampleAsyncAction()(dispatch, getState);
101 |
102 | /*
103 | Assert that dispatch has been called with example action
104 | Note: If your Jest is on version 23 or higher you may use 'toHaveBeenNthCalledWith' - this
105 | allows you to check the order in which your actions were called. This can be used as
106 | shown in the example below:
107 |
108 | expect(dispatch).toHaveBeenNthCalledWith(1, exampleAction(true));
109 | */
110 | expect(dispatch).toHaveBeenCalledWith(exampleAction(true));
111 | });
112 | });
113 | });
114 |
--------------------------------------------------------------------------------
/templates/web/react-redux/test/template.view.spec.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { shallow } from 'enzyme';
3 |
4 | import TEMPLATE_UPPER_CAMEL_CASE_NAMEView from '../TEMPLATE_KEBAB_CASE_NAME.view';
5 |
6 | describe('TEMPLATE_UPPER_CAMEL_CASE_NAMEView - Unit Test', () => {
7 | /*
8 | Here you can mock out any view function props not needed for rendering
9 | (i.e. state read/write functions from the reducer) so as to only test the view.
10 |
11 | These mocks may be omitted if no function props need to be mocked.
12 | */
13 | const exampleActionMock = jest.fn();
14 | const exampleAsyncActionMock = jest.fn();
15 |
16 | it('should render without crashing', () => {
17 | shallow(
18 |
22 | );
23 | });
24 | });
25 |
--------------------------------------------------------------------------------
/templates/web/react/_template.styles.scss:
--------------------------------------------------------------------------------
1 | /*
2 | NOTE: Remember to add this style sheet o the main style sheet found in 'src/index.scss'.
3 | */
4 |
5 | /*
6 | Add all the component styling and nested styling here.
7 | */
8 |
9 | .TEMPLATE_KEBAB_CASE_NAME-wrapper {
10 | background-color: purple;
11 | text-align: center;
12 | padding: 10px;
13 |
14 | h1 {
15 | color: orange;
16 | font-size: 40px;
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/templates/web/react/template.view.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import PropTypes from 'prop-types';
3 | /*
4 | Import all external modules here.
5 | */
6 |
7 | /*
8 | Import all local modules here.
9 | */
10 |
11 | export default class TEMPLATE_UPPER_CAMEL_CASE_NAMEView extends Component {
12 | constructor(props) {
13 | super(props);
14 |
15 | /*
16 | Add all required function binding here. If no binding is required, the constructor may be omitted.
17 | */
18 | this.renderTextExample = this.renderTextExample.bind(this);
19 | }
20 |
21 | componentDidMount() {
22 | /*
23 | If your screen needs to load data via the API immediately after rendering,
24 | call the load functions here in the `componentDidMount` function (See example below) -
25 | else, the `componentDidMount` function may be omitted.
26 | */
27 | }
28 |
29 | render() {
30 | return {this.renderTextExample()}
;
31 | }
32 |
33 | /*
34 | Add all helper functions here. Remember to 'bind' functions where 'this'
35 | needs to be used (See examples below). If 'this' does not need to be accessed,
36 | the function may be made static.
37 | */
38 | renderTextExample() {
39 | const message = `Welcome to our template view ${this.props.exampleVariable || '( ͡° ͜ʖ ͡°)'}`;
40 | return {message} ;
41 | }
42 | }
43 |
44 | /*
45 | Add all the props (variables and functions) being passe through to this component (by redux or inline).
46 | This will make code completion and IDE inspection possible, and will make it easy to see what data is relevant
47 | in this module.
48 | */
49 | TEMPLATE_UPPER_CAMEL_CASE_NAMEView.propTypes = {
50 | exampleVariable: PropTypes.string
51 | };
52 |
--------------------------------------------------------------------------------
/templates/web/react/test/template.view.spec.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { shallow } from 'enzyme';
3 |
4 | import TEMPLATE_UPPER_CAMEL_CASE_NAMEView from '../TEMPLATE_KEBAB_CASE_NAME.view';
5 |
6 | describe('TEMPLATE_UPPER_CAMEL_CASE_NAMEView - Unit Test', () => {
7 | /*
8 | Here you can mock out any view function props not needed for rendering
9 | (i.e. state read/write functions passed in as props) so as to only test the view.
10 |
11 | These mocks may be omitted if no function props need to be mocked.
12 | */
13 |
14 | it('should render without crashing', () => {
15 | shallow( );
16 | });
17 | });
18 |
--------------------------------------------------------------------------------