From 68f9946c0cd50745407b5eb79db539fe71337727 Mon Sep 17 00:00:00 2001 From: MG Date: Sat, 19 Jun 2021 21:44:35 +0200 Subject: [PATCH] fix(core): supports mocks for viewProviders #726 --- .../common/core.reflect.directive-resolve.ts | 5 +- .../src/lib/mock-component/mock-component.ts | 4 +- .../src/lib/mock/decorate-declaration.ts | 4 +- tests/issue-726/test.spec.ts | 90 +++++++++++++++++++ 4 files changed, 98 insertions(+), 5 deletions(-) create mode 100644 tests/issue-726/test.spec.ts diff --git a/libs/ng-mocks/src/lib/common/core.reflect.directive-resolve.ts b/libs/ng-mocks/src/lib/common/core.reflect.directive-resolve.ts index d0e93f057d..64bb856cdb 100644 --- a/libs/ng-mocks/src/lib/common/core.reflect.directive-resolve.ts +++ b/libs/ng-mocks/src/lib/common/core.reflect.directive-resolve.ts @@ -1,9 +1,10 @@ import { MockDirectiveResolver } from '@angular/compiler/testing'; -import { Directive } from '@angular/core'; +import { Component, Directive } from '@angular/core'; import coreReflectBodyCatch from './core.reflect.body-catch'; import coreReflectBodyGlobal from './core.reflect.body-global'; const coreReflectDirective = coreReflectBodyGlobal(MockDirectiveResolver); -export default (def: any): Directive => coreReflectBodyCatch((arg: any) => coreReflectDirective().resolve(arg))(def); +export default (def: any): Directive & Partial => + coreReflectBodyCatch((arg: any) => coreReflectDirective().resolve(arg))(def); diff --git a/libs/ng-mocks/src/lib/mock-component/mock-component.ts b/libs/ng-mocks/src/lib/mock-component/mock-component.ts index 8a9e2ae511..eaecdc0a9e 100644 --- a/libs/ng-mocks/src/lib/mock-component/mock-component.ts +++ b/libs/ng-mocks/src/lib/mock-component/mock-component.ts @@ -202,9 +202,9 @@ coreDefineProperty(ComponentMockBase, 'parameters', [ const decorateClass = (component: Type, mock: Type): void => { const meta = coreReflectDirectiveResolve(component); - const { exportAs, inputs, outputs, queries, selector, providers } = meta; + const { exportAs, inputs, outputs, queries, selector, providers, viewProviders } = meta; const template = generateTemplate(queries); - const mockMeta = { inputs, outputs, providers, queries }; + const mockMeta = { inputs, outputs, providers, viewProviders, queries }; const mockParams = { exportAs, selector, template }; Component(decorateDeclaration(component, mock, mockMeta, mockParams))(mock); }; diff --git a/libs/ng-mocks/src/lib/mock/decorate-declaration.ts b/libs/ng-mocks/src/lib/mock/decorate-declaration.ts index 9b5c3e2f2d..2b6ca54bed 100644 --- a/libs/ng-mocks/src/lib/mock/decorate-declaration.ts +++ b/libs/ng-mocks/src/lib/mock/decorate-declaration.ts @@ -36,12 +36,14 @@ export default ( outputs?: string[]; providers?: Provider[]; queries?: Record; + viewProviders?: Provider[]; }, params: T, ): T => { const data = cloneProviders(source, mock, meta.providers || []); const providers = [toExistingProvider(source, mock), ...data.providers]; - const options: T = { ...params, providers }; + const { providers: viewProviders } = cloneProviders(source, mock, meta.viewProviders || []); + const options: T = { ...params, providers, viewProviders }; if (data.setControlValueAccessor === undefined) { data.setControlValueAccessor = diff --git a/tests/issue-726/test.spec.ts b/tests/issue-726/test.spec.ts new file mode 100644 index 0000000000..7e1537dfac --- /dev/null +++ b/tests/issue-726/test.spec.ts @@ -0,0 +1,90 @@ +import { Component, Injectable, NgModule } from '@angular/core'; +import { TestBed } from '@angular/core/testing'; +import { MockBuilder, MockRenderFactory, ngMocks } from 'ng-mocks'; + +@Injectable() +class TargetService { + public readonly name = 'target'; +} + +@Component({ + selector: 'target', + template: `{{ service.name }}`, +}) +class TargetComponent { + public constructor(public readonly service: TargetService) {} +} + +@Component({ + selector: 'view', + template: ``, + viewProviders: [TargetService], +}) +class ViewComponent {} + +@Component({ + providers: [TargetService], + selector: 'provider', + template: ``, +}) +class ProviderComponent {} + +@NgModule({ + declarations: [TargetComponent, ViewComponent, ProviderComponent], + exports: [TargetComponent, ViewComponent, ProviderComponent], +}) +class TargetModule {} + +describe('issue-726', () => { + const view = MockRenderFactory(``); + const provider = MockRenderFactory( + ``, + ); + const viewComponent = MockRenderFactory(ViewComponent); + + describe('TestBed', () => { + beforeEach(() => + TestBed.configureTestingModule({ + imports: [TargetModule], + }).compileComponents(), + ); + beforeEach(view.configureTestBed); + beforeEach(provider.configureTestBed); + beforeEach(viewComponent.configureTestBed); + + it('finds the view provider', () => { + // TargetComponent doesn't have the access to TargetService. + expect(view).toThrowError(/No provider for TargetService/); + + // Container knows how to provide TargetService for its views. + expect(provider).not.toThrow(); + + // TargetService is accessed directly view ViewComponent. + const fixture = viewComponent(); + expect(() => + ngMocks.get(fixture.point, TargetService), + ).not.toThrow(); + }); + }); + + describe('MockBuilder', () => { + beforeEach(() => MockBuilder(TargetComponent, TargetModule)); + beforeEach(view.configureTestBed); + beforeEach(provider.configureTestBed); + beforeEach(viewComponent.configureTestBed); + + it('finds the view provider', () => { + // TargetComponent doesn't have the access to TargetService. + expect(view).toThrowError(/No provider for TargetService/); + + // Container knows how to provide TargetService for its views. + expect(provider).not.toThrow(); + + // TargetService is accessed directly view ViewComponent. + const fixture = viewComponent(); + expect(() => + ngMocks.get(fixture.point, TargetService), + ).not.toThrow(); + }); + }); +});