-
-
Notifications
You must be signed in to change notification settings - Fork 2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(schematics): add selector schematics (#2160)
Closes #2140
- Loading branch information
1 parent
8110c32
commit 78817c7
Showing
11 changed files
with
456 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
12 changes: 12 additions & 0 deletions
12
...c/selector/files/__name@dasherize@if-flat__/__name@dasherize__.selectors.spec.ts.template
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
<% if(feature) { %>import * as from<%= classify(name) %> from '<%= reducerPath %>'; | ||
import { select<%= classify(name) %>State } from './<%= dasherize(name) %>.selectors';<% } %> | ||
|
||
describe('<%= classify(name) %> Selectors', () => { | ||
it('should select the feature state', () => { | ||
<% if(feature) { %>const result = select<%= classify(name) %>State({ | ||
[from<%= classify(name) %>.<%= camelize(name) %>FeatureKey]: {} | ||
}); | ||
|
||
expect(result).toEqual({});<% } %> | ||
}); | ||
}); |
6 changes: 6 additions & 0 deletions
6
...cs/src/selector/files/__name@dasherize@if-flat__/__name@dasherize__.selectors.ts.template
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
import { createFeatureSelector, createSelector } from '@ngrx/store'; | ||
<% if(feature) { %>import * as from<%= classify(name) %> from '<%= reducerPath %>'; | ||
|
||
export const select<%= classify(name) %>State = createFeatureSelector<from<%= classify(name) %>.State>( | ||
from<%= classify(name) %>.<%= camelize(name) %>FeatureKey | ||
);<% } %> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,187 @@ | ||
import { tags } from '@angular-devkit/core'; | ||
import { | ||
SchematicTestRunner, | ||
UnitTestTree, | ||
} from '@angular-devkit/schematics/testing'; | ||
import * as path from 'path'; | ||
import { Schema as SelectorOptions } from './schema'; | ||
import { | ||
getTestProjectPath, | ||
createWorkspace, | ||
} from '../../../schematics-core/testing'; | ||
|
||
describe('Selector Schematic', () => { | ||
const schematicRunner = new SchematicTestRunner( | ||
'@ngrx/schematics', | ||
path.join(__dirname, '../../collection.json') | ||
); | ||
const defaultOptions: SelectorOptions = { | ||
name: 'foo', | ||
project: 'bar', | ||
spec: true, | ||
}; | ||
|
||
const projectPath = getTestProjectPath(); | ||
|
||
let appTree: UnitTestTree; | ||
|
||
beforeEach(async () => { | ||
appTree = await createWorkspace(schematicRunner, appTree); | ||
}); | ||
|
||
it('should create selector files', () => { | ||
const tree = schematicRunner.runSchematic( | ||
'selector', | ||
defaultOptions, | ||
appTree | ||
); | ||
|
||
const selectorsContent = tree.readContent( | ||
`${projectPath}/src/app/foo.selectors.ts` | ||
); | ||
const specContent = tree.readContent( | ||
`${projectPath}/src/app/foo.selectors.spec.ts` | ||
); | ||
|
||
expect(cleanString(selectorsContent)).toBe( | ||
cleanString( | ||
tags.stripIndent`import { createFeatureSelector, createSelector } from '@ngrx/store';` | ||
) | ||
); | ||
|
||
expect(cleanString(specContent)).toBe( | ||
cleanString(tags.stripIndent` | ||
describe('Foo Selectors', () => { | ||
it('should select the feature state', () => { | ||
** | ||
}); | ||
});`) | ||
); | ||
}); | ||
|
||
it('should not create a spec file if spec is false', () => { | ||
const options = { | ||
...defaultOptions, | ||
spec: false, | ||
}; | ||
const tree = schematicRunner.runSchematic('selector', options, appTree); | ||
|
||
expect( | ||
tree.files.includes(`${projectPath}/src/app/foo.selectors.spec.ts`) | ||
).toBeFalsy(); | ||
}); | ||
|
||
it('should group selectors if group is true', () => { | ||
const options = { | ||
...defaultOptions, | ||
group: true, | ||
}; | ||
const tree = schematicRunner.runSchematic('selector', options, appTree); | ||
|
||
expect( | ||
tree.files.includes(`${projectPath}/src/app/selectors/foo.selectors.ts`) | ||
).toBeTruthy(); | ||
expect( | ||
tree.files.includes( | ||
`${projectPath}/src/app/selectors/foo.selectors.spec.ts` | ||
) | ||
).toBeTruthy(); | ||
}); | ||
|
||
it('should not flatten selectors if flat is false', () => { | ||
const options = { | ||
...defaultOptions, | ||
flat: false, | ||
}; | ||
const tree = schematicRunner.runSchematic('selector', options, appTree); | ||
|
||
expect( | ||
tree.files.includes(`${projectPath}/src/app/foo/foo.selectors.ts`) | ||
).toBeTruthy(); | ||
expect( | ||
tree.files.includes(`${projectPath}/src/app/foo/foo.selectors.spec.ts`) | ||
).toBeTruthy(); | ||
}); | ||
|
||
describe('With feature flag', () => { | ||
it('should create a selector', () => { | ||
const options = { | ||
...defaultOptions, | ||
feature: true, | ||
}; | ||
|
||
const tree = schematicRunner.runSchematic('selector', options, appTree); | ||
const selectorsContent = tree.readContent( | ||
`${projectPath}/src/app/foo.selectors.ts` | ||
); | ||
const specContent = tree.readContent( | ||
`${projectPath}/src/app/foo.selectors.spec.ts` | ||
); | ||
|
||
expect(cleanString(selectorsContent)).toBe( | ||
cleanString(tags.stripIndent` | ||
import { createFeatureSelector, createSelector } from '@ngrx/store'; | ||
import * as fromFoo from './foo.reducer'; | ||
export const selectFooState = createFeatureSelector<fromFoo.State>( | ||
fromFoo.fooFeatureKey | ||
); | ||
`) | ||
); | ||
|
||
expect(cleanString(specContent)).toBe( | ||
cleanString(tags.stripIndent` | ||
import * as fromFoo from './foo.reducer'; | ||
import { selectFooState } from './foo.selectors'; | ||
describe('Foo Selectors', () => { | ||
it('should select the feature state', () => { | ||
const result = selectFooState({ | ||
[fromFoo.fooFeatureKey]: {} | ||
}); | ||
expect(result).toEqual({}); | ||
}); | ||
}); | ||
`) | ||
); | ||
}); | ||
|
||
it('should group and nest the selectors within a feature', () => { | ||
const options = { | ||
...defaultOptions, | ||
feature: true, | ||
group: true, | ||
flat: false, | ||
}; | ||
|
||
const tree = schematicRunner.runSchematic('selector', options, appTree); | ||
const selectorPath = `${projectPath}/src/app/selectors/foo/foo.selectors.ts`; | ||
const specPath = `${projectPath}/src/app/selectors/foo/foo.selectors.spec.ts`; | ||
|
||
expect(tree.files.includes(selectorPath)).toBeTruthy(); | ||
expect(tree.files.includes(specPath)).toBeTruthy(); | ||
|
||
const selectorContent = tree.readContent(selectorPath); | ||
expect(selectorContent).toMatch( | ||
/import\ \* as fromFoo from\ \'\.\.\/\.\.\/reducers\/foo\/foo\.reducer';/ | ||
); | ||
|
||
const specContent = tree.readContent(specPath); | ||
expect(specContent).toMatch( | ||
/import\ \* as fromFoo from\ \'\.\.\/\.\.\/reducers\/foo\/foo\.reducer';/ | ||
); | ||
expect(specContent).toMatch( | ||
/import\ \{ selectFooState \} from\ \'\.\/foo\.selectors';/ | ||
); | ||
}); | ||
}); | ||
|
||
function cleanString(value: string) { | ||
// ** to mark an empty line (VSCode removes whitespace lines) | ||
return value | ||
.replace(/\r\n/g, '\n') | ||
.replace(/\*\*/g, '') | ||
.trim(); | ||
} | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
import { | ||
Rule, | ||
SchematicContext, | ||
Tree, | ||
apply, | ||
applyTemplates, | ||
branchAndMerge, | ||
chain, | ||
filter, | ||
mergeWith, | ||
move, | ||
noop, | ||
url, | ||
} from '@angular-devkit/schematics'; | ||
import { | ||
getProjectPath, | ||
parseName, | ||
stringUtils, | ||
} from '@ngrx/schematics/schematics-core'; | ||
import { Schema as SelectorOptions } from './schema'; | ||
|
||
export default function(options: SelectorOptions): Rule { | ||
return (host: Tree, context: SchematicContext) => { | ||
options.path = getProjectPath(host, options); | ||
|
||
const parsedPath = parseName(options.path, options.name || ''); | ||
options.name = parsedPath.name; | ||
options.path = parsedPath.path; | ||
|
||
const templateSource = apply(url('./files'), [ | ||
options.spec | ||
? noop() | ||
: filter(path => !path.endsWith('.spec.ts.template')), | ||
applyTemplates({ | ||
...stringUtils, | ||
'if-flat': (s: string) => | ||
stringUtils.group( | ||
options.flat ? '' : s, | ||
options.group ? 'selectors' : '' | ||
), | ||
reducerPath: `${relativePath(options)}${stringUtils.dasherize( | ||
options.name | ||
)}.reducer`, | ||
...(options as object), | ||
} as any), | ||
move(parsedPath.path), | ||
]); | ||
|
||
return chain([branchAndMerge(chain([mergeWith(templateSource)]))])( | ||
host, | ||
context | ||
); | ||
}; | ||
} | ||
|
||
function relativePath(options: SelectorOptions) { | ||
if (options.feature) { | ||
return stringUtils.featurePath( | ||
options.group, | ||
options.flat, | ||
'reducers', | ||
stringUtils.dasherize(options.name) | ||
); | ||
} | ||
|
||
return ''; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
{ | ||
"$schema": "http://json-schema.org/schema", | ||
"id": "SchematicsNgRxSelector", | ||
"title": "NgRx Selector Options Schema", | ||
"type": "object", | ||
"properties": { | ||
"name": { | ||
"description": "The name of the selector.", | ||
"type": "string", | ||
"$default": { | ||
"$source": "argv", | ||
"index": 0 | ||
} | ||
}, | ||
"path": { | ||
"type": "string", | ||
"format": "path", | ||
"description": "The path to create the selectors.", | ||
"visible": false | ||
}, | ||
"project": { | ||
"type": "string", | ||
"description": "The name of the project.", | ||
"aliases": ["p"] | ||
}, | ||
"spec": { | ||
"type": "boolean", | ||
"description": "Specifies if a spec file is generated.", | ||
"default": false | ||
}, | ||
"flat": { | ||
"type": "boolean", | ||
"default": true, | ||
"description": "Flag to indicate if a dir is created." | ||
}, | ||
"group": { | ||
"type": "boolean", | ||
"default": false, | ||
"description": "Group selector file within 'selectors' folder", | ||
"aliases": ["g"] | ||
}, | ||
"feature": { | ||
"type": "boolean", | ||
"default": false, | ||
"description": "Flag to indicate if part of a feature schematic.", | ||
"visible": false | ||
} | ||
}, | ||
"required": [] | ||
} |
Oops, something went wrong.