Skip to content

Commit

Permalink
fix(#333): register mock components with entryComponents
Browse files Browse the repository at this point in the history
  • Loading branch information
satanTime committed Apr 17, 2021
1 parent 89e41bb commit 3a53431
Show file tree
Hide file tree
Showing 4 changed files with 172 additions and 11 deletions.
2 changes: 2 additions & 0 deletions libs/ng-mocks/src/lib/mock-builder/mock-builder.promise.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import addRequestedProviders from './promise/add-requested-providers';
import createNgMocksOverridesToken from './promise/create-ng-mocks-overrides-token';
import createNgMocksToken from './promise/create-ng-mocks-token';
import createNgMocksTouchesToken from './promise/create-ng-mocks-touches-token';
import handleEntryComponents from './promise/handle-entry-components';
import handleRootProviders from './promise/handle-root-providers';
import initNgModules from './promise/init-ng-modules';
import initUniverse from './promise/init-universe';
Expand Down Expand Up @@ -78,6 +79,7 @@ export class MockBuilderPromise implements IMockBuilder {
addMissedMockDeclarationsAndModules(ngModule, params);
addRequestedProviders(ngModule, params);
handleRootProviders(ngModule, params);
handleEntryComponents(ngModule);

ngModule.providers.push(createNgMocksToken());
ngModule.providers.push(createNgMocksTouchesToken());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { ComponentFactoryResolver, NgModule } from '@angular/core';

let isIvy = false;
try {
// tslint:disable-next-line no-require-imports no-var-requires
const module = require('@angular/core');
isIvy = module.ɵivyEnabled;
} catch (e) {
// nothing to do
}

import coreDefineProperty from '../../common/core.define-property';
import { extendClass } from '../../common/core.helpers';
import { NG_MOCKS } from '../../common/core.tokens';
import { isNgDef } from '../../common/func.is-ng-def';

import { NgMeta } from './types';

class EntryComponentsModule {
protected origin: ComponentFactoryResolver['resolveComponentFactory'];

public constructor(map: Map<any, any>, protected componentFactoryResolver: ComponentFactoryResolver) {
this.origin = componentFactoryResolver.resolveComponentFactory;
componentFactoryResolver.resolveComponentFactory = component =>
this.origin.call(componentFactoryResolver, map.get(component) ?? component) as any;
}
}
coreDefineProperty(EntryComponentsModule, 'parameters', [[NG_MOCKS], [ComponentFactoryResolver]]);

export default (ngModule: NgMeta): void => {
const entryComponents: any[] = [];
for (const declaration of ngModule.declarations) {
if (isNgDef(declaration, 'c')) {
entryComponents.push(declaration);
}
}
// the way to cause entryComponents to do its work
const entryComponent = extendClass(EntryComponentsModule);
NgModule({
// Ivy knows how to make any component an entry point,
// but we still would like to patch resolveComponentFactory in order to provide mocks.
entryComponents: isIvy ? /* istanbul ignore next */ [] : entryComponents,
})(entryComponent);
ngModule.imports.push(entryComponent);
};
12 changes: 1 addition & 11 deletions tests/issue-296/test.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
// tslint:disable no-duplicate-imports

import * as core from '@angular/core';
import {
Component,
ComponentFactoryResolver,
Expand Down Expand Up @@ -52,16 +51,7 @@ describe('issue-296:without-entry', () => {

it('behaves correctly with and without ivy', () => {
const render = () => MockRender(TargetComponent);
if ((core as any).ɵivyEnabled) {
// ivy does not fail
expect(render).not.toThrow();
}
if (!(core as any).ɵivyEnabled) {
// no-ivy fails with @NgModule.entryComponents
expect(render).toThrowError(
/No component factory found for ModalComponent/,
);
}
expect(render).not.toThrow();
});
});

Expand Down
124 changes: 124 additions & 0 deletions tests/issue-333/test.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import { CommonModule } from '@angular/common';
import * as core from '@angular/core';
import { MockBuilder, MockRender, ngMocks } from 'ng-mocks';

@core.Component({
selector: 'dynamic-overlay',
template:
'<ng-container *ngComponentOutlet="component"></ng-container>',
})
export class DynamicOverlayComponent {
public component?: core.Type<any>;

public attachComponent(component: core.Type<any>) {
this.component = component;
}
}

@core.NgModule({
declarations: [DynamicOverlayComponent],
exports: [DynamicOverlayComponent],
imports: [CommonModule],
})
export class OverlayModule {}

@core.Component({
selector: 'dep-component',
template: 'Dependency',
})
class DepComponent {}

@core.Component({
selector: 'mock-component',
template: '<h1 *ngIf="flag"><dep-component></dep-component></h1>',
})
class MockComponent {
public flag = true;
}

describe('issue-333', () => {
describe('1:keep', () => {
// this should work with and without ivy
beforeEach(() =>
MockBuilder(DynamicOverlayComponent, OverlayModule)
.keep(MockComponent)
.keep(DepComponent),
);

it('should render', () => {
const fixture = MockRender(DynamicOverlayComponent);

expect(ngMocks.formatHtml(fixture)).toEqual(
`<dynamic-overlay></dynamic-overlay>`,
);
});

it('should project content', () => {
const fixture = MockRender(DynamicOverlayComponent);
fixture.point.componentInstance.attachComponent(MockComponent);
fixture.detectChanges();

expect(ngMocks.formatText(fixture)).toEqual(`Dependency`);
});
});

describe('2:mock', () => {
// this should work with and without ivy
beforeEach(() =>
MockBuilder(DynamicOverlayComponent, OverlayModule)
.mock(MockComponent)
.keep(CommonModule, { export: true }),
);

it('renders a mock component', () => {
const fixture = MockRender(DynamicOverlayComponent);

expect(ngMocks.formatHtml(fixture)).toEqual(
`<dynamic-overlay></dynamic-overlay>`,
);
});

it('projects content', () => {
const fixture = MockRender(DynamicOverlayComponent);
fixture.point.componentInstance.attachComponent(MockComponent);
fixture.detectChanges();

expect(ngMocks.formatHtml(fixture)).toEqual(
`<dynamic-overlay><mock-component></mock-component></dynamic-overlay>`,
);
});

it('fails on unknown', () => {
if ((core as any).ɵivyEnabled) {
pending('ivy fails differently');
} else {
const fixture = MockRender(DynamicOverlayComponent);
fixture.point.componentInstance.attachComponent(DepComponent);
expect(() => fixture.detectChanges()).toThrowError(
/DepComponent/,
);
}
});
});

describe('3:mock', () => {
// this should work with and without ivy
beforeEach(() =>
MockBuilder(DynamicOverlayComponent, OverlayModule),
);

it('fails on unknown', () => {
if ((core as any).ɵivyEnabled) {
pending('ivy fails differently');
} else {
const fixture = MockRender(DynamicOverlayComponent);
fixture.point.componentInstance.attachComponent(
MockComponent,
);
expect(() => fixture.detectChanges()).toThrowError(
/MockComponent/,
);
}
});
});
});

0 comments on commit 3a53431

Please sign in to comment.