Skip to content

Commit f2a6ffe

Browse files
author
Jarek Kuliga
committed
feat: 🎸 add store structure generator
1 parent ff23e31 commit f2a6ffe

10 files changed

Lines changed: 146 additions & 3 deletions

File tree

‎generators/structure-inner/index.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ const moduleListMessage = (task: string) =>
1111
const moduleInputMessage = (task: string) =>
1212
`What's the new Module name to put the ${task} into?`;
1313

14-
const tasksToModuleNameIdentityCheck = [Task.API, Task.MODEL];
14+
const tasksToModuleNameIdentityCheck = [Task.API, Task.MODEL, Task.STORE];
1515

1616
export = class StructureInnerGenerator extends Generator {
1717
private module: string;
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import camelCase from "lodash/fp/camelCase";
2+
import toUpper from "lodash/fp/toUpper";
3+
import Generator from "yeoman-generator";
4+
5+
import { nameQuestion } from "../../src/questions";
6+
7+
export = class StructureApiGenerator extends Generator {
8+
private name: string;
9+
private module: string;
10+
11+
public async prompting() {
12+
const { name } = await this.prompt(
13+
nameQuestion("Store", this.options.name)
14+
);
15+
16+
this.name = this.options.name || name;
17+
}
18+
19+
public makeModule() {
20+
this.module = this.options.module || this.name;
21+
}
22+
23+
public writing() {
24+
["action", "detector", "reducer", "selector", "state"].forEach(file => {
25+
this.fs.copyTpl(
26+
this.templatePath(`${file}.ts`),
27+
this.destinationPath(
28+
`./${this.config.get("rootDir")}/${this.module}/store/${this.name}/${
29+
this.name
30+
}.${file}.ts`
31+
),
32+
{
33+
name: this.name,
34+
nameCamelCase: camelCase(this.name),
35+
nameUpperCase: toUpper(this.name)
36+
}
37+
);
38+
});
39+
}
40+
};
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export namespace <%= name %>Action {}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const <%= nameCamelCase %>Detector;
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import { combineReducers } from "redux";
2+
import { <%= name %>State } from './<%= name %>.state';
3+
4+
export const <%= nameCamelCase %>Reducer = combineReducers<<%= name %>State>({});
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { <%= name %>MountedState, <%= name %>State } from './<%= name %>.state';
2+
3+
export namespace <%= name %>Selector {
4+
const getDomain = (state?: <%= name %>MountedState) => (state && state.<%= nameCamelCase %>) || ({} as <%= name %>State)
5+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
export type <%= name %>State = {};
2+
3+
export namespace <%= name %>State {
4+
export const INITIAL: <%= name %>State = {};
5+
export const DOMAIN = "<%= nameUpperCase %>";
6+
}
7+
8+
export type <%= name %>MountedState = {
9+
<%= nameCamelCase %>?: <%= name %>State;
10+
};

‎generators/structure/index.ts‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ export = class StructureGenerator extends Generator {
2828
{
2929
name: `Module ${emoji.get("large_orange_diamond")}`,
3030
value: Task.MODULE
31+
},
32+
{
33+
name: `Store`,
34+
value: Task.STORE
3135
}
3236
]
3337
}
@@ -40,6 +44,7 @@ export = class StructureGenerator extends Generator {
4044
case Task.COMPONENT:
4145
case Task.API:
4246
case Task.MODEL:
47+
case Task.STORE:
4348
this.composeWith(require.resolve(`../structure-inner`), { task });
4449
}
4550
}

‎src/enum/Task.ts‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,17 @@ export enum Task {
44
COMPONENT = "component",
55
API = "api",
66
MODEL = "model",
7-
MODULE = "module"
7+
MODULE = "module",
8+
STORE = "store"
89
}
910

1011
export namespace Task {
1112
const LABELS = {
1213
[Task.COMPONENT]: upperFirst(Task.COMPONENT),
1314
[Task.API]: upperFirst(Task.API),
1415
[Task.MODEL]: upperFirst(Task.MODEL),
15-
[Task.MODULE]: upperFirst(Task.MODULE)
16+
[Task.MODULE]: upperFirst(Task.MODULE),
17+
[Task.STORE]: upperFirst(Task.STORE)
1618
};
1719

1820
export function getLabel(task: Task): string {
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import assert from "yeoman-assert";
2+
import helpers from "yeoman-test";
3+
4+
describe("yo codibly-ts:structure-store", () => {
5+
beforeAll(async () => {
6+
await helpers
7+
.run(require("../generators/structure"), {
8+
resolved: require.resolve("../generators/structure/index.js"),
9+
namespace: "codibly-ts:structure"
10+
})
11+
.withLocalConfig({ rootDir: "tmp" })
12+
.withPrompts({
13+
task: "store",
14+
isTaskNameSameAsModule: false,
15+
module: "UserDetails",
16+
name: "User"
17+
});
18+
});
19+
20+
const path = "tmp/UserDetails/store/User/User";
21+
22+
const actionPath = `${path}.action.ts`;
23+
const detectorPath = `${path}.detector.ts`;
24+
const reducerPath = `${path}.reducer.ts`;
25+
const selectorPath = `${path}.selector.ts`;
26+
const statePath = `${path}.state.ts`;
27+
28+
it("generates required files", () => {
29+
assert.file([
30+
actionPath,
31+
detectorPath,
32+
reducerPath,
33+
selectorPath,
34+
statePath
35+
]);
36+
});
37+
38+
it("creates proper content in Action", () => {
39+
assert.fileContent(actionPath, "namespace UserAction {}");
40+
});
41+
42+
it("creates proper content in Detector", () => {
43+
assert.fileContent(detectorPath, "const userDetector;");
44+
});
45+
46+
it("creates proper content in Reducer", () => {
47+
assert.fileContent(
48+
reducerPath,
49+
"import { UserState } from './User.state';"
50+
);
51+
assert.fileContent(
52+
reducerPath,
53+
"export const userReducer = combineReducers<UserState>({});"
54+
);
55+
});
56+
57+
it("create proper content in Selector", () => {
58+
assert.fileContent(
59+
selectorPath,
60+
"import { UserMountedState, UserState } from './User.state';"
61+
);
62+
assert.fileContent(selectorPath, "namespace UserSelector");
63+
assert.fileContent(
64+
selectorPath,
65+
"const getDomain = (state?: UserMountedState) => (state && state.user) || ({} as UserState)"
66+
);
67+
});
68+
69+
it("create proper content in State", () => {
70+
assert.fileContent(statePath, "namespace UserState");
71+
assert.fileContent(statePath, "const INITIAL: UserState = {};");
72+
assert.fileContent(statePath, 'const DOMAIN = "USER";');
73+
assert.fileContent(statePath, "type UserMountedState");
74+
});
75+
});

0 commit comments

Comments
 (0)