From 3ed8ae4d46bb5f56228175951331b2efb579a6cb Mon Sep 17 00:00:00 2001 From: satanTime Date: Fri, 6 Jan 2023 13:15:35 +0100 Subject: [PATCH] fix(MockBuilder): imports modules with providers on root level #4613 --- .../mock-builder/promise/init-ng-modules.ts | 2 + tests/issue-4613/providers.spec.ts | 189 ++++++++++++++++++ tests/issue-4613/test.spec.ts | 46 +++++ 3 files changed, 237 insertions(+) create mode 100644 tests/issue-4613/providers.spec.ts create mode 100644 tests/issue-4613/test.spec.ts diff --git a/libs/ng-mocks/src/lib/mock-builder/promise/init-ng-modules.ts b/libs/ng-mocks/src/lib/mock-builder/promise/init-ng-modules.ts index 37078d7812..96af5cd440 100644 --- a/libs/ng-mocks/src/lib/mock-builder/promise/init-ng-modules.ts +++ b/libs/ng-mocks/src/lib/mock-builder/promise/init-ng-modules.ts @@ -70,6 +70,8 @@ export default ( if (!config.dependency && config.export && !configInstance?.exported && (isNgDef(def, 'i') || !isNgDef(def))) { handleDef(meta, def, defProviders); markProviders([def]); + } else if (!config.dependency && isNgDef(def, 'm') && defProviders.has(def)) { + handleDef(meta, def, defProviders); } else if (!config.dependency && config.export && !configInstance?.exported) { handleDef(meta, def, defProviders); } else if (!ngMocksUniverse.touches.has(def) && !config.dependency) { diff --git a/tests/issue-4613/providers.spec.ts b/tests/issue-4613/providers.spec.ts new file mode 100644 index 0000000000..d7228180d3 --- /dev/null +++ b/tests/issue-4613/providers.spec.ts @@ -0,0 +1,189 @@ +import { Component, Injectable, NgModule } from '@angular/core'; + +import { isMockOf, MockBuilder, MockRender, ngMocks } from 'ng-mocks'; + +@Injectable() +class ProviderService {} + +@Injectable() +class Provider1Service {} + +@Injectable() +class Provider2Service {} + +@Component({ + selector: 'target', + template: '{{ service.constructor.name }}', + providers: [Provider2Service], +}) +class TargetComponent { + constructor(public readonly service: ProviderService) {} +} + +@NgModule({ + declarations: [TargetComponent], +}) +class TargetModule { + static for1() { + return { + ngModule: TargetModule, + providers: [ + Provider1Service, + { + provide: ProviderService, + useExisting: Provider1Service, + }, + ], + }; + } + + static for2() { + return { + ngModule: TargetModule, + providers: [ + Provider2Service, + { + provide: ProviderService, + useExisting: Provider2Service, + }, + ], + }; + } +} + +@NgModule({ + imports: [TargetModule], +}) +class DependencyModule {} + +@NgModule({ + imports: [TargetModule.for2()], +}) +class DependencyWithProvidersModule {} + +@NgModule({ + providers: [ProviderService], +}) +class ProviderModule {} + +// @see https://github.com/help-me-mom/ng-mocks/issues/4613 +describe('issue-4613', () => { + describe('fails without providers', () => { + beforeEach(() => MockBuilder(TargetComponent, DependencyModule)); + + it('fails because no provider for ProviderService', () => { + expect(() => MockRender(TargetComponent)).toThrowError( + /No provider for ProviderService/, + ); + }); + }); + + describe('works with providers', () => { + beforeEach(() => + MockBuilder(TargetComponent, [ + DependencyModule, + ProviderModule, + ]), + ); + + it('renders ProviderService', () => { + expect(ngMocks.formatText(MockRender(TargetComponent))).toEqual( + 'ProviderService', + ); + }); + }); + + describe('MockBuilder.mock', () => { + describe('works with nested Provider1Service', () => { + beforeEach(() => + MockBuilder( + [TargetComponent, ProviderService], + [DependencyModule, TargetModule.for1()], + ), + ); + + it('renders Provider1Service', () => { + expect( + ngMocks.formatText(MockRender(TargetComponent)), + ).toEqual('Provider1Service'); + + expect( + isMockOf( + ngMocks.findInstance(ProviderService), + Provider1Service, + ), + ).toEqual(true); + }); + }); + + describe('overrides Provider2Service with Provider1Service', () => { + beforeEach(() => + MockBuilder( + [TargetComponent, ProviderService], + [DependencyWithProvidersModule, TargetModule.for1()], + ), + ); + + it('renders Provider1Service', () => { + expect( + ngMocks.formatText(MockRender(TargetComponent)), + ).toEqual('Provider1Service'); + + expect( + isMockOf( + ngMocks.findInstance(ProviderService), + Provider1Service, + ), + ).toEqual(true); + }); + }); + }); + + describe('MockBuilder.keep', () => { + describe('works with nested Provider1Service', () => { + beforeEach(() => + MockBuilder([ + TargetComponent, + DependencyModule, + TargetModule.for1(), + ]), + ); + + it('renders Provider1Service', () => { + expect( + ngMocks.formatText(MockRender(TargetComponent)), + ).toEqual('Provider1Service'); + + expect( + isMockOf( + ngMocks.findInstance(ProviderService), + Provider1Service, + ), + ).toEqual(false); + }); + }); + + describe('overrides Provider2Service with Provider1Service', () => { + beforeEach(() => + MockBuilder([ + TargetComponent, + DependencyWithProvidersModule, + TargetModule.for1(), + ]), + ); + + it('renders Provider1Service', () => { + expect( + ngMocks.formatText(MockRender(TargetComponent)), + ).toEqual('Provider1Service'); + + expect( + isMockOf( + ngMocks.findInstance(ProviderService), + Provider1Service, + ), + ).toEqual(false); + }); + }); + }); +}); diff --git a/tests/issue-4613/test.spec.ts b/tests/issue-4613/test.spec.ts new file mode 100644 index 0000000000..02f93537cd --- /dev/null +++ b/tests/issue-4613/test.spec.ts @@ -0,0 +1,46 @@ +import { Component, Injectable, NgModule } from '@angular/core'; +import { TestBed } from '@angular/core/testing'; + +import { MockBuilder } from 'ng-mocks'; + +@Injectable() +class TargetService {} + +@Component({ + selector: 'target', + template: '{{ service.constructor.name }}', +}) +class TargetComponent { + constructor(public readonly service: TargetService) {} +} + +@NgModule({ + declarations: [TargetComponent], +}) +class TargetModule { + static forRoot() { + return { + ngModule: TargetModule, + providers: [TargetService], + }; + } +} + +@NgModule({ + imports: [TargetModule], +}) +class DependencyModule {} + +// @see https://github.com/help-me-mom/ng-mocks/issues/4613 +describe('issue-4613', () => { + beforeEach(() => + MockBuilder(TargetComponent, [ + TargetModule.forRoot(), + DependencyModule, + ]), + ); + + it('should create component', () => { + expect(TestBed.createComponent(TargetComponent)).toBeTruthy(); + }); +});