From c376e9343b52cef740700a7c1d4383d03e0b24c1 Mon Sep 17 00:00:00 2001 From: MG Date: Mon, 31 May 2021 21:15:33 +0200 Subject: [PATCH] fix(core): excluding StoreDevtoolsModule by default #589 --- .editorconfig | 4 +- docs/articles/guides/libraries/ngrx.md | 10 +++ .../src/lib/common/ng-mocks-universe.ts | 19 ++++- package-lock.json | 39 +++++++++- package.json | 1 + tests-angular/e2e/package.json | 1 + .../e2e/src/issue-589/dev-tools.spec.ts | 73 +++++++++++++++++++ .../e2e/src/issue-589/meta-reducers.spec.ts | 73 +++++++++++++++++++ tests/issue-589/test.spec.ts | 61 ++++++++++++++++ 9 files changed, 278 insertions(+), 3 deletions(-) create mode 100644 tests-angular/e2e/src/issue-589/dev-tools.spec.ts create mode 100644 tests-angular/e2e/src/issue-589/meta-reducers.spec.ts create mode 100644 tests/issue-589/test.spec.ts diff --git a/.editorconfig b/.editorconfig index ad2c634f6d..6ddbe8b727 100644 --- a/.editorconfig +++ b/.editorconfig @@ -12,5 +12,7 @@ tab_width = 2 trim_trailing_whitespace = true [*.md] -max_line_length = 50 trim_trailing_whitespace = false + +[README.md] +max_line_length = 50 diff --git a/docs/articles/guides/libraries/ngrx.md b/docs/articles/guides/libraries/ngrx.md index b172de3ce7..f57ee648cb 100644 --- a/docs/articles/guides/libraries/ngrx.md +++ b/docs/articles/guides/libraries/ngrx.md @@ -75,3 +75,13 @@ beforeEach(() => .keep(EffectsFeatureModule) ); ``` + +## StoreDevtoolsModule and modules with meta reducers + +:::warning +A mock meta reducer stops all reducers in the store +::: + +If you have a module which has a meta reducer, +then **please don't forget to keep it too** if you plan to keep store modules for testing. +Otherwise, no actions will be reduced and the store will be always empty. diff --git a/libs/ng-mocks/src/lib/common/ng-mocks-universe.ts b/libs/ng-mocks/src/lib/common/ng-mocks-universe.ts index d7734ab29a..a130076128 100644 --- a/libs/ng-mocks/src/lib/common/ng-mocks-universe.ts +++ b/libs/ng-mocks/src/lib/common/ng-mocks-universe.ts @@ -120,8 +120,25 @@ ngMocksUniverse.hasBuildDeclaration = (def: any): boolean => { const hasBuildDeclaration = (def: any): boolean => ngMocksUniverse.hasBuildDeclaration(def); const getBuildDeclaration = (def: any): any => ngMocksUniverse.getBuildDeclaration(def); -ngMocksUniverse.isExcludedDef = (def: any): boolean => hasBuildDeclaration(def) && getBuildDeclaration(def) === null; +ngMocksUniverse.isExcludedDef = (def: any): boolean => { + const resolution = ngMocksUniverse.getResolution(def); + if (resolution && resolution !== 'exclude') { + return false; + } + + return hasBuildDeclaration(def) && getBuildDeclaration(def) === null; +}; ngMocksUniverse.isProvidedDef = (def: any): boolean => hasBuildDeclaration(def) && getBuildDeclaration(def) !== null; +// excluding StoreDevtoolsModule by default +// istanbul ignore next +try { + // tslint:disable-next-line no-require-imports no-implicit-dependencies no-var-requires + const { StoreDevtoolsModule } = require('@ngrx/store-devtools'); + ngMocksUniverse.getDefaults().set(StoreDevtoolsModule, ['exclude']); +} catch { + // nothing to do +} + export default ((): NgMocksUniverse => ngMocksUniverse)(); diff --git a/package-lock.json b/package-lock.json index 750567c3ad..1389f62ebb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4545,6 +4545,15 @@ "tslib": "^2.0.0" } }, + "@ngrx/store-devtools": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@ngrx/store-devtools/-/store-devtools-12.0.0.tgz", + "integrity": "sha512-juWT2HXoRzZ05R2kEn9BZNlpUwHTQN7OKxiUAVL4/TRdnnHmHKyJPiG3XXmsojEyjdrZc81xE2KxrYivUbL5EA==", + "dev": true, + "requires": { + "tslib": "^2.0.0" + } + }, "@ngtools/webpack": { "version": "12.0.2", "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-12.0.2.tgz", @@ -8583,6 +8592,16 @@ "integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==", "dev": true }, + "bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "dev": true, + "optional": true, + "requires": { + "file-uri-to-path": "1.0.0" + } + }, "bl": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/bl/-/bl-4.0.3.tgz", @@ -11725,6 +11744,13 @@ "escape-string-regexp": "^1.0.5" } }, + "file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "dev": true, + "optional": true + }, "fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -15116,6 +15142,13 @@ "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", "dev": true }, + "nan": { + "version": "2.14.2", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz", + "integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==", + "dev": true, + "optional": true + }, "nanoid": { "version": "3.1.23", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.23.tgz", @@ -25682,7 +25715,11 @@ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", "dev": true, - "optional": true + "optional": true, + "requires": { + "bindings": "^1.5.0", + "nan": "^2.12.1" + } }, "glob-parent": { "version": "3.1.0", diff --git a/package.json b/package.json index 1f886d9a26..e987673a95 100644 --- a/package.json +++ b/package.json @@ -136,6 +136,7 @@ "@ng-select/ng-select": "6.1.0", "@ngrx/effects": "12.0.0", "@ngrx/store": "12.0.0", + "@ngrx/store-devtools": "12.0.0", "@semantic-release/changelog": "5.0.1", "@semantic-release/exec": "5.0.0", "@semantic-release/git": "9.0.0", diff --git a/tests-angular/e2e/package.json b/tests-angular/e2e/package.json index ed39d5408d..e6088cfbf4 100644 --- a/tests-angular/e2e/package.json +++ b/tests-angular/e2e/package.json @@ -36,6 +36,7 @@ "@ng-select/ng-select": "6.1.0", "@ngrx/effects": "12.0.0", "@ngrx/store": "12.0.0", + "@ngrx/store-devtools": "12.0.0", "@types/jasmine": "3.7.6", "@types/node": "14.17.1", "jasmine-core": "3.7.1", diff --git a/tests-angular/e2e/src/issue-589/dev-tools.spec.ts b/tests-angular/e2e/src/issue-589/dev-tools.spec.ts new file mode 100644 index 0000000000..44da7cab1f --- /dev/null +++ b/tests-angular/e2e/src/issue-589/dev-tools.spec.ts @@ -0,0 +1,73 @@ +import { CommonModule } from '@angular/common'; +import { Component, NgModule, OnInit } from '@angular/core'; +import { + createAction, + createFeatureSelector, + createReducer, + on, + Store, + StoreModule, + StoreRootModule, +} from '@ngrx/store'; +import { MockBuilder, MockRender, ngMocks } from 'ng-mocks'; + +const increaseValue = createAction('set-value'); +const resetValue = createAction('reset-value'); + +const myReducer = { + featureKey: 'test', + reducer: createReducer( + 0, + on(increaseValue, state => state + 1), + on(resetValue, () => 0), + ), +}; + +const selectValue = createFeatureSelector(myReducer.featureKey); + +@Component({ + selector: 'target', + template: '{{ value$ | async }}', +}) +class MyComponent implements OnInit { + public value$ = this.store.select(selectValue); + + public constructor(private readonly store: Store) {} + + public ngOnInit(): void { + this.store.dispatch(increaseValue()); + } + + public reset(): void { + this.store.dispatch(resetValue()); + } +} + +const metaReducer = state => state; + +@NgModule({ + declarations: [MyComponent], + exports: [MyComponent], + imports: [ + CommonModule, + StoreModule.forRoot( + { + [myReducer.featureKey]: myReducer.reducer, + }, + { + metaReducers: [metaReducer], + }, + ), + ], +}) +class MyModule {} + +describe('issue-589:meta-reducers', () => { + beforeEach(() => + MockBuilder(MyComponent, MyModule).keep(StoreRootModule), + ); + + it('keeps meta reducers', () => { + expect(ngMocks.formatText(MockRender(MyComponent))).toEqual('1'); + }); +}); diff --git a/tests-angular/e2e/src/issue-589/meta-reducers.spec.ts b/tests-angular/e2e/src/issue-589/meta-reducers.spec.ts new file mode 100644 index 0000000000..40cbac2faf --- /dev/null +++ b/tests-angular/e2e/src/issue-589/meta-reducers.spec.ts @@ -0,0 +1,73 @@ +import { CommonModule } from '@angular/common'; +import { Component, NgModule, OnInit } from '@angular/core'; +import { + createAction, + createFeatureSelector, + createReducer, + on, + Store, + StoreModule, + StoreRootModule, +} from '@ngrx/store'; +import { + INITIAL_OPTIONS, + StoreDevtoolsModule, +} from '@ngrx/store-devtools'; +import { MockBuilder, MockRender, ngMocks } from 'ng-mocks'; + +const increaseValue = createAction('set-value'); +const resetValue = createAction('reset-value'); + +const myReducer = { + featureKey: 'test', + reducer: createReducer( + 0, + on(increaseValue, state => state + 1), + on(resetValue, () => 0), + ), +}; + +const selectValue = createFeatureSelector(myReducer.featureKey); + +@Component({ + selector: 'target', + template: '{{ value$ | async }}', +}) +class MyComponent implements OnInit { + public value$ = this.store.select(selectValue); + + public constructor(private readonly store: Store) {} + + public ngOnInit(): void { + this.store.dispatch(increaseValue()); + } + + public reset(): void { + this.store.dispatch(resetValue()); + } +} + +@NgModule({ + declarations: [MyComponent], + exports: [MyComponent], + imports: [ + CommonModule, + StoreModule.forRoot({ + [myReducer.featureKey]: myReducer.reducer, + }), + StoreDevtoolsModule.instrument({ + maxAge: 6, + }), + ], +}) +class MyModule {} + +describe('issue-589:dev-tools', () => { + beforeEach(() => + MockBuilder(MyComponent, MyModule).keep(StoreRootModule), + ); + + it('excludes StoreDevtoolsModule by default', () => { + expect(ngMocks.formatText(MockRender(MyComponent))).toEqual('1'); + }); +}); diff --git a/tests/issue-589/test.spec.ts b/tests/issue-589/test.spec.ts new file mode 100644 index 0000000000..9a73529c2f --- /dev/null +++ b/tests/issue-589/test.spec.ts @@ -0,0 +1,61 @@ +import { InjectionToken, NgModule } from '@angular/core'; +import { + MockBuilder, + MockRender, + ngMocks, + NgModuleWithProviders, +} from 'ng-mocks'; + +const TOKEN = new InjectionToken('TOKEN'); + +@NgModule() +class TargetModule { + public static forRoot(): NgModuleWithProviders { + return { + ngModule: TargetModule, + providers: [ + { + provide: TOKEN, + useValue: 1, + }, + ], + }; + } +} + +@NgModule({ + imports: [TargetModule.forRoot()], +}) +class MyModule {} + +ngMocks.globalExclude(TargetModule); + +// Looks like a module with providers has some issues with excluding it globally +// and then mocking in a mock builder setup. +describe('issue-589', () => { + describe('default exclude', () => { + beforeEach(() => MockBuilder(null, MyModule)); + + it('excludes StoreDevtoolsModule', () => { + expect(() => MockRender(TOKEN)).toThrowError( + /No provider for InjectionToken TOKEN/, + ); + }); + }); + + describe('explicit keep', () => { + beforeEach(() => MockBuilder(null, MyModule).keep(TargetModule)); + + it('excludes StoreDevtoolsModule', () => { + expect(MockRender(TOKEN).point.componentInstance).toEqual(1); + }); + }); + + describe('explicit mock', () => { + beforeEach(() => MockBuilder(null, MyModule).mock(TargetModule)); + + it('excludes StoreDevtoolsModule', () => { + expect(MockRender(TOKEN).point.componentInstance).toEqual(0); + }); + }); +});