├── .commitlintrc.json
├── .editorconfig
├── .github
└── workflows
│ └── master.yml
├── .gitignore
├── .huskyrc.json
├── .lintstagedrc.json
├── .prettierrc.json
├── .versionrc.json
├── CHANGELOG.md
├── LICENSE
├── README.md
├── angular.json
├── integration
├── .browserslistrc
├── _redirects
├── app
│ ├── app.component.html
│ ├── app.component.scss
│ ├── app.component.ts
│ ├── app.module.ts
│ └── examples
│ │ ├── multiple
│ │ ├── multiple.component.html
│ │ ├── multiple.component.ts
│ │ └── multiple.module.ts
│ │ ├── single
│ │ ├── single.component.html
│ │ ├── single.component.ts
│ │ └── single.module.ts
│ │ └── states
│ │ └── zoo
│ │ ├── zoo.actions.ts
│ │ ├── zoo.model.ts
│ │ └── zoo.state.ts
├── assets
│ └── .gitkeep
├── environments
│ ├── environment.prod.ts
│ └── environment.ts
├── favicon.ico
├── index.html
├── main.ts
├── polyfills.ts
├── styles.scss
├── tsconfig.app.json
└── tsconfig.spec.json
├── jest.config.js
├── package.json
├── renovate.json
├── setup-jest.ts
├── src
├── index.ts
├── lib
│ ├── actions-executed.selector.ts
│ ├── actions-executed.state.ts
│ ├── actions-executing.module.ts
│ ├── actions-executing.selector.ts
│ └── actions-executing.state.ts
├── ng-package.json
├── ng-package.prod.json
├── package.json
├── public-api.ts
├── tests
│ ├── actions-executed.spec.ts
│ ├── actions-executing-module.spec.ts
│ ├── actions-executing.spec.ts
│ └── has-actions-executing.spec.ts
├── tsconfig.lib.json
├── tsconfig.lib.prod.json
└── tsconfig.spec.json
├── tools
└── copy-readme.ts
├── tsconfig.json
├── tsconfig.spec.json
├── tsconfig.tools.json
├── tslint.json
├── yarn.lock
└── yarn.lock.readme.md
/.commitlintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["@commitlint/config-conventional"
3 | ]
4 | }
5 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # Editor configuration, see http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | indent_style = space
7 | indent_size = 2
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.md]
12 | max_line_length = off
13 | trim_trailing_whitespace = false
14 |
--------------------------------------------------------------------------------
/.github/workflows/master.yml:
--------------------------------------------------------------------------------
1 | name: master
2 | on: push
3 |
4 | jobs:
5 | build-and-test:
6 | runs-on: ubuntu-latest
7 | name: Build and test
8 | steps:
9 | - uses: actions/checkout@v3
10 | - uses: actions/setup-node@v3
11 | with:
12 | node-version: '18.x'
13 | - name: Get yarn cache directory path
14 | id: yarn-cache-dir-path
15 | run: echo "::set-output name=dir::$(yarn cache dir)"
16 | - uses: actions/cache@v3
17 | id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
18 | with:
19 | path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
20 | key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
21 | restore-keys: |
22 | ${{ runner.os }}-yarn-
23 | - run: yarn install
24 | - run: yarn test:ci
25 | - run: yarn build
26 | - name: Coveralls
27 | uses: coverallsapp/github-action@master
28 | with:
29 | github-token: ${{ secrets.GITHUB_TOKEN }}
30 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /.angular/cache
2 | dist
3 | dist-integration
4 | dist-integration-server
5 | node_modules
6 | .idea
7 | .vscode
8 | coverage
9 | yarn-error.log
10 | .cache
11 | package-lock.json
12 | *.iml
13 |
--------------------------------------------------------------------------------
/.huskyrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "hooks": {
3 | "pre-commit": "lint-staged",
4 | "commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/.lintstagedrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "*.{ts,js,html,css,scss,md,json,yml}": ["prettier --write", "git add"]
3 | }
4 |
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "printWidth": 120,
3 | "tabWidth": 4,
4 | "useTabs": false,
5 | "semi": true,
6 | "singleQuote": true,
7 | "trailingComma": "none",
8 | "htmlWhitespaceSensitivity": "css",
9 | "jsxBracketSameLine": true,
10 | "bracketSpacing": true,
11 | "arrowParens": "always",
12 | "proseWrap": "always"
13 | }
--------------------------------------------------------------------------------
/.versionrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "packageFiles": [
3 | {
4 | "filename": "src/package.json",
5 | "type": "json"
6 | }
7 | ],
8 | "bumpFiles": [
9 | {
10 | "filename": "src/package.json",
11 | "type": "json"
12 | }
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to this project will be documented in this file. See
4 | [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
5 |
6 | ## [19.0.0](https://github.com/ngxs-labs/actions-executing/compare/v18.0.0...v19.0.0) (2024-12-24)
7 |
8 | ## [18.0.0](https://github.com/ngxs-labs/actions-executing/compare/v1.0.7...v18.0.0) (2024-07-02)
9 |
10 | ### [1.0.7](https://github.com/ngxs-labs/actions-executing/compare/v1.0.6...v1.0.7) (2024-07-02)
11 |
12 | ### [1.0.6](https://github.com/ngxs-labs/actions-executing/compare/v1.0.5...v1.0.6) (2023-04-06)
13 |
14 | ### [1.0.5](https://github.com/ngxs-labs/actions-executing/compare/v1.0.4...v1.0.5) (2023-04-04)
15 |
16 | ### [1.0.4](https://github.com/ngxs-labs/actions-executing/compare/v1.0.3...v1.0.4) (2023-03-21)
17 |
18 | ### [1.0.3](https://github.com/ngxs-labs/actions-executing/compare/v1.0.2...v1.0.3) (2023-02-02)
19 |
20 | ### [1.0.2](https://github.com/ngxs-labs/actions-executing/compare/v1.0.1...v1.0.2) (2023-02-02)
21 |
22 | ### [1.0.1](https://github.com/ngxs-labs/actions-executing/compare/v1.0.0...v1.0.1) (2023-02-02)
23 |
24 | ## [1.0.0](https://github.com/ngxs-labs/actions-executing/compare/v0.2.4...v1.0.0) (2023-02-02)
25 |
26 | ### [0.2.4](https://github.com/ngxs-labs/actions-executing/compare/v0.2.3...v0.2.4) (2023-02-02)
27 |
28 | ### [0.2.3](https://github.com/ngxs-labs/actions-executing/compare/v0.2.2...v0.2.3) (2023-02-02)
29 |
30 | ### [0.2.2](https://github.com/ngxs-labs/actions-executing/compare/v0.2.1...v0.2.2) (2023-02-01)
31 |
32 | ### [0.2.1](https://github.com/ngxs-labs/actions-executing/compare/v0.1.0...v0.2.1) (2023-02-01)
33 |
34 | ### Features
35 |
36 | - actions-executed selector
37 | ([48821c4](https://github.com/ngxs-labs/actions-executing/commit/48821c409661f0325c55647ff7d9982f9cb52891))
38 | - add hasActionsExecuting to return boolean
39 | ([e602772](https://github.com/ngxs-labs/actions-executing/commit/e6027723926af9258d8d98294ba6c3bd65bedfeb))
40 | - support empty array and return whole state
41 | ([b8ddbcc](https://github.com/ngxs-labs/actions-executing/commit/b8ddbcc64aa822fff016ccf9ebaf3b778cae2caa))
42 |
43 | ### Bug Fixes
44 |
45 | - inmutable
46 | ([7bcdd7b](https://github.com/ngxs-labs/actions-executing/commit/7bcdd7b3e46846f0e24d90134cbf528003abc9f9))
47 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
2 |
3 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
4 |
5 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 | [](https://badge.fury.io/js/%40ngxs-labs%2Factions-executing)
3 | [](https://coveralls.io/github/ngxs-labs/actions-executing?branch=master)
4 |
5 |
6 |
7 |
8 |
9 | ---
10 |
11 | # NGXS Actions Executing
12 |
13 | ## Demo
14 |
15 | [Link](https://ngxs-labs-actions-executing.netlify.app/)
16 |
17 | ## Description
18 |
19 | This plugin allows you to easily know if an action is being executed and control UI elements or control flow of your
20 | code to execute. The most common scenarios for using this plugin are to display loading spinner or disable a button
21 | while an action is executing.
22 |
23 | ## Compatibility
24 |
25 | | Angular | @ngxs-labs/actions-executing |
26 | | ------- | ------------------------------ |
27 | | non-ivy | 0.x |
28 | | ivy | 1.x (compiled in partial mode) |
29 |
30 | ## Quick start
31 |
32 | Install the plugin:
33 |
34 | - npm
35 |
36 | ```console
37 | npm install --save @ngxs-labs/actions-executing
38 | ```
39 |
40 | - yarn
41 |
42 | ```console
43 | yarn add @ngxs-labs/actions-executing
44 | ```
45 |
46 | Next, include it in you `app.module.ts`
47 |
48 | ```ts
49 | //...
50 | import { NgxsModule } from '@ngxs/store';
51 | import { NgxsActionsExecutingModule } from '@ngxs-labs/actions-executing';
52 |
53 | @NgModule({
54 | //...
55 | imports: [
56 | //...
57 | NgxsModule.forRoot([
58 | //... your states
59 | ]),
60 | NgxsActionsExecutingModule.forRoot()
61 | ]
62 | //...
63 | })
64 | export class AppModule {}
65 | ```
66 |
67 | To use it on your components you just need to include the following `@Select()`
68 |
69 | ```ts
70 | //...
71 | import { actionsExecuting, ActionsExecuting } from '@ngxs-labs/actions-executing';
72 |
73 | //...
74 | export class SingleComponent {
75 | @Select(actionsExecuting([MyAction])) myActionIsExecuting$: Observable;
76 | }
77 | ```
78 |
79 | then you can disable a button or display a loading indicator very easily
80 |
81 | ```html
82 |
85 |
86 |
87 | Loading...
88 |
89 | ```
90 |
91 | ## More examples
92 |
93 | `actionsExecuting` selector returns the type `ActionsExecuting`
94 |
95 | ```ts
96 | type ActionsExecuting = { [action: string]: number } | null;
97 | ```
98 |
99 | This allows you to know which actions and how many of them are being executed at any given time.
100 |
101 | You can also pass multiple actions to the selector and this way you'll receive updates when any of those actions are
102 | executing.
103 |
104 | ```ts
105 | @Select(actionsExecuting([Action1, Action2])) multipleActions$: Observable;
106 | ```
107 |
--------------------------------------------------------------------------------
/angular.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
3 | "version": 1,
4 | "newProjectRoot": "projects",
5 | "projects": {
6 | "integration": {
7 | "root": "",
8 | "sourceRoot": "integration",
9 | "projectType": "application",
10 | "prefix": "app",
11 | "schematics": {
12 | "@schematics/angular:component": {
13 | "style": "scss"
14 | }
15 | },
16 | "architect": {
17 | "build": {
18 | "builder": "@angular-devkit/build-angular:application",
19 | "options": {
20 | "outputPath": {
21 | "base": "dist/integration"
22 | },
23 | "index": "integration/index.html",
24 | "polyfills": ["integration/polyfills.ts"],
25 | "tsConfig": "integration/tsconfig.app.json",
26 | "assets": ["integration/favicon.ico", "integration/assets", "integration/_redirects"],
27 | "styles": ["integration/styles.scss"],
28 | "scripts": [],
29 | "extractLicenses": false,
30 | "sourceMap": true,
31 | "optimization": false,
32 | "namedChunks": true,
33 | "browser": "integration/main.ts"
34 | },
35 | "configurations": {
36 | "production": {
37 | "budgets": [
38 | {
39 | "type": "anyComponentStyle",
40 | "maximumWarning": "6kb"
41 | }
42 | ],
43 | "fileReplacements": [
44 | {
45 | "replace": "integration/environments/environment.ts",
46 | "with": "integration/environments/environment.prod.ts"
47 | }
48 | ],
49 | "optimization": true,
50 | "outputHashing": "all",
51 | "sourceMap": false,
52 | "namedChunks": false,
53 | "extractLicenses": true
54 | }
55 | },
56 | "defaultConfiguration": ""
57 | },
58 | "serve": {
59 | "builder": "@angular-devkit/build-angular:dev-server",
60 | "options": {
61 | "buildTarget": "integration:build"
62 | },
63 | "configurations": {
64 | "production": {
65 | "buildTarget": "integration:build:production"
66 | }
67 | }
68 | }
69 | }
70 | },
71 | "ngxs-actions-executing": {
72 | "root": "src",
73 | "sourceRoot": "src",
74 | "projectType": "library",
75 | "prefix": "lib",
76 | "architect": {
77 | "build": {
78 | "builder": "@angular-devkit/build-angular:ng-packagr",
79 | "options": {
80 | "tsConfig": "src/tsconfig.lib.json",
81 | "project": "src/ng-package.json"
82 | },
83 | "configurations": {
84 | "production": {
85 | "project": "src/ng-package.prod.json",
86 | "tsConfig": "src/tsconfig.lib.prod.json"
87 | }
88 | }
89 | }
90 | }
91 | }
92 | },
93 | "cli": {
94 | "analytics": false
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/integration/.browserslistrc:
--------------------------------------------------------------------------------
1 | # This file is currently used by autoprefixer to adjust CSS to support the below specified browsers
2 | # For additional information regarding the format and rule options, please see:
3 | # https://github.com/browserslist/browserslist#queries
4 | # For IE 9-11 support, please uncomment the last line of the file and adjust as needed
5 | > 0.5%
6 | last 2 versions
7 | Firefox ESR
8 | not dead
9 | # IE 9-11
--------------------------------------------------------------------------------
/integration/_redirects:
--------------------------------------------------------------------------------
1 | /* /index.html 200
--------------------------------------------------------------------------------
/integration/app/app.component.html:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
Store:
17 |
{{ store$ | async | json }}
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/integration/app/app.component.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ngxs-labs/actions-executing/51685dd4af0961c88075786494cb8ebc7ea9c600/integration/app/app.component.scss
--------------------------------------------------------------------------------
/integration/app/app.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { Store } from '@ngxs/store';
3 | import { Observable } from 'rxjs';
4 |
5 | @Component({
6 | selector: 'app-root',
7 | templateUrl: './app.component.html',
8 | styleUrls: ['./app.component.scss']
9 | })
10 | export class AppComponent {
11 | public store$: Observable = this.store.select((state) => state);
12 |
13 | constructor(private store: Store) {}
14 | }
15 |
--------------------------------------------------------------------------------
/integration/app/app.module.ts:
--------------------------------------------------------------------------------
1 | import { NgxsModule } from '@ngxs/store';
2 | import { NgxsLoggerPluginModule } from '@ngxs/logger-plugin';
3 | import { BrowserModule } from '@angular/platform-browser';
4 | import { NgxsActionsExecutingModule } from '@ngxs-labs/actions-executing';
5 | import { NgModule } from '@angular/core';
6 | import { AppComponent } from './app.component';
7 | import { environment } from '../environments/environment';
8 | import { RouterModule } from '@angular/router';
9 | import { FormsModule } from '@angular/forms';
10 | import { ZooState } from './examples/states/zoo/zoo.state';
11 | import { NgxsReduxDevtoolsPluginModule } from '@ngxs/devtools-plugin';
12 |
13 | @NgModule({
14 | declarations: [AppComponent],
15 | imports: [
16 | BrowserModule,
17 | FormsModule,
18 | RouterModule.forRoot(
19 | [
20 | {
21 | path: '',
22 | redirectTo: 'single',
23 | pathMatch: 'full'
24 | },
25 | {
26 | path: 'single',
27 | loadChildren: () => import('./examples/single/single.module').then((m) => m.FirstModule)
28 | },
29 | {
30 | path: 'multiple',
31 | loadChildren: () => import('./examples/multiple/multiple.module').then((m) => m.SecondModule)
32 | }
33 | ],
34 | {}
35 | ),
36 | NgxsModule.forRoot([ZooState], {
37 | developmentMode: !environment.production
38 | }),
39 | NgxsLoggerPluginModule.forRoot(),
40 | NgxsActionsExecutingModule.forRoot(),
41 | NgxsReduxDevtoolsPluginModule.forRoot()
42 | ],
43 | providers: [],
44 | bootstrap: [AppComponent]
45 | })
46 | export class AppModule {}
47 |
--------------------------------------------------------------------------------
/integration/app/examples/multiple/multiple.component.html:
--------------------------------------------------------------------------------
1 | Zoo State
2 |
3 | {{ zoo$ | async | json }}
4 |
5 |
6 |
7 | Dispatch Action
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | Loading...
16 |
17 |
Adding Panda...
18 |
Adding Bear...
19 |
20 |
21 |
22 |
addPandaExecuting$ = {{ addPandaExecuting$ | async | json }}
23 |
addBearExecuting$ = {{ addBearExecuting$ | async | json }}
24 |
25 |
--------------------------------------------------------------------------------
/integration/app/examples/multiple/multiple.component.ts:
--------------------------------------------------------------------------------
1 | import { ChangeDetectionStrategy, Component } from '@angular/core';
2 | import { Select, Store } from '@ngxs/store';
3 | import { actionsExecuting, ActionsExecuting } from '@ngxs-labs/actions-executing';
4 | import { AddPanda, AddBear } from '../states/zoo/zoo.actions';
5 | import { Observable } from 'rxjs';
6 | import { ZooState } from '../states/zoo/zoo.state';
7 | import { ZooStateModel } from '../states/zoo/zoo.model';
8 |
9 | @Component({
10 | selector: 'multiple',
11 | templateUrl: './multiple.component.html',
12 | changeDetection: ChangeDetectionStrategy.OnPush
13 | })
14 | export class MultipleComponent {
15 | @Select(actionsExecuting([AddPanda])) public addPandaExecuting$: Observable;
16 | @Select(actionsExecuting([AddBear])) public addBearExecuting$: Observable;
17 | @Select(actionsExecuting([AddPanda, AddBear])) public addPandaOrAddBearExecuting$: Observable;
18 | @Select(ZooState) public zoo$: Observable;
19 |
20 | constructor(private store: Store) {}
21 |
22 | public addPanda() {
23 | this.store.dispatch(new AddPanda());
24 | }
25 |
26 | public addBear() {
27 | this.store.dispatch(new AddBear());
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/integration/app/examples/multiple/multiple.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { CommonModule } from '@angular/common';
3 | import { RouterModule } from '@angular/router';
4 | import { MultipleComponent } from './multiple.component';
5 |
6 | @NgModule({
7 | declarations: [MultipleComponent],
8 | imports: [CommonModule, RouterModule.forChild([{ path: '', component: MultipleComponent }])]
9 | })
10 | export class SecondModule {}
11 |
--------------------------------------------------------------------------------
/integration/app/examples/single/single.component.html:
--------------------------------------------------------------------------------
1 | Zoo State
2 |
3 | {{ zoo$ | async | json }}
4 |
5 |
6 |
7 | Dispatch Action
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | Loading...
16 |
17 |
Adding Panda...
18 |
19 |
20 |
21 |
22 | Loading...
23 |
24 |
Adding {{ addBearExecutingCount$ | async }} Bear...
25 |
26 |
27 |
28 |
addPandaExecuting$ = {{ addPandaExecuting$ | async | json }}
29 |
addBearExecuting$ = {{ addBearExecuting$ | async | json }}
30 |
31 |
--------------------------------------------------------------------------------
/integration/app/examples/single/single.component.ts:
--------------------------------------------------------------------------------
1 | import { Observable } from 'rxjs';
2 | import { actionsExecuting, ActionsExecuting } from '@ngxs-labs/actions-executing';
3 | import { Component, OnInit } from '@angular/core';
4 | import { Select, Store } from '@ngxs/store';
5 | import { ZooState } from '../states/zoo/zoo.state';
6 | import { ZooStateModel } from '../states/zoo/zoo.model';
7 | import { AddBear, AddPanda } from '../states/zoo/zoo.actions';
8 | import { map } from 'rxjs/operators';
9 |
10 | @Component({
11 | selector: 'single',
12 | templateUrl: 'single.component.html'
13 | })
14 | export class SingleComponent implements OnInit {
15 | @Select(actionsExecuting([AddPanda])) public addPandaExecuting$: Observable;
16 | @Select(actionsExecuting([AddBear])) public addBearExecuting$: Observable;
17 | addBearExecutingCount$: Observable;
18 | @Select(ZooState) public zoo$: Observable;
19 |
20 | constructor(private store: Store) {}
21 |
22 | ngOnInit() {
23 | this.addBearExecutingCount$ = this.addBearExecuting$.pipe(
24 | map((_actionsExecuting) => _actionsExecuting[AddBear.type])
25 | );
26 | }
27 |
28 | public addPanda() {
29 | this.store.dispatch(new AddPanda());
30 | }
31 |
32 | public addBear() {
33 | this.store.dispatch(new AddBear());
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/integration/app/examples/single/single.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { CommonModule } from '@angular/common';
3 | import { RouterModule } from '@angular/router';
4 | import { SingleComponent } from './single.component';
5 | import { FormsModule } from '@angular/forms';
6 |
7 | @NgModule({
8 | declarations: [SingleComponent],
9 | imports: [CommonModule, FormsModule, RouterModule.forChild([{ path: '', component: SingleComponent }])]
10 | })
11 | export class FirstModule {}
12 |
--------------------------------------------------------------------------------
/integration/app/examples/states/zoo/zoo.actions.ts:
--------------------------------------------------------------------------------
1 | export class AddPanda {
2 | public static type = '[Zoo] AddPanda';
3 | }
4 | export class AddBear {
5 | public static type = '[Zoo] AddBear';
6 | }
7 | export class AddPandaAndBear {
8 | public static type = '[Zoo] AddPandaAndBear';
9 | }
10 |
--------------------------------------------------------------------------------
/integration/app/examples/states/zoo/zoo.model.ts:
--------------------------------------------------------------------------------
1 | export interface ZooStateModel {
2 | pandas: string[];
3 | bears: string[];
4 | }
5 |
--------------------------------------------------------------------------------
/integration/app/examples/states/zoo/zoo.state.ts:
--------------------------------------------------------------------------------
1 | import { State, Action, StateContext, Selector } from '@ngxs/store';
2 | import { ZooStateModel } from './zoo.model';
3 | import { AddPanda, AddBear, AddPandaAndBear } from './zoo.actions';
4 | import { of } from 'rxjs';
5 | import { tap, delay } from 'rxjs/operators';
6 |
7 | @State({
8 | name: 'test',
9 | defaults: {
10 | pandas: [],
11 | bears: []
12 | }
13 | })
14 | export class ZooState {
15 | @Selector() public static pandas(state: ZooStateModel) {
16 | return state.pandas;
17 | }
18 | @Selector() public static bears(state: ZooStateModel) {
19 | return state.bears;
20 | }
21 |
22 | ngxsOnInit(ctx: StateContext) {
23 | ctx.dispatch(AddPanda);
24 | }
25 |
26 | @Action([AddPanda])
27 | public addPanda({ patchState, getState }: StateContext) {
28 | return of({}).pipe(
29 | delay(1000),
30 | tap((_) => {
31 | patchState({ pandas: getState().pandas.concat('🐼') });
32 | })
33 | );
34 | }
35 |
36 | @Action([AddBear])
37 | public addBear({ patchState, getState }: StateContext) {
38 | return of({}).pipe(
39 | delay(2000),
40 | tap((_) => {
41 | patchState({ bears: getState().bears.concat('🐻') });
42 | })
43 | );
44 | }
45 |
46 | @Action([AddPandaAndBear])
47 | public addPandaAndBear({ patchState, getState, dispatch }: StateContext) {
48 | return dispatch([new AddBear(), new AddPanda()]);
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/integration/assets/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ngxs-labs/actions-executing/51685dd4af0961c88075786494cb8ebc7ea9c600/integration/assets/.gitkeep
--------------------------------------------------------------------------------
/integration/environments/environment.prod.ts:
--------------------------------------------------------------------------------
1 | export const environment = {
2 | production: true
3 | };
4 |
--------------------------------------------------------------------------------
/integration/environments/environment.ts:
--------------------------------------------------------------------------------
1 | // This file can be replaced during build by using the `fileReplacements` array.
2 | // `ng build ---prod` replaces `environment.ts` with `environment.prod.ts`.
3 | // The list of file replacements can be found in `angular.json`.
4 |
5 | export const environment = {
6 | production: false
7 | };
8 |
9 | /*
10 | * In development mode, to ignore zone related error stack frames such as
11 | * `zone.run`, `zoneDelegate.invokeTask` for easier debugging, you can
12 | * import the following file, but please comment it out in production mode
13 | * because it will have performance impact when throw error
14 | */
15 | // import 'zone.js/plugins/zone-error'; // Included with Angular CLI.
16 |
--------------------------------------------------------------------------------
/integration/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ngxs-labs/actions-executing/51685dd4af0961c88075786494cb8ebc7ea9c600/integration/favicon.ico
--------------------------------------------------------------------------------
/integration/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Ngxs Actions Executing
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/integration/main.ts:
--------------------------------------------------------------------------------
1 | import { enableProdMode } from '@angular/core';
2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
3 |
4 | import { AppModule } from './app/app.module';
5 | import { environment } from './environments/environment';
6 |
7 | if (environment.production) {
8 | enableProdMode();
9 | }
10 |
11 | platformBrowserDynamic()
12 | .bootstrapModule(AppModule)
13 | .catch((err) => console.log(err));
14 |
--------------------------------------------------------------------------------
/integration/polyfills.ts:
--------------------------------------------------------------------------------
1 | import 'core-js/proposals/reflect-metadata';
2 | import 'zone.js';
3 |
--------------------------------------------------------------------------------
/integration/styles.scss:
--------------------------------------------------------------------------------
1 | @import 'bootstrap/scss/bootstrap';
2 |
3 | html {
4 | height: 100%;
5 | @extend .bg-light;
6 | }
7 |
8 | body {
9 | @extend .bg-light;
10 | }
11 |
--------------------------------------------------------------------------------
/integration/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../out-tsc/app",
5 | "types": []
6 | },
7 | "files": ["main.ts", "polyfills.ts"],
8 | "include": ["integration/**/*.d.ts"]
9 | }
10 |
--------------------------------------------------------------------------------
/integration/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../out-tsc/spec",
5 | "module": "commonjs",
6 | "types": ["jasmine", "node"]
7 | },
8 | "files": ["test.ts", "polyfills.ts"],
9 | "include": ["**/*.spec.ts", "**/*.d.ts"]
10 | }
11 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | const { pathsToModuleNameMapper } = require('ts-jest');
2 | const { paths } = require('./tsconfig.json').compilerOptions;
3 |
4 | // eslint-disable-next-line no-undef
5 | globalThis.ngJest = {
6 | skipNgcc: false,
7 | tsconfig: 'tsconfig.spec.json'
8 | };
9 |
10 | /** @type {import('ts-jest/dist/types').JestConfigWithTsJest} */
11 | module.exports = {
12 | preset: 'jest-preset-angular',
13 | moduleNameMapper: pathsToModuleNameMapper(paths, { prefix: '' }),
14 | setupFilesAfterEnv: ['/setup-jest.ts']
15 | };
16 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ngxs-actions-executing",
3 | "scripts": {
4 | "start": "ng serve --port=4256",
5 | "build": "ng build ngxs-actions-executing --configuration production && yarn copy-readme",
6 | "copy-readme": "ts-node --project tsconfig.tools.json ./tools/copy-readme",
7 | "release": "standard-version --release-as patch",
8 | "build-integration": "ng build integration",
9 | "test": "jest --config ./jest.config.js",
10 | "test:ci": "jest --config ./jest.config.js --coverage",
11 | "test:debug": "node --inspect-brk ./node_modules/jest/bin/jest.js --runInBand --config ./jest.config.js",
12 | "format": "prettier --write",
13 | "lint": "tslint '{src,integration}/**/*.ts' --project tsconfig.json"
14 | },
15 | "dependencies": {
16 | "@angular/animations": "^18.0.5",
17 | "@angular/common": "^18.0.5",
18 | "@angular/compiler": "^18.0.5",
19 | "@angular/core": "^18.0.5",
20 | "@angular/forms": "^18.0.5",
21 | "@angular/platform-browser": "^18.0.5",
22 | "@angular/platform-browser-dynamic": "^18.0.5",
23 | "@angular/router": "^18.0.5",
24 | "@ngxs/logger-plugin": "^18.0.0",
25 | "@ngxs/store": "^18.0.0",
26 | "bootstrap": "^4.3.1",
27 | "core-js": "^3.6.0",
28 | "rxjs": "^6.5.3",
29 | "standard-version": "^9.5.0",
30 | "tslib": "^2.0.0",
31 | "zone.js": "~0.14.7"
32 | },
33 | "devDependencies": {
34 | "@angular-devkit/build-angular": "^18.0.6",
35 | "@angular/cli": "^18.0.6",
36 | "@angular/compiler-cli": "^18.0.5",
37 | "@angular/language-service": "^18.0.5",
38 | "@commitlint/cli": "^8.2.0",
39 | "@commitlint/config-angular": "^8.1.0",
40 | "@commitlint/config-conventional": "^8.2.0",
41 | "@ngxs/devtools-plugin": "^18.0.0",
42 | "@types/jest": "^29.4.0",
43 | "codelyzer": "^6.0.0",
44 | "colors": "^1.3.3",
45 | "cypress": "^3.4.1",
46 | "husky": "^3.1.0",
47 | "jest": "^29.7.0",
48 | "jest-preset-angular": "^14.1.1",
49 | "lint-staged": "^9.2.5",
50 | "ng-packagr": "^18.0.0",
51 | "prettier": "^1.18.2",
52 | "start-server-and-test": "^1.10.0",
53 | "ts-node": "^8.3.0",
54 | "typescript": "~5.4.5"
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "enabled": false
3 | }
4 |
--------------------------------------------------------------------------------
/setup-jest.ts:
--------------------------------------------------------------------------------
1 | import 'jest-preset-angular/setup-jest';
2 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * The public api for consumers of @ngxs-labs/package
3 | */
4 | export * from './public-api';
5 |
--------------------------------------------------------------------------------
/src/lib/actions-executed.selector.ts:
--------------------------------------------------------------------------------
1 | import { createSelector, getActionTypeFromInstance, ActionType } from '@ngxs/store';
2 | import { ActionsExecutedState, ActionsExecutedStateModel } from './actions-executed.state';
3 |
4 | export type ActionsExecuted = { [action: string]: number } | null;
5 |
6 | export function actionsExecuted(actionTypes: ActionType[]) {
7 | return createSelector([ActionsExecutedState], (state: ActionsExecutedStateModel) => {
8 | if (!actionTypes || actionTypes.length === 0) {
9 | if (Object.keys(state).length === 0) {
10 | return null;
11 | }
12 | return state;
13 | }
14 |
15 | return actionTypes.reduce((acc: ActionsExecuted, type: ActionType) => {
16 | const actionType = getActionTypeFromInstance(type);
17 |
18 | if (!actionType) {
19 | return acc;
20 | }
21 |
22 | if (state[actionType]) {
23 | return { ...acc, [actionType]: state[actionType] };
24 | }
25 |
26 | return acc;
27 | }, null);
28 | });
29 | }
30 |
--------------------------------------------------------------------------------
/src/lib/actions-executed.state.ts:
--------------------------------------------------------------------------------
1 | import { State, NgxsOnInit, StateContext, Actions, getActionTypeFromInstance } from '@ngxs/store';
2 | import { Subscription } from 'rxjs';
3 | import { tap } from 'rxjs/operators';
4 | import { OnDestroy, Injectable } from '@angular/core';
5 | import { ActionStatus } from '@ngxs/store/src/actions-stream';
6 |
7 | export interface ActionsExecutedStateModel {
8 | [action: string]: number;
9 | }
10 |
11 | @State({
12 | name: 'ngxs_actions_executed'
13 | })
14 | @Injectable()
15 | export class ActionsExecutedState implements NgxsOnInit, OnDestroy {
16 | private actionsExecutedSub: Subscription = new Subscription();
17 |
18 | constructor(private actions$: Actions) {}
19 |
20 | public ngxsOnInit({ patchState, getState }: StateContext) {
21 | this.actionsExecutedSub = this.actions$
22 | .pipe(
23 | tap((actionContext) => {
24 | const actionType = getActionTypeFromInstance(actionContext.action);
25 | if (!actionType) {
26 | return;
27 | }
28 |
29 | let count = getState()?.[actionType] || 0;
30 |
31 | if (actionContext.status !== ActionStatus.Dispatched) {
32 | count++;
33 | }
34 |
35 | patchState({
36 | [actionType]: count
37 | });
38 | })
39 | )
40 | .subscribe();
41 | }
42 |
43 | public ngOnDestroy() {
44 | this.actionsExecutedSub.unsubscribe();
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/lib/actions-executing.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule, ModuleWithProviders } from '@angular/core';
2 | import { NgxsModule } from '@ngxs/store';
3 | import { ActionsExecutedState } from './actions-executed.state';
4 | import { ActionsExecutingState } from './actions-executing.state';
5 |
6 | @NgModule({
7 | imports: [NgxsModule.forFeature([ActionsExecutingState, ActionsExecutedState])]
8 | })
9 | export class NgxsActionsExecutingModule {
10 | public static forRoot(): ModuleWithProviders {
11 | return {
12 | ngModule: NgxsActionsExecutingModule
13 | };
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/lib/actions-executing.selector.ts:
--------------------------------------------------------------------------------
1 | import { createSelector, getActionTypeFromInstance, ActionType } from '@ngxs/store';
2 | import { ActionsExecutingState, ActionsExecutingStateModel } from './actions-executing.state';
3 |
4 | export type ActionsExecuting = { [action: string]: number } | null;
5 |
6 | function actionsExecutingFn(actionTypes: ActionType[], state: ActionsExecutingStateModel): ActionsExecuting {
7 | if (!actionTypes || actionTypes.length === 0) {
8 | if (Object.keys(state).length === 0) {
9 | return null;
10 | }
11 | return state;
12 | }
13 |
14 | return actionTypes.reduce((acc: ActionsExecuting, type: ActionType) => {
15 | const actionType = getActionTypeFromInstance(type);
16 |
17 | if (!actionType) {
18 | return acc;
19 | }
20 |
21 | if (state[actionType]) {
22 | return { ...acc, [actionType]: state[actionType] };
23 | }
24 |
25 | return acc;
26 | }, null);
27 | }
28 |
29 | export function actionsExecuting(actionTypes?: ActionType[]): (state: ActionsExecutingStateModel) => ActionsExecuting {
30 | return createSelector(
31 | [ActionsExecutingState],
32 | (state: ActionsExecutingStateModel): ActionsExecuting => {
33 | return actionsExecutingFn(actionTypes, state);
34 | }
35 | );
36 | }
37 |
38 | export function hasActionsExecuting(actionTypes?: ActionType[]): (state: ActionsExecutingStateModel) => boolean {
39 | return createSelector([ActionsExecutingState], (state: ActionsExecutingStateModel): boolean => {
40 | const result = actionsExecutingFn(actionTypes, state);
41 | return result === null ? false : Object.values(result).some((value) => value > 0);
42 | });
43 | }
44 |
--------------------------------------------------------------------------------
/src/lib/actions-executing.state.ts:
--------------------------------------------------------------------------------
1 | import { State, NgxsOnInit, StateContext, Actions, getActionTypeFromInstance } from '@ngxs/store';
2 | import { Subscription } from 'rxjs';
3 | import { tap } from 'rxjs/operators';
4 | import { ActionStatus } from '@ngxs/store/src/actions-stream';
5 | import { OnDestroy, Injectable } from '@angular/core';
6 |
7 | export interface ActionsExecutingStateModel {
8 | [action: string]: number;
9 | }
10 |
11 | @State({
12 | name: 'ngxs_actions_executing'
13 | })
14 | @Injectable()
15 | export class ActionsExecutingState implements NgxsOnInit, OnDestroy {
16 | private _sub: Subscription = new Subscription();
17 |
18 | constructor(private actions$: Actions) {}
19 |
20 | public ngxsOnInit({ patchState, getState }: StateContext) {
21 | this._sub = this.actions$
22 | .pipe(
23 | tap((actionContext) => {
24 | const actionType = getActionTypeFromInstance(actionContext.action);
25 | if (!actionType) {
26 | return;
27 | }
28 |
29 | let count = getState()?.[actionType] || 0;
30 |
31 | if (actionContext.status === ActionStatus.Dispatched) {
32 | count++;
33 | } else if (count > 0) {
34 | count--;
35 | }
36 |
37 | patchState({
38 | [actionType]: count
39 | });
40 | })
41 | )
42 | .subscribe();
43 | }
44 |
45 | public ngOnDestroy() {
46 | this._sub.unsubscribe();
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/ng-package.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "../node_modules/ng-packagr/ng-package.schema.json",
3 | "dest": "../dist/ngxs-actions-executing",
4 | "lib": {
5 | "entryFile": "public-api.ts"
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/ng-package.prod.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "../node_modules/ng-packagr/ng-package.schema.json",
3 | "dest": "../dist/ngxs-actions-executing",
4 | "lib": {
5 | "entryFile": "public-api.ts"
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@ngxs-labs/actions-executing",
3 | "version": "19.0.0",
4 | "peerDependencies": {
5 | "@ngxs/store": ">=4.0.0 <20.0.0"
6 | },
7 | "homepage": "https://github.com/ngxs-labs/actions-executing#readme",
8 | "repository": {
9 | "type": "git",
10 | "url": "git+https://github.com/ngxs-labs/actions-executing.git"
11 | },
12 | "dependencies": {
13 | "tslib": "^2.0.0"
14 | },
15 | "license": "MIT"
16 | }
17 |
--------------------------------------------------------------------------------
/src/public-api.ts:
--------------------------------------------------------------------------------
1 | export { NgxsActionsExecutingModule } from './lib/actions-executing.module';
2 | export { actionsExecuting, hasActionsExecuting, ActionsExecuting } from './lib/actions-executing.selector';
3 | export { actionsExecuted, ActionsExecuted } from './lib/actions-executed.selector';
4 |
--------------------------------------------------------------------------------
/src/tests/actions-executed.spec.ts:
--------------------------------------------------------------------------------
1 | import { NgxsActionsExecutingModule, actionsExecuted, ActionsExecuted } from '..';
2 | import { TestBed, fakeAsync, tick } from '@angular/core/testing';
3 | import { NgxsModule, Store, State, Action, StateContext, NgxsOnInit } from '@ngxs/store';
4 | import { of, throwError } from 'rxjs';
5 | import { delay } from 'rxjs/operators';
6 | import { Injectable } from '@angular/core';
7 |
8 | describe('actionsExecuted', () => {
9 | let store: Store;
10 |
11 | class Action1 {
12 | public static type = 'ACTION 1';
13 | }
14 |
15 | class Action2 {
16 | public static type = 'ACTION 2';
17 | }
18 |
19 | class Action3 {
20 | public static type = 'ACTION 3';
21 | }
22 |
23 | class ErrorAction1 {
24 | public static type = 'ERROR ACTION 1';
25 | }
26 |
27 | class AsyncAction1 {
28 | public static type = 'ASYNC ACTION 1';
29 | }
30 |
31 | class AsyncAction2 {
32 | public static type = 'ASYNC ACTION 2';
33 | }
34 |
35 | class AsyncAction3 {
36 | public static type = 'ASYNC ACTION 3';
37 | }
38 |
39 | class AsyncErrorAction1 {
40 | public static type = 'ASYNC ERROR ACTION 1';
41 | }
42 |
43 | class NestedAsyncAction1 {
44 | public static type = 'NESTED ASYNC ACTION 1';
45 | }
46 |
47 | class NestedAsyncAction2 {
48 | public static type = 'NESTED ASYNC ACTION 2';
49 | }
50 |
51 | class NestedAsyncAction3 {
52 | public static type = 'NESTED ASYNC ACTION 3';
53 | }
54 |
55 | class NestedAsyncAction4 {
56 | public static type = 'NESTED ASYNC ACTION 4';
57 | }
58 |
59 | class NestedAsyncAction5 {
60 | public static type = 'NESTED ASYNC ACTION 5';
61 | }
62 |
63 | class NestedAsyncAction6 {
64 | public static type = 'NESTED ASYNC ACTION 6';
65 | }
66 |
67 | @State({
68 | name: 'test'
69 | })
70 | @Injectable()
71 | class TestState {
72 | @Action([Action1])
73 | public action1() {}
74 |
75 | @Action([AsyncAction1])
76 | public asyncAction1() {
77 | return of({}).pipe(delay(0));
78 | }
79 |
80 | @Action([AsyncAction2])
81 | public asyncAction2() {
82 | return of({}).pipe(delay(0));
83 | }
84 |
85 | @Action(AsyncErrorAction1)
86 | public asyncError() {
87 | return throwError(new Error('this is a test error')).pipe(delay(0));
88 | }
89 |
90 | @Action(ErrorAction1)
91 | public onError() {
92 | return throwError(new Error('this is a test error'));
93 | }
94 | }
95 |
96 | @State<{}>({
97 | name: 'nested_actions_1'
98 | })
99 | @Injectable()
100 | class NestedActions1State {
101 | @Action(NestedAsyncAction1)
102 | public nestedAsyncAction1({ dispatch }: StateContext<{}>) {
103 | return dispatch(new NestedAsyncAction2()).pipe(delay(0));
104 | }
105 |
106 | @Action(NestedAsyncAction2)
107 | public nestedAsyncAction2({ dispatch }: StateContext<{}>) {
108 | return dispatch(new NestedAsyncAction3()).pipe(delay(0));
109 | }
110 |
111 | @Action(NestedAsyncAction3)
112 | public nestedAsyncAction3() {
113 | return of({}).pipe(delay(0));
114 | }
115 | }
116 |
117 | @State({
118 | name: 'nested_actions_2'
119 | })
120 | @Injectable()
121 | class NestedActions2State {
122 | @Action([NestedAsyncAction4, NestedAsyncAction5])
123 | public combinedAction({ dispatch }: StateContext<{}>) {
124 | return dispatch(new NestedAsyncAction6()).pipe(delay(0));
125 | }
126 |
127 | @Action(NestedAsyncAction5)
128 | public nestedAsyncAction5() {
129 | return of({}).pipe(delay(100));
130 | }
131 |
132 | @Action(NestedAsyncAction6)
133 | public nestedAsyncAction6() {
134 | return of({}).pipe(delay(0));
135 | }
136 | }
137 |
138 | @State({
139 | name: 'ngxs_on_init_state'
140 | })
141 | @Injectable()
142 | class NgxsOnInitState implements NgxsOnInit {
143 | ngxsOnInit(ctx: StateContext<{}>) {
144 | ctx.dispatch(new Action3());
145 | ctx.dispatch(new AsyncAction3());
146 | }
147 |
148 | @Action([Action3])
149 | public action3() {}
150 |
151 | @Action([AsyncAction3])
152 | public asyncAction3() {
153 | return of({}).pipe(delay(0));
154 | }
155 | }
156 |
157 | describe('NgxsOnInit', () => {
158 | describe('Sync Action', () => {
159 | it('should be null when dispatching sync from ngxsOnInit', fakeAsync(() => {
160 | TestBed.configureTestingModule({
161 | imports: [NgxsModule.forRoot([NgxsOnInitState]), NgxsActionsExecutingModule.forRoot()]
162 | });
163 |
164 | store = TestBed.get(Store);
165 |
166 | tick(1);
167 |
168 | expect(store.selectSnapshot(actionsExecuted([Action3]))).toBe(null);
169 | }));
170 | });
171 |
172 | describe('Async Action', () => {
173 | it('should count when dispatching async from ngxsOnInit', fakeAsync(() => {
174 | TestBed.configureTestingModule({
175 | imports: [NgxsModule.forRoot([NgxsOnInitState]), NgxsActionsExecutingModule.forRoot()]
176 | });
177 |
178 | store = TestBed.get(Store);
179 |
180 | tick(1);
181 | expect(store.selectSnapshot(actionsExecuted([AsyncAction3]))).toEqual({ [AsyncAction3.type]: 1 });
182 | tick(1);
183 | expect(store.selectSnapshot(actionsExecuted([AsyncAction3]))).toEqual({ [AsyncAction3.type]: 1 });
184 | }));
185 | });
186 | });
187 |
188 | describe('', () => {
189 | beforeEach(() => {
190 | TestBed.configureTestingModule({
191 | imports: [
192 | NgxsModule.forRoot([TestState, NestedActions1State, NestedActions2State]),
193 | NgxsActionsExecutingModule.forRoot()
194 | ]
195 | });
196 |
197 | store = TestBed.inject(Store);
198 | });
199 |
200 | describe('Single Action', () => {
201 | describe('Sync Action', () => {
202 | it('should be 1', () => {
203 | store.dispatch(Action1);
204 |
205 | const snapshot = store.selectSnapshot(actionsExecuted([Action1]));
206 | expect(snapshot).toEqual({ [Action1.type]: 1 });
207 | });
208 |
209 | it('should be null before dispatch and 1 after complete', () => {
210 | const actionStatus: ActionsExecuted[] = [];
211 |
212 | store.select(actionsExecuted([Action1])).subscribe((_actionsExecuted) => {
213 | actionStatus.push(_actionsExecuted);
214 | });
215 |
216 | store.dispatch(new Action1());
217 |
218 | expect(actionStatus).toEqual([null, { [Action1.type]: 1 }]);
219 | });
220 |
221 | it('should be null before dispatch and 1 after error', () => {
222 | const actionStatus: ActionsExecuted[] = [];
223 |
224 | store.select(actionsExecuted([ErrorAction1])).subscribe((_actionsExecuted) => {
225 | actionStatus.push(_actionsExecuted);
226 | });
227 |
228 | store.dispatch(new ErrorAction1());
229 | expect(actionStatus).toEqual([null, { [ErrorAction1.type]: 1 }]);
230 | });
231 | });
232 |
233 | describe('Async Action', () => {
234 | it('should be null before is completed', fakeAsync(() => {
235 | store.dispatch(AsyncAction1);
236 |
237 | let snapshot = store.selectSnapshot(actionsExecuted([AsyncAction1]));
238 | expect(snapshot).toEqual(null);
239 |
240 | tick();
241 |
242 | snapshot = store.selectSnapshot(actionsExecuted([AsyncAction1]));
243 | expect(snapshot).toEqual({ 'ASYNC ACTION 1': 1 });
244 | }));
245 |
246 | it('should be null before dispatch and 1 after complete ', fakeAsync(() => {
247 | const actionStatus: ActionsExecuted[] = [];
248 |
249 | store.select(actionsExecuted([AsyncAction1])).subscribe((_actionsExecuted) => {
250 | actionStatus.push(_actionsExecuted);
251 | });
252 |
253 | store.dispatch(new AsyncAction1());
254 | tick(1);
255 | expect(actionStatus).toEqual([null, { [AsyncAction1.type]: 1 }]);
256 | }));
257 |
258 | it('should be null before dispatch and q after error', fakeAsync(() => {
259 | const actionStatus: ActionsExecuted[] = [];
260 |
261 | store.select(actionsExecuted([AsyncErrorAction1])).subscribe((_actionsExecuted) => {
262 | actionStatus.push(_actionsExecuted);
263 | });
264 |
265 | store.dispatch(new AsyncErrorAction1()).subscribe({
266 | error: (err) => {
267 | expect(err).toBeDefined();
268 | }
269 | });
270 |
271 | tick(1);
272 | expect(actionStatus).toEqual([null, { [AsyncErrorAction1.type]: 1 }]);
273 | }));
274 | });
275 | });
276 |
277 | describe('Multiple Actions', () => {
278 | describe('sync', () => {
279 | it('should count actions when completed', () => {
280 | const actionStatus: ActionsExecuted[] = [];
281 |
282 | store.select(actionsExecuted([Action1, Action2])).subscribe((_actionsExecuted) => {
283 | actionStatus.push(_actionsExecuted);
284 | });
285 |
286 | store.dispatch(new Action1());
287 | store.dispatch(new Action2());
288 | expect(actionStatus).toEqual([
289 | null,
290 | { [Action1.type]: 1 },
291 | { [Action1.type]: 1 },
292 | { [Action1.type]: 1, [Action2.type]: 1 }
293 | ]);
294 | });
295 |
296 | it('should count action and error when completed', () => {
297 | const actionStatus: ActionsExecuted[] = [];
298 |
299 | store.select(actionsExecuted([Action1, ErrorAction1])).subscribe((_actionsExecuted) => {
300 | actionStatus.push(_actionsExecuted);
301 | });
302 |
303 | store.dispatch(new Action1());
304 | store.dispatch(new ErrorAction1());
305 | expect(actionStatus).toEqual([
306 | null,
307 | { [Action1.type]: 1 },
308 | { [Action1.type]: 1 },
309 | { [Action1.type]: 1, [ErrorAction1.type]: 1 }
310 | ]);
311 | });
312 | });
313 | describe('async', () => {
314 | it('should count actions when completed', fakeAsync(() => {
315 | const actionStatus: ActionsExecuted[] = [];
316 |
317 | store.select(actionsExecuted([AsyncAction1, AsyncAction2])).subscribe((_actionsExecuted) => {
318 | actionStatus.push(_actionsExecuted);
319 | });
320 |
321 | store.dispatch(new AsyncAction1());
322 | tick(1);
323 | expect(actionStatus).toEqual([null, { [AsyncAction1.type]: 1 }]);
324 | store.dispatch(new AsyncAction2());
325 | tick(1);
326 | expect(actionStatus).toEqual([
327 | null,
328 | { [AsyncAction1.type]: 1 },
329 | { [AsyncAction1.type]: 1 },
330 | { [AsyncAction1.type]: 1, [AsyncAction2.type]: 1 }
331 | ]);
332 | }));
333 |
334 | it('should count action and error when completed', fakeAsync(() => {
335 | const actionStatus: ActionsExecuted[] = [];
336 |
337 | store.select(actionsExecuted([AsyncAction1, AsyncErrorAction1])).subscribe((_actionsExecuted) => {
338 | actionStatus.push(_actionsExecuted);
339 | });
340 |
341 | store.dispatch(new AsyncAction1());
342 | tick(1);
343 | expect(actionStatus).toEqual([null, { [AsyncAction1.type]: 1 }]);
344 | store.dispatch(new AsyncErrorAction1());
345 | tick(1);
346 | expect(actionStatus).toEqual([
347 | null,
348 | { [AsyncAction1.type]: 1 },
349 | { [AsyncAction1.type]: 1 },
350 | { [AsyncAction1.type]: 1, [AsyncErrorAction1.type]: 1 }
351 | ]);
352 | }));
353 |
354 | it('should count all actions when completed', fakeAsync(() => {
355 | const actionStatus: ActionsExecuted[] = [];
356 |
357 | store.select(actionsExecuted([AsyncAction1])).subscribe((_actionsExecuted) => {
358 | actionStatus.push(_actionsExecuted);
359 | });
360 |
361 | store.dispatch(new AsyncAction1());
362 | expect(actionStatus).toEqual([null]);
363 | store.dispatch(new AsyncAction1());
364 | tick(1);
365 | expect(actionStatus).toEqual([null, { [AsyncAction1.type]: 1 }, { [AsyncAction1.type]: 2 }]);
366 | }));
367 |
368 | it('should count all actions when completed (case 2)', fakeAsync(() => {
369 | const actionStatus: ActionsExecuted[] = [];
370 |
371 | store.select(actionsExecuted([AsyncAction1])).subscribe((_actionsExecuted) => {
372 | actionStatus.push(_actionsExecuted);
373 | });
374 |
375 | store.dispatch(new AsyncAction1());
376 | store.dispatch(new AsyncAction1());
377 | expect(actionStatus).toEqual([null]);
378 | tick(1);
379 | expect(actionStatus).toEqual([null, { [AsyncAction1.type]: 1 }, { [AsyncAction1.type]: 2 }]);
380 | }));
381 |
382 | describe('nested actions 1', () => {
383 | it('should be executing on nested actions', fakeAsync(() => {
384 | const nestedAction1Status: ActionsExecuted[] = [];
385 | const nestedAction2Status: ActionsExecuted[] = [];
386 | const nestedAction3Status: ActionsExecuted[] = [];
387 |
388 | const combinedActionStatus: ActionsExecuted[] = [];
389 |
390 | store.select(actionsExecuted([NestedAsyncAction1])).subscribe((_actionsExecuted) => {
391 | nestedAction1Status.push(_actionsExecuted);
392 | });
393 |
394 | store.select(actionsExecuted([NestedAsyncAction2])).subscribe((_actionsExecuted) => {
395 | nestedAction2Status.push(_actionsExecuted);
396 | });
397 |
398 | store.select(actionsExecuted([NestedAsyncAction3])).subscribe((_actionsExecuted) => {
399 | nestedAction3Status.push(_actionsExecuted);
400 | });
401 |
402 | store
403 | .select(actionsExecuted([NestedAsyncAction1, NestedAsyncAction2, NestedAsyncAction3]))
404 | .subscribe((_actionsExecuted) => {
405 | combinedActionStatus.push(_actionsExecuted);
406 | });
407 |
408 | store.dispatch(new NestedAsyncAction1());
409 | tick(1);
410 | expect(nestedAction1Status).toEqual([null, { [NestedAsyncAction1.type]: 1 }]);
411 | expect(nestedAction2Status).toEqual([
412 | null,
413 | { [NestedAsyncAction2.type]: 1 },
414 | { [NestedAsyncAction2.type]: 1 }
415 | ]);
416 | expect(nestedAction3Status).toEqual([
417 | null,
418 | { [NestedAsyncAction3.type]: 1 },
419 | { [NestedAsyncAction3.type]: 1 },
420 | { [NestedAsyncAction3.type]: 1 }
421 | ]);
422 |
423 | expect(combinedActionStatus).toEqual([
424 | null,
425 | { [NestedAsyncAction3.type]: 1 },
426 | { [NestedAsyncAction2.type]: 1, [NestedAsyncAction3.type]: 1 },
427 | {
428 | [NestedAsyncAction1.type]: 1,
429 | [NestedAsyncAction2.type]: 1,
430 | [NestedAsyncAction3.type]: 1
431 | }
432 | ]);
433 | }));
434 | });
435 |
436 | describe('nested actions 2', () => {
437 | it('should be executing on nested actions (scenario 1)', fakeAsync(() => {
438 | const nestedAction4Status: ActionsExecuted[] = [];
439 | const nestedAction5Status: ActionsExecuted[] = [];
440 | const nestedAction6Status: ActionsExecuted[] = [];
441 |
442 | const combinedAction45Status: ActionsExecuted[] = [];
443 | const combinedAction456Status: ActionsExecuted[] = [];
444 |
445 | store.select(actionsExecuted([NestedAsyncAction4])).subscribe((_actionsExecuted) => {
446 | nestedAction4Status.push(_actionsExecuted);
447 | });
448 |
449 | store.select(actionsExecuted([NestedAsyncAction5])).subscribe((_actionsExecuted) => {
450 | nestedAction5Status.push(_actionsExecuted);
451 | });
452 |
453 | store.select(actionsExecuted([NestedAsyncAction6])).subscribe((_actionsExecuted) => {
454 | nestedAction6Status.push(_actionsExecuted);
455 | });
456 |
457 | store
458 | .select(actionsExecuted([NestedAsyncAction4, NestedAsyncAction5]))
459 | .subscribe((_actionsExecuted) => {
460 | combinedAction45Status.push(_actionsExecuted);
461 | });
462 |
463 | store
464 | .select(actionsExecuted([NestedAsyncAction4, NestedAsyncAction5, NestedAsyncAction6]))
465 | .subscribe((_actionsExecuted) => {
466 | combinedAction456Status.push(_actionsExecuted);
467 | });
468 |
469 | store.dispatch(new NestedAsyncAction4());
470 | tick(1);
471 | expect(nestedAction4Status).toEqual([null, { [NestedAsyncAction4.type]: 1 }]);
472 | expect(nestedAction5Status).toEqual([null]);
473 | expect(nestedAction6Status).toEqual([
474 | null,
475 | { [NestedAsyncAction6.type]: 1 },
476 | { [NestedAsyncAction6.type]: 1 }
477 | ]);
478 |
479 | expect(combinedAction45Status).toEqual([null, { [NestedAsyncAction4.type]: 1 }]);
480 | expect(combinedAction456Status).toEqual([
481 | null,
482 | { [NestedAsyncAction6.type]: 1 },
483 | { [NestedAsyncAction4.type]: 1, [NestedAsyncAction6.type]: 1 }
484 | ]);
485 | }));
486 |
487 | it('should be executing on nested actions (scenario 2)', fakeAsync(() => {
488 | const nestedAction4Status: ActionsExecuted[] = [];
489 | const nestedAction5Status: ActionsExecuted[] = [];
490 | const nestedAction6Status: ActionsExecuted[] = [];
491 |
492 | const combinedAction45Status: ActionsExecuted[] = [];
493 | const combinedAction456Status: ActionsExecuted[] = [];
494 |
495 | store.select(actionsExecuted([NestedAsyncAction4])).subscribe((_actionsExecuted) => {
496 | nestedAction4Status.push(_actionsExecuted);
497 | });
498 |
499 | store.select(actionsExecuted([NestedAsyncAction5])).subscribe((_actionsExecuted) => {
500 | nestedAction5Status.push(_actionsExecuted);
501 | });
502 |
503 | store.select(actionsExecuted([NestedAsyncAction6])).subscribe((_actionsExecuted) => {
504 | nestedAction6Status.push(_actionsExecuted);
505 | });
506 |
507 | store
508 | .select(actionsExecuted([NestedAsyncAction4, NestedAsyncAction5]))
509 | .subscribe((_actionsExecuted) => {
510 | combinedAction45Status.push(_actionsExecuted);
511 | });
512 |
513 | store
514 | .select(actionsExecuted([NestedAsyncAction4, NestedAsyncAction5, NestedAsyncAction6]))
515 | .subscribe((_actionsExecuted) => {
516 | combinedAction456Status.push(_actionsExecuted);
517 | });
518 |
519 | store.dispatch([new NestedAsyncAction4(), new NestedAsyncAction5()]);
520 | tick(1);
521 | expect(nestedAction4Status).toEqual([null, { [NestedAsyncAction4.type]: 1 }]);
522 | expect(nestedAction5Status).toEqual([null]);
523 | expect(nestedAction6Status).toEqual([
524 | null,
525 | { [NestedAsyncAction6.type]: 1 },
526 | { [NestedAsyncAction6.type]: 2 },
527 | { [NestedAsyncAction6.type]: 2 }
528 | ]);
529 | tick(100);
530 | expect(nestedAction4Status).toEqual([
531 | null,
532 | { [NestedAsyncAction4.type]: 1 },
533 | { [NestedAsyncAction4.type]: 1 }
534 | ]);
535 | expect(nestedAction5Status).toEqual([null, { [NestedAsyncAction5.type]: 1 }]);
536 | expect(nestedAction6Status).toEqual([
537 | null,
538 | { [NestedAsyncAction6.type]: 1 },
539 | { [NestedAsyncAction6.type]: 2 },
540 | { [NestedAsyncAction6.type]: 2 },
541 | { [NestedAsyncAction6.type]: 2 }
542 | ]);
543 |
544 | expect(combinedAction45Status).toEqual([
545 | null,
546 | { [NestedAsyncAction4.type]: 1 },
547 | { [NestedAsyncAction4.type]: 1, [NestedAsyncAction5.type]: 1 }
548 | ]);
549 | expect(combinedAction456Status).toEqual([
550 | null,
551 | { [NestedAsyncAction6.type]: 1 },
552 | { [NestedAsyncAction6.type]: 2 },
553 | { [NestedAsyncAction4.type]: 1, [NestedAsyncAction6.type]: 2 },
554 | {
555 | [NestedAsyncAction4.type]: 1,
556 | [NestedAsyncAction5.type]: 1,
557 | [NestedAsyncAction6.type]: 2
558 | }
559 | ]);
560 | }));
561 |
562 | it('should be executing on nested actions (scenario 3)', fakeAsync(() => {
563 | const nestedAction4Status: ActionsExecuted[] = [];
564 | const nestedAction5Status: ActionsExecuted[] = [];
565 | const nestedAction6Status: ActionsExecuted[] = [];
566 |
567 | const combinedAction45Status: ActionsExecuted[] = [];
568 | const combinedAction456Status: ActionsExecuted[] = [];
569 |
570 | store.select(actionsExecuted([NestedAsyncAction4])).subscribe((_actionsExecuted) => {
571 | nestedAction4Status.push(_actionsExecuted);
572 | });
573 |
574 | store.select(actionsExecuted([NestedAsyncAction5])).subscribe((_actionsExecuted) => {
575 | nestedAction5Status.push(_actionsExecuted);
576 | });
577 |
578 | store.select(actionsExecuted([NestedAsyncAction6])).subscribe((_actionsExecuted) => {
579 | nestedAction6Status.push(_actionsExecuted);
580 | });
581 |
582 | store
583 | .select(actionsExecuted([NestedAsyncAction4, NestedAsyncAction5]))
584 | .subscribe((_actionsExecuted) => {
585 | combinedAction45Status.push(_actionsExecuted);
586 | });
587 |
588 | store
589 | .select(actionsExecuted([NestedAsyncAction4, NestedAsyncAction5, NestedAsyncAction6]))
590 | .subscribe((_actionsExecuted) => {
591 | combinedAction456Status.push(_actionsExecuted);
592 | });
593 |
594 | store.dispatch(new NestedAsyncAction5());
595 | tick(1);
596 | expect(nestedAction4Status).toEqual([null]);
597 | expect(nestedAction5Status).toEqual([null]);
598 | expect(nestedAction6Status).toEqual([null, { [NestedAsyncAction6.type]: 1 }]);
599 | tick(100);
600 | expect(nestedAction4Status).toEqual([null]);
601 | expect(nestedAction5Status).toEqual([null, { [NestedAsyncAction5.type]: 1 }]);
602 | expect(nestedAction6Status).toEqual([
603 | null,
604 | { [NestedAsyncAction6.type]: 1 },
605 | { [NestedAsyncAction6.type]: 1 }
606 | ]);
607 |
608 | expect(combinedAction45Status).toEqual([null, { [NestedAsyncAction5.type]: 1 }]);
609 | expect(combinedAction456Status).toEqual([
610 | null,
611 | { [NestedAsyncAction6.type]: 1 },
612 | { [NestedAsyncAction5.type]: 1, [NestedAsyncAction6.type]: 1 }
613 | ]);
614 | }));
615 |
616 | it('should be executing on nested actions (scenario 4)', fakeAsync(() => {
617 | const nestedAction4Status: ActionsExecuted[] = [];
618 | const nestedAction5Status: ActionsExecuted[] = [];
619 | const nestedAction6Status: ActionsExecuted[] = [];
620 |
621 | const combinedAction45Status: ActionsExecuted[] = [];
622 | const combinedAction456Status: ActionsExecuted[] = [];
623 |
624 | store.select(actionsExecuted([NestedAsyncAction4])).subscribe((_actionsExecuted) => {
625 | nestedAction4Status.push(_actionsExecuted);
626 | });
627 |
628 | store.select(actionsExecuted([NestedAsyncAction5])).subscribe((_actionsExecuted) => {
629 | nestedAction5Status.push(_actionsExecuted);
630 | });
631 |
632 | store.select(actionsExecuted([NestedAsyncAction6])).subscribe((_actionsExecuted) => {
633 | nestedAction6Status.push(_actionsExecuted);
634 | });
635 |
636 | store
637 | .select(actionsExecuted([NestedAsyncAction4, NestedAsyncAction5]))
638 | .subscribe((_actionsExecuted) => {
639 | combinedAction45Status.push(_actionsExecuted);
640 | });
641 |
642 | store
643 | .select(actionsExecuted([NestedAsyncAction4, NestedAsyncAction5, NestedAsyncAction6]))
644 | .subscribe((_actionsExecuted) => {
645 | combinedAction456Status.push(_actionsExecuted);
646 | });
647 |
648 | store.dispatch([new NestedAsyncAction4(), new NestedAsyncAction5(), new NestedAsyncAction6()]);
649 | tick(1);
650 | expect(nestedAction4Status).toEqual([null, { [NestedAsyncAction4.type]: 1 }]);
651 | expect(nestedAction5Status).toEqual([null]);
652 | expect(nestedAction6Status).toEqual([
653 | null,
654 | { [NestedAsyncAction6.type]: 1 },
655 | { [NestedAsyncAction6.type]: 2 },
656 | { [NestedAsyncAction6.type]: 3 },
657 | { [NestedAsyncAction6.type]: 3 }
658 | ]);
659 | tick(100);
660 | expect(nestedAction4Status).toEqual([
661 | null,
662 | { [NestedAsyncAction4.type]: 1 },
663 | { [NestedAsyncAction4.type]: 1 }
664 | ]);
665 | expect(nestedAction5Status).toEqual([null, { [NestedAsyncAction5.type]: 1 }]);
666 | expect(nestedAction6Status).toEqual([
667 | null,
668 | { [NestedAsyncAction6.type]: 1 },
669 | { [NestedAsyncAction6.type]: 2 },
670 | { [NestedAsyncAction6.type]: 3 },
671 | { [NestedAsyncAction6.type]: 3 },
672 | { [NestedAsyncAction6.type]: 3 }
673 | ]);
674 |
675 | expect(combinedAction45Status).toEqual([
676 | null,
677 | { [NestedAsyncAction4.type]: 1 },
678 | { [NestedAsyncAction4.type]: 1, [NestedAsyncAction5.type]: 1 }
679 | ]);
680 | expect(combinedAction456Status).toEqual([
681 | null,
682 | { [NestedAsyncAction6.type]: 1 },
683 | { [NestedAsyncAction6.type]: 2 },
684 | {
685 | [NestedAsyncAction6.type]: 3
686 | },
687 | {
688 | [NestedAsyncAction4.type]: 1,
689 | [NestedAsyncAction6.type]: 3
690 | },
691 | {
692 | [NestedAsyncAction4.type]: 1,
693 | [NestedAsyncAction5.type]: 1,
694 | [NestedAsyncAction6.type]: 3
695 | }
696 | ]);
697 | }));
698 | });
699 | });
700 | });
701 |
702 | describe('No Actions', () => {
703 | describe('sync', () => {
704 | it('should be executing between dispatch and complete', () => {
705 | const actionStatus: ActionsExecuted[] = [];
706 |
707 | store.select(actionsExecuted([])).subscribe((_actionsExecuted) => {
708 | actionStatus.push(_actionsExecuted);
709 | });
710 |
711 | store.dispatch(new Action1());
712 | store.dispatch(new Action2());
713 | expect(actionStatus).toEqual([
714 | null,
715 | { [Action1.type]: 0 },
716 | { [Action1.type]: 1 },
717 | { [Action1.type]: 1, [Action2.type]: 0 },
718 | { [Action1.type]: 1, [Action2.type]: 1 }
719 | ]);
720 | });
721 | });
722 |
723 | describe('async', () => {
724 | it('should be executing between dispatch and complete ', fakeAsync(() => {
725 | const actionStatus: ActionsExecuted[] = [];
726 |
727 | store.select(actionsExecuted([])).subscribe((_actionsExecuted) => {
728 | actionStatus.push(_actionsExecuted);
729 | });
730 |
731 | store.dispatch(new AsyncAction1());
732 | tick(1);
733 | expect(actionStatus).toEqual([null, { [AsyncAction1.type]: 0 }, { [AsyncAction1.type]: 1 }]);
734 | store.dispatch(new AsyncAction2());
735 | tick(1);
736 | expect(actionStatus).toEqual([
737 | null,
738 | { [AsyncAction1.type]: 0 },
739 | { [AsyncAction1.type]: 1 },
740 | { [AsyncAction1.type]: 1, [AsyncAction2.type]: 0 },
741 | { [AsyncAction1.type]: 1, [AsyncAction2.type]: 1 }
742 | ]);
743 | }));
744 | });
745 | });
746 | });
747 | });
748 |
--------------------------------------------------------------------------------
/src/tests/actions-executing-module.spec.ts:
--------------------------------------------------------------------------------
1 | import { NgxsActionsExecutingModule } from '..';
2 |
3 | describe('actions-executing', () => {
4 | it('should successfully create module', () => {
5 | const ofExecModule = new NgxsActionsExecutingModule();
6 |
7 | expect(ofExecModule).toBeTruthy();
8 | });
9 | });
10 |
--------------------------------------------------------------------------------
/src/tests/actions-executing.spec.ts:
--------------------------------------------------------------------------------
1 | import { NgxsActionsExecutingModule, ActionsExecuting, actionsExecuting } from '..';
2 | import { TestBed, fakeAsync, tick } from '@angular/core/testing';
3 | import { NgxsModule, Store, State, Action, StateContext, NgxsOnInit } from '@ngxs/store';
4 | import { of, throwError } from 'rxjs';
5 | import { delay } from 'rxjs/operators';
6 | import { Injectable } from '@angular/core';
7 |
8 | describe('actionsExecuting', () => {
9 | let store: Store;
10 |
11 | class Action1 {
12 | public static type = 'ACTION 1';
13 | }
14 |
15 | class Action2 {
16 | public static type = 'ACTION 2';
17 | }
18 |
19 | class Action3 {
20 | public static type = 'ACTION 3';
21 | }
22 |
23 | class ErrorAction1 {
24 | public static type = 'ERROR ACTION 1';
25 | }
26 |
27 | class AsyncAction1 {
28 | public static type = 'ASYNC ACTION 1';
29 | }
30 |
31 | class AsyncAction2 {
32 | public static type = 'ASYNC ACTION 2';
33 | }
34 |
35 | class AsyncAction3 {
36 | public static type = 'ASYNC ACTION 3';
37 | }
38 |
39 | class AsyncErrorAction1 {
40 | public static type = 'ASYNC ERROR ACTION 1';
41 | }
42 |
43 | class NestedAsyncAction1 {
44 | public static type = 'NESTED ASYNC ACTION 1';
45 | }
46 |
47 | class NestedAsyncAction2 {
48 | public static type = 'NESTED ASYNC ACTION 2';
49 | }
50 |
51 | class NestedAsyncAction3 {
52 | public static type = 'NESTED ASYNC ACTION 3';
53 | }
54 |
55 | class NestedAsyncAction4 {
56 | public static type = 'NESTED ASYNC ACTION 4';
57 | }
58 |
59 | class NestedAsyncAction5 {
60 | public static type = 'NESTED ASYNC ACTION 5';
61 | }
62 |
63 | class NestedAsyncAction6 {
64 | public static type = 'NESTED ASYNC ACTION 6';
65 | }
66 |
67 | @State({
68 | name: 'test'
69 | })
70 | @Injectable()
71 | class TestState {
72 | @Action([Action1])
73 | public action1() {}
74 |
75 | @Action([AsyncAction1])
76 | public asyncAction1() {
77 | return of({}).pipe(delay(0));
78 | }
79 |
80 | @Action([AsyncAction2])
81 | public asyncAction2() {
82 | return of({}).pipe(delay(0));
83 | }
84 |
85 | @Action(AsyncErrorAction1)
86 | public asyncError() {
87 | return throwError(new Error('this is a test error')).pipe(delay(0));
88 | }
89 |
90 | @Action(ErrorAction1)
91 | public onError() {
92 | return throwError(new Error('this is a test error'));
93 | }
94 | }
95 |
96 | @State<{}>({
97 | name: 'nested_actions_1'
98 | })
99 | @Injectable()
100 | class NestedActions1State {
101 | @Action(NestedAsyncAction1)
102 | public nestedAsyncAction1({ dispatch }: StateContext<{}>) {
103 | return dispatch(new NestedAsyncAction2()).pipe(delay(0));
104 | }
105 |
106 | @Action(NestedAsyncAction2)
107 | public nestedAsyncAction2({ dispatch }: StateContext<{}>) {
108 | return dispatch(new NestedAsyncAction3()).pipe(delay(0));
109 | }
110 |
111 | @Action(NestedAsyncAction3)
112 | public nestedAsyncAction3() {
113 | return of({}).pipe(delay(0));
114 | }
115 | }
116 |
117 | @State({
118 | name: 'nested_actions_2'
119 | })
120 | @Injectable()
121 | class NestedActions2State {
122 | @Action([NestedAsyncAction4, NestedAsyncAction5])
123 | public combinedAction({ dispatch }: StateContext<{}>) {
124 | return dispatch(new NestedAsyncAction6()).pipe(delay(0));
125 | }
126 |
127 | @Action(NestedAsyncAction5)
128 | public nestedAsyncAction5() {
129 | return of({}).pipe(delay(100));
130 | }
131 |
132 | @Action(NestedAsyncAction6)
133 | public nestedAsyncAction6() {
134 | return of({}).pipe(delay(0));
135 | }
136 | }
137 |
138 | @State({
139 | name: 'ngxs_on_init_state'
140 | })
141 | @Injectable()
142 | class NgxsOnInitState implements NgxsOnInit {
143 | ngxsOnInit(ctx: StateContext<{}>) {
144 | ctx.dispatch(new Action3());
145 | ctx.dispatch(new AsyncAction3());
146 | }
147 |
148 | @Action([Action3])
149 | public action3() {}
150 |
151 | @Action([AsyncAction3])
152 | public asyncAction3() {
153 | return of({}).pipe(delay(0));
154 | }
155 | }
156 |
157 | describe('NgxsOnInit', () => {
158 | describe('Sync and Async Action', () => {
159 | it('should be null when dispatching async or sync from ngxsOnInit', fakeAsync(() => {
160 | TestBed.configureTestingModule({
161 | imports: [NgxsModule.forRoot([NgxsOnInitState]), NgxsActionsExecutingModule.forRoot()]
162 | });
163 |
164 | store = TestBed.get(Store);
165 |
166 | tick(1);
167 |
168 | expect(store.selectSnapshot(actionsExecuting([Action3]))).toBe(null);
169 | expect(store.selectSnapshot(actionsExecuting([AsyncAction3]))).toEqual(null);
170 | }));
171 | });
172 | });
173 |
174 | describe('', () => {
175 | beforeEach(() => {
176 | TestBed.configureTestingModule({
177 | imports: [
178 | NgxsModule.forRoot([TestState, NestedActions1State, NestedActions2State]),
179 | NgxsActionsExecutingModule.forRoot()
180 | ]
181 | });
182 |
183 | store = TestBed.get(Store);
184 | });
185 |
186 | describe('Single Action', () => {
187 | describe('Sync Action', () => {
188 | it('should be null', () => {
189 | store.dispatch(Action1);
190 |
191 | const snapshot = store.selectSnapshot(actionsExecuting([Action1]));
192 | expect(snapshot).toBe(null);
193 | });
194 |
195 | it('should be executing between dispatch and complete', () => {
196 | const actionStatus: ActionsExecuting[] = [];
197 |
198 | store.select(actionsExecuting([Action1])).subscribe((_actionsExecuting) => {
199 | actionStatus.push(_actionsExecuting);
200 | });
201 |
202 | store.dispatch(new Action1());
203 | expect(actionStatus).toEqual([null, { [Action1.type]: 1 }, null]);
204 | });
205 |
206 | it('should be executing between dispatch and error', () => {
207 | const actionStatus: ActionsExecuting[] = [];
208 |
209 | store.select(actionsExecuting([ErrorAction1])).subscribe((_actionsExecuting) => {
210 | actionStatus.push(_actionsExecuting);
211 | });
212 |
213 | store.dispatch(new ErrorAction1());
214 | expect(actionStatus).toEqual([null, { [ErrorAction1.type]: 1 }, null]);
215 | });
216 | });
217 |
218 | describe('Async Action', () => {
219 | it('should be null', fakeAsync(() => {
220 | store.dispatch(AsyncAction1);
221 |
222 | let snapshot = store.selectSnapshot(actionsExecuting([AsyncAction1]));
223 | expect(snapshot).toEqual({ 'ASYNC ACTION 1': 1 });
224 |
225 | tick();
226 |
227 | snapshot = store.selectSnapshot(actionsExecuting([AsyncAction1]));
228 | expect(snapshot).toBe(null);
229 | }));
230 |
231 | it('should be executing between dispatch and complete ', fakeAsync(() => {
232 | const actionStatus: ActionsExecuting[] = [];
233 |
234 | store.select(actionsExecuting([AsyncAction1])).subscribe((_actionsExecuting) => {
235 | actionStatus.push(_actionsExecuting);
236 | });
237 |
238 | store.dispatch(new AsyncAction1());
239 | tick(1);
240 | expect(actionStatus).toEqual([null, { [AsyncAction1.type]: 1 }, null]);
241 | }));
242 |
243 | it('should be executing between dispatch and error', fakeAsync(() => {
244 | const actionStatus: ActionsExecuting[] = [];
245 |
246 | store.select(actionsExecuting([AsyncErrorAction1])).subscribe((_actionsExecuting) => {
247 | actionStatus.push(_actionsExecuting);
248 | });
249 |
250 | store.dispatch(new AsyncErrorAction1()).subscribe({
251 | error: (err) => {
252 | expect(err).toBeDefined();
253 | }
254 | });
255 |
256 | tick(1);
257 | expect(actionStatus).toEqual([null, { [AsyncErrorAction1.type]: 1 }, null]);
258 | }));
259 | });
260 | });
261 |
262 | describe('Multiple Actions', () => {
263 | describe('sync', () => {
264 | it('should be executing between dispatch and complete', () => {
265 | const actionStatus: ActionsExecuting[] = [];
266 |
267 | store.select(actionsExecuting([Action1, Action2])).subscribe((_actionsExecuting) => {
268 | actionStatus.push(_actionsExecuting);
269 | });
270 |
271 | store.dispatch(new Action1());
272 | store.dispatch(new Action2());
273 | expect(actionStatus).toEqual([null, { [Action1.type]: 1 }, null, { [Action2.type]: 1 }, null]);
274 | });
275 |
276 | it('should be executing between dispatch and error', () => {
277 | const actionStatus: ActionsExecuting[] = [];
278 |
279 | store.select(actionsExecuting([Action1, ErrorAction1])).subscribe((_actionsExecuting) => {
280 | actionStatus.push(_actionsExecuting);
281 | });
282 |
283 | store.dispatch(new Action1());
284 | store.dispatch(new ErrorAction1());
285 | expect(actionStatus).toEqual([null, { [Action1.type]: 1 }, null, { [ErrorAction1.type]: 1 }, null]);
286 | });
287 | });
288 | describe('async', () => {
289 | it('should be executing between dispatch and complete ', fakeAsync(() => {
290 | const actionStatus: ActionsExecuting[] = [];
291 |
292 | store.select(actionsExecuting([AsyncAction1, AsyncAction2])).subscribe((_actionsExecuting) => {
293 | actionStatus.push(_actionsExecuting);
294 | });
295 |
296 | store.dispatch(new AsyncAction1());
297 | tick(1);
298 | expect(actionStatus).toEqual([null, { [AsyncAction1.type]: 1 }, null]);
299 | store.dispatch(new AsyncAction2());
300 | tick(1);
301 | expect(actionStatus).toEqual([
302 | null,
303 | { [AsyncAction1.type]: 1 },
304 | null,
305 | { [AsyncAction2.type]: 1 },
306 | null
307 | ]);
308 | }));
309 |
310 | it('should be executing between dispatch and error', fakeAsync(() => {
311 | const actionStatus: ActionsExecuting[] = [];
312 |
313 | store.select(actionsExecuting([AsyncAction1, AsyncErrorAction1])).subscribe((_actionsExecuting) => {
314 | actionStatus.push(_actionsExecuting);
315 | });
316 |
317 | store.dispatch(new AsyncAction1());
318 | tick(1);
319 | expect(actionStatus).toEqual([null, { [AsyncAction1.type]: 1 }, null]);
320 | store.dispatch(new AsyncErrorAction1());
321 | tick(1);
322 | expect(actionStatus).toEqual([
323 | null,
324 | { [AsyncAction1.type]: 1 },
325 | null,
326 | { [AsyncErrorAction1.type]: 1 },
327 | null
328 | ]);
329 | }));
330 |
331 | it('should be executing when action is dispatched multiple times', fakeAsync(() => {
332 | const actionStatus: ActionsExecuting[] = [];
333 |
334 | store.select(actionsExecuting([AsyncAction1])).subscribe((_actionsExecuting) => {
335 | actionStatus.push(_actionsExecuting);
336 | });
337 |
338 | store.dispatch(new AsyncAction1());
339 | expect(actionStatus).toEqual([null, { [AsyncAction1.type]: 1 }]);
340 | store.dispatch(new AsyncAction1());
341 | tick(1);
342 | expect(actionStatus).toEqual([
343 | null,
344 | { [AsyncAction1.type]: 1 },
345 | { [AsyncAction1.type]: 2 },
346 | { [AsyncAction1.type]: 1 },
347 | null
348 | ]);
349 | }));
350 |
351 | it('should be executing when action is dispatched multiple times (case 2)', fakeAsync(() => {
352 | const actionStatus: ActionsExecuting[] = [];
353 |
354 | store.select(actionsExecuting([AsyncAction1])).subscribe((_actionsExecuting) => {
355 | actionStatus.push(_actionsExecuting);
356 | });
357 |
358 | store.dispatch(new AsyncAction1());
359 | store.dispatch(new AsyncAction1());
360 | expect(actionStatus).toEqual([null, { [AsyncAction1.type]: 1 }, { [AsyncAction1.type]: 2 }]);
361 | tick(1);
362 | expect(actionStatus).toEqual([
363 | null,
364 | { [AsyncAction1.type]: 1 },
365 | { [AsyncAction1.type]: 2 },
366 | { [AsyncAction1.type]: 1 },
367 | null
368 | ]);
369 | }));
370 |
371 | describe('nested actions 1', () => {
372 | it('should be executing on nested actions', fakeAsync(() => {
373 | const nestedAction1Status: ActionsExecuting[] = [];
374 | const nestedAction2Status: ActionsExecuting[] = [];
375 | const nestedAction3Status: ActionsExecuting[] = [];
376 |
377 | const combinedActionStatus: ActionsExecuting[] = [];
378 |
379 | store.select(actionsExecuting([NestedAsyncAction1])).subscribe((_actionsExecuting) => {
380 | nestedAction1Status.push(_actionsExecuting);
381 | });
382 |
383 | store.select(actionsExecuting([NestedAsyncAction2])).subscribe((_actionsExecuting) => {
384 | nestedAction2Status.push(_actionsExecuting);
385 | });
386 |
387 | store.select(actionsExecuting([NestedAsyncAction3])).subscribe((_actionsExecuting) => {
388 | nestedAction3Status.push(_actionsExecuting);
389 | });
390 |
391 | store
392 | .select(actionsExecuting([NestedAsyncAction1, NestedAsyncAction2, NestedAsyncAction3]))
393 | .subscribe((_actionsExecuting) => {
394 | combinedActionStatus.push(_actionsExecuting);
395 | });
396 |
397 | store.dispatch(new NestedAsyncAction1());
398 | tick(1);
399 | expect(nestedAction1Status).toEqual([
400 | null,
401 | { [NestedAsyncAction1.type]: 1 },
402 | { [NestedAsyncAction1.type]: 1 },
403 | { [NestedAsyncAction1.type]: 1 },
404 | { [NestedAsyncAction1.type]: 1 },
405 | { [NestedAsyncAction1.type]: 1 },
406 | null
407 | ]);
408 | expect(nestedAction2Status).toEqual([
409 | null,
410 | { [NestedAsyncAction2.type]: 1 },
411 | { [NestedAsyncAction2.type]: 1 },
412 | { [NestedAsyncAction2.type]: 1 },
413 | null
414 | ]);
415 | expect(nestedAction3Status).toEqual([null, { [NestedAsyncAction3.type]: 1 }, null]);
416 |
417 | expect(combinedActionStatus).toEqual([
418 | null,
419 | { [NestedAsyncAction1.type]: 1 },
420 | { [NestedAsyncAction1.type]: 1, [NestedAsyncAction2.type]: 1 },
421 | {
422 | [NestedAsyncAction1.type]: 1,
423 | [NestedAsyncAction2.type]: 1,
424 | [NestedAsyncAction3.type]: 1
425 | },
426 | { [NestedAsyncAction1.type]: 1, [NestedAsyncAction2.type]: 1 },
427 | { [NestedAsyncAction1.type]: 1 },
428 | null
429 | ]);
430 | }));
431 | });
432 |
433 | describe('nested actions 2', () => {
434 | it('should be executing on nested actions (scenario 1)', fakeAsync(() => {
435 | const nestedAction4Status: ActionsExecuting[] = [];
436 | const nestedAction5Status: ActionsExecuting[] = [];
437 | const nestedAction6Status: ActionsExecuting[] = [];
438 |
439 | const combinedAction45Status: ActionsExecuting[] = [];
440 | const combinedAction456Status: ActionsExecuting[] = [];
441 |
442 | store.select(actionsExecuting([NestedAsyncAction4])).subscribe((_actionsExecuting) => {
443 | nestedAction4Status.push(_actionsExecuting);
444 | });
445 |
446 | store.select(actionsExecuting([NestedAsyncAction5])).subscribe((_actionsExecuting) => {
447 | nestedAction5Status.push(_actionsExecuting);
448 | });
449 |
450 | store.select(actionsExecuting([NestedAsyncAction6])).subscribe((_actionsExecuting) => {
451 | nestedAction6Status.push(_actionsExecuting);
452 | });
453 |
454 | store
455 | .select(actionsExecuting([NestedAsyncAction4, NestedAsyncAction5]))
456 | .subscribe((_actionsExecuting) => {
457 | combinedAction45Status.push(_actionsExecuting);
458 | });
459 |
460 | store
461 | .select(actionsExecuting([NestedAsyncAction4, NestedAsyncAction5, NestedAsyncAction6]))
462 | .subscribe((_actionsExecuting) => {
463 | combinedAction456Status.push(_actionsExecuting);
464 | });
465 |
466 | store.dispatch(new NestedAsyncAction4());
467 | tick(1);
468 | expect(nestedAction4Status).toEqual([
469 | null,
470 | { [NestedAsyncAction4.type]: 1 },
471 | { [NestedAsyncAction4.type]: 1 },
472 | { [NestedAsyncAction4.type]: 1 },
473 | null
474 | ]);
475 | expect(nestedAction5Status).toEqual([null]);
476 | expect(nestedAction6Status).toEqual([null, { [NestedAsyncAction6.type]: 1 }, null]);
477 |
478 | expect(combinedAction45Status).toEqual([
479 | null,
480 | { [NestedAsyncAction4.type]: 1 },
481 | { [NestedAsyncAction4.type]: 1 },
482 | { [NestedAsyncAction4.type]: 1 },
483 | null
484 | ]);
485 | expect(combinedAction456Status).toEqual([
486 | null,
487 | { [NestedAsyncAction4.type]: 1 },
488 | { [NestedAsyncAction4.type]: 1, [NestedAsyncAction6.type]: 1 },
489 | { [NestedAsyncAction4.type]: 1 },
490 | null
491 | ]);
492 | }));
493 |
494 | it('should be executing on nested actions (scenario 2)', fakeAsync(() => {
495 | const nestedAction4Status: ActionsExecuting[] = [];
496 | const nestedAction5Status: ActionsExecuting[] = [];
497 | const nestedAction6Status: ActionsExecuting[] = [];
498 |
499 | const combinedAction45Status: ActionsExecuting[] = [];
500 | const combinedAction456Status: ActionsExecuting[] = [];
501 |
502 | store.select(actionsExecuting([NestedAsyncAction4])).subscribe((_actionsExecuting) => {
503 | nestedAction4Status.push(_actionsExecuting);
504 | });
505 |
506 | store.select(actionsExecuting([NestedAsyncAction5])).subscribe((_actionsExecuting) => {
507 | nestedAction5Status.push(_actionsExecuting);
508 | });
509 |
510 | store.select(actionsExecuting([NestedAsyncAction6])).subscribe((_actionsExecuting) => {
511 | nestedAction6Status.push(_actionsExecuting);
512 | });
513 |
514 | store
515 | .select(actionsExecuting([NestedAsyncAction4, NestedAsyncAction5]))
516 | .subscribe((_actionsExecuting) => {
517 | combinedAction45Status.push(_actionsExecuting);
518 | });
519 |
520 | store
521 | .select(actionsExecuting([NestedAsyncAction4, NestedAsyncAction5, NestedAsyncAction6]))
522 | .subscribe((_actionsExecuting) => {
523 | combinedAction456Status.push(_actionsExecuting);
524 | });
525 |
526 | store.dispatch([new NestedAsyncAction4(), new NestedAsyncAction5()]);
527 | tick(1);
528 | expect(nestedAction4Status).toEqual([
529 | null,
530 | { [NestedAsyncAction4.type]: 1 },
531 | { [NestedAsyncAction4.type]: 1 },
532 | { [NestedAsyncAction4.type]: 1 },
533 | { [NestedAsyncAction4.type]: 1 },
534 | { [NestedAsyncAction4.type]: 1 },
535 | { [NestedAsyncAction4.type]: 1 },
536 | null
537 | ]);
538 | expect(nestedAction5Status).toEqual([
539 | null,
540 | { [NestedAsyncAction5.type]: 1 },
541 | { [NestedAsyncAction5.type]: 1 },
542 | { [NestedAsyncAction5.type]: 1 },
543 | { [NestedAsyncAction5.type]: 1 },
544 | { [NestedAsyncAction5.type]: 1 }
545 | ]);
546 | expect(nestedAction6Status).toEqual([
547 | null,
548 | { [NestedAsyncAction6.type]: 1 },
549 | { [NestedAsyncAction6.type]: 1 },
550 | { [NestedAsyncAction6.type]: 2 },
551 | { [NestedAsyncAction6.type]: 1 },
552 | null
553 | ]);
554 | tick(100);
555 | expect(nestedAction4Status).toEqual([
556 | null,
557 | { [NestedAsyncAction4.type]: 1 },
558 | { [NestedAsyncAction4.type]: 1 },
559 | { [NestedAsyncAction4.type]: 1 },
560 | { [NestedAsyncAction4.type]: 1 },
561 | { [NestedAsyncAction4.type]: 1 },
562 | { [NestedAsyncAction4.type]: 1 },
563 | null
564 | ]);
565 | expect(nestedAction5Status).toEqual([
566 | null,
567 | { [NestedAsyncAction5.type]: 1 },
568 | { [NestedAsyncAction5.type]: 1 },
569 | { [NestedAsyncAction5.type]: 1 },
570 | { [NestedAsyncAction5.type]: 1 },
571 | { [NestedAsyncAction5.type]: 1 },
572 | null
573 | ]);
574 | expect(nestedAction6Status).toEqual([
575 | null,
576 | { [NestedAsyncAction6.type]: 1 },
577 | { [NestedAsyncAction6.type]: 1 },
578 | { [NestedAsyncAction6.type]: 2 },
579 | { [NestedAsyncAction6.type]: 1 },
580 | null
581 | ]);
582 |
583 | expect(combinedAction45Status).toEqual([
584 | null,
585 | { [NestedAsyncAction4.type]: 1 },
586 | { [NestedAsyncAction4.type]: 1 },
587 | { [NestedAsyncAction4.type]: 1, [NestedAsyncAction5.type]: 1 },
588 | { [NestedAsyncAction4.type]: 1, [NestedAsyncAction5.type]: 1 },
589 | { [NestedAsyncAction4.type]: 1, [NestedAsyncAction5.type]: 1 },
590 | { [NestedAsyncAction4.type]: 1, [NestedAsyncAction5.type]: 1 },
591 | { [NestedAsyncAction5.type]: 1 },
592 | null
593 | ]);
594 | expect(combinedAction456Status).toEqual([
595 | null,
596 | { [NestedAsyncAction4.type]: 1 },
597 | { [NestedAsyncAction4.type]: 1, [NestedAsyncAction6.type]: 1 },
598 | {
599 | [NestedAsyncAction4.type]: 1,
600 | [NestedAsyncAction5.type]: 1,
601 | [NestedAsyncAction6.type]: 1
602 | },
603 | {
604 | [NestedAsyncAction4.type]: 1,
605 | [NestedAsyncAction5.type]: 1,
606 | [NestedAsyncAction6.type]: 2
607 | },
608 | {
609 | [NestedAsyncAction4.type]: 1,
610 | [NestedAsyncAction5.type]: 1,
611 | [NestedAsyncAction6.type]: 1
612 | },
613 | { [NestedAsyncAction4.type]: 1, [NestedAsyncAction5.type]: 1 },
614 | { [NestedAsyncAction5.type]: 1 },
615 | null
616 | ]);
617 | }));
618 |
619 | it('should be executing on nested actions (scenario 3)', fakeAsync(() => {
620 | const nestedAction4Status: ActionsExecuting[] = [];
621 | const nestedAction5Status: ActionsExecuting[] = [];
622 | const nestedAction6Status: ActionsExecuting[] = [];
623 |
624 | const combinedAction45Status: ActionsExecuting[] = [];
625 | const combinedAction456Status: ActionsExecuting[] = [];
626 |
627 | store.select(actionsExecuting([NestedAsyncAction4])).subscribe((_actionsExecuting) => {
628 | nestedAction4Status.push(_actionsExecuting);
629 | });
630 |
631 | store.select(actionsExecuting([NestedAsyncAction5])).subscribe((_actionsExecuting) => {
632 | nestedAction5Status.push(_actionsExecuting);
633 | });
634 |
635 | store.select(actionsExecuting([NestedAsyncAction6])).subscribe((_actionsExecuting) => {
636 | nestedAction6Status.push(_actionsExecuting);
637 | });
638 |
639 | store
640 | .select(actionsExecuting([NestedAsyncAction4, NestedAsyncAction5]))
641 | .subscribe((_actionsExecuting) => {
642 | combinedAction45Status.push(_actionsExecuting);
643 | });
644 |
645 | store
646 | .select(actionsExecuting([NestedAsyncAction4, NestedAsyncAction5, NestedAsyncAction6]))
647 | .subscribe((_actionsExecuting) => {
648 | combinedAction456Status.push(_actionsExecuting);
649 | });
650 |
651 | store.dispatch(new NestedAsyncAction5());
652 | tick(1);
653 | expect(nestedAction4Status).toEqual([null]);
654 | expect(nestedAction5Status).toEqual([
655 | null,
656 | { [NestedAsyncAction5.type]: 1 },
657 | { [NestedAsyncAction5.type]: 1 },
658 | { [NestedAsyncAction5.type]: 1 }
659 | ]);
660 | expect(nestedAction6Status).toEqual([null, { [NestedAsyncAction6.type]: 1 }, null]);
661 | tick(100);
662 | expect(nestedAction4Status).toEqual([null]);
663 | expect(nestedAction5Status).toEqual([
664 | null,
665 | { [NestedAsyncAction5.type]: 1 },
666 | { [NestedAsyncAction5.type]: 1 },
667 | { [NestedAsyncAction5.type]: 1 },
668 | null
669 | ]);
670 | expect(nestedAction6Status).toEqual([null, { [NestedAsyncAction6.type]: 1 }, null]);
671 |
672 | expect(combinedAction45Status).toEqual([
673 | null,
674 | { [NestedAsyncAction5.type]: 1 },
675 | { [NestedAsyncAction5.type]: 1 },
676 | { [NestedAsyncAction5.type]: 1 },
677 | null
678 | ]);
679 | expect(combinedAction456Status).toEqual([
680 | null,
681 | { [NestedAsyncAction5.type]: 1 },
682 | { [NestedAsyncAction5.type]: 1, [NestedAsyncAction6.type]: 1 },
683 | { [NestedAsyncAction5.type]: 1 },
684 | null
685 | ]);
686 | }));
687 |
688 | it('should be executing on nested actions (scenario 4)', fakeAsync(() => {
689 | const nestedAction4Status: ActionsExecuting[] = [];
690 | const nestedAction5Status: ActionsExecuting[] = [];
691 | const nestedAction6Status: ActionsExecuting[] = [];
692 |
693 | const combinedAction45Status: ActionsExecuting[] = [];
694 | const combinedAction456Status: ActionsExecuting[] = [];
695 |
696 | store.select(actionsExecuting([NestedAsyncAction4])).subscribe((_actionsExecuting) => {
697 | nestedAction4Status.push(_actionsExecuting);
698 | });
699 |
700 | store.select(actionsExecuting([NestedAsyncAction5])).subscribe((_actionsExecuting) => {
701 | nestedAction5Status.push(_actionsExecuting);
702 | });
703 |
704 | store.select(actionsExecuting([NestedAsyncAction6])).subscribe((_actionsExecuting) => {
705 | nestedAction6Status.push(_actionsExecuting);
706 | });
707 |
708 | store
709 | .select(actionsExecuting([NestedAsyncAction4, NestedAsyncAction5]))
710 | .subscribe((_actionsExecuting) => {
711 | combinedAction45Status.push(_actionsExecuting);
712 | });
713 |
714 | store
715 | .select(actionsExecuting([NestedAsyncAction4, NestedAsyncAction5, NestedAsyncAction6]))
716 | .subscribe((_actionsExecuting) => {
717 | combinedAction456Status.push(_actionsExecuting);
718 | });
719 |
720 | store.dispatch([new NestedAsyncAction4(), new NestedAsyncAction5(), new NestedAsyncAction6()]);
721 | tick(1);
722 | expect(nestedAction4Status).toEqual([
723 | null,
724 | { [NestedAsyncAction4.type]: 1 },
725 | { [NestedAsyncAction4.type]: 1 },
726 | { [NestedAsyncAction4.type]: 1 },
727 | { [NestedAsyncAction4.type]: 1 },
728 | { [NestedAsyncAction4.type]: 1 },
729 | { [NestedAsyncAction4.type]: 1 },
730 | { [NestedAsyncAction4.type]: 1 },
731 | { [NestedAsyncAction4.type]: 1 },
732 | null
733 | ]);
734 | expect(nestedAction5Status).toEqual([
735 | null,
736 | { [NestedAsyncAction5.type]: 1 },
737 | { [NestedAsyncAction5.type]: 1 },
738 | { [NestedAsyncAction5.type]: 1 },
739 | { [NestedAsyncAction5.type]: 1 },
740 | { [NestedAsyncAction5.type]: 1 },
741 | { [NestedAsyncAction5.type]: 1 },
742 | { [NestedAsyncAction5.type]: 1 }
743 | ]);
744 | expect(nestedAction6Status).toEqual([
745 | null,
746 | { [NestedAsyncAction6.type]: 1 },
747 | { [NestedAsyncAction6.type]: 1 },
748 | { [NestedAsyncAction6.type]: 2 },
749 | { [NestedAsyncAction6.type]: 3 },
750 | { [NestedAsyncAction6.type]: 2 },
751 | { [NestedAsyncAction6.type]: 1 },
752 | null
753 | ]);
754 | tick(100);
755 | expect(nestedAction4Status).toEqual([
756 | null,
757 | { [NestedAsyncAction4.type]: 1 },
758 | { [NestedAsyncAction4.type]: 1 },
759 | { [NestedAsyncAction4.type]: 1 },
760 | { [NestedAsyncAction4.type]: 1 },
761 | { [NestedAsyncAction4.type]: 1 },
762 | { [NestedAsyncAction4.type]: 1 },
763 | { [NestedAsyncAction4.type]: 1 },
764 | { [NestedAsyncAction4.type]: 1 },
765 | null
766 | ]);
767 | expect(nestedAction5Status).toEqual([
768 | null,
769 | { [NestedAsyncAction5.type]: 1 },
770 | { [NestedAsyncAction5.type]: 1 },
771 | { [NestedAsyncAction5.type]: 1 },
772 | { [NestedAsyncAction5.type]: 1 },
773 | { [NestedAsyncAction5.type]: 1 },
774 | { [NestedAsyncAction5.type]: 1 },
775 | { [NestedAsyncAction5.type]: 1 },
776 | null
777 | ]);
778 | expect(nestedAction6Status).toEqual([
779 | null,
780 | { [NestedAsyncAction6.type]: 1 },
781 | { [NestedAsyncAction6.type]: 1 },
782 | { [NestedAsyncAction6.type]: 2 },
783 | { [NestedAsyncAction6.type]: 3 },
784 | { [NestedAsyncAction6.type]: 2 },
785 | { [NestedAsyncAction6.type]: 1 },
786 | null
787 | ]);
788 |
789 | expect(combinedAction45Status).toEqual([
790 | null,
791 | { [NestedAsyncAction4.type]: 1 },
792 | { [NestedAsyncAction4.type]: 1 },
793 | { [NestedAsyncAction4.type]: 1, [NestedAsyncAction5.type]: 1 },
794 | { [NestedAsyncAction4.type]: 1, [NestedAsyncAction5.type]: 1 },
795 | { [NestedAsyncAction4.type]: 1, [NestedAsyncAction5.type]: 1 },
796 | { [NestedAsyncAction4.type]: 1, [NestedAsyncAction5.type]: 1 },
797 | { [NestedAsyncAction4.type]: 1, [NestedAsyncAction5.type]: 1 },
798 | { [NestedAsyncAction4.type]: 1, [NestedAsyncAction5.type]: 1 },
799 | { [NestedAsyncAction5.type]: 1 },
800 | null
801 | ]);
802 | expect(combinedAction456Status).toEqual([
803 | null,
804 | { [NestedAsyncAction4.type]: 1 },
805 | { [NestedAsyncAction4.type]: 1, [NestedAsyncAction6.type]: 1 },
806 | {
807 | [NestedAsyncAction4.type]: 1,
808 | [NestedAsyncAction5.type]: 1,
809 | [NestedAsyncAction6.type]: 1
810 | },
811 | {
812 | [NestedAsyncAction4.type]: 1,
813 | [NestedAsyncAction5.type]: 1,
814 | [NestedAsyncAction6.type]: 2
815 | },
816 | {
817 | [NestedAsyncAction4.type]: 1,
818 | [NestedAsyncAction5.type]: 1,
819 | [NestedAsyncAction6.type]: 3
820 | },
821 | {
822 | [NestedAsyncAction4.type]: 1,
823 | [NestedAsyncAction5.type]: 1,
824 | [NestedAsyncAction6.type]: 2
825 | },
826 | {
827 | [NestedAsyncAction4.type]: 1,
828 | [NestedAsyncAction5.type]: 1,
829 | [NestedAsyncAction6.type]: 1
830 | },
831 | { [NestedAsyncAction4.type]: 1, [NestedAsyncAction5.type]: 1 },
832 | { [NestedAsyncAction5.type]: 1 },
833 | null
834 | ]);
835 | }));
836 | });
837 | });
838 | });
839 |
840 | describe('No Actions', () => {
841 | describe('sync', () => {
842 | it('should be executing between dispatch and complete', () => {
843 | const actionStatus: ActionsExecuting[] = [];
844 |
845 | store.select(actionsExecuting()).subscribe((_actionsExecuting) => {
846 | actionStatus.push(_actionsExecuting);
847 | });
848 |
849 | store.dispatch(new Action1());
850 | store.dispatch(new Action2());
851 | expect(actionStatus).toEqual([
852 | null,
853 | { [Action1.type]: 1 },
854 | { [Action1.type]: 0 },
855 | { [Action1.type]: 0, [Action2.type]: 1 },
856 | { [Action1.type]: 0, [Action2.type]: 0 }
857 | ]);
858 | });
859 | });
860 |
861 | describe('async', () => {
862 | it('should be executing between dispatch and complete ', fakeAsync(() => {
863 | const actionStatus: ActionsExecuting[] = [];
864 |
865 | store.select(actionsExecuting()).subscribe((_actionsExecuting) => {
866 | actionStatus.push(_actionsExecuting);
867 | });
868 |
869 | store.dispatch(new AsyncAction1());
870 | tick(1);
871 | expect(actionStatus).toEqual([null, { [AsyncAction1.type]: 1 }, { [AsyncAction1.type]: 0 }]);
872 | store.dispatch(new AsyncAction2());
873 | tick(1);
874 | expect(actionStatus).toEqual([
875 | null,
876 | { [AsyncAction1.type]: 1 },
877 | { [AsyncAction1.type]: 0 },
878 | { [AsyncAction1.type]: 0, [AsyncAction2.type]: 1 },
879 | { [AsyncAction1.type]: 0, [AsyncAction2.type]: 0 }
880 | ]);
881 | }));
882 | });
883 | });
884 | });
885 | });
886 |
--------------------------------------------------------------------------------
/src/tests/has-actions-executing.spec.ts:
--------------------------------------------------------------------------------
1 | import { NgxsActionsExecutingModule, hasActionsExecuting } from '..';
2 | import { TestBed, fakeAsync, tick } from '@angular/core/testing';
3 | import { NgxsModule, Store, State, Action, StateContext, NgxsOnInit } from '@ngxs/store';
4 | import { of, throwError } from 'rxjs';
5 | import { delay } from 'rxjs/operators';
6 | import { Injectable } from '@angular/core';
7 |
8 | describe('hasActionsExecuting', () => {
9 | let store: Store;
10 |
11 | class Action1 {
12 | public static type = 'ACTION 1';
13 | }
14 |
15 | class Action2 {
16 | public static type = 'ACTION 2';
17 | }
18 |
19 | class Action3 {
20 | public static type = 'ACTION 3';
21 | }
22 |
23 | class ErrorAction1 {
24 | public static type = 'ERROR ACTION 1';
25 | }
26 |
27 | class AsyncAction1 {
28 | public static type = 'ASYNC ACTION 1';
29 | }
30 |
31 | class AsyncAction2 {
32 | public static type = 'ASYNC ACTION 2';
33 | }
34 |
35 | class AsyncAction3 {
36 | public static type = 'ASYNC ACTION 3';
37 | }
38 |
39 | class AsyncErrorAction1 {
40 | public static type = 'ASYNC ERROR ACTION 1';
41 | }
42 |
43 | class NestedAsyncAction1 {
44 | public static type = 'NESTED ASYNC ACTION 1';
45 | }
46 |
47 | class NestedAsyncAction2 {
48 | public static type = 'NESTED ASYNC ACTION 2';
49 | }
50 |
51 | class NestedAsyncAction3 {
52 | public static type = 'NESTED ASYNC ACTION 3';
53 | }
54 |
55 | class NestedAsyncAction4 {
56 | public static type = 'NESTED ASYNC ACTION 4';
57 | }
58 |
59 | class NestedAsyncAction5 {
60 | public static type = 'NESTED ASYNC ACTION 5';
61 | }
62 |
63 | class NestedAsyncAction6 {
64 | public static type = 'NESTED ASYNC ACTION 6';
65 | }
66 |
67 | @State({
68 | name: 'test'
69 | })
70 | @Injectable()
71 | class TestState {
72 | @Action([Action1])
73 | public action1() {}
74 |
75 | @Action([AsyncAction1])
76 | public asyncAction1() {
77 | return of({}).pipe(delay(0));
78 | }
79 |
80 | @Action([AsyncAction2])
81 | public asyncAction2() {
82 | return of({}).pipe(delay(0));
83 | }
84 |
85 | @Action(AsyncErrorAction1)
86 | public asyncError() {
87 | return throwError(new Error('this is a test error')).pipe(delay(0));
88 | }
89 |
90 | @Action(ErrorAction1)
91 | public onError() {
92 | return throwError(new Error('this is a test error'));
93 | }
94 | }
95 |
96 | @State<{}>({
97 | name: 'nested_actions_1'
98 | })
99 | @Injectable()
100 | class NestedActions1State {
101 | @Action(NestedAsyncAction1)
102 | public nestedAsyncAction1({ dispatch }: StateContext<{}>) {
103 | return dispatch(new NestedAsyncAction2()).pipe(delay(0));
104 | }
105 |
106 | @Action(NestedAsyncAction2)
107 | public nestedAsyncAction2({ dispatch }: StateContext<{}>) {
108 | return dispatch(new NestedAsyncAction3()).pipe(delay(0));
109 | }
110 |
111 | @Action(NestedAsyncAction3)
112 | public nestedAsyncAction3() {
113 | return of({}).pipe(delay(0));
114 | }
115 | }
116 |
117 | @State({
118 | name: 'nested_actions_2'
119 | })
120 | @Injectable()
121 | class NestedActions2State {
122 | @Action([NestedAsyncAction4, NestedAsyncAction5])
123 | public combinedAction({ dispatch }: StateContext<{}>) {
124 | return dispatch(new NestedAsyncAction6()).pipe(delay(0));
125 | }
126 |
127 | @Action(NestedAsyncAction5)
128 | public nestedAsyncAction5() {
129 | return of({}).pipe(delay(100));
130 | }
131 |
132 | @Action(NestedAsyncAction6)
133 | public nestedAsyncAction6() {
134 | return of({}).pipe(delay(0));
135 | }
136 | }
137 |
138 | @State({
139 | name: 'ngxs_on_init_state'
140 | })
141 | @Injectable()
142 | class NgxsOnInitState implements NgxsOnInit {
143 | ngxsOnInit(ctx: StateContext<{}>) {
144 | ctx.dispatch(new Action3());
145 | ctx.dispatch(new AsyncAction3());
146 | }
147 |
148 | @Action([Action3])
149 | public action3() {}
150 |
151 | @Action([AsyncAction3])
152 | public asyncAction3() {
153 | return of({}).pipe(delay(0));
154 | }
155 | }
156 |
157 | describe('NgxsOnInit', () => {
158 | describe('Sync and Async Action', () => {
159 | it('should be false when dispatching async or sync from ngxsOnInit', fakeAsync(() => {
160 | TestBed.configureTestingModule({
161 | imports: [NgxsModule.forRoot([NgxsOnInitState]), NgxsActionsExecutingModule.forRoot()]
162 | });
163 |
164 | store = TestBed.get(Store);
165 |
166 | tick(1);
167 |
168 | expect(store.selectSnapshot(hasActionsExecuting([Action3]))).toBe(false);
169 | expect(store.selectSnapshot(hasActionsExecuting([AsyncAction3]))).toEqual(false);
170 | }));
171 | });
172 | });
173 |
174 | describe('', () => {
175 | beforeEach(() => {
176 | TestBed.configureTestingModule({
177 | imports: [
178 | NgxsModule.forRoot([TestState, NestedActions1State, NestedActions2State]),
179 | NgxsActionsExecutingModule.forRoot()
180 | ]
181 | });
182 |
183 | store = TestBed.get(Store);
184 | });
185 |
186 | describe('Single Action', () => {
187 | describe('Sync Action', () => {
188 | it('should be false', () => {
189 | store.dispatch(Action1);
190 |
191 | const snapshot = store.selectSnapshot(hasActionsExecuting([Action1]));
192 | expect(snapshot).toBe(false);
193 | });
194 |
195 | it('should be executing between dispatch and complete', () => {
196 | const actionStatus: boolean[] = [];
197 |
198 | store.select(hasActionsExecuting([Action1])).subscribe((_actionsExecuting) => {
199 | actionStatus.push(_actionsExecuting);
200 | });
201 |
202 | store.dispatch(new Action1());
203 | expect(actionStatus).toEqual([false, true, false]);
204 | });
205 |
206 | it('should be executing between dispatch and error', () => {
207 | const actionStatus: boolean[] = [];
208 |
209 | store.select(hasActionsExecuting([ErrorAction1])).subscribe((_actionsExecuting) => {
210 | actionStatus.push(_actionsExecuting);
211 | });
212 |
213 | store.dispatch(new ErrorAction1());
214 | expect(actionStatus).toEqual([false, true, false]);
215 | });
216 | });
217 |
218 | describe('Async Action', () => {
219 | it('should be false', fakeAsync(() => {
220 | store.dispatch(AsyncAction1);
221 |
222 | let snapshot = store.selectSnapshot(hasActionsExecuting([AsyncAction1]));
223 | expect(snapshot).toEqual(true);
224 |
225 | tick();
226 |
227 | snapshot = store.selectSnapshot(hasActionsExecuting([AsyncAction1]));
228 | expect(snapshot).toBe(false);
229 | }));
230 |
231 | it('should be executing between dispatch and complete ', fakeAsync(() => {
232 | const actionStatus: boolean[] = [];
233 |
234 | store.select(hasActionsExecuting([AsyncAction1])).subscribe((_actionsExecuting) => {
235 | actionStatus.push(_actionsExecuting);
236 | });
237 |
238 | store.dispatch(new AsyncAction1());
239 | tick(1);
240 | expect(actionStatus).toEqual([false, true, false]);
241 | }));
242 |
243 | it('should be executing between dispatch and error', fakeAsync(() => {
244 | const actionStatus: boolean[] = [];
245 |
246 | store.select(hasActionsExecuting([AsyncErrorAction1])).subscribe((_actionsExecuting) => {
247 | actionStatus.push(_actionsExecuting);
248 | });
249 |
250 | store.dispatch(new AsyncErrorAction1()).subscribe({
251 | error: (err) => {
252 | expect(err).toBeDefined();
253 | }
254 | });
255 |
256 | tick(1);
257 | expect(actionStatus).toEqual([false, true, false]);
258 | }));
259 | });
260 | });
261 |
262 | describe('Multiple Actions', () => {
263 | describe('sync', () => {
264 | it('should be executing between dispatch and complete', () => {
265 | const actionStatus: boolean[] = [];
266 |
267 | store.select(hasActionsExecuting([Action1, Action2])).subscribe((_actionsExecuting) => {
268 | actionStatus.push(_actionsExecuting);
269 | });
270 |
271 | store.dispatch(new Action1());
272 | store.dispatch(new Action2());
273 | expect(actionStatus).toEqual([false, true, false, true, false]);
274 | });
275 |
276 | it('should be executing between dispatch and error', () => {
277 | const actionStatus: boolean[] = [];
278 |
279 | store.select(hasActionsExecuting([Action1, ErrorAction1])).subscribe((_actionsExecuting) => {
280 | actionStatus.push(_actionsExecuting);
281 | });
282 |
283 | store.dispatch(new Action1());
284 | store.dispatch(new ErrorAction1());
285 | expect(actionStatus).toEqual([false, true, false, true, false]);
286 | });
287 | });
288 | describe('async', () => {
289 | it('should be executing between dispatch and complete ', fakeAsync(() => {
290 | const actionStatus: boolean[] = [];
291 |
292 | store.select(hasActionsExecuting([AsyncAction1, AsyncAction2])).subscribe((_actionsExecuting) => {
293 | actionStatus.push(_actionsExecuting);
294 | });
295 |
296 | store.dispatch(new AsyncAction1());
297 | tick(1);
298 | expect(actionStatus).toEqual([false, true, false]);
299 | store.dispatch(new AsyncAction2());
300 | tick(1);
301 | expect(actionStatus).toEqual([false, true, false, true, false]);
302 | }));
303 |
304 | it('should be executing between dispatch and error', fakeAsync(() => {
305 | const actionStatus: boolean[] = [];
306 |
307 | store
308 | .select(hasActionsExecuting([AsyncAction1, AsyncErrorAction1]))
309 | .subscribe((_actionsExecuting) => {
310 | actionStatus.push(_actionsExecuting);
311 | });
312 |
313 | store.dispatch(new AsyncAction1());
314 | tick(1);
315 | expect(actionStatus).toEqual([false, true, false]);
316 | store.dispatch(new AsyncErrorAction1());
317 | tick(1);
318 | expect(actionStatus).toEqual([false, true, false, true, false]);
319 | }));
320 |
321 | it('should be executing when action is dispatched multiple times', fakeAsync(() => {
322 | const actionStatus: boolean[] = [];
323 |
324 | store.select(hasActionsExecuting([AsyncAction1])).subscribe((_actionsExecuting) => {
325 | actionStatus.push(_actionsExecuting);
326 | });
327 |
328 | store.dispatch(new AsyncAction1());
329 | expect(actionStatus).toEqual([false, true]);
330 | store.dispatch(new AsyncAction1());
331 | tick(1);
332 | expect(actionStatus).toEqual([false, true, false]);
333 | }));
334 |
335 | it('should be executing when action is dispatched multiple times (case 2)', fakeAsync(() => {
336 | const actionStatus: boolean[] = [];
337 |
338 | store.select(hasActionsExecuting([AsyncAction1])).subscribe((_actionsExecuting) => {
339 | actionStatus.push(_actionsExecuting);
340 | });
341 |
342 | store.dispatch(new AsyncAction1());
343 | store.dispatch(new AsyncAction1());
344 | expect(actionStatus).toEqual([false, true]);
345 | tick(1);
346 | expect(actionStatus).toEqual([false, true, false]);
347 | }));
348 |
349 | describe('nested actions 1', () => {
350 | it('should be executing on nested actions', fakeAsync(() => {
351 | const nestedAction1Status: boolean[] = [];
352 | const nestedAction2Status: boolean[] = [];
353 | const nestedAction3Status: boolean[] = [];
354 |
355 | const combinedActionStatus: boolean[] = [];
356 |
357 | store.select(hasActionsExecuting([NestedAsyncAction1])).subscribe((_actionsExecuting) => {
358 | nestedAction1Status.push(_actionsExecuting);
359 | });
360 |
361 | store.select(hasActionsExecuting([NestedAsyncAction2])).subscribe((_actionsExecuting) => {
362 | nestedAction2Status.push(_actionsExecuting);
363 | });
364 |
365 | store.select(hasActionsExecuting([NestedAsyncAction3])).subscribe((_actionsExecuting) => {
366 | nestedAction3Status.push(_actionsExecuting);
367 | });
368 |
369 | store
370 | .select(hasActionsExecuting([NestedAsyncAction1, NestedAsyncAction2, NestedAsyncAction3]))
371 | .subscribe((_actionsExecuting) => {
372 | combinedActionStatus.push(_actionsExecuting);
373 | });
374 |
375 | store.dispatch(new NestedAsyncAction1());
376 | tick(1);
377 | expect(nestedAction1Status).toEqual([false, true, false]);
378 | expect(nestedAction2Status).toEqual([false, true, false]);
379 | expect(nestedAction3Status).toEqual([false, true, false]);
380 | expect(combinedActionStatus).toEqual([false, true, false]);
381 | }));
382 | });
383 |
384 | describe('nested actions 2', () => {
385 | it('should be executing on nested actions (scenario 1)', fakeAsync(() => {
386 | const nestedAction4Status: boolean[] = [];
387 | const nestedAction5Status: boolean[] = [];
388 | const nestedAction6Status: boolean[] = [];
389 |
390 | const combinedAction45Status: boolean[] = [];
391 | const combinedAction456Status: boolean[] = [];
392 |
393 | store.select(hasActionsExecuting([NestedAsyncAction4])).subscribe((_actionsExecuting) => {
394 | nestedAction4Status.push(_actionsExecuting);
395 | });
396 |
397 | store.select(hasActionsExecuting([NestedAsyncAction5])).subscribe((_actionsExecuting) => {
398 | nestedAction5Status.push(_actionsExecuting);
399 | });
400 |
401 | store.select(hasActionsExecuting([NestedAsyncAction6])).subscribe((_actionsExecuting) => {
402 | nestedAction6Status.push(_actionsExecuting);
403 | });
404 |
405 | store
406 | .select(hasActionsExecuting([NestedAsyncAction4, NestedAsyncAction5]))
407 | .subscribe((_actionsExecuting) => {
408 | combinedAction45Status.push(_actionsExecuting);
409 | });
410 |
411 | store
412 | .select(hasActionsExecuting([NestedAsyncAction4, NestedAsyncAction5, NestedAsyncAction6]))
413 | .subscribe((_actionsExecuting) => {
414 | combinedAction456Status.push(_actionsExecuting);
415 | });
416 |
417 | store.dispatch(new NestedAsyncAction4());
418 | tick(1);
419 | expect(nestedAction4Status).toEqual([false, true, false]);
420 | expect(nestedAction5Status).toEqual([false]);
421 | expect(nestedAction6Status).toEqual([false, true, false]);
422 |
423 | expect(combinedAction45Status).toEqual([false, true, false]);
424 | expect(combinedAction456Status).toEqual([false, true, false]);
425 | }));
426 |
427 | it('should be executing on nested actions (scenario 2)', fakeAsync(() => {
428 | const nestedAction4Status: boolean[] = [];
429 | const nestedAction5Status: boolean[] = [];
430 | const nestedAction6Status: boolean[] = [];
431 |
432 | const combinedAction45Status: boolean[] = [];
433 | const combinedAction456Status: boolean[] = [];
434 |
435 | store.select(hasActionsExecuting([NestedAsyncAction4])).subscribe((_actionsExecuting) => {
436 | nestedAction4Status.push(_actionsExecuting);
437 | });
438 |
439 | store.select(hasActionsExecuting([NestedAsyncAction5])).subscribe((_actionsExecuting) => {
440 | nestedAction5Status.push(_actionsExecuting);
441 | });
442 |
443 | store.select(hasActionsExecuting([NestedAsyncAction6])).subscribe((_actionsExecuting) => {
444 | nestedAction6Status.push(_actionsExecuting);
445 | });
446 |
447 | store
448 | .select(hasActionsExecuting([NestedAsyncAction4, NestedAsyncAction5]))
449 | .subscribe((_actionsExecuting) => {
450 | combinedAction45Status.push(_actionsExecuting);
451 | });
452 |
453 | store
454 | .select(hasActionsExecuting([NestedAsyncAction4, NestedAsyncAction5, NestedAsyncAction6]))
455 | .subscribe((_actionsExecuting) => {
456 | combinedAction456Status.push(_actionsExecuting);
457 | });
458 |
459 | store.dispatch([new NestedAsyncAction4(), new NestedAsyncAction5()]);
460 | tick(1);
461 | expect(nestedAction4Status).toEqual([false, true, false]);
462 | expect(nestedAction5Status).toEqual([false, true]);
463 | expect(nestedAction6Status).toEqual([false, true, false]);
464 | tick(100);
465 | expect(nestedAction4Status).toEqual([false, true, false]);
466 | expect(nestedAction5Status).toEqual([false, true, false]);
467 | expect(nestedAction6Status).toEqual([false, true, false]);
468 | expect(combinedAction45Status).toEqual([false, true, false]);
469 | expect(combinedAction456Status).toEqual([false, true, false]);
470 | }));
471 |
472 | it('should be executing on nested actions (scenario 3)', fakeAsync(() => {
473 | const nestedAction4Status: boolean[] = [];
474 | const nestedAction5Status: boolean[] = [];
475 | const nestedAction6Status: boolean[] = [];
476 |
477 | const combinedAction45Status: boolean[] = [];
478 | const combinedAction456Status: boolean[] = [];
479 |
480 | store.select(hasActionsExecuting([NestedAsyncAction4])).subscribe((_actionsExecuting) => {
481 | nestedAction4Status.push(_actionsExecuting);
482 | });
483 |
484 | store.select(hasActionsExecuting([NestedAsyncAction5])).subscribe((_actionsExecuting) => {
485 | nestedAction5Status.push(_actionsExecuting);
486 | });
487 |
488 | store.select(hasActionsExecuting([NestedAsyncAction6])).subscribe((_actionsExecuting) => {
489 | nestedAction6Status.push(_actionsExecuting);
490 | });
491 |
492 | store
493 | .select(hasActionsExecuting([NestedAsyncAction4, NestedAsyncAction5]))
494 | .subscribe((_actionsExecuting) => {
495 | combinedAction45Status.push(_actionsExecuting);
496 | });
497 |
498 | store
499 | .select(hasActionsExecuting([NestedAsyncAction4, NestedAsyncAction5, NestedAsyncAction6]))
500 | .subscribe((_actionsExecuting) => {
501 | combinedAction456Status.push(_actionsExecuting);
502 | });
503 |
504 | store.dispatch(new NestedAsyncAction5());
505 | tick(1);
506 | expect(nestedAction4Status).toEqual([false]);
507 | expect(nestedAction5Status).toEqual([false, true]);
508 | expect(nestedAction6Status).toEqual([false, true, false]);
509 | tick(100);
510 | expect(nestedAction4Status).toEqual([false]);
511 | expect(nestedAction5Status).toEqual([false, true, false]);
512 | expect(nestedAction6Status).toEqual([false, true, false]);
513 | expect(combinedAction45Status).toEqual([false, true, false]);
514 | expect(combinedAction456Status).toEqual([false, true, false]);
515 | }));
516 |
517 | it('should be executing on nested actions (scenario 4)', fakeAsync(() => {
518 | const nestedAction4Status: boolean[] = [];
519 | const nestedAction5Status: boolean[] = [];
520 | const nestedAction6Status: boolean[] = [];
521 |
522 | const combinedAction45Status: boolean[] = [];
523 | const combinedAction456Status: boolean[] = [];
524 |
525 | store.select(hasActionsExecuting([NestedAsyncAction4])).subscribe((_actionsExecuting) => {
526 | nestedAction4Status.push(_actionsExecuting);
527 | });
528 |
529 | store.select(hasActionsExecuting([NestedAsyncAction5])).subscribe((_actionsExecuting) => {
530 | nestedAction5Status.push(_actionsExecuting);
531 | });
532 |
533 | store.select(hasActionsExecuting([NestedAsyncAction6])).subscribe((_actionsExecuting) => {
534 | nestedAction6Status.push(_actionsExecuting);
535 | });
536 |
537 | store
538 | .select(hasActionsExecuting([NestedAsyncAction4, NestedAsyncAction5]))
539 | .subscribe((_actionsExecuting) => {
540 | combinedAction45Status.push(_actionsExecuting);
541 | });
542 |
543 | store
544 | .select(hasActionsExecuting([NestedAsyncAction4, NestedAsyncAction5, NestedAsyncAction6]))
545 | .subscribe((_actionsExecuting) => {
546 | combinedAction456Status.push(_actionsExecuting);
547 | });
548 |
549 | store.dispatch([new NestedAsyncAction4(), new NestedAsyncAction5(), new NestedAsyncAction6()]);
550 | tick(1);
551 | expect(nestedAction4Status).toEqual([false, true, false]);
552 | expect(nestedAction5Status).toEqual([false, true]);
553 | expect(nestedAction6Status).toEqual([false, true, false]);
554 | tick(100);
555 | expect(nestedAction4Status).toEqual([false, true, false]);
556 | expect(nestedAction5Status).toEqual([false, true, false]);
557 | expect(nestedAction6Status).toEqual([false, true, false]);
558 |
559 | expect(combinedAction45Status).toEqual([false, true, false]);
560 | expect(combinedAction456Status).toEqual([false, true, false]);
561 | }));
562 | });
563 | });
564 | });
565 |
566 | describe('No Actions', () => {
567 | describe('sync', () => {
568 | it('should be executing between dispatch and complete', () => {
569 | const actionStatus: boolean[] = [];
570 |
571 | store.select(hasActionsExecuting()).subscribe((_actionsExecuting) => {
572 | actionStatus.push(_actionsExecuting);
573 | });
574 |
575 | store.dispatch(new Action1());
576 | store.dispatch(new Action2());
577 | expect(actionStatus).toEqual([false, true, false, true, false]);
578 | });
579 | });
580 |
581 | describe('async', () => {
582 | it('should be executing between dispatch and complete ', fakeAsync(() => {
583 | const actionStatus: boolean[] = [];
584 |
585 | store.select(hasActionsExecuting()).subscribe((_actionsExecuting) => {
586 | actionStatus.push(_actionsExecuting);
587 | });
588 |
589 | store.dispatch(new AsyncAction1());
590 | tick(1);
591 | expect(actionStatus).toEqual([false, true, false]);
592 | store.dispatch(new AsyncAction2());
593 | tick(1);
594 | expect(actionStatus).toEqual([false, true, false, true, false]);
595 | }));
596 | });
597 | });
598 | });
599 | });
600 |
--------------------------------------------------------------------------------
/src/tsconfig.lib.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../out-tsc/lib",
5 | "declarationMap": true,
6 | "declaration": true,
7 | "inlineSources": true,
8 | "types": [],
9 | "lib": ["dom", "es2018"]
10 | },
11 | "angularCompilerOptions": {
12 | "skipTemplateCodegen": true,
13 | "strictMetadataEmit": true,
14 | "fullTemplateTypeCheck": true,
15 | "strictInjectionParameters": true,
16 | "enableResourceInlining": true
17 | },
18 | "exclude": ["src/test.ts", "**/*.spec.ts"]
19 | }
20 |
--------------------------------------------------------------------------------
/src/tsconfig.lib.prod.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.lib.json",
3 | "compilerOptions": {
4 | "declarationMap": false
5 | },
6 | "angularCompilerOptions": {
7 | "compilationMode": "partial"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../out-tsc/spec",
5 | "types": ["jest", "node"]
6 | },
7 | "files": ["src/test.ts"],
8 | "include": ["**/*.spec.ts", "**/*.d.ts"]
9 | }
10 |
--------------------------------------------------------------------------------
/tools/copy-readme.ts:
--------------------------------------------------------------------------------
1 | import { join } from 'path';
2 | import { existsSync, createReadStream, createWriteStream } from 'fs';
3 | import { name } from '../package.json';
4 |
5 | function copyReadmeAfterSuccessfulBuild(): void {
6 | const path = join(__dirname, '../README.md');
7 | const noReadme = !existsSync(path);
8 |
9 | if (noReadme) {
10 | return console.log(`README.md doesn't exist on the root level!`);
11 | }
12 |
13 | createReadStream(path)
14 | .pipe(createWriteStream(join(__dirname, `../dist/${name}/README.md`)))
15 | .on('finish', () => {
16 | console.log(`Successfully copied README.md into dist/${name} folder!`);
17 | });
18 | }
19 |
20 | copyReadmeAfterSuccessfulBuild();
21 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compileOnSave": false,
3 | "compilerOptions": {
4 | "baseUrl": "./",
5 | "noUnusedLocals": true,
6 | "outDir": "./dist/out-tsc",
7 | "sourceMap": true,
8 | "moduleResolution": "node",
9 | "resolveJsonModule": true,
10 | "esModuleInterop": true,
11 | "experimentalDecorators": true,
12 | "target": "ES2022",
13 | "lib": ["es2017", "dom"],
14 | "module": "es2020",
15 | "typeRoots": ["node_modules/@types"],
16 | "paths": {
17 | "@ngxs-labs/actions-executing": ["src/public-api.ts"]
18 | },
19 | "useDefineForClassFields": false
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "./out-tsc/spec",
5 | "module": "CommonJs",
6 | "types": ["jest"]
7 | },
8 | "include": ["src/**/*.spec.ts", "src/**/*.d.ts"]
9 | }
10 |
--------------------------------------------------------------------------------
/tsconfig.tools.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "resolveJsonModule": true,
5 | "allowJs": true,
6 | "module": "commonjs"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "rulesDirectory": [
3 | "node_modules/codelyzer"
4 | ],
5 | "rules": {
6 | "arrow-return-shorthand": true,
7 | "callable-types": true,
8 | "class-name": true,
9 | "comment-format": [
10 | true,
11 | "check-space"
12 | ],
13 | "curly": true,
14 | "deprecation": {
15 | "severity": "warn"
16 | },
17 | "eofline": true,
18 | "forin": true,
19 | "import-blacklist": [
20 | true,
21 | "rxjs/Rx"
22 | ],
23 | "import-spacing": true,
24 | "indent": [
25 | true,
26 | "spaces"
27 | ],
28 | "interface-over-type-literal": true,
29 | "label-position": true,
30 | "max-line-length": [
31 | true,
32 | 140
33 | ],
34 | "member-ordering": [
35 | true,
36 | {
37 | "order": [
38 | "static-field",
39 | "instance-field",
40 | "static-method",
41 | "instance-method"
42 | ]
43 | }
44 | ],
45 | "no-arg": true,
46 | "no-bitwise": true,
47 | "no-console": [
48 | true,
49 | "debug",
50 | "info",
51 | "time",
52 | "timeEnd",
53 | "trace"
54 | ],
55 | "no-construct": true,
56 | "no-debugger": true,
57 | "no-duplicate-super": true,
58 | "no-empty": false,
59 | "no-empty-interface": true,
60 | "no-eval": true,
61 | "no-misused-new": true,
62 | "no-non-null-assertion": true,
63 | "no-shadowed-variable": true,
64 | "no-string-literal": false,
65 | "no-string-throw": true,
66 | "no-switch-case-fall-through": true,
67 | "no-trailing-whitespace": true,
68 | "no-unused-expression": true,
69 | "no-var-keyword": true,
70 | "object-literal-sort-keys": false,
71 | "one-line": [
72 | true,
73 | "check-open-brace",
74 | "check-catch",
75 | "check-else",
76 | "check-whitespace"
77 | ],
78 | "prefer-const": true,
79 | "quotemark": [
80 | true,
81 | "single"
82 | ],
83 | "radix": true,
84 | "semicolon": [
85 | true,
86 | "always"
87 | ],
88 | "triple-equals": [
89 | true,
90 | "allow-null-check"
91 | ],
92 | "typedef-whitespace": [
93 | true,
94 | {
95 | "call-signature": "nospace",
96 | "index-signature": "nospace",
97 | "parameter": "nospace",
98 | "property-declaration": "nospace",
99 | "variable-declaration": "nospace"
100 | }
101 | ],
102 | "unified-signatures": true,
103 | "variable-name": false,
104 | "whitespace": [
105 | true,
106 | "check-branch",
107 | "check-decl",
108 | "check-operator",
109 | "check-separator",
110 | "check-type"
111 | ],
112 | "no-output-rename": true,
113 | "use-life-cycle-interface": true,
114 | "use-pipe-transform-interface": true,
115 | "component-class-suffix": true,
116 | "directive-class-suffix": true,
117 | "directive-selector-prefix": [
118 | false,
119 | "app"
120 | ],
121 | "component-selector-prefix": [
122 | false,
123 | "app"
124 | ],
125 | "directive-selector-name": [
126 | false,
127 | "camelCase"
128 | ],
129 | "component-selector-name": [
130 | false,
131 | "kebab-case"
132 | ],
133 | "directive-selector-type": [
134 | false,
135 | "attribute"
136 | ],
137 | "component-selector": [
138 | false,
139 | "element",
140 | "my-prefix",
141 | "kebab-case"
142 | ],
143 | "no-unnecessary-initializer": false,
144 | "no-inferrable-types": false,
145 | "no-any": true,
146 | "no-duplicate-variable": true,
147 | "member-access": false
148 | }
149 | }
--------------------------------------------------------------------------------
/yarn.lock.readme.md:
--------------------------------------------------------------------------------
1 | All of our npm dependencies are locked via the `yarn.lock` file for the following reasons:
2 |
3 | - our project has lots of dependencies which update at unpredictable times, so it's important that
4 | we update them explicitly once in a while rather than implicitly when any of us runs `yarn install`
5 | - locked dependencies allow us to reuse yarn cache on travis, significantly speeding up our builds
6 | (by 5 minutes or more)
7 | - locked dependencies allow us to detect when node_modules folder is out of date after a branch switch
8 | which allows us to build the project with the correct dependencies every time
9 |
10 | Before changing a dependency, do the following:
11 |
12 | - make sure you are in sync with `upstream/master`: `git fetch upstream && git rebase upstream/master`
13 | - ensure that your `node_modules` directory is not stale by running `yarn install`
14 |
15 |
16 | To add a new dependency do the following: `yarn add --dev`
17 |
18 | To update an existing dependency do the following: run `yarn upgrade @ --dev`
19 | or `yarn upgrade --dev` to update to the latest version that matches version constraint
20 | in `package.json`
21 |
22 | To Remove an existing dependency do the following: run `yarn remove `
23 |
24 |
25 | Once you've changed the dependency, commit the changes to `package.json` & `yarn.lock`, and you are done.
26 |
--------------------------------------------------------------------------------