feat(angular): add standalone support to NgRx generator (#14141)
This commit is contained in:
parent
49aa44bb81
commit
5fba936254
@ -28,13 +28,17 @@
|
||||
"module": {
|
||||
"type": "string",
|
||||
"description": "The path to the `NgModule` where the feature state will be registered. The host directory will create/use the new state directory.",
|
||||
"x-prompt": "What is the path to the module where this NgRx state should be registered?",
|
||||
"x-deprecated": "This option will be removed in a future version of Nx. Please switch to using --parent instead."
|
||||
},
|
||||
"parent": {
|
||||
"type": "string",
|
||||
"description": "The path to the `NgModule` where the feature state will be registered. The host directory will create/use the new state directory.",
|
||||
"x-prompt": "What is the path to the module where this NgRx state should be registered?"
|
||||
"description": "The path to the `NgModule` or the `Routes` definition file (for Standalone API usage) where the feature state will be registered. The host directory will create/use the new state directory.",
|
||||
"x-prompt": "What is the path to the module or Routes definition where this NgRx state should be registered?"
|
||||
},
|
||||
"route": {
|
||||
"type": "string",
|
||||
"description": "The route that the Standalone NgRx Providers should be added to.",
|
||||
"default": "''"
|
||||
},
|
||||
"directory": {
|
||||
"type": "string",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`ngrx generated unit tests should generate specs for the ngrx effects 1`] = `
|
||||
exports[`ngrx NgModule Syntax generated unit tests should generate specs for the ngrx effects 1`] = `
|
||||
"import { TestBed } from '@angular/core/testing';
|
||||
import { provideMockActions } from '@ngrx/effects/testing';
|
||||
import { Action } from '@ngrx/store';
|
||||
@ -41,7 +41,7 @@ describe('SuperUsersEffects', () => {
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`ngrx generated unit tests should generate specs for the ngrx facade 1`] = `
|
||||
exports[`ngrx NgModule Syntax generated unit tests should generate specs for the ngrx facade 1`] = `
|
||||
"import { NgModule } from '@angular/core';
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { EffectsModule } from '@ngrx/effects';
|
||||
@ -144,7 +144,7 @@ describe('SuperUsersFacade', () => {
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`ngrx generated unit tests should generate specs for the ngrx reducer 1`] = `
|
||||
exports[`ngrx NgModule Syntax generated unit tests should generate specs for the ngrx reducer 1`] = `
|
||||
"import { Action } from '@ngrx/store';
|
||||
|
||||
import * as SuperUsersActions from './super-users.actions';
|
||||
@ -185,7 +185,7 @@ describe('SuperUsers Reducer', () => {
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`ngrx generated unit tests should generate specs for the ngrx selectors 1`] = `
|
||||
exports[`ngrx NgModule Syntax generated unit tests should generate specs for the ngrx selectors 1`] = `
|
||||
"import { SuperUsersEntity } from './super-users.models';
|
||||
import { superUsersAdapter, SuperUsersPartialState, initialSuperUsersState } from './super-users.reducer';
|
||||
import * as SuperUsersSelectors from './super-users.selectors';
|
||||
@ -247,7 +247,7 @@ describe('SuperUsers Selectors', () => {
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`ngrx should add a root module with feature module when minimal is set to false 1`] = `
|
||||
exports[`ngrx NgModule Syntax should add a root module with feature module when minimal is set to false 1`] = `
|
||||
"
|
||||
import { NgModule } from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
@ -273,7 +273,7 @@ import { StoreRouterConnectingModule } from '@ngrx/router-store';
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`ngrx should add a root module with feature module when minimal is set to false using --module 1`] = `
|
||||
exports[`ngrx NgModule Syntax should add a root module with feature module when minimal is set to false using --module 1`] = `
|
||||
"
|
||||
import { NgModule } from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
@ -299,7 +299,7 @@ import { StoreRouterConnectingModule } from '@ngrx/router-store';
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`ngrx should add an empty root module when minimal and root are set to true 1`] = `
|
||||
exports[`ngrx NgModule Syntax should add an empty root module when minimal and root are set to true 1`] = `
|
||||
"
|
||||
import { NgModule } from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
@ -323,7 +323,7 @@ import { StoreRouterConnectingModule } from '@ngrx/router-store';
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`ngrx should generate a models file for the feature 1`] = `
|
||||
exports[`ngrx NgModule Syntax should generate a models file for the feature 1`] = `
|
||||
"/**
|
||||
* Interface for the 'Users' data
|
||||
*/
|
||||
@ -333,7 +333,7 @@ export interface UsersEntity {
|
||||
};"
|
||||
`;
|
||||
|
||||
exports[`ngrx should generate the ngrx actions 1`] = `
|
||||
exports[`ngrx NgModule Syntax should generate the ngrx actions 1`] = `
|
||||
"import { createAction, props } from '@ngrx/store';
|
||||
import { UsersEntity } from './users.models';
|
||||
|
||||
@ -353,36 +353,33 @@ export const loadUsersFailure = createAction(
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`ngrx should generate the ngrx effects 1`] = `
|
||||
exports[`ngrx NgModule Syntax should generate the ngrx effects 1`] = `
|
||||
"import { Injectable, inject } from '@angular/core';
|
||||
import { createEffect, Actions, ofType } from '@ngrx/effects';
|
||||
import { fetch } from '@nrwl/angular';
|
||||
|
||||
import * as UsersActions from './users.actions';
|
||||
import * as UsersFeature from './users.reducer';
|
||||
|
||||
import {switchMap, catchError, of} from 'rxjs';
|
||||
|
||||
@Injectable()
|
||||
export class UsersEffects {
|
||||
private actions$ = inject(Actions);
|
||||
|
||||
init$ = createEffect(() => this.actions$.pipe(
|
||||
ofType(UsersActions.initUsers),
|
||||
fetch({
|
||||
run: (action) => {
|
||||
// Your custom service 'load' logic goes here. For now just return a success action...
|
||||
return UsersActions.loadUsersSuccess({ users: [] });
|
||||
},
|
||||
onError: (action, error) => {
|
||||
switchMap(() => of(UsersActions.loadUsersSuccess({ users: [] }))),
|
||||
catchError((error) => {
|
||||
console.error('Error', error);
|
||||
return UsersActions.loadUsersFailure({ error });
|
||||
return of(UsersActions.loadUsersFailure({ error }));
|
||||
}
|
||||
})
|
||||
)
|
||||
));
|
||||
}
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`ngrx should generate the ngrx facade 1`] = `
|
||||
exports[`ngrx NgModule Syntax should generate the ngrx facade 1`] = `
|
||||
"import { Injectable, inject } from '@angular/core';
|
||||
import { select, Store, Action } from '@ngrx/store';
|
||||
|
||||
@ -413,7 +410,7 @@ export class UsersFacade {
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`ngrx should generate the ngrx reducer 1`] = `
|
||||
exports[`ngrx NgModule Syntax should generate the ngrx reducer 1`] = `
|
||||
"import { EntityState, EntityAdapter, createEntityAdapter } from '@ngrx/entity';
|
||||
import { createReducer, on, Action } from '@ngrx/store';
|
||||
|
||||
@ -458,7 +455,7 @@ export function usersReducer(state: UsersState | undefined, action: Action) {
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`ngrx should generate the ngrx selectors 1`] = `
|
||||
exports[`ngrx NgModule Syntax should generate the ngrx selectors 1`] = `
|
||||
"import { createFeatureSelector, createSelector } from '@ngrx/store';
|
||||
import { USERS_FEATURE_KEY, UsersState, usersAdapter } from './users.reducer';
|
||||
|
||||
@ -500,7 +497,7 @@ export const selectEntity = createSelector(
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`ngrx should not generate imports when skipImport is true 1`] = `
|
||||
exports[`ngrx NgModule Syntax should not generate imports when skipImport is true 1`] = `
|
||||
"
|
||||
import { NgModule } from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
@ -515,7 +512,7 @@ exports[`ngrx should not generate imports when skipImport is true 1`] = `
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`ngrx should update the entry point file correctly when barrels is true 1`] = `
|
||||
exports[`ngrx NgModule Syntax should update the entry point file correctly when barrels is true 1`] = `
|
||||
"import * as SuperUsersActions from './lib/+state/super-users.actions';
|
||||
|
||||
import * as SuperUsersFeature from './lib/+state/super-users.reducer';
|
||||
@ -532,7 +529,7 @@ export { SuperUsersActions, SuperUsersFeature, SuperUsersSelectors };
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`ngrx should update the entry point file with no facade 1`] = `
|
||||
exports[`ngrx NgModule Syntax should update the entry point file with no facade 1`] = `
|
||||
"export * from './lib/+state/super-users.models';
|
||||
export * from './lib/+state/super-users.selectors';
|
||||
export * from './lib/+state/super-users.reducer';
|
||||
@ -542,7 +539,7 @@ export * from './lib/+state/super-users.actions';
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`ngrx should update the entry point file with the right exports 1`] = `
|
||||
exports[`ngrx NgModule Syntax should update the entry point file with the right exports 1`] = `
|
||||
"export * from './lib/+state/super-users.facade';
|
||||
export * from './lib/+state/super-users.models';
|
||||
export * from './lib/+state/super-users.selectors';
|
||||
@ -552,3 +549,58 @@ export * from './lib/+state/super-users.actions';
|
||||
export * from './lib/flights.module';
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`ngrx Standalone APIs should add a root module with feature module when minimal is set to false 1`] = `
|
||||
"import { bootstrapApplication } from '@angular/platform-browser';
|
||||
import { provideRouter, withEnabledBlockingInitialNavigation } from '@angular/router';
|
||||
import { AppComponent } from './app/app.component';
|
||||
import { appRoutes } from './app/app.routes';
|
||||
import { provideStore, provideState } from '@ngrx/store';
|
||||
import { provideEffects } from '@ngrx/effects';
|
||||
import * as fromUsers from './+state/users.reducer';
|
||||
import { UsersEffects } from './+state/users.effects';
|
||||
|
||||
bootstrapApplication(AppComponent, {
|
||||
providers: [provideEffects(UsersEffects),provideState(fromUsers.USERS_FEATURE_KEY, fromUsers.usersReducer),provideEffects(),provideStore(),provideRouter(appRoutes, withEnabledBlockingInitialNavigation())],
|
||||
}).catch((err) => console.error(err));"
|
||||
`;
|
||||
|
||||
exports[`ngrx Standalone APIs should add an empty provideStore when minimal and root are set to true 1`] = `
|
||||
"import { bootstrapApplication } from '@angular/platform-browser';
|
||||
import { provideRouter, withEnabledBlockingInitialNavigation } from '@angular/router';
|
||||
import { AppComponent } from './app/app.component';
|
||||
import { appRoutes } from './app/app.routes';
|
||||
import { provideStore, provideState } from '@ngrx/store';
|
||||
import { provideEffects } from '@ngrx/effects';
|
||||
|
||||
bootstrapApplication(AppComponent, {
|
||||
providers: [provideEffects(),provideStore(),provideRouter(appRoutes, withEnabledBlockingInitialNavigation())],
|
||||
}).catch((err) => console.error(err));"
|
||||
`;
|
||||
|
||||
exports[`ngrx Standalone APIs should add facade provider when facade is true 1`] = `
|
||||
"import { bootstrapApplication } from '@angular/platform-browser';
|
||||
import { provideRouter, withEnabledBlockingInitialNavigation } from '@angular/router';
|
||||
import { AppComponent } from './app/app.component';
|
||||
import { appRoutes } from './app/app.routes';
|
||||
import { provideStore, provideState } from '@ngrx/store';
|
||||
import { provideEffects } from '@ngrx/effects';
|
||||
import * as fromUsers from './+state/users.reducer';
|
||||
import { UsersEffects } from './+state/users.effects';
|
||||
import { UsersFacade } from './+state/users.facade';
|
||||
|
||||
bootstrapApplication(AppComponent, {
|
||||
providers: [provideEffects(UsersEffects),provideState(fromUsers.USERS_FEATURE_KEY, fromUsers.usersReducer),provideEffects(),provideStore(),UsersFacade,provideRouter(appRoutes, withEnabledBlockingInitialNavigation())],
|
||||
}).catch((err) => console.error(err));"
|
||||
`;
|
||||
|
||||
exports[`ngrx Standalone APIs should add facade provider when facade is true and --root is false 1`] = `
|
||||
"import { Routes } from '@angular/router';
|
||||
import { NxWelcomeComponent } from './nx-welcome.component';
|
||||
import { provideStore, provideState } from '@ngrx/store';
|
||||
import { provideEffects } from '@ngrx/effects';
|
||||
import * as fromUsers from './+state/users.reducer';
|
||||
import { UsersEffects } from './+state/users.effects';
|
||||
import { UsersFacade } from './+state/users.facade';
|
||||
export const appRoutes: Routes = [{ path: '', component: NxWelcomeComponent , providers: [UsersFacade, provideState(fromUsers.USERS_FEATURE_KEY, fromUsers.usersReducer), provideEffects(UsersEffects)]}];"
|
||||
`;
|
||||
|
||||
@ -1,25 +1,22 @@
|
||||
import { Injectable, inject } from '@angular/core';
|
||||
import { createEffect, Actions, ofType } from '@ngrx/effects';
|
||||
import { fetch } from '@nrwl/angular';
|
||||
|
||||
import * as <%= className %>Actions from './<%= fileName %>.actions';
|
||||
import * as <%= className %>Feature from './<%= fileName %>.reducer';
|
||||
|
||||
import {switchMap, catchError, of} from 'rxjs';
|
||||
|
||||
@Injectable()
|
||||
export class <%= className %>Effects {
|
||||
private actions$ = inject(Actions);
|
||||
|
||||
init$ = createEffect(() => this.actions$.pipe(
|
||||
ofType(<%= className %>Actions.init<%= className %>),
|
||||
fetch({
|
||||
run: (action) => {
|
||||
// Your custom service 'load' logic goes here. For now just return a success action...
|
||||
return <%= className %>Actions.load<%= className %>Success({ <%= propertyName %>: [] });
|
||||
},
|
||||
onError: (action, error) => {
|
||||
switchMap(() => of(<%= className %>Actions.load<%= className %>Success({ <%= propertyName %>: [] }))),
|
||||
catchError((error) => {
|
||||
console.error('Error', error);
|
||||
return <%= className %>Actions.load<%= className %>Failure({ error });
|
||||
return of(<%= className %>Actions.load<%= className %>Failure({ error }));
|
||||
}
|
||||
})
|
||||
)
|
||||
));
|
||||
}
|
||||
|
||||
@ -5,9 +5,139 @@ import type { SourceFile } from 'typescript';
|
||||
import { createSourceFile, ScriptTarget } from 'typescript';
|
||||
import {
|
||||
addImportToModule,
|
||||
addProviderToBootstrapApplication,
|
||||
addProviderToModule,
|
||||
} from '../../../utils/nx-devkit/ast-utils';
|
||||
import type { NormalizedNgRxGeneratorOptions } from './normalize-options';
|
||||
import { addProviderToRoute } from '../../../utils/nx-devkit/route-utils';
|
||||
|
||||
function addRootStoreImport(
|
||||
tree: Tree,
|
||||
isParentStandalone: boolean,
|
||||
route: string,
|
||||
sourceFile: SourceFile,
|
||||
parentPath: string,
|
||||
provideRootStore: string,
|
||||
storeForRoot: string
|
||||
) {
|
||||
if (isParentStandalone) {
|
||||
if (tree.read(parentPath, 'utf-8').includes('bootstrapApplication')) {
|
||||
addProviderToBootstrapApplication(tree, parentPath, provideRootStore);
|
||||
} else {
|
||||
addProviderToRoute(tree, parentPath, route, provideRootStore);
|
||||
}
|
||||
} else {
|
||||
sourceFile = addImportToModule(tree, sourceFile, parentPath, storeForRoot);
|
||||
}
|
||||
return sourceFile;
|
||||
}
|
||||
|
||||
function addRootEffectsImport(
|
||||
tree: Tree,
|
||||
isParentStandalone: boolean,
|
||||
route: string,
|
||||
sourceFile: SourceFile,
|
||||
parentPath: string,
|
||||
provideRootEffects: string,
|
||||
effectsForEmptyRoot: string
|
||||
) {
|
||||
if (isParentStandalone) {
|
||||
if (tree.read(parentPath, 'utf-8').includes('bootstrapApplication')) {
|
||||
addProviderToBootstrapApplication(tree, parentPath, provideRootEffects);
|
||||
} else {
|
||||
addProviderToRoute(tree, parentPath, route, provideRootEffects);
|
||||
}
|
||||
} else {
|
||||
sourceFile = addImportToModule(
|
||||
tree,
|
||||
sourceFile,
|
||||
parentPath,
|
||||
effectsForEmptyRoot
|
||||
);
|
||||
}
|
||||
|
||||
return sourceFile;
|
||||
}
|
||||
|
||||
function addRouterStoreImport(
|
||||
tree: Tree,
|
||||
sourceFile: SourceFile,
|
||||
addImport: (
|
||||
source: SourceFile,
|
||||
symbolName: string,
|
||||
fileName: string,
|
||||
isDefault?: boolean
|
||||
) => SourceFile,
|
||||
parentPath: string,
|
||||
storeRouterModule: string
|
||||
) {
|
||||
sourceFile = addImport(
|
||||
sourceFile,
|
||||
'StoreRouterConnectingModule',
|
||||
'@ngrx/router-store'
|
||||
);
|
||||
return addImportToModule(tree, sourceFile, parentPath, storeRouterModule);
|
||||
}
|
||||
|
||||
function addStoreForFeatureImport(
|
||||
tree: Tree,
|
||||
isParentStandalone,
|
||||
route: string,
|
||||
sourceFile: SourceFile,
|
||||
parentPath: string,
|
||||
provideStoreForFeature: string,
|
||||
storeForFeature: string
|
||||
) {
|
||||
if (isParentStandalone) {
|
||||
if (tree.read(parentPath, 'utf-8').includes('bootstrapApplication')) {
|
||||
addProviderToBootstrapApplication(
|
||||
tree,
|
||||
parentPath,
|
||||
provideStoreForFeature
|
||||
);
|
||||
} else {
|
||||
addProviderToRoute(tree, parentPath, route, provideStoreForFeature);
|
||||
}
|
||||
} else {
|
||||
sourceFile = addImportToModule(
|
||||
tree,
|
||||
sourceFile,
|
||||
parentPath,
|
||||
storeForFeature
|
||||
);
|
||||
}
|
||||
return sourceFile;
|
||||
}
|
||||
|
||||
function addEffectsForFeatureImport(
|
||||
tree: Tree,
|
||||
isParentStandalone,
|
||||
route: string,
|
||||
sourceFile: SourceFile,
|
||||
parentPath: string,
|
||||
provideEffectsForFeature: string,
|
||||
effectsForFeature: string
|
||||
) {
|
||||
if (isParentStandalone) {
|
||||
if (tree.read(parentPath, 'utf-8').includes('bootstrapApplication')) {
|
||||
addProviderToBootstrapApplication(
|
||||
tree,
|
||||
parentPath,
|
||||
provideEffectsForFeature
|
||||
);
|
||||
} else {
|
||||
addProviderToRoute(tree, parentPath, route, provideEffectsForFeature);
|
||||
}
|
||||
} else {
|
||||
sourceFile = addImportToModule(
|
||||
tree,
|
||||
sourceFile,
|
||||
parentPath,
|
||||
effectsForFeature
|
||||
);
|
||||
}
|
||||
return sourceFile;
|
||||
}
|
||||
|
||||
export function addImportsToModule(
|
||||
tree: Tree,
|
||||
@ -21,6 +151,9 @@ export function addImportsToModule(
|
||||
ScriptTarget.Latest,
|
||||
true
|
||||
);
|
||||
|
||||
const isParentStandalone = !sourceText.includes('@NgModule');
|
||||
|
||||
const addImport = (
|
||||
source: SourceFile,
|
||||
symbolName: string,
|
||||
@ -65,30 +198,48 @@ export function addImportsToModule(
|
||||
const effectsForFeature = `EffectsModule.forFeature([${effectsName}])`;
|
||||
const storeRouterModule = 'StoreRouterConnectingModule.forRoot()';
|
||||
|
||||
const provideRootStore = `provideStore()`;
|
||||
const provideRootEffects = `provideEffects()`;
|
||||
const provideEffectsForFeature = `provideEffects(${effectsName})`;
|
||||
const provideStoreForFeature = `provideState(from${className}.${constantName}_FEATURE_KEY, from${className}.${propertyName}Reducer)`;
|
||||
|
||||
if (isParentStandalone) {
|
||||
sourceFile = addImport(sourceFile, 'provideStore', '@ngrx/store');
|
||||
sourceFile = addImport(sourceFile, 'provideState', '@ngrx/store');
|
||||
sourceFile = addImport(sourceFile, 'provideEffects', '@ngrx/effects');
|
||||
} else {
|
||||
sourceFile = addImport(sourceFile, 'StoreModule', '@ngrx/store');
|
||||
sourceFile = addImport(sourceFile, 'EffectsModule', '@ngrx/effects');
|
||||
}
|
||||
|
||||
// this is just a heuristic
|
||||
const hasRouter = sourceText.indexOf('RouterModule') > -1;
|
||||
|
||||
sourceFile = addImport(sourceFile, 'StoreModule', '@ngrx/store');
|
||||
sourceFile = addImport(sourceFile, 'EffectsModule', '@ngrx/effects');
|
||||
|
||||
if (options.minimal && options.root) {
|
||||
sourceFile = addImportToModule(tree, sourceFile, parentPath, storeForRoot);
|
||||
sourceFile = addImportToModule(
|
||||
sourceFile = addRootStoreImport(
|
||||
tree,
|
||||
isParentStandalone,
|
||||
options.route,
|
||||
sourceFile,
|
||||
parentPath,
|
||||
provideRootStore,
|
||||
storeForRoot
|
||||
);
|
||||
sourceFile = addRootEffectsImport(
|
||||
tree,
|
||||
isParentStandalone,
|
||||
options.route,
|
||||
sourceFile,
|
||||
parentPath,
|
||||
provideRootEffects,
|
||||
effectsForEmptyRoot
|
||||
);
|
||||
|
||||
if (hasRouter) {
|
||||
sourceFile = addImport(
|
||||
sourceFile,
|
||||
'StoreRouterConnectingModule',
|
||||
'@ngrx/router-store'
|
||||
);
|
||||
sourceFile = addImportToModule(
|
||||
if (hasRouter && !isParentStandalone) {
|
||||
sourceFile = addRouterStoreImport(
|
||||
tree,
|
||||
sourceFile,
|
||||
addImport,
|
||||
parentPath,
|
||||
storeRouterModule
|
||||
);
|
||||
@ -100,12 +251,20 @@ export function addImportsToModule(
|
||||
|
||||
if (options.facade) {
|
||||
sourceFile = addImport(sourceFile, facadeName, facadePath);
|
||||
sourceFile = addProviderToModule(
|
||||
tree,
|
||||
sourceFile,
|
||||
parentPath,
|
||||
facadeName
|
||||
);
|
||||
if (isParentStandalone) {
|
||||
if (tree.read(parentPath, 'utf-8').includes('bootstrapApplication')) {
|
||||
addProviderToBootstrapApplication(tree, parentPath, facadeName);
|
||||
} else {
|
||||
addProviderToRoute(tree, parentPath, options.route, facadeName);
|
||||
}
|
||||
} else {
|
||||
sourceFile = addProviderToModule(
|
||||
tree,
|
||||
sourceFile,
|
||||
parentPath,
|
||||
facadeName
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return sourceFile;
|
||||
@ -114,52 +273,76 @@ export function addImportsToModule(
|
||||
if (options.root) {
|
||||
sourceFile = addCommonImports();
|
||||
|
||||
sourceFile = addImportToModule(
|
||||
sourceFile = addRootStoreImport(
|
||||
tree,
|
||||
isParentStandalone,
|
||||
options.route,
|
||||
sourceFile,
|
||||
parentPath,
|
||||
provideRootStore,
|
||||
storeForRoot
|
||||
);
|
||||
sourceFile = addImportToModule(
|
||||
|
||||
sourceFile = addRootEffectsImport(
|
||||
tree,
|
||||
isParentStandalone,
|
||||
options.route,
|
||||
sourceFile,
|
||||
parentPath,
|
||||
provideRootEffects,
|
||||
effectsForRoot
|
||||
);
|
||||
|
||||
if (hasRouter) {
|
||||
sourceFile = addImport(
|
||||
sourceFile,
|
||||
'StoreRouterConnectingModule',
|
||||
'@ngrx/router-store'
|
||||
);
|
||||
sourceFile = addImportToModule(
|
||||
if (hasRouter && !isParentStandalone) {
|
||||
sourceFile = addRouterStoreImport(
|
||||
tree,
|
||||
sourceFile,
|
||||
addImport,
|
||||
parentPath,
|
||||
storeRouterModule
|
||||
);
|
||||
}
|
||||
|
||||
sourceFile = addImportToModule(
|
||||
sourceFile = addStoreForFeatureImport(
|
||||
tree,
|
||||
isParentStandalone,
|
||||
options.route,
|
||||
sourceFile,
|
||||
parentPath,
|
||||
provideStoreForFeature,
|
||||
storeForFeature
|
||||
);
|
||||
|
||||
if (isParentStandalone) {
|
||||
addEffectsForFeatureImport(
|
||||
tree,
|
||||
isParentStandalone,
|
||||
options.route,
|
||||
sourceFile,
|
||||
parentPath,
|
||||
provideEffectsForFeature,
|
||||
effectsForFeature
|
||||
);
|
||||
}
|
||||
} else {
|
||||
sourceFile = addCommonImports();
|
||||
|
||||
sourceFile = addImportToModule(
|
||||
sourceFile = addStoreForFeatureImport(
|
||||
tree,
|
||||
isParentStandalone,
|
||||
options.route,
|
||||
sourceFile,
|
||||
parentPath,
|
||||
provideStoreForFeature,
|
||||
storeForFeature
|
||||
);
|
||||
sourceFile = addImportToModule(
|
||||
sourceFile = addEffectsForFeatureImport(
|
||||
tree,
|
||||
isParentStandalone,
|
||||
options.route,
|
||||
sourceFile,
|
||||
parentPath,
|
||||
provideEffectsForFeature,
|
||||
effectsForFeature
|
||||
);
|
||||
}
|
||||
|
||||
@ -16,6 +16,7 @@ export function normalizeOptions(
|
||||
: options.parent
|
||||
? dirname(options.parent)
|
||||
: undefined,
|
||||
route: options.route ?? "''",
|
||||
directory: names(options.directory).fileName,
|
||||
};
|
||||
}
|
||||
|
||||
@ -11,6 +11,7 @@ import {
|
||||
} from '../../utils/nx-devkit/testing';
|
||||
import { ngrxVersion } from '../../utils/versions';
|
||||
import { ngrxGenerator } from './ngrx';
|
||||
import applicationGenerator from '../application/application';
|
||||
import type { NgRxGeneratorOptions } from './schema';
|
||||
|
||||
describe('ngrx', () => {
|
||||
@ -25,6 +26,13 @@ describe('ngrx', () => {
|
||||
name: 'users',
|
||||
};
|
||||
|
||||
const defaultStandaloneOptions: NgRxGeneratorOptions = {
|
||||
directory: '+state',
|
||||
minimal: true,
|
||||
parent: 'apps/my-app/src/main.ts',
|
||||
name: 'users',
|
||||
};
|
||||
|
||||
const defaultModuleOptions: NgRxGeneratorOptions = {
|
||||
directory: '+state',
|
||||
minimal: true,
|
||||
@ -37,442 +45,546 @@ describe('ngrx', () => {
|
||||
const expectFileToNotExist = (file: string) =>
|
||||
expect(tree.exists(file)).not.toBeTruthy();
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
||||
createApp(tree, 'myapp');
|
||||
appConfig = getAppConfig();
|
||||
statePath = `${dirname(appConfig.appModule)}/+state`;
|
||||
});
|
||||
|
||||
it('should error when the module could not be found', async () => {
|
||||
const modulePath = 'not-existing.module.ts';
|
||||
|
||||
await expect(
|
||||
ngrxGenerator(tree, {
|
||||
...defaultOptions,
|
||||
module: modulePath,
|
||||
})
|
||||
).rejects.toThrowError(`Module does not exist: ${modulePath}.`);
|
||||
});
|
||||
|
||||
it('should error when the module could not be found using --module', async () => {
|
||||
const modulePath = 'not-existing.module.ts';
|
||||
|
||||
await expect(
|
||||
ngrxGenerator(tree, {
|
||||
...defaultOptions,
|
||||
module: modulePath,
|
||||
})
|
||||
).rejects.toThrowError(`Module does not exist: ${modulePath}.`);
|
||||
});
|
||||
|
||||
it('should add an empty root module when minimal and root are set to true', async () => {
|
||||
await ngrxGenerator(tree, {
|
||||
...defaultOptions,
|
||||
root: true,
|
||||
minimal: true,
|
||||
describe('NgModule Syntax', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
||||
createApp(tree, 'myapp');
|
||||
appConfig = getAppConfig();
|
||||
statePath = `${dirname(appConfig.appModule)}/+state`;
|
||||
});
|
||||
|
||||
expect(
|
||||
tree.read('/apps/myapp/src/app/app.module.ts', 'utf-8')
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
it('should error when the module could not be found', async () => {
|
||||
const modulePath = 'not-existing.module.ts';
|
||||
|
||||
it('should not generate files when minimal and root are set to true', async () => {
|
||||
await ngrxGenerator(tree, {
|
||||
...defaultOptions,
|
||||
root: true,
|
||||
minimal: true,
|
||||
await expect(
|
||||
ngrxGenerator(tree, {
|
||||
...defaultOptions,
|
||||
module: modulePath,
|
||||
})
|
||||
).rejects.toThrowError(`Module does not exist: ${modulePath}.`);
|
||||
});
|
||||
|
||||
expect(tree.exists('/apps/myapp/src/app/+state/users.actions.ts')).toBe(
|
||||
false
|
||||
);
|
||||
expect(tree.exists('/apps/myapp/src/app/+state/users.effects.ts')).toBe(
|
||||
false
|
||||
);
|
||||
expect(
|
||||
tree.exists('/apps/myapp/src/app/+state/users.effects.spec.ts')
|
||||
).toBe(false);
|
||||
expect(tree.exists('/apps/myapp/src/app/+state/users.reducer.ts')).toBe(
|
||||
false
|
||||
);
|
||||
expect(tree.exists('/apps/myapp/src/app/+state/users.selectors.ts')).toBe(
|
||||
false
|
||||
);
|
||||
expect(
|
||||
tree.exists('/apps/myapp/src/app/+state/users.selectors.spec.ts')
|
||||
).toBe(false);
|
||||
});
|
||||
it('should error when the module could not be found using --module', async () => {
|
||||
const modulePath = 'not-existing.module.ts';
|
||||
|
||||
it('should add a root module with feature module when minimal is set to false', async () => {
|
||||
await ngrxGenerator(tree, {
|
||||
...defaultOptions,
|
||||
root: true,
|
||||
minimal: false,
|
||||
await expect(
|
||||
ngrxGenerator(tree, {
|
||||
...defaultOptions,
|
||||
module: modulePath,
|
||||
})
|
||||
).rejects.toThrowError(`Module does not exist: ${modulePath}.`);
|
||||
});
|
||||
|
||||
expect(
|
||||
tree.read('/apps/myapp/src/app/app.module.ts', 'utf-8')
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should add a root module with feature module when minimal is set to false using --module', async () => {
|
||||
await ngrxGenerator(tree, {
|
||||
...defaultModuleOptions,
|
||||
root: true,
|
||||
minimal: false,
|
||||
});
|
||||
|
||||
expect(
|
||||
tree.read('/apps/myapp/src/app/app.module.ts', 'utf-8')
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should not add RouterStoreModule when the module does not reference the router', async () => {
|
||||
createApp(tree, 'no-router-app', false);
|
||||
|
||||
await ngrxGenerator(tree, {
|
||||
...defaultOptions,
|
||||
module: 'apps/no-router-app/src/app/app.module.ts',
|
||||
root: true,
|
||||
});
|
||||
|
||||
const appModule = tree.read(
|
||||
'/apps/no-router-app/src/app/app.module.ts',
|
||||
'utf-8'
|
||||
);
|
||||
expect(appModule).not.toContain('StoreRouterConnectingModule.forRoot()');
|
||||
});
|
||||
|
||||
it('should add facade provider when facade is true', async () => {
|
||||
await ngrxGenerator(tree, {
|
||||
...defaultOptions,
|
||||
root: true,
|
||||
minimal: false,
|
||||
facade: true,
|
||||
});
|
||||
|
||||
expect(tree.read('/apps/myapp/src/app/app.module.ts', 'utf-8')).toContain(
|
||||
'providers: [UsersFacade]'
|
||||
);
|
||||
});
|
||||
|
||||
it('should not add facade provider when facade is false', async () => {
|
||||
await ngrxGenerator(tree, {
|
||||
...defaultOptions,
|
||||
root: true,
|
||||
minimal: false,
|
||||
facade: false,
|
||||
});
|
||||
|
||||
expect(
|
||||
tree.read('/apps/myapp/src/app/app.module.ts', 'utf-8')
|
||||
).not.toContain('providers: [UsersFacade]');
|
||||
});
|
||||
|
||||
it('should not add facade provider when minimal is true', async () => {
|
||||
await ngrxGenerator(tree, {
|
||||
...defaultOptions,
|
||||
root: true,
|
||||
minimal: true,
|
||||
facade: true,
|
||||
});
|
||||
|
||||
expect(
|
||||
tree.read('/apps/myapp/src/app/app.module.ts', 'utf-8')
|
||||
).not.toContain('providers: [UsersFacade]');
|
||||
});
|
||||
|
||||
it('should not generate imports when skipImport is true', async () => {
|
||||
await ngrxGenerator(tree, {
|
||||
...defaultOptions,
|
||||
minimal: false,
|
||||
skipImport: true,
|
||||
});
|
||||
|
||||
expectFileToExist('/apps/myapp/src/app/+state/users.actions.ts');
|
||||
expectFileToExist('/apps/myapp/src/app/+state/users.effects.ts');
|
||||
expectFileToExist('/apps/myapp/src/app/+state/users.effects.spec.ts');
|
||||
expectFileToExist('/apps/myapp/src/app/+state/users.reducer.ts');
|
||||
expectFileToExist('/apps/myapp/src/app/+state/users.selectors.ts');
|
||||
expectFileToExist('/apps/myapp/src/app/+state/users.selectors.spec.ts');
|
||||
expect(
|
||||
tree.read('/apps/myapp/src/app/app.module.ts', 'utf-8')
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should update package.json', async () => {
|
||||
await ngrxGenerator(tree, defaultOptions);
|
||||
|
||||
const packageJson = devkit.readJson(tree, 'package.json');
|
||||
expect(packageJson.dependencies['@ngrx/store']).toEqual(ngrxVersion);
|
||||
expect(packageJson.dependencies['@ngrx/effects']).toEqual(ngrxVersion);
|
||||
expect(packageJson.dependencies['@ngrx/entity']).toEqual(ngrxVersion);
|
||||
expect(packageJson.dependencies['@ngrx/router-store']).toEqual(ngrxVersion);
|
||||
expect(packageJson.dependencies['@ngrx/component-store']).toEqual(
|
||||
ngrxVersion
|
||||
);
|
||||
expect(packageJson.devDependencies['@ngrx/schematics']).toEqual(
|
||||
ngrxVersion
|
||||
);
|
||||
expect(packageJson.devDependencies['@ngrx/store-devtools']).toEqual(
|
||||
ngrxVersion
|
||||
);
|
||||
expect(packageJson.devDependencies['jasmine-marbles']).toBeDefined();
|
||||
});
|
||||
|
||||
it('should not update package.json when skipPackageJson is true', async () => {
|
||||
await ngrxGenerator(tree, { ...defaultOptions, skipPackageJson: true });
|
||||
|
||||
const packageJson = devkit.readJson(tree, 'package.json');
|
||||
expect(packageJson.dependencies['@ngrx/store']).toBeUndefined();
|
||||
expect(packageJson.dependencies['@ngrx/effects']).toBeUndefined();
|
||||
expect(packageJson.dependencies['@ngrx/entity']).toBeUndefined();
|
||||
expect(packageJson.dependencies['@ngrx/router-store']).toBeUndefined();
|
||||
expect(packageJson.dependencies['@ngrx/component-store']).toBeUndefined();
|
||||
expect(packageJson.devDependencies['@ngrx/schematics']).toBeUndefined();
|
||||
expect(packageJson.devDependencies['@ngrx/store-devtools']).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should generate files without a facade', async () => {
|
||||
await ngrxGenerator(tree, {
|
||||
...defaultOptions,
|
||||
module: appConfig.appModule,
|
||||
});
|
||||
|
||||
expectFileToExist(`${statePath}/users.actions.ts`);
|
||||
expectFileToExist(`${statePath}/users.effects.ts`);
|
||||
expectFileToExist(`${statePath}/users.effects.spec.ts`);
|
||||
expectFileToExist(`${statePath}/users.models.ts`);
|
||||
expectFileToExist(`${statePath}/users.reducer.ts`);
|
||||
expectFileToExist(`${statePath}/users.reducer.spec.ts`);
|
||||
expectFileToExist(`${statePath}/users.selectors.ts`);
|
||||
expectFileToExist(`${statePath}/users.selectors.spec.ts`);
|
||||
expectFileToNotExist(`${statePath}/users.facade.ts`);
|
||||
expectFileToNotExist(`${statePath}/users.facade.spec.ts`);
|
||||
});
|
||||
|
||||
it('should generate files with a facade', async () => {
|
||||
await ngrxGenerator(tree, {
|
||||
...defaultOptions,
|
||||
module: appConfig.appModule,
|
||||
facade: true,
|
||||
});
|
||||
|
||||
expectFileToExist(`${statePath}/users.actions.ts`);
|
||||
expectFileToExist(`${statePath}/users.effects.ts`);
|
||||
expectFileToExist(`${statePath}/users.effects.spec.ts`);
|
||||
expectFileToExist(`${statePath}/users.facade.ts`);
|
||||
expectFileToExist(`${statePath}/users.facade.spec.ts`);
|
||||
expectFileToExist(`${statePath}/users.models.ts`);
|
||||
expectFileToExist(`${statePath}/users.reducer.ts`);
|
||||
expectFileToExist(`${statePath}/users.reducer.spec.ts`);
|
||||
expectFileToExist(`${statePath}/users.selectors.ts`);
|
||||
expectFileToExist(`${statePath}/users.selectors.spec.ts`);
|
||||
});
|
||||
|
||||
it('should generate the ngrx actions', async () => {
|
||||
await ngrxGenerator(tree, {
|
||||
...defaultOptions,
|
||||
module: appConfig.appModule,
|
||||
});
|
||||
|
||||
expect(
|
||||
tree.read(`${statePath}/users.actions.ts`, 'utf-8')
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should generate the ngrx effects', async () => {
|
||||
await ngrxGenerator(tree, {
|
||||
...defaultOptions,
|
||||
module: appConfig.appModule,
|
||||
});
|
||||
|
||||
expect(
|
||||
tree.read(`${statePath}/users.effects.ts`, 'utf-8')
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should generate the ngrx facade', async () => {
|
||||
await ngrxGenerator(tree, {
|
||||
...defaultOptions,
|
||||
module: appConfig.appModule,
|
||||
facade: true,
|
||||
});
|
||||
|
||||
expect(
|
||||
tree.read(`${statePath}/users.facade.ts`, 'utf-8')
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should generate a models file for the feature', async () => {
|
||||
await ngrxGenerator(tree, {
|
||||
...defaultOptions,
|
||||
module: appConfig.appModule,
|
||||
minimal: false,
|
||||
});
|
||||
|
||||
expect(
|
||||
tree.read(`${statePath}/users.models.ts`, 'utf-8')
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should generate the ngrx reducer', async () => {
|
||||
await ngrxGenerator(tree, {
|
||||
...defaultOptions,
|
||||
module: appConfig.appModule,
|
||||
});
|
||||
|
||||
expect(
|
||||
tree.read(`${statePath}/users.reducer.ts`, 'utf-8')
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should generate the ngrx selectors', async () => {
|
||||
await ngrxGenerator(tree, {
|
||||
...defaultOptions,
|
||||
module: appConfig.appModule,
|
||||
});
|
||||
|
||||
expect(
|
||||
tree.read(`${statePath}/users.selectors.ts`, 'utf-8')
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should generate with custom directory', async () => {
|
||||
statePath = '/apps/myapp/src/app/my-custom-directory';
|
||||
|
||||
await ngrxGenerator(tree, {
|
||||
...defaultOptions,
|
||||
directory: 'my-custom-directory',
|
||||
minimal: false,
|
||||
facade: true,
|
||||
});
|
||||
|
||||
expectFileToExist(`${statePath}/users.actions.ts`);
|
||||
expectFileToExist(`${statePath}/users.effects.ts`);
|
||||
expectFileToExist(`${statePath}/users.effects.spec.ts`);
|
||||
expectFileToExist(`${statePath}/users.facade.ts`);
|
||||
expectFileToExist(`${statePath}/users.facade.spec.ts`);
|
||||
expectFileToExist(`${statePath}/users.models.ts`);
|
||||
expectFileToExist(`${statePath}/users.reducer.ts`);
|
||||
expectFileToExist(`${statePath}/users.reducer.spec.ts`);
|
||||
expectFileToExist(`${statePath}/users.selectors.ts`);
|
||||
expectFileToExist(`${statePath}/users.selectors.spec.ts`);
|
||||
});
|
||||
|
||||
it('should update the entry point file with the right exports', async () => {
|
||||
createLib(tree, 'flights');
|
||||
let libConfig = getLibConfig();
|
||||
|
||||
await ngrxGenerator(tree, {
|
||||
...defaultOptions,
|
||||
name: 'super-users',
|
||||
module: libConfig.module,
|
||||
facade: true,
|
||||
});
|
||||
|
||||
expect(tree.read(libConfig.barrel, 'utf-8')).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should update the entry point file correctly when barrels is true', async () => {
|
||||
createLib(tree, 'flights');
|
||||
let libConfig = getLibConfig();
|
||||
|
||||
await ngrxGenerator(tree, {
|
||||
...defaultOptions,
|
||||
name: 'super-users',
|
||||
module: libConfig.module,
|
||||
facade: true,
|
||||
barrels: true,
|
||||
});
|
||||
|
||||
expect(tree.read(libConfig.barrel, 'utf-8')).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should update the entry point file with no facade', async () => {
|
||||
createLib(tree, 'flights');
|
||||
let libConfig = getLibConfig();
|
||||
|
||||
await ngrxGenerator(tree, {
|
||||
...defaultOptions,
|
||||
name: 'super-users',
|
||||
module: libConfig.module,
|
||||
facade: false,
|
||||
});
|
||||
|
||||
expect(tree.read(libConfig.barrel, 'utf-8')).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should format files', async () => {
|
||||
jest.spyOn(devkit, 'formatFiles');
|
||||
|
||||
await ngrxGenerator(tree, defaultOptions);
|
||||
|
||||
expect(devkit.formatFiles).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not format files when skipFormat is true', async () => {
|
||||
jest.spyOn(devkit, 'formatFiles');
|
||||
|
||||
await ngrxGenerator(tree, { ...defaultOptions, skipFormat: true });
|
||||
|
||||
expect(devkit.formatFiles).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe('generated unit tests', () => {
|
||||
it('should generate specs for the ngrx effects', async () => {
|
||||
it('should add an empty root module when minimal and root are set to true', async () => {
|
||||
await ngrxGenerator(tree, {
|
||||
...defaultOptions,
|
||||
name: 'super-users',
|
||||
module: appConfig.appModule,
|
||||
root: true,
|
||||
minimal: true,
|
||||
});
|
||||
|
||||
expect(
|
||||
tree.read('/apps/myapp/src/app/app.module.ts', 'utf-8')
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should not generate files when minimal and root are set to true', async () => {
|
||||
await ngrxGenerator(tree, {
|
||||
...defaultOptions,
|
||||
root: true,
|
||||
minimal: true,
|
||||
});
|
||||
|
||||
expect(tree.exists('/apps/myapp/src/app/+state/users.actions.ts')).toBe(
|
||||
false
|
||||
);
|
||||
expect(tree.exists('/apps/myapp/src/app/+state/users.effects.ts')).toBe(
|
||||
false
|
||||
);
|
||||
expect(
|
||||
tree.exists('/apps/myapp/src/app/+state/users.effects.spec.ts')
|
||||
).toBe(false);
|
||||
expect(tree.exists('/apps/myapp/src/app/+state/users.reducer.ts')).toBe(
|
||||
false
|
||||
);
|
||||
expect(tree.exists('/apps/myapp/src/app/+state/users.selectors.ts')).toBe(
|
||||
false
|
||||
);
|
||||
expect(
|
||||
tree.exists('/apps/myapp/src/app/+state/users.selectors.spec.ts')
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('should add a root module with feature module when minimal is set to false', async () => {
|
||||
await ngrxGenerator(tree, {
|
||||
...defaultOptions,
|
||||
root: true,
|
||||
minimal: false,
|
||||
});
|
||||
|
||||
expect(
|
||||
tree.read(`${statePath}/super-users.effects.spec.ts`, 'utf-8')
|
||||
tree.read('/apps/myapp/src/app/app.module.ts', 'utf-8')
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should generate specs for the ngrx facade', async () => {
|
||||
it('should add a root module with feature module when minimal is set to false using --module', async () => {
|
||||
await ngrxGenerator(tree, {
|
||||
...defaultModuleOptions,
|
||||
root: true,
|
||||
minimal: false,
|
||||
});
|
||||
|
||||
expect(
|
||||
tree.read('/apps/myapp/src/app/app.module.ts', 'utf-8')
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should not add RouterStoreModule when the module does not reference the router', async () => {
|
||||
createApp(tree, 'no-router-app', false);
|
||||
|
||||
await ngrxGenerator(tree, {
|
||||
...defaultOptions,
|
||||
name: 'super-users',
|
||||
module: appConfig.appModule,
|
||||
module: 'apps/no-router-app/src/app/app.module.ts',
|
||||
root: true,
|
||||
});
|
||||
|
||||
const appModule = tree.read(
|
||||
'/apps/no-router-app/src/app/app.module.ts',
|
||||
'utf-8'
|
||||
);
|
||||
expect(appModule).not.toContain('StoreRouterConnectingModule.forRoot()');
|
||||
});
|
||||
|
||||
it('should add facade provider when facade is true', async () => {
|
||||
await ngrxGenerator(tree, {
|
||||
...defaultOptions,
|
||||
root: true,
|
||||
minimal: false,
|
||||
facade: true,
|
||||
});
|
||||
|
||||
expect(tree.read('/apps/myapp/src/app/app.module.ts', 'utf-8')).toContain(
|
||||
'providers: [UsersFacade]'
|
||||
);
|
||||
});
|
||||
|
||||
it('should not add facade provider when facade is false', async () => {
|
||||
await ngrxGenerator(tree, {
|
||||
...defaultOptions,
|
||||
root: true,
|
||||
minimal: false,
|
||||
facade: false,
|
||||
});
|
||||
|
||||
expect(
|
||||
tree.read(`${statePath}/super-users.facade.spec.ts`, 'utf-8')
|
||||
tree.read('/apps/myapp/src/app/app.module.ts', 'utf-8')
|
||||
).not.toContain('providers: [UsersFacade]');
|
||||
});
|
||||
|
||||
it('should not add facade provider when minimal is true', async () => {
|
||||
await ngrxGenerator(tree, {
|
||||
...defaultOptions,
|
||||
root: true,
|
||||
minimal: true,
|
||||
facade: true,
|
||||
});
|
||||
|
||||
expect(
|
||||
tree.read('/apps/myapp/src/app/app.module.ts', 'utf-8')
|
||||
).not.toContain('providers: [UsersFacade]');
|
||||
});
|
||||
|
||||
it('should not generate imports when skipImport is true', async () => {
|
||||
await ngrxGenerator(tree, {
|
||||
...defaultOptions,
|
||||
minimal: false,
|
||||
skipImport: true,
|
||||
});
|
||||
|
||||
expectFileToExist('/apps/myapp/src/app/+state/users.actions.ts');
|
||||
expectFileToExist('/apps/myapp/src/app/+state/users.effects.ts');
|
||||
expectFileToExist('/apps/myapp/src/app/+state/users.effects.spec.ts');
|
||||
expectFileToExist('/apps/myapp/src/app/+state/users.reducer.ts');
|
||||
expectFileToExist('/apps/myapp/src/app/+state/users.selectors.ts');
|
||||
expectFileToExist('/apps/myapp/src/app/+state/users.selectors.spec.ts');
|
||||
expect(
|
||||
tree.read('/apps/myapp/src/app/app.module.ts', 'utf-8')
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should generate specs for the ngrx reducer', async () => {
|
||||
it('should update package.json', async () => {
|
||||
await ngrxGenerator(tree, defaultOptions);
|
||||
|
||||
const packageJson = devkit.readJson(tree, 'package.json');
|
||||
expect(packageJson.dependencies['@ngrx/store']).toEqual(ngrxVersion);
|
||||
expect(packageJson.dependencies['@ngrx/effects']).toEqual(ngrxVersion);
|
||||
expect(packageJson.dependencies['@ngrx/entity']).toEqual(ngrxVersion);
|
||||
expect(packageJson.dependencies['@ngrx/router-store']).toEqual(
|
||||
ngrxVersion
|
||||
);
|
||||
expect(packageJson.dependencies['@ngrx/component-store']).toEqual(
|
||||
ngrxVersion
|
||||
);
|
||||
expect(packageJson.devDependencies['@ngrx/schematics']).toEqual(
|
||||
ngrxVersion
|
||||
);
|
||||
expect(packageJson.devDependencies['@ngrx/store-devtools']).toEqual(
|
||||
ngrxVersion
|
||||
);
|
||||
expect(packageJson.devDependencies['jasmine-marbles']).toBeDefined();
|
||||
});
|
||||
|
||||
it('should not update package.json when skipPackageJson is true', async () => {
|
||||
await ngrxGenerator(tree, { ...defaultOptions, skipPackageJson: true });
|
||||
|
||||
const packageJson = devkit.readJson(tree, 'package.json');
|
||||
expect(packageJson.dependencies['@ngrx/store']).toBeUndefined();
|
||||
expect(packageJson.dependencies['@ngrx/effects']).toBeUndefined();
|
||||
expect(packageJson.dependencies['@ngrx/entity']).toBeUndefined();
|
||||
expect(packageJson.dependencies['@ngrx/router-store']).toBeUndefined();
|
||||
expect(packageJson.dependencies['@ngrx/component-store']).toBeUndefined();
|
||||
expect(packageJson.devDependencies['@ngrx/schematics']).toBeUndefined();
|
||||
expect(
|
||||
packageJson.devDependencies['@ngrx/store-devtools']
|
||||
).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should generate files without a facade', async () => {
|
||||
await ngrxGenerator(tree, {
|
||||
...defaultOptions,
|
||||
module: appConfig.appModule,
|
||||
});
|
||||
|
||||
expectFileToExist(`${statePath}/users.actions.ts`);
|
||||
expectFileToExist(`${statePath}/users.effects.ts`);
|
||||
expectFileToExist(`${statePath}/users.effects.spec.ts`);
|
||||
expectFileToExist(`${statePath}/users.models.ts`);
|
||||
expectFileToExist(`${statePath}/users.reducer.ts`);
|
||||
expectFileToExist(`${statePath}/users.reducer.spec.ts`);
|
||||
expectFileToExist(`${statePath}/users.selectors.ts`);
|
||||
expectFileToExist(`${statePath}/users.selectors.spec.ts`);
|
||||
expectFileToNotExist(`${statePath}/users.facade.ts`);
|
||||
expectFileToNotExist(`${statePath}/users.facade.spec.ts`);
|
||||
});
|
||||
|
||||
it('should generate files with a facade', async () => {
|
||||
await ngrxGenerator(tree, {
|
||||
...defaultOptions,
|
||||
module: appConfig.appModule,
|
||||
facade: true,
|
||||
});
|
||||
|
||||
expectFileToExist(`${statePath}/users.actions.ts`);
|
||||
expectFileToExist(`${statePath}/users.effects.ts`);
|
||||
expectFileToExist(`${statePath}/users.effects.spec.ts`);
|
||||
expectFileToExist(`${statePath}/users.facade.ts`);
|
||||
expectFileToExist(`${statePath}/users.facade.spec.ts`);
|
||||
expectFileToExist(`${statePath}/users.models.ts`);
|
||||
expectFileToExist(`${statePath}/users.reducer.ts`);
|
||||
expectFileToExist(`${statePath}/users.reducer.spec.ts`);
|
||||
expectFileToExist(`${statePath}/users.selectors.ts`);
|
||||
expectFileToExist(`${statePath}/users.selectors.spec.ts`);
|
||||
});
|
||||
|
||||
it('should generate the ngrx actions', async () => {
|
||||
await ngrxGenerator(tree, {
|
||||
...defaultOptions,
|
||||
module: appConfig.appModule,
|
||||
});
|
||||
|
||||
expect(
|
||||
tree.read(`${statePath}/users.actions.ts`, 'utf-8')
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should generate the ngrx effects', async () => {
|
||||
await ngrxGenerator(tree, {
|
||||
...defaultOptions,
|
||||
module: appConfig.appModule,
|
||||
});
|
||||
|
||||
expect(
|
||||
tree.read(`${statePath}/users.effects.ts`, 'utf-8')
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should generate the ngrx facade', async () => {
|
||||
await ngrxGenerator(tree, {
|
||||
...defaultOptions,
|
||||
module: appConfig.appModule,
|
||||
facade: true,
|
||||
});
|
||||
|
||||
expect(
|
||||
tree.read(`${statePath}/users.facade.ts`, 'utf-8')
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should generate a models file for the feature', async () => {
|
||||
await ngrxGenerator(tree, {
|
||||
...defaultOptions,
|
||||
name: 'super-users',
|
||||
module: appConfig.appModule,
|
||||
minimal: false,
|
||||
});
|
||||
|
||||
expect(
|
||||
tree.read(`${statePath}/super-users.reducer.spec.ts`, 'utf-8')
|
||||
tree.read(`${statePath}/users.models.ts`, 'utf-8')
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should generate specs for the ngrx selectors', async () => {
|
||||
it('should generate the ngrx reducer', async () => {
|
||||
await ngrxGenerator(tree, {
|
||||
...defaultOptions,
|
||||
name: 'super-users',
|
||||
module: appConfig.appModule,
|
||||
minimal: false,
|
||||
});
|
||||
|
||||
expect(
|
||||
tree.read(`${statePath}/super-users.selectors.spec.ts`, 'utf-8')
|
||||
tree.read(`${statePath}/users.reducer.ts`, 'utf-8')
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should generate the ngrx selectors', async () => {
|
||||
await ngrxGenerator(tree, {
|
||||
...defaultOptions,
|
||||
module: appConfig.appModule,
|
||||
});
|
||||
|
||||
expect(
|
||||
tree.read(`${statePath}/users.selectors.ts`, 'utf-8')
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should generate with custom directory', async () => {
|
||||
statePath = '/apps/myapp/src/app/my-custom-directory';
|
||||
|
||||
await ngrxGenerator(tree, {
|
||||
...defaultOptions,
|
||||
directory: 'my-custom-directory',
|
||||
minimal: false,
|
||||
facade: true,
|
||||
});
|
||||
|
||||
expectFileToExist(`${statePath}/users.actions.ts`);
|
||||
expectFileToExist(`${statePath}/users.effects.ts`);
|
||||
expectFileToExist(`${statePath}/users.effects.spec.ts`);
|
||||
expectFileToExist(`${statePath}/users.facade.ts`);
|
||||
expectFileToExist(`${statePath}/users.facade.spec.ts`);
|
||||
expectFileToExist(`${statePath}/users.models.ts`);
|
||||
expectFileToExist(`${statePath}/users.reducer.ts`);
|
||||
expectFileToExist(`${statePath}/users.reducer.spec.ts`);
|
||||
expectFileToExist(`${statePath}/users.selectors.ts`);
|
||||
expectFileToExist(`${statePath}/users.selectors.spec.ts`);
|
||||
});
|
||||
|
||||
it('should update the entry point file with the right exports', async () => {
|
||||
createLib(tree, 'flights');
|
||||
let libConfig = getLibConfig();
|
||||
|
||||
await ngrxGenerator(tree, {
|
||||
...defaultOptions,
|
||||
name: 'super-users',
|
||||
module: libConfig.module,
|
||||
facade: true,
|
||||
});
|
||||
|
||||
expect(tree.read(libConfig.barrel, 'utf-8')).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should update the entry point file correctly when barrels is true', async () => {
|
||||
createLib(tree, 'flights');
|
||||
let libConfig = getLibConfig();
|
||||
|
||||
await ngrxGenerator(tree, {
|
||||
...defaultOptions,
|
||||
name: 'super-users',
|
||||
module: libConfig.module,
|
||||
facade: true,
|
||||
barrels: true,
|
||||
});
|
||||
|
||||
expect(tree.read(libConfig.barrel, 'utf-8')).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should update the entry point file with no facade', async () => {
|
||||
createLib(tree, 'flights');
|
||||
let libConfig = getLibConfig();
|
||||
|
||||
await ngrxGenerator(tree, {
|
||||
...defaultOptions,
|
||||
name: 'super-users',
|
||||
module: libConfig.module,
|
||||
facade: false,
|
||||
});
|
||||
|
||||
expect(tree.read(libConfig.barrel, 'utf-8')).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should format files', async () => {
|
||||
jest.spyOn(devkit, 'formatFiles');
|
||||
|
||||
await ngrxGenerator(tree, defaultOptions);
|
||||
|
||||
expect(devkit.formatFiles).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not format files when skipFormat is true', async () => {
|
||||
jest.spyOn(devkit, 'formatFiles');
|
||||
|
||||
await ngrxGenerator(tree, { ...defaultOptions, skipFormat: true });
|
||||
|
||||
expect(devkit.formatFiles).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe('generated unit tests', () => {
|
||||
it('should generate specs for the ngrx effects', async () => {
|
||||
await ngrxGenerator(tree, {
|
||||
...defaultOptions,
|
||||
name: 'super-users',
|
||||
module: appConfig.appModule,
|
||||
minimal: false,
|
||||
});
|
||||
|
||||
expect(
|
||||
tree.read(`${statePath}/super-users.effects.spec.ts`, 'utf-8')
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should generate specs for the ngrx facade', async () => {
|
||||
await ngrxGenerator(tree, {
|
||||
...defaultOptions,
|
||||
name: 'super-users',
|
||||
module: appConfig.appModule,
|
||||
minimal: false,
|
||||
facade: true,
|
||||
});
|
||||
|
||||
expect(
|
||||
tree.read(`${statePath}/super-users.facade.spec.ts`, 'utf-8')
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should generate specs for the ngrx reducer', async () => {
|
||||
await ngrxGenerator(tree, {
|
||||
...defaultOptions,
|
||||
name: 'super-users',
|
||||
module: appConfig.appModule,
|
||||
minimal: false,
|
||||
});
|
||||
|
||||
expect(
|
||||
tree.read(`${statePath}/super-users.reducer.spec.ts`, 'utf-8')
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should generate specs for the ngrx selectors', async () => {
|
||||
await ngrxGenerator(tree, {
|
||||
...defaultOptions,
|
||||
name: 'super-users',
|
||||
module: appConfig.appModule,
|
||||
minimal: false,
|
||||
});
|
||||
|
||||
expect(
|
||||
tree.read(`${statePath}/super-users.selectors.spec.ts`, 'utf-8')
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Standalone APIs', () => {
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks();
|
||||
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
||||
await applicationGenerator(tree, {
|
||||
name: 'my-app',
|
||||
standalone: true,
|
||||
routing: true,
|
||||
});
|
||||
tree.write(
|
||||
'apps/my-app/src/app/app.component.html',
|
||||
'<router-outlet></router-outlet>'
|
||||
);
|
||||
tree.write(
|
||||
'apps/my-app/src/app/app.routes.ts',
|
||||
`import { Routes } from '@angular/router';
|
||||
import { NxWelcomeComponent } from './nx-welcome.component';
|
||||
export const appRoutes: Routes = [{ path: '', component: NxWelcomeComponent }];`
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw when the parent cannot be found', async () => {
|
||||
// ARRANGE
|
||||
const parentPath = 'apps/my-app/src/app/non-existent.routes.ts';
|
||||
|
||||
// ACT & ASSERT
|
||||
await expect(
|
||||
ngrxGenerator(tree, {
|
||||
...defaultStandaloneOptions,
|
||||
parent: parentPath,
|
||||
})
|
||||
).rejects.toThrowError(`Parent does not exist: ${parentPath}.`);
|
||||
});
|
||||
|
||||
it('should add an empty provideStore when minimal and root are set to true', async () => {
|
||||
await ngrxGenerator(tree, {
|
||||
...defaultStandaloneOptions,
|
||||
root: true,
|
||||
minimal: true,
|
||||
});
|
||||
|
||||
expect(tree.read('/apps/my-app/src/main.ts', 'utf-8')).toMatchSnapshot();
|
||||
expect(tree.exists('/apps/my-app/src/app/+state/users.actions.ts')).toBe(
|
||||
false
|
||||
);
|
||||
expect(tree.exists('/apps/my-app/src/app/+state/users.effects.ts')).toBe(
|
||||
false
|
||||
);
|
||||
expect(
|
||||
tree.exists('/apps/my-app/src/app/+state/users.effects.spec.ts')
|
||||
).toBe(false);
|
||||
expect(tree.exists('/apps/my-app/src/app/+state/users.reducer.ts')).toBe(
|
||||
false
|
||||
);
|
||||
expect(
|
||||
tree.exists('/apps/my-app/src/app/+state/users.selectors.ts')
|
||||
).toBe(false);
|
||||
expect(
|
||||
tree.exists('/apps/my-app/src/app/+state/users.selectors.spec.ts')
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('should add a root module with feature module when minimal is set to false', async () => {
|
||||
await ngrxGenerator(tree, {
|
||||
...defaultStandaloneOptions,
|
||||
root: true,
|
||||
minimal: false,
|
||||
});
|
||||
|
||||
expect(tree.read('/apps/my-app/src/main.ts', 'utf-8')).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should add facade provider when facade is true', async () => {
|
||||
await ngrxGenerator(tree, {
|
||||
...defaultStandaloneOptions,
|
||||
root: true,
|
||||
minimal: false,
|
||||
facade: true,
|
||||
});
|
||||
|
||||
expect(tree.read('/apps/my-app/src/main.ts', 'utf-8')).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should add facade provider when facade is true and --root is false', async () => {
|
||||
await ngrxGenerator(tree, {
|
||||
...defaultStandaloneOptions,
|
||||
root: false,
|
||||
minimal: false,
|
||||
facade: true,
|
||||
parent: 'apps/my-app/src/app/app.routes.ts',
|
||||
});
|
||||
|
||||
expect(
|
||||
tree.read('/apps/my-app/src/app/app.routes.ts', 'utf-8')
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
@ -3,6 +3,7 @@ export interface NgRxGeneratorOptions {
|
||||
minimal: boolean;
|
||||
module?: string;
|
||||
parent?: string;
|
||||
route?: string;
|
||||
name: string;
|
||||
barrels?: boolean;
|
||||
facade?: boolean;
|
||||
|
||||
@ -28,13 +28,17 @@
|
||||
"module": {
|
||||
"type": "string",
|
||||
"description": "The path to the `NgModule` where the feature state will be registered. The host directory will create/use the new state directory.",
|
||||
"x-prompt": "What is the path to the module where this NgRx state should be registered?",
|
||||
"x-deprecated": "This option will be removed in a future version of Nx. Please switch to using --parent instead."
|
||||
},
|
||||
"parent": {
|
||||
"type": "string",
|
||||
"description": "The path to the `NgModule` where the feature state will be registered. The host directory will create/use the new state directory.",
|
||||
"x-prompt": "What is the path to the module where this NgRx state should be registered?"
|
||||
"description": "The path to the `NgModule` or the `Routes` definition file (for Standalone API usage) where the feature state will be registered. The host directory will create/use the new state directory.",
|
||||
"x-prompt": "What is the path to the module or Routes definition where this NgRx state should be registered?"
|
||||
},
|
||||
"route": {
|
||||
"type": "string",
|
||||
"description": "The route that the Standalone NgRx Providers should be added to.",
|
||||
"default": "''"
|
||||
},
|
||||
"directory": {
|
||||
"type": "string",
|
||||
|
||||
@ -653,6 +653,22 @@ export function addProviderToModule(
|
||||
);
|
||||
}
|
||||
|
||||
export function addProviderToComponent(
|
||||
host: Tree,
|
||||
source: ts.SourceFile,
|
||||
componentPath: string,
|
||||
symbolName: string
|
||||
): ts.SourceFile {
|
||||
return _addSymbolToDecoratorMetadata(
|
||||
host,
|
||||
source,
|
||||
componentPath,
|
||||
'providers',
|
||||
symbolName,
|
||||
'Component'
|
||||
);
|
||||
}
|
||||
|
||||
export function addDeclarationToModule(
|
||||
host: Tree,
|
||||
source: ts.SourceFile,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user