From f1082fe4e14d90e9c453900b93d559bb2c3468cf Mon Sep 17 00:00:00 2001 From: Brandon Date: Sat, 13 Jan 2018 12:50:58 -0800 Subject: [PATCH] feat(Schematics): Add effect to registered effects array (#717) --- modules/schematics/src/effect/index.spec.ts | 73 ++++++++++++++++++- modules/schematics/src/utility/ast-utils.ts | 43 ++++++++++- .../src/utility/test/create-app-module.ts | 31 ++++++++ 3 files changed, 145 insertions(+), 2 deletions(-) diff --git a/modules/schematics/src/effect/index.spec.ts b/modules/schematics/src/effect/index.spec.ts index 84a0a0fac5..106aa5eaf9 100644 --- a/modules/schematics/src/effect/index.spec.ts +++ b/modules/schematics/src/effect/index.spec.ts @@ -1,7 +1,11 @@ import { Tree, VirtualTree } from '@angular-devkit/schematics'; import { SchematicTestRunner } from '@angular-devkit/schematics/testing'; import * as path from 'path'; -import { createAppModule, getFileContent } from '../utility/test'; +import { + createAppModule, + getFileContent, + createAppModuleWithEffects, +} from '../utility/test'; import { Schema as EffectOptions } from './schema'; describe('Effect Schematic', () => { @@ -16,6 +20,8 @@ describe('Effect Schematic', () => { spec: true, module: undefined, flat: false, + feature: false, + root: false, }; let appTree: Tree; @@ -77,4 +83,69 @@ describe('Effect Schematic', () => { ); expect(files.indexOf('/src/app/foo/foo.effects.spec.ts')).toEqual(-1); }); + + it('should register the root effect in the provided module', () => { + const options = { ...defaultOptions, root: true, module: 'app.module.ts' }; + + const tree = schematicRunner.runSchematic('effect', options, appTree); + const content = getFileContent(tree, '/src/app/app.module.ts'); + + expect(content).toMatch(/EffectsModule\.forRoot\(\[FooEffects\]\)/); + }); + + it('should register the feature effect in the provided module', () => { + const options = { ...defaultOptions, module: 'app.module.ts' }; + + const tree = schematicRunner.runSchematic('effect', options, appTree); + const content = getFileContent(tree, '/src/app/app.module.ts'); + + expect(content).toMatch(/EffectsModule\.forFeature\(\[FooEffects\]\)/); + }); + + it('should add an effect to the empty array of registered effects', () => { + const storeModule = '/src/app/store.module.ts'; + const options = { ...defaultOptions, module: 'store.module.ts' }; + appTree = createAppModuleWithEffects( + appTree, + storeModule, + 'EffectsModule.forRoot([])' + ); + + const tree = schematicRunner.runSchematic('effect', options, appTree); + const content = getFileContent(tree, storeModule); + + expect(content).toMatch(/EffectsModule\.forRoot\(\[FooEffects\]\)/); + }); + + it('should add an effect to the existing registered effects', () => { + const storeModule = '/src/app/store.module.ts'; + const options = { ...defaultOptions, module: 'store.module.ts' }; + appTree = createAppModuleWithEffects( + appTree, + storeModule, + 'EffectsModule.forRoot([UserEffects])' + ); + + const tree = schematicRunner.runSchematic('effect', options, appTree); + const content = getFileContent(tree, '/src/app/store.module.ts'); + + expect(content).toMatch( + /EffectsModule\.forRoot\(\[UserEffects, FooEffects\]\)/ + ); + }); + + it('should not add an effect to registered effects defined with a variable', () => { + const storeModule = '/src/app/store.module.ts'; + const options = { ...defaultOptions, module: 'store.module.ts' }; + appTree = createAppModuleWithEffects( + appTree, + storeModule, + 'EffectsModule.forRoot(effects)' + ); + + const tree = schematicRunner.runSchematic('effect', options, appTree); + const content = getFileContent(tree, '/src/app/store.module.ts'); + + expect(content).not.toMatch(/EffectsModule\.forRoot\(\[FooEffects\]\)/); + }); }); diff --git a/modules/schematics/src/utility/ast-utils.ts b/modules/schematics/src/utility/ast-utils.ts index c249445242..e67f3daabf 100644 --- a/modules/schematics/src/utility/ast-utils.ts +++ b/modules/schematics/src/utility/ast-utils.ts @@ -352,9 +352,50 @@ function _addSymbolToNgModuleMetadata( return []; } - node = node[node.length - 1]; + const effectsModule = nodeArray.find(node => + node.getText().includes('EffectsModule') + ); + + if (effectsModule && symbolName.includes('EffectsModule')) { + const effectsArgs = (effectsModule as ts.CallExpression).arguments.shift(); + + if ( + effectsArgs && + effectsArgs.kind === ts.SyntaxKind.ArrayLiteralExpression + ) { + const effectsElements = (effectsArgs as ts.ArrayLiteralExpression) + .elements; + const [, effectsSymbol] = (symbolName).match(/\[(.*)\]/); + + let epos; + if (effectsElements.length === 0) { + epos = effectsArgs.getStart() + 1; + return [new InsertChange(ngModulePath, epos, effectsSymbol)]; + } else { + const lastEffect = effectsElements[ + effectsElements.length - 1 + ] as ts.Expression; + epos = lastEffect.getEnd(); + // Get the indentation of the last element, if any. + const text: any = lastEffect.getFullText(source); + + let effectInsert: string; + if (text.match('^\r?\r?\n')) { + effectInsert = `,${text.match(/^\r?\n\s+/)[0]}${effectsSymbol}`; + } else { + effectInsert = `, ${effectsSymbol}`; + } + + return [new InsertChange(ngModulePath, epos, effectInsert)]; + } + } else { + return []; + } + } } + node = node[node.length - 1]; + let toInsert: string; let position = node.getEnd(); if (node.kind == ts.SyntaxKind.ObjectLiteralExpression) { diff --git a/modules/schematics/src/utility/test/create-app-module.ts b/modules/schematics/src/utility/test/create-app-module.ts index 69b41a6611..b431715436 100644 --- a/modules/schematics/src/utility/test/create-app-module.ts +++ b/modules/schematics/src/utility/test/create-app-module.ts @@ -24,3 +24,34 @@ export function createAppModule(tree: Tree, path?: string): Tree { return tree; } + +export function createAppModuleWithEffects( + tree: Tree, + path: string, + effects?: string +): Tree { + tree.create( + path || '/src/app/app.module.ts', + ` + import { BrowserModule } from '@angular/platform-browser'; + import { NgModule } from '@angular/core'; + import { AppComponent } from './app.component'; + import { EffectsModule } from '@ngrx/effects'; + + @NgModule({ + declarations: [ + AppComponent + ], + imports: [ + BrowserModule, + ${effects} + ], + providers: [], + bootstrap: [AppComponent] + }) + export class AppModule { } + ` + ); + + return tree; +}