cleanup(angular): migrate ngrx generator to nx devkit (#6057)
This commit is contained in:
parent
8915c6ba4e
commit
5cac8ba9ac
@ -1,6 +1,6 @@
|
||||
# ngrx
|
||||
|
||||
Add an ngrx config to a project
|
||||
Add NgRx support to an application or library.
|
||||
|
||||
## Usage
|
||||
|
||||
@ -66,23 +66,27 @@ The path to NgModule where the feature state will be registered. The host direct
|
||||
|
||||
Type: `string`
|
||||
|
||||
Name of the NgRx feature state, such as "products" or "users"). Recommended to use the plural form of the name.
|
||||
Name of the NgRx feature state, such as `products` or `users`. Recommended to use the plural form of the name.
|
||||
|
||||
### onlyAddFiles
|
||||
### ~~onlyAddFiles~~
|
||||
|
||||
Default: `false`
|
||||
|
||||
Type: `boolean`
|
||||
|
||||
**Deprecated**, use `skipImport`. Only add new NgRx files, without changing the module file (e.g., --onlyAddFiles).
|
||||
**Deprecated:** Use the `skipImport` option instead.
|
||||
|
||||
### onlyEmptyRoot
|
||||
Only add new NgRx files, without changing the module file (e.g., --onlyAddFiles).
|
||||
|
||||
### ~~onlyEmptyRoot~~
|
||||
|
||||
Default: `false`
|
||||
|
||||
Type: `boolean`
|
||||
|
||||
**Deprecated**, use `minimal`. Do not generate any files. Only generate StoreModule.forRoot and EffectsModule.forRoot (e.g., --onlyEmptyRoot).
|
||||
**Deprecated:** Use the `minimal` option instead.
|
||||
|
||||
Do not generate any files. Only generate StoreModule.forRoot and EffectsModule.forRoot (e.g., --onlyEmptyRoot).
|
||||
|
||||
### root
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
# ngrx
|
||||
|
||||
Add an ngrx config to a project
|
||||
Add NgRx support to an application or library.
|
||||
|
||||
## Usage
|
||||
|
||||
@ -66,23 +66,27 @@ The path to NgModule where the feature state will be registered. The host direct
|
||||
|
||||
Type: `string`
|
||||
|
||||
Name of the NgRx feature state, such as "products" or "users"). Recommended to use the plural form of the name.
|
||||
Name of the NgRx feature state, such as `products` or `users`. Recommended to use the plural form of the name.
|
||||
|
||||
### onlyAddFiles
|
||||
### ~~onlyAddFiles~~
|
||||
|
||||
Default: `false`
|
||||
|
||||
Type: `boolean`
|
||||
|
||||
**Deprecated**, use `skipImport`. Only add new NgRx files, without changing the module file (e.g., --onlyAddFiles).
|
||||
**Deprecated:** Use the `skipImport` option instead.
|
||||
|
||||
### onlyEmptyRoot
|
||||
Only add new NgRx files, without changing the module file (e.g., --onlyAddFiles).
|
||||
|
||||
### ~~onlyEmptyRoot~~
|
||||
|
||||
Default: `false`
|
||||
|
||||
Type: `boolean`
|
||||
|
||||
**Deprecated**, use `minimal`. Do not generate any files. Only generate StoreModule.forRoot and EffectsModule.forRoot (e.g., --onlyEmptyRoot).
|
||||
**Deprecated:** Use the `minimal` option instead.
|
||||
|
||||
Do not generate any files. Only generate StoreModule.forRoot and EffectsModule.forRoot (e.g., --onlyEmptyRoot).
|
||||
|
||||
### root
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
# ngrx
|
||||
|
||||
Add an ngrx config to a project
|
||||
Add NgRx support to an application or library.
|
||||
|
||||
## Usage
|
||||
|
||||
@ -66,23 +66,27 @@ The path to NgModule where the feature state will be registered. The host direct
|
||||
|
||||
Type: `string`
|
||||
|
||||
Name of the NgRx feature state, such as "products" or "users"). Recommended to use the plural form of the name.
|
||||
Name of the NgRx feature state, such as `products` or `users`. Recommended to use the plural form of the name.
|
||||
|
||||
### onlyAddFiles
|
||||
### ~~onlyAddFiles~~
|
||||
|
||||
Default: `false`
|
||||
|
||||
Type: `boolean`
|
||||
|
||||
**Deprecated**, use `skipImport`. Only add new NgRx files, without changing the module file (e.g., --onlyAddFiles).
|
||||
**Deprecated:** Use the `skipImport` option instead.
|
||||
|
||||
### onlyEmptyRoot
|
||||
Only add new NgRx files, without changing the module file (e.g., --onlyAddFiles).
|
||||
|
||||
### ~~onlyEmptyRoot~~
|
||||
|
||||
Default: `false`
|
||||
|
||||
Type: `boolean`
|
||||
|
||||
**Deprecated**, use `minimal`. Do not generate any files. Only generate StoreModule.forRoot and EffectsModule.forRoot (e.g., --onlyEmptyRoot).
|
||||
**Deprecated:** Use the `minimal` option instead.
|
||||
|
||||
Do not generate any files. Only generate StoreModule.forRoot and EffectsModule.forRoot (e.g., --onlyEmptyRoot).
|
||||
|
||||
### root
|
||||
|
||||
|
||||
@ -38,9 +38,9 @@
|
||||
},
|
||||
|
||||
"ngrx": {
|
||||
"factory": "./src/schematics/ngrx/ngrx",
|
||||
"schema": "./src/schematics/ngrx/schema.json",
|
||||
"description": "Add an ngrx config to a project"
|
||||
"factory": "./src/generators/ngrx/compat",
|
||||
"schema": "./src/generators/ngrx/schema.json",
|
||||
"description": "Add NgRx support to an application or library."
|
||||
},
|
||||
|
||||
"downgrade-module": {
|
||||
@ -166,6 +166,11 @@
|
||||
"aliases": ["mv"],
|
||||
"description": "Move an Angular application or library to another folder."
|
||||
},
|
||||
"ngrx": {
|
||||
"factory": "./src/generators/ngrx/ngrx",
|
||||
"schema": "./src/generators/ngrx/schema.json",
|
||||
"description": "Add NgRx support to an application or library."
|
||||
},
|
||||
"stories": {
|
||||
"factory": "./src/generators/stories/stories",
|
||||
"schema": "./src/generators/stories/schema.json",
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
export * from './src/generators/application/application';
|
||||
export * from './src/generators/convert-tslint-to-eslint/convert-tslint-to-eslint';
|
||||
export * from './src/generators/karma/karma';
|
||||
export * from './src/generators/karma-project/karma-project';
|
||||
export * from './src/generators/library/library';
|
||||
export * from './src/generators/move/move';
|
||||
export * from './src/generators/ngrx/ngrx';
|
||||
export * from './src/generators/stories/stories';
|
||||
export * from './src/generators/application/application';
|
||||
export * from './src/generators/storybook-configuration/storybook-configuration';
|
||||
export * from './src/generators/storybook-migrate-defaults-5-to-6/storybook-migrate-defaults-5-to-6';
|
||||
export * from './src/generators/storybook-migrate-stories-to-6-2/migrate-stories-to-6-2';
|
||||
export * from './src/schematics/generators';
|
||||
|
||||
@ -0,0 +1,537 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`NgRx generator classes syntax should generate the ngrx actions 1`] = `
|
||||
"import {Action} from '@ngrx/store';
|
||||
import {Entity} from './users.reducer';
|
||||
|
||||
export enum UsersActionTypes {
|
||||
LoadUsers = '[Users] Load Users',
|
||||
UsersLoaded = '[Users] Users Loaded',
|
||||
UsersLoadError = '[Users] Users Load Error'
|
||||
}
|
||||
|
||||
export class LoadUsers implements Action {
|
||||
readonly type = UsersActionTypes.LoadUsers;
|
||||
}
|
||||
|
||||
export class UsersLoadError implements Action {
|
||||
readonly type = UsersActionTypes.UsersLoadError;
|
||||
|
||||
constructor(public payload: any) {}
|
||||
}
|
||||
|
||||
export class UsersLoaded implements Action {
|
||||
readonly type = UsersActionTypes.UsersLoaded;
|
||||
|
||||
constructor(public payload: Entity[]) {}
|
||||
}
|
||||
|
||||
export type UsersAction = LoadUsers | UsersLoaded | UsersLoadError;
|
||||
|
||||
export const fromUsersActions = {
|
||||
LoadUsers,
|
||||
UsersLoaded,
|
||||
UsersLoadError
|
||||
};
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`NgRx generator classes syntax should generate the ngrx effects 1`] = `
|
||||
"import { Injectable } from '@angular/core';
|
||||
import { Effect, Actions, ofType } from '@ngrx/effects';
|
||||
import { fetch } from '@nrwl/angular';
|
||||
|
||||
import { LoadUsers, UsersLoaded, UsersLoadError, UsersActionTypes } from './users.actions';
|
||||
import { UsersPartialState } from './users.reducer';
|
||||
|
||||
@Injectable()
|
||||
export class UsersEffects {
|
||||
@Effect() loadUsers$ = this.actions$.pipe(
|
||||
ofType(UsersActionTypes.LoadUsers),
|
||||
fetch({
|
||||
run: (action: LoadUsers, state: UsersPartialState) => {
|
||||
// Your custom REST 'load' logic goes here. For now just return an empty list...
|
||||
return new UsersLoaded([]);
|
||||
},
|
||||
|
||||
onError: (action: LoadUsers, error) => {
|
||||
console.error('Error', error);
|
||||
return new UsersLoadError(error);
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
constructor(
|
||||
private readonly actions$: Actions
|
||||
) {}
|
||||
}
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`NgRx generator classes syntax should generate the ngrx facade 1`] = `
|
||||
"import { Injectable } from '@angular/core';
|
||||
import { select, Store } from '@ngrx/store';
|
||||
|
||||
import { UsersPartialState } from './users.reducer';
|
||||
import { usersQuery } from './users.selectors';
|
||||
import { LoadUsers } from './users.actions';
|
||||
|
||||
@Injectable()
|
||||
export class UsersFacade {
|
||||
loaded$ = this.store.pipe(select(usersQuery.getLoaded));
|
||||
allUsers$ = this.store.pipe(select(usersQuery.getAllUsers));
|
||||
selectedUsers$ = this.store.pipe(select(usersQuery.getSelectedUsers));
|
||||
|
||||
constructor(private readonly store: Store<UsersPartialState>) {}
|
||||
|
||||
loadAll() {
|
||||
this.store.dispatch(new LoadUsers());
|
||||
}
|
||||
}
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`NgRx generator classes syntax should generate the ngrx reducer 1`] = `
|
||||
"import { UsersAction, UsersActionTypes } from './users.actions';
|
||||
|
||||
export const USERS_FEATURE_KEY = 'users';
|
||||
|
||||
/**
|
||||
* Interface for the 'Users' data used in
|
||||
* - UsersState, and the reducer function
|
||||
*
|
||||
* Note: replace if already defined in another module
|
||||
*/
|
||||
export interface Entity {
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface UsersState {
|
||||
list: Entity[]; // list of Users; analogous to a sql normalized table
|
||||
selectedId?: string | number; // which Users record has been selected
|
||||
loaded: boolean; // has the Users list been loaded
|
||||
error?: any; // last none error (if any)
|
||||
};
|
||||
|
||||
export interface UsersPartialState {
|
||||
readonly [USERS_FEATURE_KEY]: UsersState;
|
||||
}
|
||||
|
||||
export const initialState: UsersState = {
|
||||
list: [],
|
||||
loaded: false
|
||||
};
|
||||
|
||||
export function reducer(
|
||||
state: UsersState = initialState,
|
||||
action: UsersAction): UsersState
|
||||
{
|
||||
switch (action.type) {
|
||||
case UsersActionTypes.UsersLoaded: {
|
||||
state = {
|
||||
...state,
|
||||
list: action.payload,
|
||||
loaded: true
|
||||
};
|
||||
break;
|
||||
}
|
||||
}
|
||||
return state;
|
||||
}
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`NgRx generator classes syntax should generate the ngrx selectors 1`] = `
|
||||
"import { createFeatureSelector, createSelector } from '@ngrx/store';
|
||||
import { USERS_FEATURE_KEY, UsersState } from './users.reducer';
|
||||
|
||||
// Lookup the 'Users' feature state managed by NgRx
|
||||
const getUsersState = createFeatureSelector<UsersState>(USERS_FEATURE_KEY);
|
||||
|
||||
const getLoaded = createSelector( getUsersState, (state:UsersState) => state.loaded );
|
||||
const getError = createSelector( getUsersState, (state:UsersState) => state.error );
|
||||
|
||||
const getAllUsers = createSelector( getUsersState, getLoaded, (state:UsersState, isLoaded) => {
|
||||
return isLoaded ? state.list : [ ];
|
||||
});
|
||||
const getSelectedId = createSelector( getUsersState, (state:UsersState) => state.selectedId );
|
||||
const getSelectedUsers = createSelector( getAllUsers, getSelectedId, (users, id) => {
|
||||
const result = users.find(it => it['id'] === id);
|
||||
return result ? Object.assign({}, result) : undefined;
|
||||
});
|
||||
|
||||
export const usersQuery = {
|
||||
getLoaded,
|
||||
getError,
|
||||
getAllUsers,
|
||||
getSelectedUsers
|
||||
};
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`NgRx generator classes 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';
|
||||
|
||||
import * as SuperUsersSelectors from './lib/+state/super-users.selectors';
|
||||
|
||||
export * from './lib/+state/super-users.facade';
|
||||
|
||||
export { SuperUsersActions, SuperUsersFeature, SuperUsersSelectors };
|
||||
|
||||
export * from './lib/flights.module';
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`NgRx generator classes syntax should update the entry point file with no facade 1`] = `
|
||||
"export * from './lib/+state/super-users.selectors';
|
||||
export * from './lib/+state/super-users.reducer';
|
||||
export * from './lib/+state/super-users.actions';
|
||||
|
||||
export * from './lib/flights.module';
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`NgRx generator classes 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.selectors';
|
||||
export * from './lib/+state/super-users.reducer';
|
||||
export * from './lib/+state/super-users.actions';
|
||||
|
||||
export * from './lib/flights.module';
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`NgRx generator classes syntax should use DataPersistence when useDataPersistence is true 1`] = `
|
||||
"import { Injectable } from '@angular/core';
|
||||
import { Effect, Actions } from '@ngrx/effects';
|
||||
import { DataPersistence } from '@nrwl/angular';
|
||||
|
||||
import { LoadUsers, UsersLoaded, UsersLoadError, UsersActionTypes } from './users.actions';
|
||||
import { UsersPartialState } from './users.reducer';
|
||||
|
||||
@Injectable()
|
||||
export class UsersEffects {
|
||||
@Effect() loadUsers$ = this.dataPersistence.fetch(UsersActionTypes.LoadUsers, {
|
||||
run: (action: LoadUsers, state: UsersPartialState) => {
|
||||
// Your custom REST 'load' logic goes here. For now just return an empty list...
|
||||
return new UsersLoaded([]);
|
||||
},
|
||||
|
||||
onError: (action: LoadUsers, error) => {
|
||||
console.error('Error', error);
|
||||
return new UsersLoadError(error);
|
||||
}
|
||||
});
|
||||
|
||||
constructor(
|
||||
private readonly actions$: Actions,
|
||||
private readonly dataPersistence: DataPersistence<UsersPartialState>
|
||||
) {}
|
||||
}
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`NgRx generator classes syntax unit tests should generate specs for the ngrx effects 1`] = `
|
||||
"import { TestBed } from '@angular/core/testing';
|
||||
import { EffectsModule } from '@ngrx/effects';
|
||||
import { provideMockActions } from '@ngrx/effects/testing';
|
||||
import { Action, StoreModule } from '@ngrx/store';
|
||||
import { NxModule } from '@nrwl/angular';
|
||||
import { hot } from '@nrwl/angular/testing';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
import { SuperUsersEffects } from './super-users.effects';
|
||||
import { LoadSuperUsers, SuperUsersLoaded } from './super-users.actions';
|
||||
|
||||
describe('SuperUsersEffects', () => {
|
||||
let actions: Observable<Action>;
|
||||
let effects: SuperUsersEffects;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
NxModule.forRoot(),
|
||||
StoreModule.forRoot({}),
|
||||
EffectsModule.forRoot([])
|
||||
],
|
||||
providers: [
|
||||
SuperUsersEffects,
|
||||
provideMockActions(() => actions)
|
||||
],
|
||||
});
|
||||
|
||||
effects = TestBed.inject(SuperUsersEffects);
|
||||
});
|
||||
|
||||
describe('loadSuperUsers$', () => {
|
||||
it('should work', () => {
|
||||
actions = hot('-a-|', { a: new LoadSuperUsers() });
|
||||
|
||||
expect(effects.loadSuperUsers$).toBeObservable(
|
||||
hot('-a-|', { a: new SuperUsersLoaded([]) })
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`NgRx generator classes syntax unit tests should generate specs for the ngrx effects correctly when useDataPersistence is true 1`] = `
|
||||
"import { TestBed } from '@angular/core/testing';
|
||||
import { EffectsModule } from '@ngrx/effects';
|
||||
import { provideMockActions } from '@ngrx/effects/testing';
|
||||
import { Action, StoreModule } from '@ngrx/store';
|
||||
import { NxModule, DataPersistence } from '@nrwl/angular';
|
||||
import { hot } from '@nrwl/angular/testing';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
import { SuperUsersEffects } from './super-users.effects';
|
||||
import { LoadSuperUsers, SuperUsersLoaded } from './super-users.actions';
|
||||
|
||||
describe('SuperUsersEffects', () => {
|
||||
let actions: Observable<Action>;
|
||||
let effects: SuperUsersEffects;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
NxModule.forRoot(),
|
||||
StoreModule.forRoot({}),
|
||||
EffectsModule.forRoot([])
|
||||
],
|
||||
providers: [
|
||||
SuperUsersEffects,
|
||||
DataPersistence,
|
||||
provideMockActions(() => actions)
|
||||
],
|
||||
});
|
||||
|
||||
effects = TestBed.inject(SuperUsersEffects);
|
||||
});
|
||||
|
||||
describe('loadSuperUsers$', () => {
|
||||
it('should work', () => {
|
||||
actions = hot('-a-|', { a: new LoadSuperUsers() });
|
||||
|
||||
expect(effects.loadSuperUsers$).toBeObservable(
|
||||
hot('-a-|', { a: new SuperUsersLoaded([]) })
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`NgRx generator classes syntax 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';
|
||||
import { StoreModule, Store } from '@ngrx/store';
|
||||
import { NxModule } from '@nrwl/angular';
|
||||
import { readFirst } from '@nrwl/angular/testing';
|
||||
|
||||
import { LoadSuperUsers, SuperUsersLoaded } from './super-users.actions';
|
||||
import { SuperUsersEffects } from './super-users.effects';
|
||||
import { SuperUsersFacade } from './super-users.facade';
|
||||
import {
|
||||
SuperUsersState,
|
||||
Entity,
|
||||
initialState,
|
||||
reducer
|
||||
} from './super-users.reducer';
|
||||
import { superUsersQuery } from './super-users.selectors';
|
||||
|
||||
interface TestSchema {
|
||||
superUsers: SuperUsersState;
|
||||
}
|
||||
|
||||
describe('SuperUsersFacade', () => {
|
||||
let facade: SuperUsersFacade;
|
||||
let store: Store<TestSchema>;
|
||||
const createSuperUsers = (id: string, name?: string): Entity => ({
|
||||
id,
|
||||
name: name || \`name-\${id}\`
|
||||
});
|
||||
|
||||
describe('used in NgModule', () => {
|
||||
beforeEach(() => {
|
||||
@NgModule({
|
||||
imports: [
|
||||
StoreModule.forFeature('superUsers', reducer, { initialState }),
|
||||
EffectsModule.forFeature([SuperUsersEffects])
|
||||
],
|
||||
providers: [SuperUsersFacade]
|
||||
})
|
||||
class CustomFeatureModule {}
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
NxModule.forRoot(),
|
||||
StoreModule.forRoot({}),
|
||||
EffectsModule.forRoot([]),
|
||||
CustomFeatureModule,
|
||||
]
|
||||
})
|
||||
class RootModule {}
|
||||
TestBed.configureTestingModule({ imports: [RootModule] });
|
||||
|
||||
store = TestBed.inject(Store);
|
||||
facade = TestBed.inject(SuperUsersFacade);
|
||||
});
|
||||
|
||||
/**
|
||||
* The initially generated facade::loadAll() returns empty array
|
||||
*/
|
||||
it('loadAll() should return empty list with loaded == true', async (done) => {
|
||||
try {
|
||||
let list = await readFirst(facade.allSuperUsers$);
|
||||
let isLoaded = await readFirst(facade.loaded$);
|
||||
|
||||
expect(list.length).toBe(0);
|
||||
expect(isLoaded).toBe(false);
|
||||
|
||||
facade.loadAll();
|
||||
|
||||
list = await readFirst(facade.allSuperUsers$);
|
||||
isLoaded = await readFirst(facade.loaded$);
|
||||
|
||||
expect(list.length).toBe(0);
|
||||
expect(isLoaded).toBe(true);
|
||||
|
||||
done();
|
||||
} catch (err) {
|
||||
done.fail(err);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Use \`SuperUsersLoaded\` to manually submit list for state management
|
||||
*/
|
||||
it('allSuperUsers$ should return the loaded list; and loaded flag == true', async (done) => {
|
||||
try {
|
||||
let list = await readFirst(facade.allSuperUsers$);
|
||||
let isLoaded = await readFirst(facade.loaded$);
|
||||
|
||||
expect(list.length).toBe(0);
|
||||
expect(isLoaded).toBe(false);
|
||||
|
||||
store.dispatch(new SuperUsersLoaded([
|
||||
createSuperUsers('AAA'),
|
||||
createSuperUsers('BBB')
|
||||
]));
|
||||
|
||||
list = await readFirst(facade.allSuperUsers$);
|
||||
isLoaded = await readFirst(facade.loaded$);
|
||||
|
||||
expect(list.length).toBe(2);
|
||||
expect(isLoaded).toBe(true);
|
||||
|
||||
done();
|
||||
} catch (err) {
|
||||
done.fail(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`NgRx generator classes syntax unit tests should generate specs for the ngrx reducer 1`] = `
|
||||
"import { SuperUsersLoaded } from './super-users.actions';
|
||||
import { SuperUsersState, Entity, initialState, reducer } from './super-users.reducer';
|
||||
|
||||
describe('SuperUsers Reducer', () => {
|
||||
const getSuperUsersId = (it: Entity) => it.id;
|
||||
const createSuperUsers = (id: string, name = ''): Entity => ({
|
||||
id,
|
||||
name: name || \`name-\${id}\`
|
||||
});
|
||||
|
||||
describe('valid SuperUsers actions', () => {
|
||||
it('should return the list of known SuperUsers', () => {
|
||||
const superUsers = [createSuperUsers('PRODUCT-AAA'), createSuperUsers('PRODUCT-zzz')];
|
||||
const action = new SuperUsersLoaded(superUsers);
|
||||
const result: SuperUsersState = reducer(initialState, action);
|
||||
const selId: string = getSuperUsersId(result.list[1]);
|
||||
|
||||
expect(result.loaded).toBe(true);
|
||||
expect(result.list.length).toBe(2);
|
||||
expect(selId).toBe('PRODUCT-zzz');
|
||||
});
|
||||
});
|
||||
|
||||
describe('unknown action', () => {
|
||||
it('should return the previous state', () => {
|
||||
const action = {} as any;
|
||||
const result = reducer(initialState, action);
|
||||
|
||||
expect(result).toBe(initialState);
|
||||
});
|
||||
});
|
||||
});
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`NgRx generator classes syntax unit tests should generate specs for the ngrx selectors 1`] = `
|
||||
"import { Entity, SuperUsersPartialState } from './super-users.reducer';
|
||||
import { superUsersQuery } from './super-users.selectors';
|
||||
|
||||
describe('SuperUsers Selectors', () => {
|
||||
const ERROR_MSG = 'No Error Available';
|
||||
const getSuperUsersId = (it: Entity) => it.id;
|
||||
|
||||
let storeState: SuperUsersPartialState;
|
||||
|
||||
beforeEach(() => {
|
||||
const createSuperUsers = (id: string, name = ''): Entity => ({
|
||||
id,
|
||||
name: name || \`name-\${id}\`
|
||||
});
|
||||
storeState = {
|
||||
superUsers: {
|
||||
list : [
|
||||
createSuperUsers('PRODUCT-AAA'),
|
||||
createSuperUsers('PRODUCT-BBB'),
|
||||
createSuperUsers('PRODUCT-CCC')
|
||||
],
|
||||
selectedId: 'PRODUCT-BBB',
|
||||
error: ERROR_MSG,
|
||||
loaded: true
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
describe('SuperUsers Selectors', () => {
|
||||
it('getAllSuperUsers() should return the list of SuperUsers', () => {
|
||||
const results = superUsersQuery.getAllSuperUsers(storeState);
|
||||
const selId = getSuperUsersId(results[1]);
|
||||
|
||||
expect(results.length).toBe(3);
|
||||
expect(selId).toBe('PRODUCT-BBB');
|
||||
});
|
||||
|
||||
it('getSelectedSuperUsers() should return the selected Entity', () => {
|
||||
const result = superUsersQuery.getSelectedSuperUsers(storeState) as Entity;
|
||||
const selId = getSuperUsersId(result);
|
||||
|
||||
expect(selId).toBe('PRODUCT-BBB');
|
||||
});
|
||||
|
||||
it('getLoaded() should return the current \\"loaded\\" status', () => {
|
||||
const result = superUsersQuery.getLoaded(storeState);
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('getError() should return the current \\"error\\" storeState', () => {
|
||||
const result = superUsersQuery.getError(storeState);
|
||||
|
||||
expect(result).toBe(ERROR_MSG);
|
||||
});
|
||||
});
|
||||
});
|
||||
"
|
||||
`;
|
||||
@ -0,0 +1,556 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`NgRx generator creators syntax should generate a models file for the feature 1`] = `
|
||||
"/**
|
||||
* Interface for the 'Users' data
|
||||
*/
|
||||
export interface UsersEntity {
|
||||
id: string | number; // Primary ID
|
||||
name: string;
|
||||
};"
|
||||
`;
|
||||
|
||||
exports[`NgRx generator creators syntax should generate the ngrx actions 1`] = `
|
||||
"import { createAction, props } from '@ngrx/store';
|
||||
import { UsersEntity } from './users.models';
|
||||
|
||||
export const init = createAction(
|
||||
'[Users Page] Init'
|
||||
);
|
||||
|
||||
export const loadUsersSuccess = createAction(
|
||||
'[Users/API] Load Users Success',
|
||||
props<{ users: UsersEntity[] }>()
|
||||
);
|
||||
|
||||
export const loadUsersFailure = createAction(
|
||||
'[Users/API] Load Users Failure',
|
||||
props<{ error: any }>()
|
||||
);
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`NgRx generator creators syntax should generate the ngrx effects 1`] = `
|
||||
"import { Injectable } 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';
|
||||
|
||||
@Injectable()
|
||||
export class UsersEffects {
|
||||
init$ = createEffect(() => this.actions$.pipe(
|
||||
ofType(UsersActions.init),
|
||||
fetch({
|
||||
run: action => {
|
||||
// Your custom service 'load' logic goes here. For now just return a success action...
|
||||
return UsersActions.loadUsersSuccess({ users: [] });
|
||||
},
|
||||
onError: (action, error) => {
|
||||
console.error('Error', error);
|
||||
return UsersActions.loadUsersFailure({ error });
|
||||
}
|
||||
})
|
||||
));
|
||||
|
||||
constructor(
|
||||
private readonly actions$: Actions
|
||||
) {}
|
||||
}
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`NgRx generator creators syntax should generate the ngrx facade 1`] = `
|
||||
"import { Injectable } from '@angular/core';
|
||||
import { select, Store, Action } from '@ngrx/store';
|
||||
|
||||
import * as UsersActions from './users.actions';
|
||||
import * as UsersFeature from './users.reducer';
|
||||
import * as UsersSelectors from './users.selectors';
|
||||
|
||||
@Injectable()
|
||||
export class UsersFacade {
|
||||
/**
|
||||
* Combine pieces of state using createSelector,
|
||||
* and expose them as observables through the facade.
|
||||
*/
|
||||
loaded$ = this.store.pipe(select(UsersSelectors.getUsersLoaded));
|
||||
allUsers$ = this.store.pipe(select(UsersSelectors.getAllUsers));
|
||||
selectedUsers$ = this.store.pipe(select(UsersSelectors.getSelected));
|
||||
|
||||
constructor(private readonly store: Store) {}
|
||||
|
||||
/**
|
||||
* Use the initialization action to perform one
|
||||
* or more tasks in your Effects.
|
||||
*/
|
||||
init() {
|
||||
this.store.dispatch(UsersActions.init());
|
||||
}
|
||||
}
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`NgRx generator creators syntax should generate the ngrx reducer 1`] = `
|
||||
"import { EntityState, EntityAdapter, createEntityAdapter } from '@ngrx/entity';
|
||||
import { createReducer, on, Action } from '@ngrx/store';
|
||||
|
||||
import * as UsersActions from './users.actions';
|
||||
import { UsersEntity } from './users.models';
|
||||
|
||||
export const USERS_FEATURE_KEY = 'users';
|
||||
|
||||
export interface State extends EntityState<UsersEntity> {
|
||||
selectedId?: string | number; // which Users record has been selected
|
||||
loaded: boolean; // has the Users list been loaded
|
||||
error?: string | null; // last known error (if any)
|
||||
}
|
||||
|
||||
export interface UsersPartialState {
|
||||
readonly [USERS_FEATURE_KEY]: State;
|
||||
}
|
||||
|
||||
export const usersAdapter: EntityAdapter<UsersEntity> = createEntityAdapter<UsersEntity>();
|
||||
|
||||
export const initialState: State = usersAdapter.getInitialState({
|
||||
// set initial required properties
|
||||
loaded: false
|
||||
});
|
||||
|
||||
const usersReducer = createReducer(
|
||||
initialState,
|
||||
on(UsersActions.init,
|
||||
state => ({ ...state, loaded: false, error: null })
|
||||
),
|
||||
on(UsersActions.loadUsersSuccess,
|
||||
(state, { users }) => usersAdapter.setAll(users, { ...state, loaded: true })
|
||||
),
|
||||
on(UsersActions.loadUsersFailure,
|
||||
(state, { error }) => ({ ...state, error })
|
||||
),
|
||||
);
|
||||
|
||||
export function reducer(state: State | undefined, action: Action) {
|
||||
return usersReducer(state, action);
|
||||
}
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`NgRx generator creators syntax should generate the ngrx selectors 1`] = `
|
||||
"import { createFeatureSelector, createSelector } from '@ngrx/store';
|
||||
import { USERS_FEATURE_KEY, State, usersAdapter } from './users.reducer';
|
||||
|
||||
// Lookup the 'Users' feature state managed by NgRx
|
||||
export const getUsersState = createFeatureSelector<State>(USERS_FEATURE_KEY);
|
||||
|
||||
const { selectAll, selectEntities } = usersAdapter.getSelectors();
|
||||
|
||||
export const getUsersLoaded = createSelector(
|
||||
getUsersState,
|
||||
(state: State) => state.loaded
|
||||
);
|
||||
|
||||
export const getUsersError = createSelector(
|
||||
getUsersState,
|
||||
(state: State) => state.error
|
||||
);
|
||||
|
||||
export const getAllUsers = createSelector(
|
||||
getUsersState,
|
||||
(state: State) => selectAll(state)
|
||||
);
|
||||
|
||||
export const getUsersEntities = createSelector(
|
||||
getUsersState,
|
||||
(state: State) => selectEntities(state)
|
||||
);
|
||||
|
||||
export const getSelectedId = createSelector(
|
||||
getUsersState,
|
||||
(state: State) => state.selectedId
|
||||
);
|
||||
|
||||
export const getSelected = createSelector(
|
||||
getUsersEntities,
|
||||
getSelectedId,
|
||||
(entities, selectedId) => (selectedId ? entities[selectedId] : undefined)
|
||||
);
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`NgRx generator creators 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';
|
||||
|
||||
import * as SuperUsersSelectors from './lib/+state/super-users.selectors';
|
||||
|
||||
export * from './lib/+state/super-users.facade';
|
||||
|
||||
export * from './lib/+state/super-users.models';
|
||||
|
||||
export { SuperUsersActions, SuperUsersFeature, SuperUsersSelectors };
|
||||
|
||||
export * from './lib/flights.module';
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`NgRx generator creators 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';
|
||||
export * from './lib/+state/super-users.actions';
|
||||
|
||||
export * from './lib/flights.module';
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`NgRx generator creators 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';
|
||||
export * from './lib/+state/super-users.reducer';
|
||||
export * from './lib/+state/super-users.actions';
|
||||
|
||||
export * from './lib/flights.module';
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`NgRx generator creators syntax should use DataPersistence when useDataPersistence is true 1`] = `
|
||||
"import { Injectable } from '@angular/core';
|
||||
import { createEffect, Actions, ofType } from '@ngrx/effects';
|
||||
import { DataPersistence } from '@nrwl/angular';
|
||||
|
||||
import * as UsersActions from './users.actions';
|
||||
import * as UsersFeature from './users.reducer';
|
||||
|
||||
@Injectable()
|
||||
export class UsersEffects {
|
||||
init$ = createEffect(() => this.dataPersistence.fetch(UsersActions.init, {
|
||||
run: (action: ReturnType<typeof UsersActions.init>, state: UsersFeature.UsersPartialState) => {
|
||||
// Your custom service 'load' logic goes here. For now just return a success action...
|
||||
return UsersActions.loadUsersSuccess({ users: [] });
|
||||
},
|
||||
onError: (action: ReturnType<typeof UsersActions.init>, error) => {
|
||||
console.error('Error', error);
|
||||
return UsersActions.loadUsersFailure({ error });
|
||||
}
|
||||
}));
|
||||
|
||||
constructor(
|
||||
private readonly actions$: Actions,
|
||||
private readonly dataPersistence: DataPersistence<UsersFeature.UsersPartialState>
|
||||
) {}
|
||||
}
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`NgRx generator creators syntax 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';
|
||||
import { provideMockStore } from '@ngrx/store/testing';
|
||||
import { NxModule } from '@nrwl/angular';
|
||||
import { hot } from '@nrwl/angular/testing';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
import * as SuperUsersActions from './super-users.actions';
|
||||
import { SuperUsersEffects } from './super-users.effects';
|
||||
|
||||
describe('SuperUsersEffects', () => {
|
||||
let actions: Observable<Action>;
|
||||
let effects: SuperUsersEffects;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
NxModule.forRoot(),
|
||||
],
|
||||
providers: [
|
||||
SuperUsersEffects,
|
||||
provideMockActions(() => actions),
|
||||
provideMockStore()
|
||||
],
|
||||
});
|
||||
|
||||
effects = TestBed.inject(SuperUsersEffects);
|
||||
});
|
||||
|
||||
describe('init$', () => {
|
||||
it('should work', () => {
|
||||
actions = hot('-a-|', { a: SuperUsersActions.init() });
|
||||
|
||||
const expected = hot('-a-|', { a: SuperUsersActions.loadSuperUsersSuccess({ superUsers: [] }) });
|
||||
|
||||
expect(effects.init$).toBeObservable(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`NgRx generator creators syntax unit tests should generate specs for the ngrx effects correctly when useDataPersistence is true 1`] = `
|
||||
"import { TestBed } from '@angular/core/testing';
|
||||
import { provideMockActions } from '@ngrx/effects/testing';
|
||||
import { Action } from '@ngrx/store';
|
||||
import { provideMockStore } from '@ngrx/store/testing';
|
||||
import { NxModule, DataPersistence } from '@nrwl/angular';
|
||||
import { hot } from '@nrwl/angular/testing';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
import * as SuperUsersActions from './super-users.actions';
|
||||
import { SuperUsersEffects } from './super-users.effects';
|
||||
|
||||
describe('SuperUsersEffects', () => {
|
||||
let actions: Observable<Action>;
|
||||
let effects: SuperUsersEffects;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
NxModule.forRoot(),
|
||||
],
|
||||
providers: [
|
||||
SuperUsersEffects,
|
||||
DataPersistence,
|
||||
provideMockActions(() => actions),
|
||||
provideMockStore()
|
||||
],
|
||||
});
|
||||
|
||||
effects = TestBed.inject(SuperUsersEffects);
|
||||
});
|
||||
|
||||
describe('init$', () => {
|
||||
it('should work', () => {
|
||||
actions = hot('-a-|', { a: SuperUsersActions.init() });
|
||||
|
||||
const expected = hot('-a-|', { a: SuperUsersActions.loadSuperUsersSuccess({ superUsers: [] }) });
|
||||
|
||||
expect(effects.init$).toBeObservable(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`NgRx generator creators syntax 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';
|
||||
import { StoreModule, Store } from '@ngrx/store';
|
||||
import { NxModule } from '@nrwl/angular';
|
||||
import { readFirst } from '@nrwl/angular/testing';
|
||||
|
||||
import * as SuperUsersActions from './super-users.actions';
|
||||
import { SuperUsersEffects } from './super-users.effects';
|
||||
import { SuperUsersFacade } from './super-users.facade';
|
||||
import { SuperUsersEntity } from './super-users.models';
|
||||
import {
|
||||
SUPER_USERS_FEATURE_KEY,
|
||||
State,
|
||||
initialState,
|
||||
reducer
|
||||
} from './super-users.reducer';
|
||||
import * as SuperUsersSelectors from './super-users.selectors';
|
||||
|
||||
interface TestSchema {
|
||||
superUsers: State;
|
||||
}
|
||||
|
||||
describe('SuperUsersFacade', () => {
|
||||
let facade: SuperUsersFacade;
|
||||
let store: Store<TestSchema>;
|
||||
const createSuperUsersEntity = (id: string, name = ''): SuperUsersEntity => ({
|
||||
id,
|
||||
name: name || \`name-\${id}\`
|
||||
});
|
||||
|
||||
describe('used in NgModule', () => {
|
||||
beforeEach(() => {
|
||||
@NgModule({
|
||||
imports: [
|
||||
StoreModule.forFeature(SUPER_USERS_FEATURE_KEY, reducer),
|
||||
EffectsModule.forFeature([SuperUsersEffects])
|
||||
],
|
||||
providers: [SuperUsersFacade]
|
||||
})
|
||||
class CustomFeatureModule {}
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
NxModule.forRoot(),
|
||||
StoreModule.forRoot({}),
|
||||
EffectsModule.forRoot([]),
|
||||
CustomFeatureModule,
|
||||
]
|
||||
})
|
||||
class RootModule {}
|
||||
TestBed.configureTestingModule({ imports: [RootModule] });
|
||||
|
||||
store = TestBed.inject(Store);
|
||||
facade = TestBed.inject(SuperUsersFacade);
|
||||
});
|
||||
|
||||
/**
|
||||
* The initially generated facade::loadAll() returns empty array
|
||||
*/
|
||||
it('loadAll() should return empty list with loaded == true', async (done) => {
|
||||
try {
|
||||
let list = await readFirst(facade.allSuperUsers$);
|
||||
let isLoaded = await readFirst(facade.loaded$);
|
||||
|
||||
expect(list.length).toBe(0);
|
||||
expect(isLoaded).toBe(false);
|
||||
|
||||
facade.init();
|
||||
|
||||
list = await readFirst(facade.allSuperUsers$);
|
||||
isLoaded = await readFirst(facade.loaded$);
|
||||
|
||||
expect(list.length).toBe(0);
|
||||
expect(isLoaded).toBe(true);
|
||||
|
||||
done();
|
||||
} catch (err) {
|
||||
done.fail(err);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Use \`loadSuperUsersSuccess\` to manually update list
|
||||
*/
|
||||
it('allSuperUsers$ should return the loaded list; and loaded flag == true', async (done) => {
|
||||
try {
|
||||
let list = await readFirst(facade.allSuperUsers$);
|
||||
let isLoaded = await readFirst(facade.loaded$);
|
||||
|
||||
expect(list.length).toBe(0);
|
||||
expect(isLoaded).toBe(false);
|
||||
|
||||
store.dispatch(SuperUsersActions.loadSuperUsersSuccess({
|
||||
superUsers: [
|
||||
createSuperUsersEntity('AAA'),
|
||||
createSuperUsersEntity('BBB')
|
||||
]})
|
||||
);
|
||||
|
||||
list = await readFirst(facade.allSuperUsers$);
|
||||
isLoaded = await readFirst(facade.loaded$);
|
||||
|
||||
expect(list.length).toBe(2);
|
||||
expect(isLoaded).toBe(true);
|
||||
|
||||
done();
|
||||
} catch (err) {
|
||||
done.fail(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`NgRx generator creators syntax unit tests should generate specs for the ngrx reducer 1`] = `
|
||||
"import { Action } from '@ngrx/store';
|
||||
|
||||
import * as SuperUsersActions from './super-users.actions';
|
||||
import { SuperUsersEntity } from './super-users.models';
|
||||
import { State, initialState, reducer } from './super-users.reducer';
|
||||
|
||||
describe('SuperUsers Reducer', () => {
|
||||
const createSuperUsersEntity = (id: string, name = ''): SuperUsersEntity => ({
|
||||
id,
|
||||
name: name || \`name-\${id}\`
|
||||
});
|
||||
|
||||
describe('valid SuperUsers actions', () => {
|
||||
it('loadSuperUsersSuccess should return the list of known SuperUsers', () => {
|
||||
const superUsers = [
|
||||
createSuperUsersEntity('PRODUCT-AAA'),
|
||||
createSuperUsersEntity('PRODUCT-zzz')
|
||||
];
|
||||
const action = SuperUsersActions.loadSuperUsersSuccess({ superUsers });
|
||||
|
||||
const result: State = reducer(initialState, action);
|
||||
|
||||
expect(result.loaded).toBe(true);
|
||||
expect(result.ids.length).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('unknown action', () => {
|
||||
it('should return the previous state', () => {
|
||||
const action = {} as Action;
|
||||
|
||||
const result = reducer(initialState, action);
|
||||
|
||||
expect(result).toBe(initialState);
|
||||
});
|
||||
});
|
||||
});
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`NgRx generator creators syntax unit tests should generate specs for the ngrx selectors 1`] = `
|
||||
"import { SuperUsersEntity } from './super-users.models';
|
||||
import { superUsersAdapter, SuperUsersPartialState, initialState } from './super-users.reducer';
|
||||
import * as SuperUsersSelectors from './super-users.selectors';
|
||||
|
||||
describe('SuperUsers Selectors', () => {
|
||||
const ERROR_MSG = 'No Error Available';
|
||||
const getSuperUsersId = (it: SuperUsersEntity) => it.id;
|
||||
const createSuperUsersEntity = (id: string, name = '') => ({
|
||||
id,
|
||||
name: name || \`name-\${id}\`
|
||||
}) as SuperUsersEntity;
|
||||
|
||||
let state: SuperUsersPartialState;
|
||||
|
||||
beforeEach(() => {
|
||||
state = {
|
||||
superUsers: superUsersAdapter.setAll([
|
||||
createSuperUsersEntity('PRODUCT-AAA'),
|
||||
createSuperUsersEntity('PRODUCT-BBB'),
|
||||
createSuperUsersEntity('PRODUCT-CCC')
|
||||
], {
|
||||
...initialState,
|
||||
selectedId : 'PRODUCT-BBB',
|
||||
error: ERROR_MSG,
|
||||
loaded: true
|
||||
})
|
||||
};
|
||||
});
|
||||
|
||||
describe('SuperUsers Selectors', () => {
|
||||
it('getAllSuperUsers() should return the list of SuperUsers', () => {
|
||||
const results = SuperUsersSelectors.getAllSuperUsers(state);
|
||||
const selId = getSuperUsersId(results[1]);
|
||||
|
||||
expect(results.length).toBe(3);
|
||||
expect(selId).toBe('PRODUCT-BBB');
|
||||
});
|
||||
|
||||
it('getSelected() should return the selected Entity', () => {
|
||||
const result = SuperUsersSelectors.getSelected(state) as SuperUsersEntity;
|
||||
const selId = getSuperUsersId(result);
|
||||
|
||||
expect(selId).toBe('PRODUCT-BBB');
|
||||
});
|
||||
|
||||
it('getSuperUsersLoaded() should return the current \\"loaded\\" status', () => {
|
||||
const result = SuperUsersSelectors.getSuperUsersLoaded(state);
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('getSuperUsersError() should return the current \\"error\\" state', () => {
|
||||
const result = SuperUsersSelectors.getSuperUsersError(state);
|
||||
|
||||
expect(result).toBe(ERROR_MSG);
|
||||
});
|
||||
});
|
||||
});
|
||||
"
|
||||
`;
|
||||
@ -0,0 +1,112 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`ngrx should add a root module with feature module when minimal and onlyEmptyRoot are false 1`] = `
|
||||
"
|
||||
import { NgModule } from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { AppComponent } from './app.component';
|
||||
import { StoreModule } from '@ngrx/store';
|
||||
import { EffectsModule } from '@ngrx/effects';
|
||||
import * as fromUsers from './+state/users.reducer';
|
||||
import { UsersEffects } from './+state/users.effects';
|
||||
import { NxModule } from '@nrwl/angular';
|
||||
import { StoreDevtoolsModule } from '@ngrx/store-devtools';
|
||||
import { environment } from '../environments/environment';
|
||||
import { StoreRouterConnectingModule } from '@ngrx/router-store';
|
||||
@NgModule({
|
||||
imports: [BrowserModule, RouterModule.forRoot([]), NxModule.forRoot(), StoreModule.forRoot({}, {
|
||||
metaReducers: !environment.production ? [] : [],
|
||||
runtimeChecks: {
|
||||
strictActionImmutability: true,
|
||||
strictStateImmutability: true
|
||||
}
|
||||
}), EffectsModule.forRoot([UsersEffects]), !environment.production ? StoreDevtoolsModule.instrument() : [], StoreRouterConnectingModule.forRoot(), StoreModule.forFeature(fromUsers.USERS_FEATURE_KEY, fromUsers.reducer)],
|
||||
declarations: [AppComponent],
|
||||
bootstrap: [AppComponent]
|
||||
})
|
||||
export class AppModule {}
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`ngrx should add an empty root module when minimal is true 1`] = `
|
||||
"
|
||||
import { NgModule } from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { AppComponent } from './app.component';
|
||||
import { StoreModule } from '@ngrx/store';
|
||||
import { EffectsModule } from '@ngrx/effects';
|
||||
import { StoreDevtoolsModule } from '@ngrx/store-devtools';
|
||||
import { environment } from '../environments/environment';
|
||||
import { StoreRouterConnectingModule } from '@ngrx/router-store';
|
||||
@NgModule({
|
||||
imports: [BrowserModule, RouterModule.forRoot([]), StoreModule.forRoot({}, {
|
||||
metaReducers: !environment.production ? [] : [],
|
||||
runtimeChecks: {
|
||||
strictActionImmutability: true,
|
||||
strictStateImmutability: true
|
||||
}
|
||||
}), EffectsModule.forRoot([]), !environment.production ? StoreDevtoolsModule.instrument() : [], StoreRouterConnectingModule.forRoot()],
|
||||
declarations: [AppComponent],
|
||||
bootstrap: [AppComponent]
|
||||
})
|
||||
export class AppModule {}
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`ngrx should add an empty root module when onlyEmptyRoot is true 1`] = `
|
||||
"
|
||||
import { NgModule } from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { AppComponent } from './app.component';
|
||||
import { StoreModule } from '@ngrx/store';
|
||||
import { EffectsModule } from '@ngrx/effects';
|
||||
import { StoreDevtoolsModule } from '@ngrx/store-devtools';
|
||||
import { environment } from '../environments/environment';
|
||||
import { StoreRouterConnectingModule } from '@ngrx/router-store';
|
||||
@NgModule({
|
||||
imports: [BrowserModule, RouterModule.forRoot([]), StoreModule.forRoot({}, {
|
||||
metaReducers: !environment.production ? [] : [],
|
||||
runtimeChecks: {
|
||||
strictActionImmutability: true,
|
||||
strictStateImmutability: true
|
||||
}
|
||||
}), EffectsModule.forRoot([]), !environment.production ? StoreDevtoolsModule.instrument() : [], StoreRouterConnectingModule.forRoot()],
|
||||
declarations: [AppComponent],
|
||||
bootstrap: [AppComponent]
|
||||
})
|
||||
export class AppModule {}
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`ngrx should only add files when onlyAddFiles is true 1`] = `
|
||||
"
|
||||
import { NgModule } from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { AppComponent } from './app.component';
|
||||
@NgModule({
|
||||
imports: [BrowserModule, RouterModule.forRoot([])],
|
||||
declarations: [AppComponent],
|
||||
bootstrap: [AppComponent]
|
||||
})
|
||||
export class AppModule {}
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`ngrx should only add files when skipImport is true 1`] = `
|
||||
"
|
||||
import { NgModule } from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { AppComponent } from './app.component';
|
||||
@NgModule({
|
||||
imports: [BrowserModule, RouterModule.forRoot([])],
|
||||
declarations: [AppComponent],
|
||||
bootstrap: [AppComponent]
|
||||
})
|
||||
export class AppModule {}
|
||||
"
|
||||
`;
|
||||
4
packages/angular/src/generators/ngrx/compat.ts
Normal file
4
packages/angular/src/generators/ngrx/compat.ts
Normal file
@ -0,0 +1,4 @@
|
||||
import { convertNxGenerator } from '@nrwl/devkit';
|
||||
import { ngrxGenerator } from './ngrx';
|
||||
|
||||
export default convertNxGenerator(ngrxGenerator);
|
||||
@ -2,23 +2,25 @@ import {Action} from '@ngrx/store';
|
||||
import {Entity} from './<%= fileName %>.reducer';
|
||||
|
||||
export enum <%= className %>ActionTypes {
|
||||
Load<%= className %> = '[<%= className %>] Load <%= className %>',
|
||||
<%= className %>Loaded = '[<%= className %>] <%= className %> Loaded',
|
||||
<%= className %>LoadError = '[<%= className %>] <%= className %> Load Error'
|
||||
Load<%= className %> = '[<%= className %>] Load <%= className %>',
|
||||
<%= className %>Loaded = '[<%= className %>] <%= className %> Loaded',
|
||||
<%= className %>LoadError = '[<%= className %>] <%= className %> Load Error'
|
||||
}
|
||||
|
||||
export class Load<%= className %> implements Action {
|
||||
readonly type = <%= className %>ActionTypes.Load<%= className %>;
|
||||
readonly type = <%= className %>ActionTypes.Load<%= className %>;
|
||||
}
|
||||
|
||||
export class <%= className %>LoadError implements Action {
|
||||
readonly type = <%= className %>ActionTypes.<%= className %>LoadError;
|
||||
constructor(public payload: any) { }
|
||||
readonly type = <%= className %>ActionTypes.<%= className %>LoadError;
|
||||
|
||||
constructor(public payload: any) {}
|
||||
}
|
||||
|
||||
export class <%= className %>Loaded implements Action {
|
||||
readonly type = <%= className %>ActionTypes.<%= className %>Loaded;
|
||||
constructor(public payload: Entity[]) { }
|
||||
readonly type = <%= className %>ActionTypes.<%= className %>Loaded;
|
||||
|
||||
constructor(public payload: Entity[]) {}
|
||||
}
|
||||
|
||||
export type <%= className %>Action = Load<%= className %> | <%= className %>Loaded | <%= className %>LoadError;
|
||||
@ -1,13 +1,10 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
import { EffectsModule } from '@ngrx/effects';
|
||||
import { Action, StoreModule } from '@ngrx/store';
|
||||
import { provideMockActions } from '@ngrx/effects/testing';
|
||||
|
||||
import { Action, StoreModule } from '@ngrx/store';
|
||||
import { NxModule<% if (useDataPersistence) { %>, DataPersistence<% } %> } from '@nrwl/angular';
|
||||
import { hot } from '@nrwl/angular/testing';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
import { <%= className %>Effects } from './<%= fileName %>.effects';
|
||||
import { Load<%= className %>, <%= className %>Loaded } from './<%= fileName %>.actions';
|
||||
@ -24,8 +21,8 @@ describe('<%= className %>Effects', () => {
|
||||
EffectsModule.forRoot([])
|
||||
],
|
||||
providers: [
|
||||
<%= className %>Effects,
|
||||
<% if (useDataPersistence) { %>DataPersistence,<% } %>
|
||||
<%= className %>Effects,<% if (useDataPersistence) { %>
|
||||
DataPersistence,<% } %>
|
||||
provideMockActions(() => actions)
|
||||
],
|
||||
});
|
||||
@ -35,9 +32,10 @@ describe('<%= className %>Effects', () => {
|
||||
|
||||
describe('load<%= className %>$', () => {
|
||||
it('should work', () => {
|
||||
actions = hot('-a-|', {a: new Load<%= className %>()});
|
||||
actions = hot('-a-|', { a: new Load<%= className %>() });
|
||||
|
||||
expect(effects.load<%= className %>$).toBeObservable(
|
||||
hot('-a-|', {a: new <%= className %>Loaded([])})
|
||||
hot('-a-|', { a: new <%= className %>Loaded([]) })
|
||||
);
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,39 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Effect, Actions<% if (!useDataPersistence) { %>, ofType<% }%> } from '@ngrx/effects';
|
||||
import { <% if (useDataPersistence) { %>DataPersistence<% } else { %>fetch<% } %> } from '@nrwl/angular';
|
||||
|
||||
import { Load<%= className %>, <%= className %>Loaded, <%= className %>LoadError, <%= className %>ActionTypes } from './<%= fileName %>.actions';
|
||||
import { <%= className %>PartialState } from './<%= fileName %>.reducer';
|
||||
|
||||
@Injectable()
|
||||
export class <%= className %>Effects {
|
||||
@Effect() load<%= className %>$ = <% if (useDataPersistence) { %>this.dataPersistence.fetch(<%= className %>ActionTypes.Load<%= className %>, {
|
||||
run: (action: Load<%= className %>, state: <%= className %>PartialState) => {
|
||||
// Your custom REST 'load' logic goes here. For now just return an empty list...
|
||||
return new <%= className %>Loaded([]);
|
||||
},
|
||||
|
||||
onError: (action: Load<%= className %>, error) => {
|
||||
console.error('Error', error);
|
||||
return new <%= className %>LoadError(error);
|
||||
}
|
||||
}); <% } else { %> this.actions$.pipe(
|
||||
ofType(<%= className %>ActionTypes.Load<%= className %>),
|
||||
fetch({
|
||||
run: (action: Load<%= className %>, state: <%= className %>PartialState) => {
|
||||
// Your custom REST 'load' logic goes here. For now just return an empty list...
|
||||
return new <%= className %>Loaded([]);
|
||||
},
|
||||
|
||||
onError: (action: Load<%= className %>, error) => {
|
||||
console.error('Error', error);
|
||||
return new <%= className %>LoadError(error);
|
||||
}
|
||||
})
|
||||
);<% } %>
|
||||
|
||||
constructor(
|
||||
private readonly actions$: Actions<% if (useDataPersistence) { %>,
|
||||
private readonly dataPersistence: DataPersistence<<%= className %>PartialState><% } %>
|
||||
) {}
|
||||
}
|
||||
@ -1,26 +1,23 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { readFirst } from '@nrwl/angular/testing';
|
||||
|
||||
import { EffectsModule } from '@ngrx/effects';
|
||||
import { StoreModule, Store } from '@ngrx/store';
|
||||
|
||||
import { NxModule } from '@nrwl/angular';
|
||||
import { readFirst } from '@nrwl/angular/testing';
|
||||
|
||||
import { Load<%= className %>, <%= className %>Loaded } from './<%= fileName %>.actions';
|
||||
import { <%= className %>Effects } from './<%= fileName %>.effects';
|
||||
import { <%= className %>Facade } from './<%= fileName %>.facade';
|
||||
|
||||
import { <%= propertyName %>Query } from './<%= fileName %>.selectors';
|
||||
import { Load<%= className %>, <%= className %>Loaded } from './<%= fileName %>.actions';
|
||||
import {
|
||||
<%= className %>State,
|
||||
Entity,
|
||||
initialState,
|
||||
reducer
|
||||
} from './<%= fileName %>.reducer';
|
||||
import { <%= propertyName %>Query } from './<%= fileName %>.selectors';
|
||||
|
||||
interface TestSchema {
|
||||
'<%= propertyName %>': <%= className %>State
|
||||
<%= propertyName %>: <%= className %>State;
|
||||
}
|
||||
|
||||
describe('<%= className %>Facade', () => {
|
||||
@ -1,5 +1,4 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
import { select, Store } from '@ngrx/store';
|
||||
|
||||
import { <%= className %>PartialState } from './<%= fileName %>.reducer';
|
||||
@ -8,12 +7,11 @@ import { Load<%= className %> } from './<%= fileName %>.actions';
|
||||
|
||||
@Injectable()
|
||||
export class <%= className %>Facade {
|
||||
|
||||
loaded$ = this.store.pipe(select(<%= propertyName %>Query.getLoaded));
|
||||
all<%= className %>$ = this.store.pipe(select(<%= propertyName %>Query.getAll<%= className %>));
|
||||
selected<%= className %>$ = this.store.pipe(select(<%= propertyName %>Query.getSelected<%= className %>));
|
||||
|
||||
constructor(private store: Store<<%= className %>PartialState>) { }
|
||||
constructor(private readonly store: Store<<%= className %>PartialState>) {}
|
||||
|
||||
loadAll() {
|
||||
this.store.dispatch(new Load<%= className %>());
|
||||
@ -1,5 +1,3 @@
|
||||
import { Action } from '@ngrx/store';
|
||||
|
||||
import { <%= className %>Loaded } from './<%= fileName %>.actions';
|
||||
import { <%= className %>State, Entity, initialState, reducer } from './<%= fileName %>.reducer';
|
||||
|
||||
@ -10,12 +8,12 @@ describe('<%= className %> Reducer', () => {
|
||||
name: name || `name-${id}`
|
||||
});
|
||||
|
||||
describe('valid <%= className %> actions ', () => {
|
||||
it('should return set the list of known <%= className %>', () => {
|
||||
const <%= propertyName %>s = [create<%= className %>( 'PRODUCT-AAA' ),create<%= className %>( 'PRODUCT-zzz' )];
|
||||
const action = new <%= className %>Loaded(<%= propertyName %>s);
|
||||
const result : <%= className %>State = reducer(initialState, action);
|
||||
const selId : string = get<%= className %>Id(result.list[1]);
|
||||
describe('valid <%= className %> actions', () => {
|
||||
it('should return the list of known <%= className %>', () => {
|
||||
const <%= propertyName %> = [create<%= className %>('PRODUCT-AAA'), create<%= className %>('PRODUCT-zzz')];
|
||||
const action = new <%= className %>Loaded(<%= propertyName %>);
|
||||
const result: <%= className %>State = reducer(initialState, action);
|
||||
const selId: string = get<%= className %>Id(result.list[1]);
|
||||
|
||||
expect(result.loaded).toBe(true);
|
||||
expect(result.list.length).toBe(2);
|
||||
@ -25,7 +23,7 @@ describe('<%= className %> Reducer', () => {
|
||||
|
||||
describe('unknown action', () => {
|
||||
it('should return the previous state', () => {
|
||||
const action = {} as Action;
|
||||
const action = {} as any;
|
||||
const result = reducer(initialState, action);
|
||||
|
||||
expect(result).toBe(initialState);
|
||||
@ -8,11 +8,11 @@ describe('<%= className %> Selectors', () => {
|
||||
let storeState: <%= className %>PartialState;
|
||||
|
||||
beforeEach(() => {
|
||||
const create<%= className %> = (id: string, name = ''): Entity => ({
|
||||
id,
|
||||
name: name || `name-${id}`
|
||||
});
|
||||
storeState = {
|
||||
const create<%= className %> = (id: string, name = ''): Entity => ({
|
||||
id,
|
||||
name: name || `name-${id}`
|
||||
});
|
||||
storeState = {
|
||||
<%= propertyName %>: {
|
||||
list : [
|
||||
create<%= className %>('PRODUCT-AAA'),
|
||||
@ -22,8 +22,8 @@ describe('<%= className %> Selectors', () => {
|
||||
selectedId: 'PRODUCT-BBB',
|
||||
error: ERROR_MSG,
|
||||
loaded: true
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
describe('<%= className %> Selectors', () => {
|
||||
@ -42,13 +42,13 @@ describe('<%= className %> Selectors', () => {
|
||||
expect(selId).toBe('PRODUCT-BBB');
|
||||
});
|
||||
|
||||
it('getLoaded() should return the current \'loaded\' status', () => {
|
||||
it('getLoaded() should return the current "loaded" status', () => {
|
||||
const result = <%= propertyName %>Query.getLoaded(storeState);
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('getError() should return the current \'error\' storeState', () => {
|
||||
it('getError() should return the current "error" storeState', () => {
|
||||
const result = <%= propertyName %>Query.getError(storeState);
|
||||
|
||||
expect(result).toBe(ERROR_MSG);
|
||||
@ -12,8 +12,8 @@ const getAll<%= className %> = createSelector( get<%= className %>State, getLoad
|
||||
});
|
||||
const getSelectedId = createSelector( get<%= className %>State, (state:<%= className %>State) => state.selectedId );
|
||||
const getSelected<%= className %> = createSelector( getAll<%= className %>, getSelectedId, (<%= propertyName %>, id) => {
|
||||
const result = <%= propertyName %>.find(it => it['id'] === id);
|
||||
return result ? Object.assign({}, result) : undefined;
|
||||
const result = <%= propertyName %>.find(it => it['id'] === id);
|
||||
return result ? Object.assign({}, result) : undefined;
|
||||
});
|
||||
|
||||
export const <%= propertyName %>Query = {
|
||||
@ -1,18 +1,16 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { provideMockActions } from '@ngrx/effects/testing';
|
||||
import { Action } from '@ngrx/store';
|
||||
import { provideMockStore } from '@ngrx/store/testing';
|
||||
import { NxModule<% if (useDataPersistence) { %>, DataPersistence<% } %> } from '@nrwl/angular';
|
||||
import { hot } from '@nrwl/angular/testing';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
import { provideMockActions } from '@ngrx/effects/testing';
|
||||
import { provideMockStore } from '@ngrx/store/testing';
|
||||
|
||||
import { NxModule, DataPersistence } from '@nrwl/angular';
|
||||
import { hot } from '@nrwl/angular/testing';
|
||||
|
||||
import { <%= className %>Effects } from './<%= fileName %>.effects';
|
||||
import * as <%= className %>Actions from './<%= fileName %>.actions';
|
||||
import { <%= className %>Effects } from './<%= fileName %>.effects';
|
||||
|
||||
describe('<%= className %>Effects', () => {
|
||||
let actions: Observable<any>;
|
||||
let actions: Observable<Action>;
|
||||
let effects: <%= className %>Effects;
|
||||
|
||||
beforeEach(() => {
|
||||
@ -21,8 +19,8 @@ describe('<%= className %>Effects', () => {
|
||||
NxModule.forRoot(),
|
||||
],
|
||||
providers: [
|
||||
<%= className %>Effects,
|
||||
DataPersistence,
|
||||
<%= className %>Effects,<% if (useDataPersistence) { %>
|
||||
DataPersistence,<% } %>
|
||||
provideMockActions(() => actions),
|
||||
provideMockStore()
|
||||
],
|
||||
@ -33,7 +31,7 @@ describe('<%= className %>Effects', () => {
|
||||
|
||||
describe('init$', () => {
|
||||
it('should work', () => {
|
||||
actions = hot('-a-|', {a: <%= className %>Actions.init()});
|
||||
actions = hot('-a-|', { a: <%= className %>Actions.init() });
|
||||
|
||||
const expected = hot('-a-|', { a: <%= className %>Actions.load<%= className %>Success({ <%= propertyName %>: [] }) });
|
||||
|
||||
@ -2,40 +2,36 @@ import { Injectable } from '@angular/core';
|
||||
import { createEffect, Actions, ofType } from '@ngrx/effects';
|
||||
import { <% if (useDataPersistence) { %>DataPersistence<% } %><% if (!useDataPersistence) { %>fetch<% } %> } from '@nrwl/angular';
|
||||
|
||||
import * as <%= className %>Feature from './<%= fileName %>.reducer';
|
||||
import * as <%= className %>Actions from './<%= fileName %>.actions';
|
||||
import * as <%= className %>Feature from './<%= fileName %>.reducer';
|
||||
|
||||
@Injectable()
|
||||
export class <%= className %>Effects {
|
||||
<% if (useDataPersistence) { %> init$ = createEffect(() => this.dataPersistence.fetch(<%= className %>Actions.init, {
|
||||
<% if (useDataPersistence) { %>init$ = createEffect(() => this.dataPersistence.fetch(<%= className %>Actions.init, {
|
||||
run: (action: ReturnType<typeof <%= className %>Actions.init>, state: <%= className %>Feature.<%= className %>PartialState) => {
|
||||
// Your custom service 'load' logic goes here. For now just return a success action...
|
||||
return <%= className %>Actions.load<%= className %>Success({ <%= propertyName %>: [] });
|
||||
},
|
||||
|
||||
onError: (action: ReturnType<typeof <%= className %>Actions.init>, error) => {
|
||||
console.error('Error', error);
|
||||
return <%= className %>Actions.load<%= className %>Failure({ error });
|
||||
}
|
||||
}));
|
||||
<% } else { %> init$ = createEffect(() => this.actions$.pipe(
|
||||
ofType(<%= className %>Actions.init),
|
||||
fetch({
|
||||
run: action => {
|
||||
// Your custom service 'load' logic goes here. For now just return a success action...
|
||||
return <%= className %>Actions.load<%= className %>Success({ <%= propertyName %>: [] });
|
||||
},
|
||||
}));<% } else { %>init$ = createEffect(() => this.actions$.pipe(
|
||||
ofType(<%= className %>Actions.init),
|
||||
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) => {
|
||||
console.error('Error', error);
|
||||
return <%= className %>Actions.load<%= className %>Failure({ error });
|
||||
}
|
||||
})
|
||||
));<% } %>
|
||||
|
||||
onError: (action, error) => {
|
||||
console.error('Error', error);
|
||||
return <%= className %>Actions.load<%= className %>Failure({ error });
|
||||
}
|
||||
}))
|
||||
);
|
||||
<% } %>
|
||||
|
||||
constructor(
|
||||
private actions$: Actions,
|
||||
<% if (useDataPersistence) { %> private dataPersistence: DataPersistence<<%= className %>Feature.<%= className %>PartialState>, <% } %>
|
||||
) { }
|
||||
constructor(
|
||||
private readonly actions$: Actions<% if (useDataPersistence) { %>,
|
||||
private readonly dataPersistence: DataPersistence<<%= className %>Feature.<%= className %>PartialState><% } %>
|
||||
) {}
|
||||
}
|
||||
@ -1,27 +1,24 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { readFirst } from '@nrwl/angular/testing';
|
||||
|
||||
import { EffectsModule } from '@ngrx/effects';
|
||||
import { StoreModule, Store } from '@ngrx/store';
|
||||
|
||||
import { NxModule } from '@nrwl/angular';
|
||||
import { readFirst } from '@nrwl/angular/testing';
|
||||
|
||||
import { <%= className %>Entity } from './<%= fileName %>.models';
|
||||
import * as <%= className %>Actions from './<%= fileName %>.actions';
|
||||
import { <%= className %>Effects } from './<%= fileName %>.effects';
|
||||
import { <%= className %>Facade } from './<%= fileName %>.facade';
|
||||
|
||||
import * as <%= className %>Selectors from './<%= fileName %>.selectors';
|
||||
import * as <%= className %>Actions from './<%= fileName %>.actions';
|
||||
import { <%= className %>Entity } from './<%= fileName %>.models';
|
||||
import {
|
||||
<%= constantName %>_FEATURE_KEY,
|
||||
State,
|
||||
initialState,
|
||||
reducer
|
||||
} from './<%= fileName %>.reducer';
|
||||
import * as <%= className %>Selectors from './<%= fileName %>.selectors';
|
||||
|
||||
interface TestSchema {
|
||||
'<%= propertyName %>': State
|
||||
<%= propertyName %>: State;
|
||||
}
|
||||
|
||||
describe('<%= className %>Facade', () => {
|
||||
@ -1,5 +1,4 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
import { select, Store, Action } from '@ngrx/store';
|
||||
|
||||
import * as <%= className %>Actions from './<%= fileName %>.actions';
|
||||
@ -16,7 +15,7 @@ export class <%= className %>Facade {
|
||||
all<%= className %>$ = this.store.pipe(select(<%= className %>Selectors.getAll<%= className %>));
|
||||
selected<%= className %>$ = this.store.pipe(select(<%= className %>Selectors.getSelected));
|
||||
|
||||
constructor(private store: Store) { }
|
||||
constructor(private readonly store: Store) {}
|
||||
|
||||
/**
|
||||
* Use the initialization action to perform one
|
||||
@ -2,6 +2,6 @@
|
||||
* Interface for the '<%= className %>' data
|
||||
*/
|
||||
export interface <%= className %>Entity {
|
||||
id: string | number; // Primary ID
|
||||
id: string | number; // Primary ID
|
||||
name: string;
|
||||
};
|
||||
@ -1,5 +1,7 @@
|
||||
import { <%= className %>Entity } from './<%= fileName %>.models';
|
||||
import { Action } from '@ngrx/store';
|
||||
|
||||
import * as <%= className %>Actions from './<%= fileName %>.actions';
|
||||
import { <%= className %>Entity } from './<%= fileName %>.models';
|
||||
import { State, initialState, reducer } from './<%= fileName %>.reducer';
|
||||
|
||||
describe('<%= className %> Reducer', () => {
|
||||
@ -9,10 +11,10 @@ describe('<%= className %> Reducer', () => {
|
||||
});
|
||||
|
||||
describe('valid <%= className %> actions', () => {
|
||||
it('load<%= className %>Success should return set the list of known <%= className %>', () => {
|
||||
it('load<%= className %>Success should return the list of known <%= className %>', () => {
|
||||
const <%= propertyName %> = [
|
||||
create<%= className %>Entity( 'PRODUCT-AAA' ),
|
||||
create<%= className %>Entity( 'PRODUCT-zzz' )
|
||||
create<%= className %>Entity('PRODUCT-AAA'),
|
||||
create<%= className %>Entity('PRODUCT-zzz')
|
||||
];
|
||||
const action = <%= className %>Actions.load<%= className %>Success({ <%= propertyName %> });
|
||||
|
||||
@ -25,7 +27,7 @@ describe('<%= className %> Reducer', () => {
|
||||
|
||||
describe('unknown action', () => {
|
||||
it('should return the previous state', () => {
|
||||
const action = {} as any;
|
||||
const action = {} as Action;
|
||||
|
||||
const result = reducer(initialState, action);
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { createReducer, on, Action } from '@ngrx/store';
|
||||
import { EntityState, EntityAdapter, createEntityAdapter } from '@ngrx/entity';
|
||||
import { createReducer, on, Action } from '@ngrx/store';
|
||||
|
||||
import * as <%= className %>Actions from './<%= fileName %>.actions';
|
||||
import { <%= className %>Entity } from './<%= fileName %>.models';
|
||||
@ -43,13 +43,13 @@ describe('<%= className %> Selectors', () => {
|
||||
expect(selId).toBe('PRODUCT-BBB');
|
||||
});
|
||||
|
||||
it('get<%= className %>Loaded() should return the current \'loaded\' status', () => {
|
||||
it('get<%= className %>Loaded() should return the current "loaded" status', () => {
|
||||
const result = <%= className %>Selectors.get<%= className %>Loaded(state);
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('get<%= className %>Error() should return the current \'error\' state', () => {
|
||||
it('get<%= className %>Error() should return the current "error" state', () => {
|
||||
const result = <%= className %>Selectors.get<%= className %>Error(state);
|
||||
|
||||
expect(result).toBe(ERROR_MSG);
|
||||
@ -0,0 +1,92 @@
|
||||
import type { Tree } from '@nrwl/devkit';
|
||||
import { joinPathFragments, names } from '@nrwl/devkit';
|
||||
import { addGlobal } from '@nrwl/workspace/src/utilities/ast-utils';
|
||||
import { dirname } from 'path';
|
||||
import { createSourceFile, ScriptTarget } from 'typescript';
|
||||
import type { NgRxGeneratorOptions } from '../schema';
|
||||
|
||||
/**
|
||||
* Add ngrx feature exports to the public barrel in the feature library
|
||||
*/
|
||||
export function addExportsToBarrel(
|
||||
tree: Tree,
|
||||
options: NgRxGeneratorOptions
|
||||
): void {
|
||||
// Don't update the public barrel for the root state, only for feature states
|
||||
if (options.root) {
|
||||
return;
|
||||
}
|
||||
|
||||
const indexFilePath = joinPathFragments(
|
||||
dirname(options.module),
|
||||
'..',
|
||||
'index.ts'
|
||||
);
|
||||
if (!tree.exists(indexFilePath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const indexSourceText = tree.read(indexFilePath, 'utf-8');
|
||||
let sourceFile = createSourceFile(
|
||||
indexFilePath,
|
||||
indexSourceText,
|
||||
ScriptTarget.Latest,
|
||||
true
|
||||
);
|
||||
|
||||
// Public API for the feature interfaces, selectors, and facade
|
||||
const { className, fileName } = names(options.name);
|
||||
const statePath = `./lib/${options.directory}/${fileName}`;
|
||||
|
||||
sourceFile = addGlobal(
|
||||
tree,
|
||||
sourceFile,
|
||||
indexFilePath,
|
||||
options.barrels
|
||||
? `import * as ${className}Actions from '${statePath}.actions';`
|
||||
: `export * from '${statePath}.actions';`
|
||||
);
|
||||
sourceFile = addGlobal(
|
||||
tree,
|
||||
sourceFile,
|
||||
indexFilePath,
|
||||
options.barrels
|
||||
? `import * as ${className}Feature from '${statePath}.reducer';`
|
||||
: `export * from '${statePath}.reducer';`
|
||||
);
|
||||
sourceFile = addGlobal(
|
||||
tree,
|
||||
sourceFile,
|
||||
indexFilePath,
|
||||
options.barrels
|
||||
? `import * as ${className}Selectors from '${statePath}.selectors';`
|
||||
: `export * from '${statePath}.selectors';`
|
||||
);
|
||||
|
||||
if (options.barrels) {
|
||||
sourceFile = addGlobal(
|
||||
tree,
|
||||
sourceFile,
|
||||
indexFilePath,
|
||||
`export { ${className}Actions, ${className}Feature, ${className}Selectors };`
|
||||
);
|
||||
}
|
||||
|
||||
if (options.syntax === 'creators') {
|
||||
sourceFile = addGlobal(
|
||||
tree,
|
||||
sourceFile,
|
||||
indexFilePath,
|
||||
`export * from '${statePath}.models';`
|
||||
);
|
||||
}
|
||||
|
||||
if (options.facade) {
|
||||
sourceFile = addGlobal(
|
||||
tree,
|
||||
sourceFile,
|
||||
indexFilePath,
|
||||
`export * from '${statePath}.facade';`
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,198 @@
|
||||
import type { Tree } from '@nrwl/devkit';
|
||||
import { names } from '@nrwl/devkit';
|
||||
import { insertImport } from '@nrwl/workspace/src/utilities/ast-utils';
|
||||
import type { SourceFile } from 'typescript';
|
||||
import { createSourceFile, ScriptTarget } from 'typescript';
|
||||
import {
|
||||
addImportToModule,
|
||||
addProviderToModule,
|
||||
} from '../../../utils/nx-devkit/ast-utils';
|
||||
import type { NgRxGeneratorOptions } from '../schema';
|
||||
|
||||
export function addImportsToModule(
|
||||
tree: Tree,
|
||||
options: NgRxGeneratorOptions
|
||||
): void {
|
||||
const modulePath = options.module;
|
||||
const sourceText = tree.read(modulePath, 'utf-8');
|
||||
let sourceFile = createSourceFile(
|
||||
modulePath,
|
||||
sourceText,
|
||||
ScriptTarget.Latest,
|
||||
true
|
||||
);
|
||||
const addImport = (
|
||||
source: SourceFile,
|
||||
symbolName: string,
|
||||
fileName: string,
|
||||
isDefault = false
|
||||
): SourceFile => {
|
||||
return insertImport(
|
||||
tree,
|
||||
source,
|
||||
modulePath,
|
||||
symbolName,
|
||||
fileName,
|
||||
isDefault
|
||||
);
|
||||
};
|
||||
|
||||
const dir = `./${names(options.directory).fileName}`;
|
||||
const pathPrefix = `${dir}/${names(options.name).fileName}`;
|
||||
const reducerPath = `${pathPrefix}.reducer`;
|
||||
const effectsPath = `${pathPrefix}.effects`;
|
||||
const facadePath = `${pathPrefix}.facade`;
|
||||
|
||||
const constantName = `${names(options.name).constantName}`;
|
||||
const effectsName = `${names(options.name).className}Effects`;
|
||||
const facadeName = `${names(options.name).className}Facade`;
|
||||
const className = `${names(options.name).className}`;
|
||||
const reducerImports = `* as from${className}`;
|
||||
|
||||
const storeMetaReducers = `metaReducers: !environment.production ? [] : []`;
|
||||
|
||||
const storeForRoot = `StoreModule.forRoot({}, {
|
||||
${storeMetaReducers},
|
||||
runtimeChecks: {
|
||||
strictActionImmutability: true,
|
||||
strictStateImmutability: true
|
||||
}
|
||||
})`;
|
||||
const nxModule = 'NxModule.forRoot()';
|
||||
const effectsForRoot = `EffectsModule.forRoot([${effectsName}])`;
|
||||
const effectsForEmptyRoot = `EffectsModule.forRoot([])`;
|
||||
const storeForFeature = `StoreModule.forFeature(from${className}.${constantName}_FEATURE_KEY, from${className}.reducer)`;
|
||||
const effectsForFeature = `EffectsModule.forFeature([${effectsName}])`;
|
||||
const devTools = `!environment.production ? StoreDevtoolsModule.instrument() : []`;
|
||||
const storeRouterModule = 'StoreRouterConnectingModule.forRoot()';
|
||||
|
||||
// this is just a heuristic
|
||||
const hasRouter = sourceText.indexOf('RouterModule') > -1;
|
||||
const hasNxModule = sourceText.includes(nxModule);
|
||||
|
||||
sourceFile = addImport(sourceFile, 'StoreModule', '@ngrx/store');
|
||||
sourceFile = addImport(sourceFile, 'EffectsModule', '@ngrx/effects');
|
||||
|
||||
if ((options.onlyEmptyRoot || options.minimal) && options.root) {
|
||||
sourceFile = addImport(
|
||||
sourceFile,
|
||||
'StoreDevtoolsModule',
|
||||
'@ngrx/store-devtools'
|
||||
);
|
||||
sourceFile = addImport(
|
||||
sourceFile,
|
||||
'environment',
|
||||
'../environments/environment'
|
||||
);
|
||||
|
||||
sourceFile = addImportToModule(tree, sourceFile, modulePath, storeForRoot);
|
||||
sourceFile = addImportToModule(
|
||||
tree,
|
||||
sourceFile,
|
||||
modulePath,
|
||||
effectsForEmptyRoot
|
||||
);
|
||||
sourceFile = addImportToModule(tree, sourceFile, modulePath, devTools);
|
||||
|
||||
if (hasRouter) {
|
||||
sourceFile = addImport(
|
||||
sourceFile,
|
||||
'StoreRouterConnectingModule',
|
||||
'@ngrx/router-store'
|
||||
);
|
||||
sourceFile = addImportToModule(
|
||||
tree,
|
||||
sourceFile,
|
||||
modulePath,
|
||||
storeRouterModule
|
||||
);
|
||||
}
|
||||
} else {
|
||||
const addCommonImports = (): SourceFile => {
|
||||
sourceFile = addImport(sourceFile, reducerImports, reducerPath, true);
|
||||
sourceFile = addImport(sourceFile, effectsName, effectsPath);
|
||||
|
||||
if (options.facade) {
|
||||
sourceFile = addImport(sourceFile, facadeName, facadePath);
|
||||
sourceFile = addProviderToModule(
|
||||
tree,
|
||||
sourceFile,
|
||||
modulePath,
|
||||
facadeName
|
||||
);
|
||||
}
|
||||
|
||||
return sourceFile;
|
||||
};
|
||||
|
||||
if (options.root) {
|
||||
sourceFile = addCommonImports();
|
||||
|
||||
if (!hasNxModule) {
|
||||
sourceFile = addImport(sourceFile, 'NxModule', '@nrwl/angular');
|
||||
sourceFile = addImportToModule(tree, sourceFile, modulePath, nxModule);
|
||||
}
|
||||
|
||||
sourceFile = addImport(
|
||||
sourceFile,
|
||||
'StoreDevtoolsModule',
|
||||
'@ngrx/store-devtools'
|
||||
);
|
||||
sourceFile = addImport(
|
||||
sourceFile,
|
||||
'environment',
|
||||
'../environments/environment'
|
||||
);
|
||||
|
||||
sourceFile = addImportToModule(
|
||||
tree,
|
||||
sourceFile,
|
||||
modulePath,
|
||||
storeForRoot
|
||||
);
|
||||
sourceFile = addImportToModule(
|
||||
tree,
|
||||
sourceFile,
|
||||
modulePath,
|
||||
effectsForRoot
|
||||
);
|
||||
sourceFile = addImportToModule(tree, sourceFile, modulePath, devTools);
|
||||
|
||||
if (hasRouter) {
|
||||
sourceFile = addImport(
|
||||
sourceFile,
|
||||
'StoreRouterConnectingModule',
|
||||
'@ngrx/router-store'
|
||||
);
|
||||
sourceFile = addImportToModule(
|
||||
tree,
|
||||
sourceFile,
|
||||
modulePath,
|
||||
storeRouterModule
|
||||
);
|
||||
}
|
||||
|
||||
sourceFile = addImportToModule(
|
||||
tree,
|
||||
sourceFile,
|
||||
modulePath,
|
||||
storeForFeature
|
||||
);
|
||||
} else {
|
||||
sourceFile = addCommonImports();
|
||||
|
||||
sourceFile = addImportToModule(
|
||||
tree,
|
||||
sourceFile,
|
||||
modulePath,
|
||||
storeForFeature
|
||||
);
|
||||
sourceFile = addImportToModule(
|
||||
tree,
|
||||
sourceFile,
|
||||
modulePath,
|
||||
effectsForFeature
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,9 +1,10 @@
|
||||
import { Rule } from '@angular-devkit/schematics';
|
||||
import { addDepsToPackageJson } from '@nrwl/workspace';
|
||||
import type { GeneratorCallback, Tree } from '@nrwl/devkit';
|
||||
import { addDependenciesToPackageJson } from '@nrwl/devkit';
|
||||
import { ngrxVersion } from '../../../utils/versions';
|
||||
|
||||
export function addNgRxToPackageJson(): Rule {
|
||||
return addDepsToPackageJson(
|
||||
export function addNgRxToPackageJson(tree: Tree): GeneratorCallback {
|
||||
return addDependenciesToPackageJson(
|
||||
tree,
|
||||
{
|
||||
'@ngrx/store': ngrxVersion,
|
||||
'@ngrx/effects': ngrxVersion,
|
||||
48
packages/angular/src/generators/ngrx/lib/generate-files.ts
Normal file
48
packages/angular/src/generators/ngrx/lib/generate-files.ts
Normal file
@ -0,0 +1,48 @@
|
||||
import type { Tree } from '@nrwl/devkit';
|
||||
import { generateFiles, joinPathFragments, names } from '@nrwl/devkit';
|
||||
import { dirname } from 'path';
|
||||
import type { NgRxGeneratorOptions } from '../schema';
|
||||
|
||||
/**
|
||||
* Generate 'feature' scaffolding: actions, reducer, effects, interfaces, selectors, facade
|
||||
*/
|
||||
export function generateNgrxFilesFromTemplates(
|
||||
tree: Tree,
|
||||
options: NgRxGeneratorOptions
|
||||
): void {
|
||||
const name = options.name;
|
||||
const moduleDir = dirname(options.module);
|
||||
const templatesDir =
|
||||
!options.syntax || options.syntax === 'creators'
|
||||
? './files/creator-syntax'
|
||||
: './files/classes-syntax';
|
||||
const projectNames = names(name);
|
||||
|
||||
generateFiles(
|
||||
tree,
|
||||
joinPathFragments(__dirname, '..', templatesDir),
|
||||
moduleDir,
|
||||
{
|
||||
...options,
|
||||
tmpl: '',
|
||||
...projectNames,
|
||||
}
|
||||
);
|
||||
|
||||
if (!options.facade) {
|
||||
tree.delete(
|
||||
joinPathFragments(
|
||||
moduleDir,
|
||||
options.directory,
|
||||
`${projectNames.fileName}.facade.ts`
|
||||
)
|
||||
);
|
||||
tree.delete(
|
||||
joinPathFragments(
|
||||
moduleDir,
|
||||
options.directory,
|
||||
`${projectNames.fileName}.facade.spec.ts`
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,5 @@
|
||||
export { RequestContext } from './request-context';
|
||||
export { addExportsToBarrel } from './add-exports-barrel';
|
||||
export { addImportsToModule } from './add-imports-to-module';
|
||||
export { addNgRxToPackageJson } from './add-ngrx-to-package-json';
|
||||
export { addExportsToBarrel } from './add-exports-barrel';
|
||||
export { generateNgrxFilesFromTemplates } from './generate-files';
|
||||
export { normalizeOptions } from './normalize-options';
|
||||
@ -0,0 +1,20 @@
|
||||
import { names } from '@nrwl/devkit';
|
||||
import type { NgRxGeneratorOptions } from '../schema';
|
||||
|
||||
export function normalizeOptions(
|
||||
options: NgRxGeneratorOptions
|
||||
): NgRxGeneratorOptions {
|
||||
const normalizedOptions = {
|
||||
...options,
|
||||
directory: names(options.directory).fileName,
|
||||
};
|
||||
|
||||
if (options.minimal) {
|
||||
normalizedOptions.onlyEmptyRoot = true;
|
||||
}
|
||||
if (options.skipImport) {
|
||||
normalizedOptions.onlyAddFiles = true;
|
||||
}
|
||||
|
||||
return normalizedOptions;
|
||||
}
|
||||
278
packages/angular/src/generators/ngrx/ngrx.classes.spec.ts
Normal file
278
packages/angular/src/generators/ngrx/ngrx.classes.spec.ts
Normal file
@ -0,0 +1,278 @@
|
||||
import { Tree } from '@nrwl/devkit';
|
||||
import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing';
|
||||
import { dirname } from 'path';
|
||||
import {
|
||||
AppConfig,
|
||||
createApp,
|
||||
createLib,
|
||||
getAppConfig,
|
||||
getLibConfig,
|
||||
} from '../../utils/nx-devkit/testing';
|
||||
import { ngrxGenerator } from './ngrx';
|
||||
import { NgRxGeneratorOptions } from './schema';
|
||||
|
||||
describe('NgRx generator', () => {
|
||||
let appConfig: AppConfig;
|
||||
let statePath: string;
|
||||
let tree: Tree;
|
||||
|
||||
const defaultOptions: NgRxGeneratorOptions = {
|
||||
directory: '+state',
|
||||
minimal: true,
|
||||
module: 'apps/myapp/src/app/app.module.ts',
|
||||
name: 'users',
|
||||
useDataPersistence: false,
|
||||
syntax: 'classes',
|
||||
};
|
||||
|
||||
const expectFileToExist = (file: string) =>
|
||||
expect(tree.exists(file)).toBeTruthy();
|
||||
const expectFileToNotExist = (file: string) =>
|
||||
expect(tree.exists(file)).not.toBeTruthy();
|
||||
|
||||
beforeEach(() => {
|
||||
tree = createTreeWithEmptyWorkspace();
|
||||
createApp(tree, 'myapp');
|
||||
appConfig = getAppConfig();
|
||||
statePath = `${dirname(appConfig.appModule)}/+state`;
|
||||
});
|
||||
|
||||
describe('classes syntax', () => {
|
||||
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.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.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 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 use DataPersistence when useDataPersistence is true', async () => {
|
||||
await ngrxGenerator(tree, {
|
||||
...defaultOptions,
|
||||
module: appConfig.appModule,
|
||||
minimal: false,
|
||||
useDataPersistence: true,
|
||||
});
|
||||
|
||||
expect(
|
||||
tree.read(`${statePath}/users.effects.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.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();
|
||||
});
|
||||
|
||||
describe('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 effects correctly when useDataPersistence is true', async () => {
|
||||
await ngrxGenerator(tree, {
|
||||
...defaultOptions,
|
||||
name: 'super-users',
|
||||
module: appConfig.appModule,
|
||||
minimal: false,
|
||||
useDataPersistence: true,
|
||||
});
|
||||
|
||||
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();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
293
packages/angular/src/generators/ngrx/ngrx.creators.spec.ts
Normal file
293
packages/angular/src/generators/ngrx/ngrx.creators.spec.ts
Normal file
@ -0,0 +1,293 @@
|
||||
import { Tree } from '@nrwl/devkit';
|
||||
import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing';
|
||||
import { dirname } from 'path';
|
||||
import {
|
||||
AppConfig,
|
||||
createApp,
|
||||
createLib,
|
||||
getAppConfig,
|
||||
getLibConfig,
|
||||
} from '../../utils/nx-devkit/testing';
|
||||
import { ngrxGenerator } from './ngrx';
|
||||
import { NgRxGeneratorOptions } from './schema';
|
||||
|
||||
describe('NgRx generator', () => {
|
||||
let appConfig: AppConfig;
|
||||
let statePath: string;
|
||||
let tree: Tree;
|
||||
|
||||
const defaultOptions: NgRxGeneratorOptions = {
|
||||
directory: '+state',
|
||||
minimal: true,
|
||||
module: 'apps/myapp/src/app/app.module.ts',
|
||||
name: 'users',
|
||||
useDataPersistence: false,
|
||||
syntax: 'creators',
|
||||
};
|
||||
|
||||
const expectFileToExist = (file: string) =>
|
||||
expect(tree.exists(file)).toBeTruthy();
|
||||
const expectFileToNotExist = (file: string) =>
|
||||
expect(tree.exists(file)).not.toBeTruthy();
|
||||
|
||||
beforeEach(() => {
|
||||
tree = createTreeWithEmptyWorkspace();
|
||||
createApp(tree, 'myapp');
|
||||
appConfig = getAppConfig();
|
||||
statePath = `${dirname(appConfig.appModule)}/+state`;
|
||||
});
|
||||
|
||||
describe('creators syntax', () => {
|
||||
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 use DataPersistence when useDataPersistence is true', async () => {
|
||||
await ngrxGenerator(tree, {
|
||||
...defaultOptions,
|
||||
module: appConfig.appModule,
|
||||
minimal: false,
|
||||
useDataPersistence: true,
|
||||
});
|
||||
|
||||
expect(
|
||||
tree.read(`${statePath}/users.effects.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();
|
||||
});
|
||||
|
||||
describe('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 effects correctly when useDataPersistence is true', async () => {
|
||||
await ngrxGenerator(tree, {
|
||||
...defaultOptions,
|
||||
name: 'super-users',
|
||||
module: appConfig.appModule,
|
||||
minimal: false,
|
||||
useDataPersistence: true,
|
||||
});
|
||||
|
||||
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();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
229
packages/angular/src/generators/ngrx/ngrx.spec.ts
Normal file
229
packages/angular/src/generators/ngrx/ngrx.spec.ts
Normal file
@ -0,0 +1,229 @@
|
||||
import type { Tree } from '@nrwl/devkit';
|
||||
import * as devkit from '@nrwl/devkit';
|
||||
import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing';
|
||||
import { createApp } from '../../utils/nx-devkit/testing';
|
||||
import { ngrxVersion } from '../../utils/versions';
|
||||
import { ngrxGenerator } from './ngrx';
|
||||
import type { NgRxGeneratorOptions } from './schema';
|
||||
|
||||
describe('ngrx', () => {
|
||||
let tree: Tree;
|
||||
const defaultOptions: NgRxGeneratorOptions = {
|
||||
directory: '+state',
|
||||
minimal: true,
|
||||
module: 'apps/myapp/src/app/app.module.ts',
|
||||
name: 'users',
|
||||
useDataPersistence: false,
|
||||
};
|
||||
|
||||
const expectFileToExist = (file: string) =>
|
||||
expect(tree.exists(file)).toBeTruthy();
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
tree = createTreeWithEmptyWorkspace();
|
||||
createApp(tree, 'myapp');
|
||||
});
|
||||
|
||||
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 add an empty root module when minimal is true', async () => {
|
||||
await ngrxGenerator(tree, {
|
||||
...defaultOptions,
|
||||
root: true,
|
||||
minimal: true,
|
||||
});
|
||||
|
||||
expect(
|
||||
tree.read('/apps/myapp/src/app/app.module.ts', 'utf-8')
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should add an empty root module when onlyEmptyRoot is true', async () => {
|
||||
await ngrxGenerator(tree, {
|
||||
...defaultOptions,
|
||||
root: true,
|
||||
minimal: false,
|
||||
onlyEmptyRoot: true,
|
||||
});
|
||||
|
||||
expect(
|
||||
tree.read('/apps/myapp/src/app/app.module.ts', 'utf-8')
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should add a root module with feature module when minimal and onlyEmptyRoot are false', async () => {
|
||||
await ngrxGenerator(tree, {
|
||||
...defaultOptions,
|
||||
root: true,
|
||||
minimal: false,
|
||||
onlyEmptyRoot: 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 add facade provider when onlyEmptyRoot is true', async () => {
|
||||
await ngrxGenerator(tree, {
|
||||
...defaultOptions,
|
||||
root: true,
|
||||
minimal: false,
|
||||
onlyEmptyRoot: true,
|
||||
facade: true,
|
||||
});
|
||||
|
||||
expect(
|
||||
tree.read('/apps/myapp/src/app/app.module.ts', 'utf-8')
|
||||
).not.toContain('providers: [UsersFacade]');
|
||||
});
|
||||
|
||||
it('should only add files 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 only add files when onlyAddFiles is true', async () => {
|
||||
await ngrxGenerator(tree, {
|
||||
...defaultOptions,
|
||||
minimal: false,
|
||||
onlyAddFiles: 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
|
||||
);
|
||||
});
|
||||
|
||||
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 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();
|
||||
});
|
||||
});
|
||||
46
packages/angular/src/generators/ngrx/ngrx.ts
Normal file
46
packages/angular/src/generators/ngrx/ngrx.ts
Normal file
@ -0,0 +1,46 @@
|
||||
import type { GeneratorCallback, Tree } from '@nrwl/devkit';
|
||||
import { formatFiles } from '@nrwl/devkit';
|
||||
import {
|
||||
addExportsToBarrel,
|
||||
addImportsToModule,
|
||||
addNgRxToPackageJson,
|
||||
generateNgrxFilesFromTemplates,
|
||||
normalizeOptions,
|
||||
} from './lib';
|
||||
import type { NgRxGeneratorOptions } from './schema';
|
||||
|
||||
export async function ngrxGenerator(
|
||||
tree: Tree,
|
||||
options: NgRxGeneratorOptions
|
||||
): Promise<GeneratorCallback> {
|
||||
const normalizedOptions = normalizeOptions(options);
|
||||
|
||||
if (!tree.exists(normalizedOptions.module)) {
|
||||
throw new Error(`Module does not exist: ${normalizedOptions.module}.`);
|
||||
}
|
||||
|
||||
if (
|
||||
!normalizedOptions.onlyEmptyRoot ||
|
||||
(!normalizedOptions.root && normalizedOptions.minimal)
|
||||
) {
|
||||
generateNgrxFilesFromTemplates(tree, normalizedOptions);
|
||||
}
|
||||
|
||||
if (!normalizedOptions.onlyAddFiles) {
|
||||
addImportsToModule(tree, normalizedOptions);
|
||||
addExportsToBarrel(tree, normalizedOptions);
|
||||
}
|
||||
|
||||
let packageInstallationTask: GeneratorCallback = () => {};
|
||||
if (!normalizedOptions.skipPackageJson) {
|
||||
packageInstallationTask = addNgRxToPackageJson(tree);
|
||||
}
|
||||
|
||||
if (!normalizedOptions.skipFormat) {
|
||||
await formatFiles(tree);
|
||||
}
|
||||
|
||||
return packageInstallationTask;
|
||||
}
|
||||
|
||||
export default ngrxGenerator;
|
||||
24
packages/angular/src/generators/ngrx/schema.d.ts
vendored
Normal file
24
packages/angular/src/generators/ngrx/schema.d.ts
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
export interface NgRxGeneratorOptions {
|
||||
directory: string;
|
||||
minimal: boolean;
|
||||
module: string;
|
||||
name: string;
|
||||
useDataPersistence: boolean;
|
||||
barrels?: boolean;
|
||||
facade?: boolean;
|
||||
root?: boolean;
|
||||
skipFormat?: boolean;
|
||||
skipImport?: boolean;
|
||||
skipPackageJson?: boolean;
|
||||
syntax?: 'classes' | 'creators';
|
||||
|
||||
/**
|
||||
* @deprecated use `skipImport`.
|
||||
*/
|
||||
onlyAddFiles?: boolean;
|
||||
|
||||
/**
|
||||
* @deprecated use `minimal`.
|
||||
*/
|
||||
onlyEmptyRoot?: boolean;
|
||||
}
|
||||
@ -1,17 +1,18 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/schema",
|
||||
"$id": "SchematicsNxNgrx",
|
||||
"title": "Add NgRx support to an application or library",
|
||||
"$id": "NxNgrxGenerator",
|
||||
"title": "Add NgRx support to an application or library.",
|
||||
"cli": "nx",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Name of the NgRx feature state, such as \"products\" or \"users\"). Recommended to use the plural form of the name.",
|
||||
"description": "Name of the NgRx feature state, such as `products` or `users`. Recommended to use the plural form of the name.",
|
||||
"$default": {
|
||||
"$source": "argv",
|
||||
"index": 0
|
||||
},
|
||||
"x-prompt": "What name would you like to use for the NgRx feature state? An example would be \"users\"."
|
||||
"x-prompt": "What name would you like to use for the NgRx feature state? An example would be `users`."
|
||||
},
|
||||
"module": {
|
||||
"type": "string",
|
||||
@ -43,7 +44,8 @@
|
||||
"onlyAddFiles": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "**Deprecated**, use `skipImport`. Only add new NgRx files, without changing the module file (e.g., --onlyAddFiles)."
|
||||
"description": "Only add new NgRx files, without changing the module file (e.g., --onlyAddFiles).",
|
||||
"x-deprecated": "Use the `skipImport` option instead."
|
||||
},
|
||||
"minimal": {
|
||||
"type": "boolean",
|
||||
@ -53,7 +55,8 @@
|
||||
"onlyEmptyRoot": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "**Deprecated**, use `minimal`. Do not generate any files. Only generate StoreModule.forRoot and EffectsModule.forRoot (e.g., --onlyEmptyRoot)."
|
||||
"description": "Do not generate any files. Only generate StoreModule.forRoot and EffectsModule.forRoot (e.g., --onlyEmptyRoot).",
|
||||
"x-deprecated": "Use the `minimal` option instead."
|
||||
},
|
||||
"skipFormat": {
|
||||
"description": "Skip formatting of generated files.",
|
||||
@ -82,5 +85,6 @@
|
||||
"description": "Use barrels to re-export actions, state, and selectors."
|
||||
}
|
||||
},
|
||||
"required": ["module"]
|
||||
"additionalProperties": false,
|
||||
"required": ["module", "name"]
|
||||
}
|
||||
@ -31,7 +31,7 @@ describe('StorybookConfiguration generator', () => {
|
||||
'../../../../storybook/collection.json'
|
||||
),
|
||||
});
|
||||
jest.resetModuleRegistry();
|
||||
jest.resetModules();
|
||||
jest.doMock('@storybook/angular/package.json', () => ({
|
||||
version: '6.2.0',
|
||||
}));
|
||||
|
||||
@ -1 +0,0 @@
|
||||
export { ngrxGenerator } from './ngrx/ngrx';
|
||||
@ -1,46 +0,0 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Effect, Actions <% if (!useDataPersistence) { %>,ofType<% }%> } from '@ngrx/effects';
|
||||
import { <% if (useDataPersistence) { %>DataPersistence<% } else { %>fetch<% } %> } from '@nrwl/angular';
|
||||
|
||||
import { <%= className %>PartialState } from './<%= fileName %>.reducer';
|
||||
import { Load<%= className %>, <%= className %>Loaded, <%= className %>LoadError, <%= className %>ActionTypes } from './<%= fileName %>.actions';
|
||||
|
||||
@Injectable()
|
||||
export class <%= className %>Effects {
|
||||
@Effect() load<%= className %>$ =
|
||||
|
||||
<% if (useDataPersistence) { %>
|
||||
this.dataPersistence.fetch(<%= className %>ActionTypes.Load<%= className %>, {
|
||||
run: (action: Load<%= className %>, state: <%= className %>PartialState) => {
|
||||
// Your custom REST 'load' logic goes here. For now just return an empty list...
|
||||
return new <%= className %>Loaded([]);
|
||||
},
|
||||
|
||||
onError: (action: Load<%= className %>, error) => {
|
||||
console.error('Error', error);
|
||||
return new <%= className %>LoadError(error);
|
||||
}
|
||||
});
|
||||
<% } else { %>
|
||||
this.actions$.pipe(
|
||||
ofType(<%= className %>ActionTypes.Load<%= className %>),
|
||||
fetch({
|
||||
run: (action: Load<%= className %>, state: <%= className %>PartialState) => {
|
||||
// Your custom REST 'load' logic goes here. For now just return an empty list...
|
||||
return new <%= className %>Loaded([]);
|
||||
},
|
||||
|
||||
onError: (action: Load<%= className %>, error) => {
|
||||
console.error('Error', error);
|
||||
return new <%= className %>LoadError(error);
|
||||
}
|
||||
})
|
||||
);
|
||||
<% } %>
|
||||
|
||||
|
||||
|
||||
constructor(
|
||||
private actions$: Actions,
|
||||
<% if (useDataPersistence) { %>private dataPersistence: DataPersistence<<%= className %>PartialState><% } %>) { }
|
||||
}
|
||||
@ -1,747 +0,0 @@
|
||||
import { UnitTestTree } from '@angular-devkit/schematics/testing';
|
||||
import { Tree } from '@angular-devkit/schematics';
|
||||
import { readJsonInTree } from '@nrwl/workspace';
|
||||
|
||||
import { getFileContent } from '@nrwl/workspace/testing';
|
||||
import {
|
||||
AppConfig,
|
||||
createApp,
|
||||
createLib,
|
||||
getAppConfig,
|
||||
getLibConfig,
|
||||
runSchematic,
|
||||
} from '../../utils/testing';
|
||||
import { createEmptyWorkspace } from '@nrwl/workspace/testing';
|
||||
import * as path from 'path';
|
||||
|
||||
describe('ngrx', () => {
|
||||
let appTree: Tree;
|
||||
|
||||
beforeEach(() => {
|
||||
appTree = Tree.empty();
|
||||
appTree = createEmptyWorkspace(appTree);
|
||||
appTree = createApp(appTree, 'myapp');
|
||||
});
|
||||
|
||||
it('should add empty root', async () => {
|
||||
const tree = await runSchematic(
|
||||
'ngrx',
|
||||
{
|
||||
name: 'state',
|
||||
module: 'apps/myapp/src/app/app.module.ts',
|
||||
onlyEmptyRoot: true,
|
||||
minimal: false,
|
||||
root: true,
|
||||
},
|
||||
appTree
|
||||
);
|
||||
const appModule = getFileContent(tree, '/apps/myapp/src/app/app.module.ts');
|
||||
|
||||
expect(
|
||||
tree.exists('apps/myapp/src/app/+state/state.actions.ts')
|
||||
).toBeFalsy();
|
||||
|
||||
expect(appModule).toContain('StoreModule.forRoot(');
|
||||
expect(appModule).toContain('runtimeChecks: {');
|
||||
expect(appModule).toContain('strictActionImmutability: true');
|
||||
expect(appModule).toContain('strictStateImmutability: true');
|
||||
expect(appModule).toContain('EffectsModule.forRoot');
|
||||
});
|
||||
|
||||
it('should add empty root with minimal option', async () => {
|
||||
const tree = await runSchematic(
|
||||
'ngrx',
|
||||
{
|
||||
name: 'state',
|
||||
module: 'apps/myapp/src/app/app.module.ts',
|
||||
root: true,
|
||||
onlyEmptyRoot: false,
|
||||
minimal: true,
|
||||
},
|
||||
appTree
|
||||
);
|
||||
const appModule = getFileContent(tree, '/apps/myapp/src/app/app.module.ts');
|
||||
|
||||
expect(
|
||||
tree.exists('apps/myapp/src/app/+state/state.actions.ts')
|
||||
).toBeFalsy();
|
||||
|
||||
expect(appModule).toContain('StoreModule.forRoot(');
|
||||
expect(appModule).toContain('runtimeChecks: {');
|
||||
expect(appModule).toContain('strictActionImmutability: true');
|
||||
expect(appModule).toContain('strictStateImmutability: true');
|
||||
expect(appModule).toContain('EffectsModule.forRoot([])');
|
||||
});
|
||||
|
||||
it('should add root', async () => {
|
||||
const tree = await runSchematic(
|
||||
'ngrx',
|
||||
{
|
||||
name: 'app',
|
||||
module: 'apps/myapp/src/app/app.module.ts',
|
||||
root: true,
|
||||
minimal: false,
|
||||
},
|
||||
appTree
|
||||
);
|
||||
|
||||
[
|
||||
'/apps/myapp/src/app/+state/app.actions.ts',
|
||||
'/apps/myapp/src/app/+state/app.effects.ts',
|
||||
'/apps/myapp/src/app/+state/app.effects.spec.ts',
|
||||
'/apps/myapp/src/app/+state/app.reducer.ts',
|
||||
'/apps/myapp/src/app/+state/app.reducer.spec.ts',
|
||||
'/apps/myapp/src/app/+state/app.selectors.ts',
|
||||
'/apps/myapp/src/app/+state/app.selectors.spec.ts',
|
||||
].forEach((fileName) => {
|
||||
expect(tree.exists(fileName)).toBeTruthy();
|
||||
});
|
||||
|
||||
// Since we did not include the `--facade` option
|
||||
expect(tree.exists('/apps/myapp/src/app/+state/app.facade.ts')).toBeFalsy();
|
||||
expect(
|
||||
tree.exists('/apps/myapp/src/app/+state/app.facade.spec.ts')
|
||||
).toBeFalsy();
|
||||
|
||||
const appModule = getFileContent(tree, '/apps/myapp/src/app/app.module.ts');
|
||||
|
||||
expect(appModule).toContain(`import { NxModule } from '@nrwl/angular';`);
|
||||
expect(appModule).toContain(
|
||||
`import * as fromApp from './+state/app.reducer';`
|
||||
);
|
||||
expect(appModule).toContain('NxModule.forRoot');
|
||||
expect(appModule).toContain('StoreModule.forRoot');
|
||||
expect(appModule).toContain(
|
||||
`StoreModule.forFeature(fromApp.APP_FEATURE_KEY, fromApp.reducer)`
|
||||
);
|
||||
expect(appModule).toContain('EffectsModule.forRoot');
|
||||
expect(appModule).toContain(
|
||||
'metaReducers: !environment.production ? [] : []'
|
||||
);
|
||||
});
|
||||
|
||||
it('should add facade to root', async () => {
|
||||
const tree = await runSchematic(
|
||||
'ngrx',
|
||||
{
|
||||
name: 'app',
|
||||
module: 'apps/myapp/src/app/app.module.ts',
|
||||
root: true,
|
||||
facade: true,
|
||||
minimal: false,
|
||||
},
|
||||
appTree
|
||||
);
|
||||
|
||||
const appModule = getFileContent(tree, '/apps/myapp/src/app/app.module.ts');
|
||||
|
||||
expect(appModule).toContain(`import { NxModule } from '@nrwl/angular';`);
|
||||
expect(appModule).toContain('NxModule.forRoot');
|
||||
expect(appModule).toContain('StoreModule.forRoot');
|
||||
expect(appModule).toContain('EffectsModule.forRoot');
|
||||
expect(appModule).toContain(
|
||||
'metaReducers: !environment.production ? [] : []'
|
||||
);
|
||||
|
||||
// Do not add Effects file to providers; already registered in EffectsModule
|
||||
expect(appModule).toContain('providers: [AppFacade]');
|
||||
|
||||
[
|
||||
'/apps/myapp/src/app/+state/app.actions.ts',
|
||||
'/apps/myapp/src/app/+state/app.effects.ts',
|
||||
'/apps/myapp/src/app/+state/app.effects.spec.ts',
|
||||
'/apps/myapp/src/app/+state/app.reducer.ts',
|
||||
'/apps/myapp/src/app/+state/app.reducer.spec.ts',
|
||||
'/apps/myapp/src/app/+state/app.facade.ts',
|
||||
'/apps/myapp/src/app/+state/app.facade.spec.ts',
|
||||
'/apps/myapp/src/app/+state/app.selectors.ts',
|
||||
'/apps/myapp/src/app/+state/app.selectors.spec.ts',
|
||||
].forEach((fileName) => {
|
||||
expect(tree.exists(fileName)).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
it('should not add RouterStoreModule only if the module does not reference the router', async () => {
|
||||
const newTree = createApp(appTree, 'myapp-norouter', false);
|
||||
const tree = await runSchematic(
|
||||
'ngrx',
|
||||
{
|
||||
name: 'app',
|
||||
module: 'apps/myapp-norouter/src/app/app.module.ts',
|
||||
root: true,
|
||||
},
|
||||
newTree
|
||||
);
|
||||
const appModule = getFileContent(
|
||||
tree,
|
||||
'/apps/myapp-norouter/src/app/app.module.ts'
|
||||
);
|
||||
expect(appModule).not.toContain('StoreRouterConnectingModule.forRoot()');
|
||||
});
|
||||
|
||||
it('should add feature', async () => {
|
||||
const tree = await runSchematic(
|
||||
'ngrx',
|
||||
{
|
||||
name: 'state',
|
||||
module: 'apps/myapp/src/app/app.module.ts',
|
||||
minimal: false,
|
||||
},
|
||||
appTree
|
||||
);
|
||||
|
||||
const appModule = getFileContent(tree, '/apps/myapp/src/app/app.module.ts');
|
||||
expect(appModule).toContain('StoreModule.forFeature');
|
||||
expect(appModule).toContain('EffectsModule.forFeature');
|
||||
expect(appModule).not.toContain('!environment.production ? [] : []');
|
||||
|
||||
expect(
|
||||
tree.exists(`/apps/myapp/src/app/+state/state.actions.ts`)
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should add with custom directoryName', async () => {
|
||||
const tree = await runSchematic(
|
||||
'ngrx',
|
||||
{
|
||||
name: 'state',
|
||||
module: 'apps/myapp/src/app/app.module.ts',
|
||||
directory: 'myCustomState',
|
||||
minimal: false,
|
||||
},
|
||||
appTree
|
||||
);
|
||||
|
||||
const appModule = getFileContent(tree, '/apps/myapp/src/app/app.module.ts');
|
||||
expect(appModule).toContain('StoreModule.forFeature');
|
||||
expect(appModule).toContain('EffectsModule.forFeature');
|
||||
expect(appModule).not.toContain('!environment.production ? [] : []');
|
||||
|
||||
expect(
|
||||
tree.exists(`/apps/myapp/src/app/my-custom-state/state.actions.ts`)
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should only add files', async () => {
|
||||
const tree = await runSchematic(
|
||||
'ngrx',
|
||||
{
|
||||
name: 'state',
|
||||
module: 'apps/myapp/src/app/app.module.ts',
|
||||
onlyAddFiles: true,
|
||||
facade: true,
|
||||
minimal: false,
|
||||
},
|
||||
appTree
|
||||
);
|
||||
|
||||
const appModule = getFileContent(tree, '/apps/myapp/src/app/app.module.ts');
|
||||
expect(appModule).not.toContain('StoreModule');
|
||||
expect(appModule).not.toContain('!environment.production ? [] : []');
|
||||
|
||||
[
|
||||
'/apps/myapp/src/app/+state/state.effects.ts',
|
||||
'/apps/myapp/src/app/+state/state.facade.ts',
|
||||
'/apps/myapp/src/app/+state/state.reducer.ts',
|
||||
'/apps/myapp/src/app/+state/state.selectors.ts',
|
||||
'/apps/myapp/src/app/+state/state.effects.spec.ts',
|
||||
'/apps/myapp/src/app/+state/state.facade.spec.ts',
|
||||
'/apps/myapp/src/app/+state/state.selectors.spec.ts',
|
||||
].forEach((fileName) => {
|
||||
expect(tree.exists(fileName)).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
it('should only add files with skipImport option', async () => {
|
||||
const tree = await runSchematic(
|
||||
'ngrx',
|
||||
{
|
||||
name: 'state',
|
||||
module: 'apps/myapp/src/app/app.module.ts',
|
||||
onlyAddFiles: false,
|
||||
skipImport: true,
|
||||
facade: true,
|
||||
minimal: false,
|
||||
},
|
||||
appTree
|
||||
);
|
||||
|
||||
const appModule = getFileContent(tree, '/apps/myapp/src/app/app.module.ts');
|
||||
expect(appModule).not.toContain('StoreModule');
|
||||
expect(appModule).not.toContain('!environment.production ? [] : []');
|
||||
|
||||
[
|
||||
'/apps/myapp/src/app/+state/state.effects.ts',
|
||||
'/apps/myapp/src/app/+state/state.facade.ts',
|
||||
'/apps/myapp/src/app/+state/state.reducer.ts',
|
||||
'/apps/myapp/src/app/+state/state.selectors.ts',
|
||||
'/apps/myapp/src/app/+state/state.effects.spec.ts',
|
||||
'/apps/myapp/src/app/+state/state.facade.spec.ts',
|
||||
'/apps/myapp/src/app/+state/state.selectors.spec.ts',
|
||||
].forEach((fileName) => {
|
||||
expect(tree.exists(fileName)).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
it('should update package.json', async () => {
|
||||
const tree = await runSchematic(
|
||||
'ngrx',
|
||||
{
|
||||
name: 'state',
|
||||
module: 'apps/myapp/src/app/app.module.ts',
|
||||
},
|
||||
appTree
|
||||
);
|
||||
const packageJson = readJsonInTree(tree, 'package.json');
|
||||
|
||||
expect(packageJson.dependencies['@ngrx/store']).toBeDefined();
|
||||
expect(packageJson.dependencies['@ngrx/router-store']).toBeDefined();
|
||||
expect(packageJson.dependencies['@ngrx/effects']).toBeDefined();
|
||||
expect(packageJson.devDependencies['@ngrx/store-devtools']).toBeDefined();
|
||||
});
|
||||
|
||||
it('should error when no module is provided', async () => {
|
||||
try {
|
||||
await runSchematic(
|
||||
'ngrx',
|
||||
{
|
||||
name: 'state',
|
||||
module: '',
|
||||
},
|
||||
appTree
|
||||
);
|
||||
fail();
|
||||
} catch (e) {
|
||||
expect(e.message).toEqual('The required --module option must be passed');
|
||||
}
|
||||
});
|
||||
|
||||
it('should error the module could not be found', async () => {
|
||||
try {
|
||||
await runSchematic(
|
||||
'ngrx',
|
||||
{
|
||||
name: 'state',
|
||||
module: 'does-not-exist.ts',
|
||||
},
|
||||
appTree
|
||||
);
|
||||
} catch (e) {
|
||||
expect(e.message).toEqual('Path does not exist: does-not-exist.ts');
|
||||
}
|
||||
});
|
||||
|
||||
describe('code generation', () => {
|
||||
it('should scaffold the ngrx "user" files without a facade', async () => {
|
||||
const appConfig = getAppConfig();
|
||||
const hasFile = (file) => expect(tree.exists(file)).toBeTruthy();
|
||||
const missingFile = (file) => expect(tree.exists(file)).not.toBeTruthy();
|
||||
const statePath = `${path.dirname(appConfig.appModule)}/+state`;
|
||||
|
||||
const tree = await buildNgrxTree(appConfig);
|
||||
|
||||
hasFile(`${statePath}/user.actions.ts`);
|
||||
hasFile(`${statePath}/user.effects.ts`);
|
||||
hasFile(`${statePath}/user.effects.spec.ts`);
|
||||
missingFile(`${statePath}/user.facade.ts`);
|
||||
missingFile(`${statePath}/user.facade.spec.ts`);
|
||||
hasFile(`${statePath}/user.reducer.ts`);
|
||||
hasFile(`${statePath}/user.reducer.spec.ts`);
|
||||
hasFile(`${statePath}/user.selectors.ts`);
|
||||
});
|
||||
|
||||
it('should scaffold the ngrx "user" files WITH a facade', async () => {
|
||||
const appConfig = getAppConfig();
|
||||
const hasFile = (file) => expect(tree.exists(file)).toBeTruthy();
|
||||
const tree = await buildNgrxTree(appConfig, 'user', true);
|
||||
const statePath = `${path.dirname(appConfig.appModule)}/+state`;
|
||||
|
||||
hasFile(`${statePath}/user.actions.ts`);
|
||||
hasFile(`${statePath}/user.effects.ts`);
|
||||
hasFile(`${statePath}/user.facade.ts`);
|
||||
hasFile(`${statePath}/user.reducer.ts`);
|
||||
hasFile(`${statePath}/user.selectors.ts`);
|
||||
|
||||
hasFile(`${statePath}/user.reducer.spec.ts`);
|
||||
hasFile(`${statePath}/user.effects.spec.ts`);
|
||||
hasFile(`${statePath}/user.selectors.spec.ts`);
|
||||
hasFile(`${statePath}/user.facade.spec.ts`);
|
||||
});
|
||||
|
||||
it('should build the ngrx actions', async () => {
|
||||
const appConfig = getAppConfig();
|
||||
const tree = await buildNgrxTree(appConfig, 'users');
|
||||
|
||||
const statePath = `${path.dirname(appConfig.appModule)}/+state`;
|
||||
const content = getFileContent(tree, `${statePath}/users.actions.ts`);
|
||||
|
||||
expect(content).toContain('UsersActionTypes');
|
||||
|
||||
expect(content).toContain(`LoadUsers = '[Users] Load Users'`);
|
||||
expect(content).toContain(`UsersLoaded = '[Users] Users Loaded'`);
|
||||
expect(content).toContain(`UsersLoadError = '[Users] Users Load Error'`);
|
||||
|
||||
expect(content).toContain('class LoadUsers implements Action');
|
||||
expect(content).toContain('class UsersLoaded implements Action');
|
||||
expect(content).toContain(
|
||||
'type UsersAction = LoadUsers | UsersLoaded | UsersLoadError'
|
||||
);
|
||||
expect(content).toContain('export const fromUsersActions');
|
||||
});
|
||||
|
||||
it('should build the ngrx selectors', async () => {
|
||||
const appConfig = getAppConfig();
|
||||
const tree = await buildNgrxTree(appConfig, 'users');
|
||||
|
||||
const statePath = `${path.dirname(appConfig.appModule)}/+state`;
|
||||
const content = getFileContent(tree, `${statePath}/users.selectors.ts`);
|
||||
|
||||
[
|
||||
`import { USERS_FEATURE_KEY, UsersState } from './users.reducer'`,
|
||||
`export const usersQuery`,
|
||||
].forEach((text) => {
|
||||
expect(content).toContain(text);
|
||||
});
|
||||
});
|
||||
|
||||
it('should build the ngrx facade', async () => {
|
||||
const appConfig = getAppConfig();
|
||||
const includeFacade = true;
|
||||
const tree = await buildNgrxTree(appConfig, 'users', includeFacade);
|
||||
|
||||
const statePath = `${path.dirname(appConfig.appModule)}/+state`;
|
||||
const content = getFileContent(tree, `${statePath}/users.facade.ts`);
|
||||
|
||||
[
|
||||
`import { UsersPartialState } from './users.reducer'`,
|
||||
`import { usersQuery } from './users.selectors'`,
|
||||
`export class UsersFacade`,
|
||||
].forEach((text) => {
|
||||
expect(content).toContain(text);
|
||||
});
|
||||
});
|
||||
|
||||
it('should build the ngrx reducer', async () => {
|
||||
const appConfig = getAppConfig();
|
||||
const tree = await buildNgrxTree(appConfig, 'user');
|
||||
|
||||
const statePath = `${path.dirname(appConfig.appModule)}/+state`;
|
||||
const content = getFileContent(tree, `${statePath}/user.reducer.ts`);
|
||||
|
||||
[
|
||||
`import { UserAction, UserActionTypes } from \'./user.actions\'`,
|
||||
`export interface User`,
|
||||
`export interface UserState`,
|
||||
'export function reducer',
|
||||
'state: UserState = initialState',
|
||||
'action: UserAction',
|
||||
'): UserState',
|
||||
'case UserActionTypes.UserLoaded',
|
||||
].forEach((text) => {
|
||||
expect(content).toContain(text);
|
||||
});
|
||||
});
|
||||
|
||||
it('should build the ngrx effects', async () => {
|
||||
const appConfig = getAppConfig();
|
||||
const tree = await buildNgrxTree(appConfig, 'users');
|
||||
const statePath = `${path.dirname(appConfig.appModule)}/+state`;
|
||||
const content = getFileContent(tree, `${statePath}/users.effects.ts`);
|
||||
|
||||
[
|
||||
`import { DataPersistence } from '@nrwl/angular'`,
|
||||
`
|
||||
import {
|
||||
LoadUsers,
|
||||
UsersLoaded,
|
||||
UsersLoadError,
|
||||
UsersActionTypes,
|
||||
} from './users.actions';`,
|
||||
`loadUsers$`,
|
||||
`run: (action: LoadUsers, state: UsersPartialState)`,
|
||||
`return new UsersLoaded([])`,
|
||||
`return new UsersLoadError(error)`,
|
||||
'private actions$: Actions',
|
||||
'private dataPersistence: DataPersistence<UsersPartialState>',
|
||||
].forEach((text) => {
|
||||
expect(content).toContain(text);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('unit tests', () => {
|
||||
it('should produce proper specs for the ngrx reducer', async () => {
|
||||
const appConfig = getAppConfig();
|
||||
const tree = await buildNgrxTree(appConfig);
|
||||
|
||||
const statePath = `${path.dirname(appConfig.appModule)}/+state`;
|
||||
const contents = tree.readContent(`${statePath}/user.reducer.spec.ts`);
|
||||
|
||||
expect(contents).toContain(`describe('User Reducer', () => {`);
|
||||
expect(contents).toContain(
|
||||
'const result = reducer(initialState, action);'
|
||||
);
|
||||
});
|
||||
|
||||
it('should update the barrel API with exports for ngrx facade, selector, and reducer', async () => {
|
||||
appTree = createLib(appTree, 'flights');
|
||||
let libConfig = getLibConfig();
|
||||
let tree = await runSchematic(
|
||||
'ngrx',
|
||||
{
|
||||
name: 'super-users',
|
||||
module: libConfig.module,
|
||||
facade: true,
|
||||
},
|
||||
appTree
|
||||
);
|
||||
|
||||
const barrel = tree.readContent(libConfig.barrel);
|
||||
expect(barrel).toContain(
|
||||
`export * from './lib/+state/super-users.facade';`
|
||||
);
|
||||
});
|
||||
|
||||
it('should not update the barrel API with a facade', async () => {
|
||||
appTree = createLib(appTree, 'flights');
|
||||
let libConfig = getLibConfig();
|
||||
let tree = await runSchematic(
|
||||
'ngrx',
|
||||
{
|
||||
name: 'super-users',
|
||||
module: libConfig.module,
|
||||
facade: false,
|
||||
},
|
||||
appTree
|
||||
);
|
||||
|
||||
const barrel = tree.readContent(libConfig.barrel);
|
||||
expect(barrel).not.toContain(
|
||||
`export * from './lib/+state/super-users.facade';`
|
||||
);
|
||||
});
|
||||
|
||||
it('should produce proper tests for the ngrx reducer for a name with a dash', async () => {
|
||||
const appConfig = getAppConfig();
|
||||
const tree = await runSchematic(
|
||||
'ngrx',
|
||||
{
|
||||
name: 'super-users',
|
||||
module: appConfig.appModule,
|
||||
minimal: false,
|
||||
},
|
||||
appTree
|
||||
);
|
||||
|
||||
const statePath = `${path.dirname(appConfig.appModule)}/+state`;
|
||||
const contents = tree.readContent(
|
||||
`${statePath}/super-users.reducer.spec.ts`
|
||||
);
|
||||
|
||||
expect(contents).toContain(`describe('SuperUsers Reducer', () => {`);
|
||||
expect(contents).toContain(
|
||||
`const result = reducer(initialState, action);`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('creators syntax', () => {
|
||||
let appConfig = getAppConfig();
|
||||
let tree: UnitTestTree;
|
||||
let statePath: string;
|
||||
|
||||
beforeEach(async () => {
|
||||
appConfig = getAppConfig();
|
||||
tree = await runSchematic(
|
||||
'ngrx',
|
||||
{
|
||||
name: 'users',
|
||||
module: appConfig.appModule,
|
||||
syntax: 'creators',
|
||||
minimal: false,
|
||||
facade: true,
|
||||
useDataPersistence: false,
|
||||
},
|
||||
appTree
|
||||
);
|
||||
|
||||
statePath = `${path.dirname(appConfig.appModule)}/+state`;
|
||||
});
|
||||
|
||||
it('should generate a set of actions for the feature', async () => {
|
||||
const content = tree.readContent(`${statePath}/users.actions.ts`);
|
||||
|
||||
[
|
||||
'[Users Page] Init',
|
||||
'[Users/API] Load Users Success',
|
||||
'props<{ users: UsersEntity[] }>()',
|
||||
'[Users/API] Load Users Failure',
|
||||
'props<{ error: any }>()',
|
||||
].forEach((text) => {
|
||||
expect(content).toContain(text);
|
||||
});
|
||||
});
|
||||
|
||||
it('should generate a reducer for the feature', async () => {
|
||||
const content = tree.readContent(`${statePath}/users.reducer.ts`);
|
||||
|
||||
[
|
||||
`export const USERS_FEATURE_KEY = 'users';`,
|
||||
`const usersReducer = createReducer`,
|
||||
'export function reducer(state: State | undefined, action: Action) {',
|
||||
].forEach((text) => {
|
||||
expect(content).toContain(text);
|
||||
});
|
||||
});
|
||||
|
||||
it('should generate effects for the feature', async () => {
|
||||
const content = tree.readContent(`${statePath}/users.effects.ts`);
|
||||
|
||||
[
|
||||
`import { createEffect, Actions, ofType } from '@ngrx/effects';`,
|
||||
'fetch({',
|
||||
].forEach((text) => {
|
||||
expect(content).toContain(text);
|
||||
});
|
||||
});
|
||||
|
||||
it('should generate selectors for the feature', async () => {
|
||||
const content = tree.readContent(`${statePath}/users.selectors.ts`);
|
||||
|
||||
[
|
||||
`import { USERS_FEATURE_KEY, State, usersAdapter } from './users.reducer';`,
|
||||
'const { selectAll, selectEntities } = usersAdapter.getSelectors();',
|
||||
].forEach((text) => {
|
||||
expect(content).toContain(text);
|
||||
});
|
||||
});
|
||||
|
||||
it('should generate a facade for the feature if enabled', async () => {
|
||||
const content = tree.readContent(`${statePath}/users.facade.ts`);
|
||||
|
||||
[
|
||||
`loaded$ = this.store.pipe(select(UsersSelectors.getUsersLoaded));`,
|
||||
`allUsers$ = this.store.pipe(select(UsersSelectors.getAllUsers));`,
|
||||
`selectedUsers$ = this.store.pipe(select(UsersSelectors.getSelected));`,
|
||||
].forEach((text) => {
|
||||
expect(content).toContain(text);
|
||||
});
|
||||
});
|
||||
|
||||
it('should generate a models file for the feature', async () => {
|
||||
const content = tree.readContent(`${statePath}/users.models.ts`);
|
||||
|
||||
[
|
||||
'export interface UsersEntity',
|
||||
'id: string | number; // Primary ID',
|
||||
].forEach((text) => {
|
||||
expect(content).toContain(text);
|
||||
});
|
||||
});
|
||||
|
||||
it('should use DataPersistence operators when useDataPersistence is set to false', async () => {
|
||||
appTree = Tree.empty();
|
||||
appTree = createEmptyWorkspace(appTree);
|
||||
appTree = createApp(appTree, 'myapp');
|
||||
const tree = await runSchematic(
|
||||
'ngrx',
|
||||
{
|
||||
name: 'users',
|
||||
module: appConfig.appModule,
|
||||
syntax: 'creators',
|
||||
facade: true,
|
||||
minimal: false,
|
||||
},
|
||||
appTree
|
||||
);
|
||||
const content = tree.readContent(`${statePath}/users.effects.ts`);
|
||||
|
||||
[`{ fetch }`, `, ofType`, `ofType(UsersActions.init),`].forEach(
|
||||
(text) => {
|
||||
expect(content).toContain(text);
|
||||
}
|
||||
);
|
||||
|
||||
expect(content).not.toContain('dataPersistence.fetch');
|
||||
});
|
||||
|
||||
it('should re-export actions, state, and selectors using barrels if enabled', async () => {
|
||||
appTree = Tree.empty();
|
||||
appTree = createEmptyWorkspace(appTree);
|
||||
appTree = createApp(appTree, 'myapp');
|
||||
appTree.create('/apps/myapp/src/index.ts', '');
|
||||
|
||||
const tree = await runSchematic(
|
||||
'ngrx',
|
||||
{
|
||||
name: 'users',
|
||||
module: appConfig.appModule,
|
||||
syntax: 'creators',
|
||||
barrels: true,
|
||||
},
|
||||
appTree
|
||||
);
|
||||
|
||||
const content = tree.readContent('/apps/myapp/src/index.ts');
|
||||
|
||||
[
|
||||
`import * as UsersActions from './lib/+state/users.actions';`,
|
||||
`import * as UsersFeature from './lib/+state/users.reducer';`,
|
||||
`import * as UsersSelectors from './lib/+state/users.selectors';`,
|
||||
`export { UsersActions, UsersFeature, UsersSelectors };`,
|
||||
`export * from './lib/+state/users.models';`,
|
||||
].forEach((text) => {
|
||||
expect(content).toContain(text);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('classes syntax', () => {
|
||||
it('should use fetch operator when useDataPersistence is set to false', async () => {
|
||||
const appConfig = getAppConfig();
|
||||
const tree = await runSchematic(
|
||||
'ngrx',
|
||||
{
|
||||
name: 'users',
|
||||
module: appConfig.appModule,
|
||||
syntax: 'classes',
|
||||
minimal: false,
|
||||
facade: true,
|
||||
useDataPersistence: false,
|
||||
},
|
||||
appTree
|
||||
);
|
||||
|
||||
const statePath = `${path.dirname(appConfig.appModule)}/+state`;
|
||||
const content = tree.readContent(`${statePath}/users.effects.ts`);
|
||||
|
||||
[`{ fetch }`, `, ofType`, `ofType(UsersActionTypes.LoadUsers),`].forEach(
|
||||
(text) => {
|
||||
expect(content).toContain(text);
|
||||
}
|
||||
);
|
||||
|
||||
expect(content).not.toContain('dataPersistence.fetch');
|
||||
});
|
||||
});
|
||||
|
||||
async function buildNgrxTree(
|
||||
appConfig: AppConfig,
|
||||
featureName: string = 'user',
|
||||
withFacade = false,
|
||||
useDataPersistence = true
|
||||
): Promise<UnitTestTree> {
|
||||
return await runSchematic(
|
||||
'ngrx',
|
||||
{
|
||||
name: featureName,
|
||||
module: appConfig.appModule,
|
||||
facade: withFacade,
|
||||
syntax: 'classes',
|
||||
minimal: false,
|
||||
useDataPersistence,
|
||||
},
|
||||
appTree
|
||||
);
|
||||
}
|
||||
});
|
||||
@ -1,119 +0,0 @@
|
||||
import {
|
||||
apply,
|
||||
chain,
|
||||
url,
|
||||
mergeWith,
|
||||
template,
|
||||
move,
|
||||
noop,
|
||||
filter,
|
||||
Rule,
|
||||
Tree,
|
||||
SchematicContext,
|
||||
} from '@angular-devkit/schematics';
|
||||
|
||||
import { Schema } from './schema';
|
||||
import * as path from 'path';
|
||||
|
||||
import {
|
||||
addImportsToModule,
|
||||
addNgRxToPackageJson,
|
||||
addExportsToBarrel,
|
||||
RequestContext,
|
||||
} from './rules';
|
||||
import { formatFiles } from '@nrwl/workspace';
|
||||
import { names } from '@nrwl/devkit';
|
||||
import { wrapAngularDevkitSchematic } from '@nrwl/devkit/ngcli-adapter';
|
||||
|
||||
/**
|
||||
* Rule to generate the Nx 'ngrx' Collection
|
||||
* Note: see https://nx.dev/latest/angular/guides/misc-ngrx for guide to generated files
|
||||
*/
|
||||
export default function generateNgrxCollection(_options: Schema): Rule {
|
||||
return (host: Tree, context: SchematicContext) => {
|
||||
const options = normalizeOptions(_options);
|
||||
|
||||
if (!options.module) {
|
||||
throw new Error(`The required --module option must be passed`);
|
||||
} else if (!host.exists(options.module)) {
|
||||
throw new Error(`Path does not exist: ${options.module}`);
|
||||
}
|
||||
|
||||
const requestContext: RequestContext = {
|
||||
featureName: options.name,
|
||||
moduleDir: path.dirname(options.module),
|
||||
options,
|
||||
host,
|
||||
};
|
||||
|
||||
if (options.minimal) {
|
||||
options.onlyEmptyRoot = true;
|
||||
}
|
||||
|
||||
if (options.skipImport) {
|
||||
options.onlyAddFiles = true;
|
||||
}
|
||||
|
||||
const fileGeneration =
|
||||
!options.onlyEmptyRoot || (!options.root && options.minimal)
|
||||
? [generateNgrxFilesFromTemplates(options)]
|
||||
: [];
|
||||
|
||||
const moduleModification = !options.onlyAddFiles
|
||||
? [
|
||||
addImportsToModule(requestContext),
|
||||
addExportsToBarrel(requestContext.options),
|
||||
]
|
||||
: [];
|
||||
|
||||
const packageJsonModification = !options.skipPackageJson
|
||||
? [addNgRxToPackageJson()]
|
||||
: [];
|
||||
|
||||
return chain([
|
||||
...fileGeneration,
|
||||
...moduleModification,
|
||||
...packageJsonModification,
|
||||
formatFiles(options),
|
||||
])(host, context);
|
||||
};
|
||||
}
|
||||
|
||||
// ********************************************************
|
||||
// Internal Function
|
||||
// ********************************************************
|
||||
|
||||
/**
|
||||
* Generate 'feature' scaffolding: actions, reducer, effects, interfaces, selectors, facade
|
||||
*/
|
||||
function generateNgrxFilesFromTemplates(options: Schema) {
|
||||
const name = options.name;
|
||||
const moduleDir = path.dirname(options.module);
|
||||
const excludeFacade = (path) => path.match(/^((?!facade).)*$/);
|
||||
|
||||
const templateSource = apply(
|
||||
url(options.syntax === 'creators' ? './creator-files' : './files'),
|
||||
[
|
||||
!options.facade ? filter(excludeFacade) : noop(),
|
||||
template({ ...options, tmpl: '', ...names(name) }),
|
||||
move(moduleDir),
|
||||
]
|
||||
);
|
||||
|
||||
return mergeWith(templateSource);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the parent 'directory' for the specified
|
||||
*/
|
||||
function normalizeOptions(options: Schema): Schema {
|
||||
return {
|
||||
...options,
|
||||
directory: names(options.directory).fileName,
|
||||
};
|
||||
}
|
||||
|
||||
export const ngrxGenerator = wrapAngularDevkitSchematic(
|
||||
'@nrwl/angular',
|
||||
'ngrx'
|
||||
);
|
||||
@ -1,93 +0,0 @@
|
||||
import * as ts from 'typescript';
|
||||
import * as path from 'path';
|
||||
import { Rule, Tree } from '@angular-devkit/schematics';
|
||||
|
||||
import { insert, addGlobal } from '@nrwl/workspace';
|
||||
import { Schema } from '../schema';
|
||||
import { names } from '@nrwl/devkit';
|
||||
|
||||
/**
|
||||
* Add ngrx feature exports to the public barrel in the feature library
|
||||
*/
|
||||
export function addExportsToBarrel(options: Schema): Rule {
|
||||
return (host: Tree) => {
|
||||
if (!host.exists(options.module)) {
|
||||
throw new Error(
|
||||
`Specified module path (${options.module}) does not exist`
|
||||
);
|
||||
}
|
||||
|
||||
// Only update the public barrel for feature libraries
|
||||
if (options.root != true) {
|
||||
const moduleDir = path.dirname(options.module);
|
||||
const indexFilePath = path.join(moduleDir, '../index.ts');
|
||||
const hasFacade = options.facade == true;
|
||||
const addModels = options.syntax === 'creators';
|
||||
const className = `${names(options.name).className}`;
|
||||
const exportBarrels = options.barrels === true;
|
||||
|
||||
const buffer = host.read(indexFilePath);
|
||||
if (!!buffer) {
|
||||
// AST to 'index.ts' barrel for the public API
|
||||
const indexSource = buffer!.toString('utf-8');
|
||||
const indexSourceFile = ts.createSourceFile(
|
||||
indexFilePath,
|
||||
indexSource,
|
||||
ts.ScriptTarget.Latest,
|
||||
true
|
||||
);
|
||||
|
||||
// Public API for the feature interfaces, selectors, and facade
|
||||
const { fileName } = names(options.name);
|
||||
const statePath = `./lib/${options.directory}/${fileName}`;
|
||||
|
||||
insert(host, indexFilePath, [
|
||||
...addGlobal(
|
||||
indexSourceFile,
|
||||
indexFilePath,
|
||||
exportBarrels
|
||||
? `import * as ${className}Actions from '${statePath}.actions';`
|
||||
: `export * from '${statePath}.actions';`
|
||||
),
|
||||
...addGlobal(
|
||||
indexSourceFile,
|
||||
indexFilePath,
|
||||
exportBarrels
|
||||
? `import * as ${className}Feature from '${statePath}.reducer';`
|
||||
: `export * from '${statePath}.reducer';`
|
||||
),
|
||||
...addGlobal(
|
||||
indexSourceFile,
|
||||
indexFilePath,
|
||||
exportBarrels
|
||||
? `import * as ${className}Selectors from '${statePath}.selectors';`
|
||||
: `export * from '${statePath}.selectors';`
|
||||
),
|
||||
...(exportBarrels
|
||||
? addGlobal(
|
||||
indexSourceFile,
|
||||
indexFilePath,
|
||||
`export { ${className}Actions, ${className}Feature, ${className}Selectors };`
|
||||
)
|
||||
: []),
|
||||
...(addModels
|
||||
? addGlobal(
|
||||
indexSourceFile,
|
||||
indexFilePath,
|
||||
`export * from '${statePath}.models';`
|
||||
)
|
||||
: []),
|
||||
...(hasFacade
|
||||
? addGlobal(
|
||||
indexSourceFile,
|
||||
indexFilePath,
|
||||
`export * from '${statePath}.facade';`
|
||||
)
|
||||
: []),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
return host;
|
||||
};
|
||||
}
|
||||
@ -1,133 +0,0 @@
|
||||
import { Rule, Tree } from '@angular-devkit/schematics';
|
||||
import * as ts from 'typescript';
|
||||
import { insert } from '@nrwl/workspace';
|
||||
import { RequestContext } from './request-context';
|
||||
import {
|
||||
addImportToModule,
|
||||
addProviderToModule,
|
||||
} from '../../../utils/ast-utils';
|
||||
import { Change, insertImport } from '@nrwl/workspace/src/utils/ast-utils';
|
||||
import { names } from '@nrwl/devkit';
|
||||
|
||||
export function addImportsToModule(context: RequestContext): Rule {
|
||||
return (host: Tree) => {
|
||||
const modulePath = context.options.module;
|
||||
const sourceText = host.read(modulePath)!.toString('utf-8');
|
||||
const source = ts.createSourceFile(
|
||||
modulePath,
|
||||
sourceText,
|
||||
ts.ScriptTarget.Latest,
|
||||
true
|
||||
);
|
||||
const addImport = (
|
||||
symbolName: string,
|
||||
fileName: string,
|
||||
isDefault = false
|
||||
): Change => {
|
||||
return insertImport(source, modulePath, symbolName, fileName, isDefault);
|
||||
};
|
||||
|
||||
const dir = `./${names(context.options.directory).fileName}`;
|
||||
const pathPrefix = `${dir}/${names(context.featureName).fileName}`;
|
||||
const reducerPath = `${pathPrefix}.reducer`;
|
||||
const effectsPath = `${pathPrefix}.effects`;
|
||||
const facadePath = `${pathPrefix}.facade`;
|
||||
|
||||
const constantName = `${names(context.featureName).constantName}`;
|
||||
const effectsName = `${names(context.featureName).className}Effects`;
|
||||
const facadeName = `${names(context.featureName).className}Facade`;
|
||||
const className = `${names(context.featureName).className}`;
|
||||
const reducerImports = `* as from${className}`;
|
||||
|
||||
const storeMetaReducers = `metaReducers: !environment.production ? [] : []`;
|
||||
|
||||
const storeForRoot = `StoreModule.forRoot({},
|
||||
{
|
||||
${storeMetaReducers},
|
||||
runtimeChecks: {
|
||||
strictActionImmutability: true,
|
||||
strictStateImmutability: true
|
||||
}
|
||||
}
|
||||
)`;
|
||||
const nxModule = 'NxModule.forRoot()';
|
||||
const effectsForRoot = `EffectsModule.forRoot([${effectsName}])`;
|
||||
const effectsForEmptyRoot = `EffectsModule.forRoot([])`;
|
||||
const storeForFeature = `StoreModule.forFeature(from${className}.${constantName}_FEATURE_KEY, from${className}.reducer)`;
|
||||
const effectsForFeature = `EffectsModule.forFeature([${effectsName}])`;
|
||||
const devTools = `!environment.production ? StoreDevtoolsModule.instrument() : []`;
|
||||
const storeRouterModule = 'StoreRouterConnectingModule.forRoot()';
|
||||
|
||||
// InsertImport [symbol,source] value pairs
|
||||
const nxModuleImport = ['NxModule', '@nrwl/angular'];
|
||||
const storeModule = ['StoreModule', '@ngrx/store'];
|
||||
const effectsModule = ['EffectsModule', '@ngrx/effects'];
|
||||
const storeDevTools = ['StoreDevtoolsModule', '@ngrx/store-devtools'];
|
||||
const environment = ['environment', '../environments/environment'];
|
||||
const storeRouter = ['StoreRouterConnectingModule', '@ngrx/router-store'];
|
||||
|
||||
// this is just a heuristic
|
||||
const hasRouter = sourceText.indexOf('RouterModule') > -1;
|
||||
const hasNxModule = sourceText.includes('NxModule.forRoot()');
|
||||
|
||||
if (
|
||||
(context.options.onlyEmptyRoot || context.options.minimal) &&
|
||||
context.options.root
|
||||
) {
|
||||
insert(host, modulePath, [
|
||||
addImport.apply(this, storeModule),
|
||||
addImport.apply(this, effectsModule),
|
||||
addImport.apply(this, storeDevTools),
|
||||
addImport.apply(this, environment),
|
||||
...(hasRouter ? [addImport.apply(this, storeRouter)] : []),
|
||||
...addImportToModule(source, modulePath, storeForRoot),
|
||||
...addImportToModule(source, modulePath, effectsForEmptyRoot),
|
||||
...addImportToModule(source, modulePath, devTools),
|
||||
...(hasRouter
|
||||
? addImportToModule(source, modulePath, storeRouterModule)
|
||||
: []),
|
||||
]);
|
||||
} else {
|
||||
let common = [
|
||||
addImport.apply(this, storeModule),
|
||||
addImport.apply(this, effectsModule),
|
||||
addImport(reducerImports, reducerPath, true),
|
||||
addImport(effectsName, effectsPath),
|
||||
];
|
||||
if (context.options.facade) {
|
||||
common = [
|
||||
...common,
|
||||
addImport(facadeName, facadePath),
|
||||
...addProviderToModule(source, modulePath, `${facadeName}`),
|
||||
];
|
||||
}
|
||||
|
||||
if (context.options.root) {
|
||||
insert(host, modulePath, [
|
||||
...common,
|
||||
...(!hasNxModule ? [addImport.apply(this, nxModuleImport)] : []),
|
||||
addImport.apply(this, storeDevTools),
|
||||
addImport.apply(this, environment),
|
||||
...(hasRouter ? [addImport.apply(this, storeRouter)] : []),
|
||||
...(!hasNxModule
|
||||
? addImportToModule(source, modulePath, nxModule)
|
||||
: []),
|
||||
...addImportToModule(source, modulePath, storeForRoot),
|
||||
...addImportToModule(source, modulePath, effectsForRoot),
|
||||
...addImportToModule(source, modulePath, devTools),
|
||||
...(hasRouter
|
||||
? addImportToModule(source, modulePath, storeRouterModule)
|
||||
: []),
|
||||
...addImportToModule(source, modulePath, storeForFeature),
|
||||
]);
|
||||
} else {
|
||||
insert(host, modulePath, [
|
||||
...common,
|
||||
...addImportToModule(source, modulePath, storeForFeature),
|
||||
...addImportToModule(source, modulePath, effectsForFeature),
|
||||
]);
|
||||
}
|
||||
}
|
||||
return host;
|
||||
};
|
||||
}
|
||||
@ -1,23 +0,0 @@
|
||||
import * as path from 'path';
|
||||
|
||||
import { Tree } from '@angular-devkit/schematics';
|
||||
import { Schema } from '../schema';
|
||||
import { stringUtils } from '@nrwl/workspace';
|
||||
|
||||
/**
|
||||
* Schematic request context
|
||||
*/
|
||||
export interface RequestContext {
|
||||
featureName: string;
|
||||
moduleDir: string;
|
||||
options?: Schema;
|
||||
host?: Tree;
|
||||
}
|
||||
|
||||
export function buildNameToNgrxFile(context: RequestContext, suffice: string) {
|
||||
return path.join(
|
||||
context.moduleDir,
|
||||
context.options.directory,
|
||||
`${stringUtils.dasherize(context.featureName)}.${suffice}`
|
||||
);
|
||||
}
|
||||
23
packages/angular/src/schematics/ngrx/schema.d.ts
vendored
23
packages/angular/src/schematics/ngrx/schema.d.ts
vendored
@ -1,23 +0,0 @@
|
||||
export interface Schema {
|
||||
name: string;
|
||||
module: string;
|
||||
directory: string;
|
||||
root: boolean;
|
||||
facade: boolean;
|
||||
minimal: boolean;
|
||||
skipImport: boolean;
|
||||
/**
|
||||
* @deprecated use `minimal`
|
||||
*/
|
||||
onlyEmptyRoot: boolean;
|
||||
|
||||
/**
|
||||
* @deprecated use `skipImport`
|
||||
*/
|
||||
onlyAddFiles: boolean;
|
||||
skipFormat: boolean;
|
||||
skipPackageJson: boolean;
|
||||
syntax?: string;
|
||||
useDataPersistence: boolean;
|
||||
barrels: boolean;
|
||||
}
|
||||
@ -8,6 +8,7 @@ import {
|
||||
pathFormat,
|
||||
} from '@angular-devkit/schematics/src/formats';
|
||||
import {
|
||||
formatDeprecated,
|
||||
generateJsonFile,
|
||||
generateMarkdownFile,
|
||||
sortAlphabeticallyFunction,
|
||||
@ -136,9 +137,9 @@ function generateTemplate(
|
||||
: ``;
|
||||
|
||||
template += dedent`
|
||||
### ${option.name} ${option.required ? '(*__required__*)' : ''} ${
|
||||
option.hidden ? '(__hidden__)' : ''
|
||||
}
|
||||
### ${option.deprecated ? `~~${option.name}~~` : option.name} ${
|
||||
option.required ? '(*__required__*)' : ''
|
||||
} ${option.hidden ? '(__hidden__)' : ''}
|
||||
|
||||
${
|
||||
!!option.aliases.length
|
||||
@ -151,11 +152,13 @@ function generateTemplate(
|
||||
: `Default: \`${option.default}\`\n`
|
||||
}
|
||||
Type: \`${option.type}\`
|
||||
|
||||
${enumStr}
|
||||
|
||||
${option.description}
|
||||
`;
|
||||
|
||||
template += dedent`
|
||||
${enumStr}
|
||||
|
||||
${formatDeprecated(option.description, option.deprecated)}
|
||||
`;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user