diff --git a/lib/common/mock.ts b/lib/common/mock.ts index 7bc5483fed..7a0be3dcc4 100644 --- a/lib/common/mock.ts +++ b/lib/common/mock.ts @@ -3,13 +3,16 @@ import { EventEmitter, Injector, Optional } from '@angular/core'; import { NgControl } from '@angular/forms'; +import { IMockBuilderConfig } from '../mock-builder/types'; import mockServiceHelper from '../mock-service/helper'; import ngMocksUniverse from './ng-mocks-universe'; export type ngMocksMockConfig = { + config?: IMockBuilderConfig; outputs?: string[]; setNgValueAccessor?: boolean; + viewChildRefs?: Map; }; export class Mock { diff --git a/lib/mock-builder/mock-builder-performance.ts b/lib/mock-builder/mock-builder-performance.ts index 780ce841a7..38dd0c0d9f 100644 --- a/lib/mock-builder/mock-builder-performance.ts +++ b/lib/mock-builder/mock-builder-performance.ts @@ -7,6 +7,18 @@ import ngMocksUniverse from '../common/ng-mocks-universe'; import { MockBuilderPromise } from './mock-builder-promise'; import { IMockBuilderResult } from './types'; +const requiredMetadata = ( + ngModule: TestModuleMetadata, +): { + declarations: any[]; + imports: any[]; + providers: any[]; +} => ({ + declarations: [...(ngModule.declarations || /* istanbul ignore next */ [])], + imports: [...(ngModule.imports || /* istanbul ignore next */ [])], + providers: [...(ngModule.providers || /* istanbul ignore next */ [])], +}); + export class MockBuilderPerformance extends MockBuilderPromise { public build(): NgModule { let ngModule: TestModuleMetadata; @@ -19,11 +31,7 @@ export class MockBuilderPerformance extends MockBuilderPromise { ngModule = ngMocksUniverse.global.get('builder:module'); // avoiding influences on cache when users extend the testing module. - return { - declarations: [...(ngModule.declarations || /* istanbul ignore next */ [])], - imports: [...(ngModule.imports || /* istanbul ignore next */ [])], - providers: [...(ngModule.providers || /* istanbul ignore next */ [])], - }; + return requiredMetadata(ngModule); } // removal of cached promise in case of mismatch @@ -75,11 +83,7 @@ export class MockBuilderPerformance extends MockBuilderPromise { ngMocksUniverse.global.set('builder:module', ngModule); // avoiding influences on cache when users extend the testing module. - return { - declarations: [...(ngModule.declarations || /* istanbul ignore next */ [])], - imports: [...(ngModule.imports || /* istanbul ignore next */ [])], - providers: [...(ngModule.providers || /* istanbul ignore next */ [])], - }; + return requiredMetadata(ngModule); } public async then( diff --git a/lib/mock-component/mock-component.ts b/lib/mock-component/mock-component.ts index 06796e5624..31ab54e5a5 100644 --- a/lib/mock-component/mock-component.ts +++ b/lib/mock-component/mock-component.ts @@ -20,6 +20,62 @@ import decorateDeclaration from '../mock/decorate-declaration'; import { MockedComponent } from './types'; +class ComponentMockBase extends MockControlValueAccessor implements AfterContentInit { + /* istanbul ignore next */ + public constructor(changeDetector: ChangeDetectorRef, injector: Injector) { + super(injector); + this.__ngMocksInstall(changeDetector); + } + + public ngAfterContentInit(): void { + const config: any = this.__ngMocksConfig?.config; + if (!(this as any).__rendered && config && config.render) { + for (const block of Object.keys(config.render)) { + const { $implicit, variables } = + config.render[block] !== true + ? config.render[block] + : { + $implicit: undefined, + variables: {}, + }; + (this as any).__render(block, $implicit, variables); + } + (this as any).__rendered = true; + } + } + + private __ngMocksInstall(changeDetector: ChangeDetectorRef): void { + const refs: any = this.__ngMocksConfig?.viewChildRefs; + + // Providing method to hide any @ContentChild based on its selector. + (this as any).__hide = (contentChildSelector: string) => { + const key = refs?.get(contentChildSelector); + if (key) { + (this as any)[`mockRender_${contentChildSelector}`] = false; + changeDetector.detectChanges(); + } + }; + + // Providing a method to render any @ContentChild based on its selector. + (this as any).__render = (contentChildSelector: string, $implicit?: any, variables?: Record) => { + const key = refs?.get(contentChildSelector); + let templateRef: TemplateRef; + let viewContainer: ViewContainerRef; + if (key) { + (this as any)[`mockRender_${contentChildSelector}`] = true; + changeDetector.detectChanges(); + viewContainer = (this as any)[`__mockView_${key}`]; + templateRef = (this as any)[key]; + if (viewContainer && templateRef) { + viewContainer.clear(); + viewContainer.createEmbeddedView(templateRef, { ...variables, $implicit } as any); + changeDetector.detectChanges(); + } + } + }; + } +} + export function MockComponents(...components: Array>): Array>> { return components.map(MockComponent); } @@ -81,73 +137,16 @@ export function MockComponent(component: Type): Type { - const key = viewChildRefs.get(contentChildSelector); - if (key) { - (this as any)[`mockRender_${contentChildSelector}`] = false; - changeDetector.detectChanges(); - } - }; - - // Providing a method to render any @ContentChild based on its selector. - (this as any).__render = (contentChildSelector: string, $implicit?: any, variables?: Record) => { - const key = viewChildRefs.get(contentChildSelector); - let templateRef: TemplateRef; - let viewContainer: ViewContainerRef; - if (key) { - (this as any)[`mockRender_${contentChildSelector}`] = true; - changeDetector.detectChanges(); - viewContainer = (this as any)[`__mockView_${key}`]; - templateRef = (this as any)[key]; - if (viewContainer && templateRef) { - viewContainer.clear(); - viewContainer.createEmbeddedView(templateRef, { ...variables, $implicit } as any); - changeDetector.detectChanges(); - } - } - }; + super(changeDetector, injector); } } (ComponentMock as any).parameters = [ChangeDetectorRef, Injector]; - const mockMeta = { - inputs, - outputs, - providers, - queries, - }; - const mockParams = { - exportAs, - selector, - template, - }; + const mockMeta = { inputs, outputs, providers, queries, viewChildRefs }; + const mockParams = { exportAs, selector, template }; const options = decorateDeclaration(component, ComponentMock, mockMeta, mockParams); Component(options)(ComponentMock); diff --git a/lib/mock-directive/mock-directive.ts b/lib/mock-directive/mock-directive.ts index bee41aad7c..6ec0daec98 100644 --- a/lib/mock-directive/mock-directive.ts +++ b/lib/mock-directive/mock-directive.ts @@ -11,6 +11,50 @@ import decorateDeclaration from '../mock/decorate-declaration'; import { MockedDirective } from './types'; +class DirectiveMockBase extends MockControlValueAccessor implements OnInit { + /* istanbul ignore next */ + public constructor( + injector: Injector, + element?: ElementRef, + template?: TemplateRef, + viewContainer?: ViewContainerRef, + ) { + super(injector); + this.__ngMocksInstall(element, template, viewContainer); + } + + public ngOnInit(): void { + const config: any = this.__ngMocksConfig?.config; + if (config?.render) { + const { $implicit, variables } = + config.render !== true + ? config.render + : { + $implicit: undefined, + variables: {}, + }; + (this as any).__render($implicit, variables); + } + } + + private __ngMocksInstall(element?: ElementRef, template?: TemplateRef, viewContainer?: ViewContainerRef): void { + // Basically any directive on ng-template is treated as structural, even it doesn't control render process. + // In our case we don't if we should render it or not and due to this we do nothing. + (this as any).__element = element; + (this as any).__template = template; + (this as any).__viewContainer = viewContainer; + (this as any).__isStructural = template && viewContainer; + + // Providing method to render mock values. + (this as any).__render = ($implicit?: any, variables?: Record) => { + if (viewContainer && template) { + viewContainer.clear(); + viewContainer.createEmbeddedView(template, { ...variables, $implicit }); + } + }; + } +} + export function MockDirectives(...directives: Array>): Array>> { return directives.map(MockDirective); } @@ -41,52 +85,15 @@ export function MockDirective(directive: Type): Type, - @Optional() viewContainer?: ViewContainerRef, - ) { - super(injector); - this.__ngMocksInstall(element, template, viewContainer); - } - - public ngOnInit(): void { - if (config && config.render) { - const { $implicit, variables } = - config.render !== true - ? config.render - : { - $implicit: undefined, - variables: {}, - }; - (this as any).__render($implicit, variables); - } - } - - private __ngMocksInstall( element?: ElementRef, template?: TemplateRef, viewContainer?: ViewContainerRef, - ): void { - // Basically any directive on ng-template is treated as structural, even it doesn't control render process. - // In our case we don't if we should render it or not and due to this we do nothing. - (this as any).__element = element; - (this as any).__template = template; - (this as any).__viewContainer = viewContainer; - (this as any).__isStructural = template && viewContainer; - - // Providing method to render mock values. - (this as any).__render = ($implicit?: any, variables?: Record) => { - if (viewContainer && template) { - viewContainer.clear(); - viewContainer.createEmbeddedView(template, { ...variables, $implicit }); - } - }; + ) { + super(injector, element, template, viewContainer); } } (DirectiveMock as any).parameters = [ @@ -96,16 +103,8 @@ export function MockDirective(directive: Type): Type( outputs?: string[]; providers?: Provider[]; queries?: Record; + viewChildRefs?: Map; }, params: T, ): T => { @@ -32,8 +34,10 @@ export default ( mockServiceHelper.extractMethodsFromPrototype(source.prototype).indexOf('writeValue') !== -1; } MockOf(source, { + config: ngMocksUniverse.config.get(source), outputs: meta.outputs, setNgValueAccessor: data.setNgValueAccessor, + viewChildRefs: meta.viewChildRefs, })(mock); /* istanbul ignore else */