From b5cb39cd2964d4f1d3400414ed6b9a924f588986 Mon Sep 17 00:00:00 2001 From: MG Date: Sun, 10 Jan 2021 09:08:43 +0100 Subject: [PATCH] fix(mock-module): excludes modules with providers correctly closes #271 --- lib/mock-builder/mock-builder.promise.ts | 3 ++ lib/mock-module/mock-ng-def.ts | 37 ++++++++++++++++++----- tests/issue-271/test.spec.ts | 38 ++++++++++++++++++++++++ 3 files changed, 71 insertions(+), 7 deletions(-) create mode 100644 tests/issue-271/test.spec.ts diff --git a/lib/mock-builder/mock-builder.promise.ts b/lib/mock-builder/mock-builder.promise.ts index 071a531cec..9d8cb53865 100644 --- a/lib/mock-builder/mock-builder.promise.ts +++ b/lib/mock-builder/mock-builder.promise.ts @@ -1,5 +1,6 @@ import { NgModule, Provider } from '@angular/core'; import { TestBed } from '@angular/core/testing'; +import ngMocksUniverse from 'ng-mocks/dist/lib/common/ng-mocks-universe'; import { flatten, mapValues } from '../common/core.helpers'; import { Type } from '../common/core.types'; @@ -72,6 +73,7 @@ export class MockBuilderPromise implements IMockBuilder { public build(): NgModule { this.stash.backup(); + ngMocksUniverse.config.set('mockNgDefResolver', new Map()); const params = this.combineParams(); @@ -85,6 +87,7 @@ export class MockBuilderPromise implements IMockBuilder { ngModule.providers.push(createNgMocksTouchesToken()); ngModule.providers.push(createNgMocksOverridesToken(this.replaceDef, this.defValue)); + ngMocksUniverse.config.delete('mockNgDefResolver'); this.stash.restore(); return ngModule; diff --git a/lib/mock-module/mock-ng-def.ts b/lib/mock-module/mock-ng-def.ts index 42a43e5305..73af9c9399 100644 --- a/lib/mock-module/mock-ng-def.ts +++ b/lib/mock-module/mock-ng-def.ts @@ -86,18 +86,33 @@ const createResolveProvider = (resolutions: Map, change: () => void): const createResolveWithProviders = (def: any, mockDef: any): boolean => mockDef && mockDef.ngModule && isNgModuleDefWithProviders(def); +const createResolveExisting = (def: any, resolutions: Map, change: (flag?: boolean) => void): any => { + const mockDef = resolutions.get(def); + if (def !== mockDef) { + change(); + } + + return mockDef; +}; + +const createResolveExcluded = (def: any, resolutions: Map, change: (flag?: boolean) => void): void => { + resolutions.set(def, undefined); + + change(); +}; + const createResolve = (resolutions: Map, change: (flag?: boolean) => void): ((def: any) => any) => ( def: any, ) => { if (resolutions.has(def)) { - return resolutions.get(def); + return createResolveExisting(def, resolutions, change); } - if (ngMocksUniverse.isExcludedDef(def)) { - resolutions.set(def, undefined); - return change(); + const detectedDef = isNgModuleDefWithProviders(def) ? def.ngModule : def; + if (ngMocksUniverse.isExcludedDef(detectedDef)) { + return createResolveExcluded(def, resolutions, change); } - ngMocksUniverse.touches.add(isNgModuleDefWithProviders(def) ? def.ngModule : def); + ngMocksUniverse.touches.add(detectedDef); const mockDef = processDef(def); if (createResolveWithProviders(def, mockDef)) { @@ -145,11 +160,11 @@ const resolveDefForExport = ( const createResolvers = ( change: () => void, + resolutions: Map, ): { resolve: (def: any) => any; resolveProvider: (def: Provider) => any; } => { - const resolutions = new Map(); const resolve = createResolve(resolutions, change); const resolveProvider = createResolveProvider(resolutions, change); @@ -185,13 +200,21 @@ const addExports = ( }; export default (ngModuleDef: NgModule, ngModule?: Type): [boolean, NgModule] => { + const hasResolver = ngMocksUniverse.config.has('mockNgDefResolver'); + if (!hasResolver) { + ngMocksUniverse.config.set('mockNgDefResolver', new Map()); + } let changed = !ngMocksUniverse.flags.has('skipMock'); const change = (flag = true) => { changed = changed || flag; }; - const { resolve, resolveProvider } = createResolvers(change); + const { resolve, resolveProvider } = createResolvers(change, ngMocksUniverse.config.get('mockNgDefResolver')); const mockModuleDef = processMeta(ngModuleDef, resolve, resolveProvider); addExports(resolve, change, ngModuleDef, mockModuleDef, ngModule); + if (!hasResolver) { + ngMocksUniverse.config.delete('mockNgDefResolver'); + } + return [changed, mockModuleDef]; }; diff --git a/tests/issue-271/test.spec.ts b/tests/issue-271/test.spec.ts new file mode 100644 index 0000000000..229da0bb09 --- /dev/null +++ b/tests/issue-271/test.spec.ts @@ -0,0 +1,38 @@ +import { Injectable, NgModule } from '@angular/core'; +import { + MockBuilder, + MockRender, + NgModuleWithProviders, +} from 'ng-mocks'; + +@Injectable() +class TargetService { + public readonly name = 'target'; +} + +@NgModule() +class TargetModule { + public static forRoot(): NgModuleWithProviders { + return { + ngModule: TargetModule, + providers: [TargetService], + }; + } +} + +@NgModule({ + imports: [TargetModule.forRoot()], +}) +class AppModule {} + +describe('issue-271', () => { + beforeEach(() => + MockBuilder(null, AppModule).exclude(TargetModule), + ); + + it('excludes modules with providers', () => { + expect(() => MockRender(TargetService)).toThrowError( + /No provider for TargetService/, + ); + }); +});