Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: state, store and starter-kit schematics support a project option #2089

Merged
merged 4 commits into from
Dec 15, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 9 additions & 6 deletions docs/concepts/state.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,15 @@ You have the option to enter the options yourself
ng generate @ngxs/store:state --name NAME_OF_YOUR_STATE
```

| Option | Description | Required | Default Value |
| :----- | :------------------------------------------------------------- | :------: | :------------------- |
| --name | The name of the state | Yes | |
| --path | The path to create the state | No | App's root directory |
| --spec | Boolean flag to indicate if a unit test file should be created | No | `true` |
| --flat | Boolean flag to indicate if a dir is created | No | `false` |
| Option | Description | Required | Default Value |
| :-------- | :------------------------------------------------------------- | :------: | :-------------------------- |
| --name | The name of the state | Yes | |
| --path | The path to create the state | No | App's root directory |
| --spec | Boolean flag to indicate if a unit test file should be created | No | `true` |
| --flat | Boolean flag to indicate if a dir is created | No | `false` |
| --project | Name of the project as it is defined in your angular.json | No | Workspace's default project |

> When working with multiple projects within a workspace, you can explicitly specify the `project` where you want to install the **state**. The schematic will automatically detect whether the provided project is a standalone or not, and it will generate the necessary files accordingly.

🪄 **This command will**:

Expand Down
15 changes: 9 additions & 6 deletions docs/concepts/store.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,15 @@ You have the option to enter the options yourself
ng generate @ngxs/store:store --name NAME_OF_YOUR_STORE
```

| Option | Description | Required | Default Value |
| :----- | :------------------------------------------------------------- | :------: | :------------------- |
| --name | The name of the store | Yes | |
| --path | The path to create the store | No | App's root directory |
| --spec | Boolean flag to indicate if a unit test file should be created | No | `true` |
| --flat | Boolean flag to indicate if a dir is created | No | `false` |
| Option | Description | Required | Default Value |
| :-------- | :------------------------------------------------------------- | :------: | :-------------------------- |
| --name | The name of the store | Yes | |
| --path | The path to create the store | No | App's root directory |
| --spec | Boolean flag to indicate if a unit test file should be created | No | `true` |
| --flat | Boolean flag to indicate if a dir is created | No | `false` |
| --project | Name of the project as it is defined in your angular.json | No | Workspace's default project |

> When working with multiple projects within a workspace, you can explicitly specify the `project` where you want to install the **store**. The schematic will automatically detect whether the provided project is a standalone or not, and it will generate the necessary files accordingly.

🪄 **This command will**:

Expand Down
11 changes: 7 additions & 4 deletions docs/introduction/starter-kit.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,13 @@ You have the option to enter the options yourself
ng generate @ngxs/store:starter-kit --path YOUR_PATH
```

| Option | Description | Required | Default Value |
| :----- | :------------------------------------------------------------- | :------: | :------------ |
| --path | The path to create the starter kit | Yes | |
| --spec | Boolean flag to indicate if a unit test file should be created | No | `true` |
| Option | Description | Required | Default Value |
| :-------- | :------------------------------------------------------------- | :------: | :-------------------------- |
| --path | The path to create the starter kit | Yes | |
| --spec | Boolean flag to indicate if a unit test file should be created | No | `true` |
| --project | Name of the project as it is defined in your angular.json | No | Workspace's default project |

> When working with multiple projects within a workspace, you can explicitly specify the `project` where you want to install the **starter kit**. The schematic will automatically detect whether the provided project is a standalone or not, and it will generate the necessary files accordingly.

🪄 **This command will**:

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { NgxsModule, Store } from '@ngxs/store';
import { <% if (isStandalone) { %> provideStore, <% } else { %> NgxsModule, <% } %> Store } from '@ngxs/store';
import { TestBed } from '@angular/core/testing';
import { AuthenticationStateModel, AuthState } from './auth.state';
import { SetAuthData } from './auth.actions';
Expand All @@ -8,7 +8,8 @@ describe('[TEST]: AuthStore', () => {

beforeEach(() => {
TestBed.configureTestingModule({
imports: [NgxsModule.forRoot([AuthState])],
<% if (isStandalone) { %> providers: [provideStore([AuthState])]
<% } else { %> imports: [NgxsModule.forRoot([AuthState])] <% } %>
});

store = TestBed.inject(Store);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { NgxsModule, Store } from '@ngxs/store';
import { <% if (isStandalone) { %> provideStore, <% } else { %> NgxsModule, <% } %> Store } from '@ngxs/store';
import { TestBed } from '@angular/core/testing';
import { DictionaryState, DictionaryStateModel } from './dictionary.state';
import { DictionaryReset, SetDictionaryData } from './dictionary.actions';
Expand Down Expand Up @@ -29,7 +29,8 @@ describe('[TEST]: Dictionary state', () => {

beforeEach(() => {
TestBed.configureTestingModule({
imports: [NgxsModule.forRoot([DictionaryState])],
<% if (isStandalone) { %> providers: [provideStore([DictionaryState])]
<% } else { %> imports: [NgxsModule.forRoot([DictionaryState])] <% } %>
});

store = TestBed.inject(Store);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { NgxsModule, Store } from '@ngxs/store';
import { <% if (isStandalone) { %> provideStore, <% } else { %> NgxsModule, <% } %> Store } from '@ngxs/store';
import { TestBed } from '@angular/core/testing';
import { UserStateModel, UserState } from './user.state';
import { SetUser } from './user.actions';
Expand All @@ -8,7 +8,8 @@ describe('[TEST]: User state', () => {

beforeEach(() => {
TestBed.configureTestingModule({
imports: [NgxsModule.forRoot([UserState])],
<% if (isStandalone) { %> providers: [provideStore([UserState])]
<% } else { %> imports: [NgxsModule.forRoot([UserState])] <% } %>
});

store = TestBed.inject(Store);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,57 +1,87 @@
import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing';
import * as path from 'path';
import { workspaceRoot } from '@nrwl/devkit';
import { Schema as ApplicationOptions } from '@schematics/angular/application/schema';
import { Schema as WorkspaceOptions } from '@schematics/angular/workspace/schema';
import * as path from 'path';
import { StarterKitSchema } from './starter-kit.schema';

describe('Generate ngxs starter kit', () => {
const angularSchematicRunner = new SchematicTestRunner(
'@schematics/angular',
path.join(workspaceRoot, 'node_modules/@schematics/angular/collection.json')
);

const runner: SchematicTestRunner = new SchematicTestRunner(
'.',
path.join(workspaceRoot, 'packages/store/schematics/collection.json')
);
const workspaceOptions: WorkspaceOptions = {
name: 'workspace',
newProjectRoot: 'projects',
version: '1.0.0'
};

const appOptions: ApplicationOptions = {
name: 'foo',
inlineStyle: false,
inlineTemplate: false,
routing: true,
skipTests: false,
skipPackageJson: false
};

let appTree: UnitTestTree;
beforeEach(async () => {
appTree = await angularSchematicRunner.runSchematic('workspace', workspaceOptions);
appTree = await angularSchematicRunner.runSchematic('application', appOptions, appTree);
});

it('should generate store in default root folder', async () => {
const options: StarterKitSchema = {
spec: true,
path: './src'
};
const tree: UnitTestTree = await runner
.runSchematicAsync('starter-kit', options)
.toPromise();
const tree: UnitTestTree = await runner.runSchematic('starter-kit', options, appTree);

const files: string[] = tree.files;
expect(files).toEqual([
'/src/store/store.config.ts',
'/src/store/store.module.ts',
'/src/store/auth/auth.actions.ts',
'/src/store/auth/auth.state.spec.ts',
'/src/store/auth/auth.state.ts',
'/src/store/dashboard/index.ts',
'/src/store/dashboard/states/dictionary/dictionary.actions.ts',
'/src/store/dashboard/states/dictionary/dictionary.state.spec.ts',
'/src/store/dashboard/states/dictionary/dictionary.state.ts',
'/src/store/dashboard/states/user/user.actions.ts',
'/src/store/dashboard/states/user/user.state.spec.ts',
'/src/store/dashboard/states/user/user.state.ts'
]);
expect(files).toEqual(
expect.arrayContaining([
'/src/store/store.config.ts',
'/src/store/store.module.ts',
'/src/store/auth/auth.actions.ts',
'/src/store/auth/auth.state.spec.ts',
'/src/store/auth/auth.state.ts',
'/src/store/dashboard/index.ts',
'/src/store/dashboard/states/dictionary/dictionary.actions.ts',
'/src/store/dashboard/states/dictionary/dictionary.state.spec.ts',
'/src/store/dashboard/states/dictionary/dictionary.state.ts',
'/src/store/dashboard/states/user/user.actions.ts',
'/src/store/dashboard/states/user/user.state.spec.ts',
'/src/store/dashboard/states/user/user.state.ts'
])
);
});

it('should generate store in default root folder with spec false', async () => {
const options: StarterKitSchema = {
spec: false,
path: './src'
};
const tree: UnitTestTree = await runner
.runSchematicAsync('starter-kit', options)
.toPromise();

const tree: UnitTestTree = await runner.runSchematic('starter-kit', options, appTree);

const files: string[] = tree.files;
expect(files).toEqual([
'/src/store/store.config.ts',
'/src/store/store.module.ts',
'/src/store/auth/auth.actions.ts',
'/src/store/auth/auth.state.ts',
'/src/store/dashboard/index.ts',
'/src/store/dashboard/states/dictionary/dictionary.actions.ts',
'/src/store/dashboard/states/dictionary/dictionary.state.ts',
'/src/store/dashboard/states/user/user.actions.ts',
'/src/store/dashboard/states/user/user.state.ts'
]);
expect(files).toEqual(
expect.arrayContaining([
'/src/store/store.config.ts',
'/src/store/store.module.ts',
'/src/store/auth/auth.actions.ts',
'/src/store/auth/auth.state.ts',
'/src/store/dashboard/index.ts',
'/src/store/dashboard/states/dictionary/dictionary.actions.ts',
'/src/store/dashboard/states/dictionary/dictionary.state.ts',
'/src/store/dashboard/states/user/user.actions.ts',
'/src/store/dashboard/states/user/user.state.ts'
])
);
});
});
Original file line number Diff line number Diff line change
@@ -1,10 +1,22 @@
import { Rule, url } from '@angular-devkit/schematics';
import { StarterKitSchema } from './starter-kit.schema';
import { normalizePath } from '../utils/normalize-options';
import { Rule, Tree, url } from '@angular-devkit/schematics';
import { generateFiles } from '../utils/generate-utils';
import { isStandaloneApp } from '../utils/ng-utils/ng-ast-utils';
import { getProjectMainFile } from '../utils/ng-utils/project';
import { normalizePath } from '../utils/normalize-options';
import { StarterKitSchema } from './starter-kit.schema';

export function starterKit(options: StarterKitSchema): Rule {
const normalizedPath = normalizePath(options.path);
return (host: Tree) => {
const mainFile = getProjectMainFile(host, options.project);
const isStandalone = isStandaloneApp(host, mainFile);

const normalizedPath = normalizePath(options.path);

return generateFiles(url('./files'), normalizedPath, options, options.spec);
return generateFiles(
url('./files'),
normalizedPath,
{ ...options, isStandalone },
options.spec
);
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,8 @@ export interface StarterKitSchema {
* The spec flag
*/
spec?: boolean;
/**
* The application project name to add the Ngxs module/provider.
*/
project?: string;
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { TestBed } from '@angular/core/testing';
import { NgxsModule, Store } from '@ngxs/store';
import { <% if (isStandalone) { %> provideStore, <% } else { %> NgxsModule, <% } %> Store } from '@ngxs/store';
import { <%= classify(name) %>State, <%= classify(name) %>StateModel } from './<%= dasherize(name) %>.state';

describe('<%= classify(name) %> state', () => {
let store: Store;

beforeEach(() => {
TestBed.configureTestingModule({
imports: [NgxsModule.forRoot([<%= classify(name) %>State])]
<% if (isStandalone) { %> providers: [provideStore([<%= classify(name) %>State])]
<% } else { %> imports: [NgxsModule.forRoot([<%= classify(name) %>State])] <% } %>
});

store = TestBed.inject(Store);
Expand Down
4 changes: 4 additions & 0 deletions packages/store/schematics/src/state/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@
"type": "boolean",
"default": false,
"description": "Flag to indicate if a dir is created."
},
"project": {
"type": "string",
"description": "The name of the project."
}
},
"required": ["name"]
Expand Down
49 changes: 41 additions & 8 deletions packages/store/schematics/src/state/state.factory.spec.ts
Original file line number Diff line number Diff line change
@@ -1,53 +1,86 @@
import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing';
import { workspaceRoot } from '@nrwl/devkit';

import { Schema as ApplicationOptions } from '@schematics/angular/application/schema';
import { Schema as WorkspaceOptions } from '@schematics/angular/workspace/schema';
import * as path from 'path';
import { StateSchema } from './state.schema';

describe('Generate ngxs state', () => {
const angularSchematicRunner = new SchematicTestRunner(
'@schematics/angular',
path.join(workspaceRoot, 'node_modules/@schematics/angular/collection.json')
);

const runner: SchematicTestRunner = new SchematicTestRunner(
'.',
path.join(workspaceRoot, 'packages/store/schematics/collection.json')
);
const defaultOptions: StateSchema = {
name: 'todos'
};

const workspaceOptions: WorkspaceOptions = {
name: 'workspace',
newProjectRoot: 'projects',
version: '1.0.0'
};

const appOptions: ApplicationOptions = {
name: 'foo',
inlineStyle: false,
inlineTemplate: false,
routing: true,
skipTests: false,
skipPackageJson: false
};

let appTree: UnitTestTree;
beforeEach(async () => {
appTree = await angularSchematicRunner.runSchematic('workspace', workspaceOptions);
appTree = await angularSchematicRunner.runSchematic('application', appOptions, appTree);
});

it('should manage name only', async () => {
const options: StateSchema = {
...defaultOptions
};
const tree: UnitTestTree = await runner.runSchematicAsync('state', options).toPromise();
const tree: UnitTestTree = await runner.runSchematic('state', options, appTree);
const files: string[] = tree.files;
expect(files).toEqual(['/todos/todos.state.spec.ts', '/todos/todos.state.ts']);
expect(files).toEqual(
expect.arrayContaining(['/todos/todos.state.spec.ts', '/todos/todos.state.ts'])
);
});

it('should not create a separate folder if "flat" is set to "true"', async () => {
const options: StateSchema = {
...defaultOptions,
flat: true
};
const tree: UnitTestTree = await runner.runSchematicAsync('state', options).toPromise();
const tree: UnitTestTree = await runner.runSchematic('state', options, appTree);
const files: string[] = tree.files;
expect(files).toEqual(['/todos.state.spec.ts', '/todos.state.ts']);
expect(files).toEqual(expect.arrayContaining(['/todos.state.spec.ts', '/todos.state.ts']));
});

it('should manage name with spec true', async () => {
const options: StateSchema = {
...defaultOptions,
spec: true
};
const tree: UnitTestTree = await runner.runSchematicAsync('state', options).toPromise();
const tree: UnitTestTree = await runner.runSchematic('state', options, appTree);
const files: string[] = tree.files;
expect(files).toEqual(['/todos/todos.state.spec.ts', '/todos/todos.state.ts']);
expect(files).toEqual(
expect.arrayContaining(['/todos/todos.state.spec.ts', '/todos/todos.state.ts'])
);
});

it('should manage name with spec false', async () => {
const options: StateSchema = {
...defaultOptions,
spec: false
};
const tree: UnitTestTree = await runner.runSchematicAsync('state', options).toPromise();
const tree: UnitTestTree = await runner.runSchematic('state', options, appTree);
const files: string[] = tree.files;
expect(files).toEqual(['/todos/todos.state.ts']);
expect(files).toEqual(expect.arrayContaining(['/todos/todos.state.ts']));
});
});
Loading