Skip to content

Commit

Permalink
feat(core): ViewContainerRef.createComponent respects mocks #4742
Browse files Browse the repository at this point in the history
  • Loading branch information
satanTime committed Jan 21, 2023
1 parent 2abd719 commit 266e066
Show file tree
Hide file tree
Showing 3 changed files with 100 additions and 20 deletions.
64 changes: 44 additions & 20 deletions libs/ng-mocks/src/lib/common/ng-mocks-global-overrides.ts
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,48 @@ const resetTestingModule =
return original.call(instance);
};

// Monkey-patching ViewContainerRef.createComponent to replace dynamic imports with mocked declarations.
const patchVcrInstance = (vcrInstance: ViewContainerRef) => {
if (!(ViewContainerRef as any).ngMocksOverridesPatched) {
coreDefineProperty(ViewContainerRef, 'ngMocksOverridesPatched', true);

// istanbul ignore else
if (vcrInstance.createComponent) {
const createComponent = vcrInstance.createComponent;
const patchedCreateComponent = helperCreateClone(
createComponent,
undefined,
undefined,
function (component: any, ...createComponentArgs: any[]) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const thisVrc: ViewContainerRef = this;
const map = coreInjector(NG_MOCKS, thisVrc.injector);

return createComponent.apply(thisVrc, [map?.get(component) ?? component, ...createComponentArgs] as any);
},
);

coreDefineProperty(vcrInstance.constructor.prototype, 'createComponent', patchedCreateComponent, true);
coreDefineProperty(vcrInstance, 'createComponent', patchedCreateComponent, true);
}
}
};

const createComponent =
(original: TestBedStatic['createComponent'], instance: TestBedStatic): TestBedStatic['createComponent'] =>
component => {
const fixture = original.call(instance, component);
try {
const vcr = fixture.debugElement.injector.get(ViewContainerRef);
patchVcrInstance(vcr);
} catch {
// nothing to do
}

return fixture as never;
};

const viewContainerInstall = () => {
const vcr: any = ViewContainerRef;

Expand All @@ -289,32 +331,14 @@ const viewContainerInstall = () => {
'__NG_ELEMENT_ID__',
helperCreateClone(ngElementId, undefined, undefined, (...ngElementIdArgs: any[]) => {
const vcrInstance = ngElementId.apply(ngElementId, ngElementIdArgs);

const createComponent = vcrInstance.createComponent;
coreDefineProperty(
vcrInstance,
'createComponent',
helperCreateClone(
createComponent,
undefined,
undefined,
(component: any, ...createComponentArgs: any[]) => {
const map = coreInjector(NG_MOCKS, vcrInstance.injector);

return createComponent.apply(vcrInstance, [
map?.get(component) ?? component,
...createComponentArgs,
] as any);
},
),
true,
);
patchVcrInstance(vcrInstance);

return vcrInstance;
}),
true,
);
}
coreDefineProperty(TestBed, 'createComponent', createComponent(TestBed.createComponent as never, TestBed as never));

coreDefineProperty(ViewContainerRef, 'ngMocksOverridesInstalled', true);
}
Expand Down
8 changes: 8 additions & 0 deletions tests-e2e/src/issue-4693/child.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { Component } from '@angular/core';

@Component({
standalone: true,
selector: 'child',
template: 'child',
})
export class ChildComponent {}
48 changes: 48 additions & 0 deletions tests-e2e/src/issue-4693/test.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { Component, OnInit, ViewContainerRef } from '@angular/core';
import { isMockOf, MockBuilder, MockRender, ngMocks } from 'ng-mocks';

import { ChildComponent } from './child.component';

@Component({
standalone: true,
selector: 'target',
template: '',
})
export class TargetComponent implements OnInit {
constructor(public readonly containerRef: ViewContainerRef) {}

async ngOnInit() {
const { ChildComponent } = await import('./child.component');
this.containerRef.createComponent(ChildComponent);
}
}

describe('issue-4693', () => {
describe('real', () => {
beforeEach(() => MockBuilder(TargetComponent));

it('loads lazy component', async () => {
const fixture = MockRender(TargetComponent);
await fixture.whenStable();
const el = ngMocks.find(ChildComponent);
expect(ngMocks.formatText(el)).toEqual('child');
expect(isMockOf(el.componentInstance, ChildComponent)).toEqual(
false,
);
});
});

describe('mock', () => {
beforeEach(() => MockBuilder(TargetComponent, ChildComponent));

it('loads lazy component as a mock', async () => {
const fixture = MockRender(TargetComponent);
await fixture.whenStable();
const el = ngMocks.find(ChildComponent);
expect(ngMocks.formatText(el)).toEqual('');
expect(isMockOf(el.componentInstance, ChildComponent)).toEqual(
true,
);
});
});
});

0 comments on commit 266e066

Please sign in to comment.