Skip to content

Commit dde1498

Browse files
committed
fix(core): Allow TestBed.configureTestingModule to work with recursive cycle of standalone components.
When having a recursive circle of imports on standalone components, `queueTypesFromModulesArrayRecur` triggered a `Maximum call stack size exceeded` error. This commit fixes this. Fixes #49469
1 parent 8d99ad0 commit dde1498

File tree

2 files changed

+47
-6
lines changed

2 files changed

+47
-6
lines changed

packages/core/test/acceptance/standalone_spec.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -910,5 +910,39 @@ describe('standalone components, directives, and pipes', () => {
910910

911911
expect(isStandalone(Module)).toBeFalse();
912912
});
913+
914+
it('should render a recursive cycle of standalone components', () => {
915+
@Component({
916+
selector: 'cmp-a',
917+
standalone: true,
918+
template: '<ng-template [ngIf]="false"><cmp-c></cmp-c></ng-template>A',
919+
imports: [forwardRef(() => StandaloneCmpC)],
920+
})
921+
class StandaloneCmpA {
922+
}
923+
924+
@Component({
925+
selector: 'cmp-b',
926+
standalone: true,
927+
template: '(<cmp-a></cmp-a>)B',
928+
imports: [StandaloneCmpA],
929+
})
930+
class StandaloneCmpB {
931+
}
932+
933+
@Component({
934+
selector: 'cmp-c',
935+
standalone: true,
936+
template: '(<cmp-b></cmp-b>)C',
937+
imports: [StandaloneCmpB],
938+
})
939+
class StandaloneCmpC {
940+
}
941+
942+
TestBed.configureTestingModule({imports: [StandaloneCmpC]});
943+
const fixture = TestBed.createComponent(StandaloneCmpC);
944+
fixture.detectChanges();
945+
expect(fixture.nativeElement.textContent).toBe('((A)B)C');
946+
});
913947
});
914948
});

packages/core/testing/src/test_bed_compiler.ts

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -580,20 +580,21 @@ export class TestBedCompiler {
580580
}
581581

582582
private queueTypesFromModulesArray(arr: any[]): void {
583-
// Because we may encounter the same NgModule while processing the imports and exports of an
584-
// NgModule tree, we cache them in this set so we can skip ones that have already been seen
585-
// encountered. In some test setups, this caching resulted in 10X runtime improvement.
586-
const processedNgModuleDefs = new Set();
583+
// Because we may encounter the same NgModule or a standalone Component while processing
584+
// the dependencies of an NgModule or a standalone Component, we cache them in this set so we
585+
// can skip ones that have already been seen encountered. In some test setups, this caching
586+
// resulted in 10X runtime improvement.
587+
const processedDefs = new Set();
587588
const queueTypesFromModulesArrayRecur = (arr: any[]): void => {
588589
for (const value of arr) {
589590
if (Array.isArray(value)) {
590591
queueTypesFromModulesArrayRecur(value);
591592
} else if (hasNgModuleDef(value)) {
592593
const def = value.ɵmod;
593-
if (processedNgModuleDefs.has(def)) {
594+
if (processedDefs.has(def)) {
594595
continue;
595596
}
596-
processedNgModuleDefs.add(def);
597+
processedDefs.add(def);
597598
// Look through declarations, imports, and exports, and queue
598599
// everything found there.
599600
this.queueTypeArray(maybeUnwrapFn(def.declarations), value);
@@ -604,6 +605,12 @@ export class TestBedCompiler {
604605
} else if (isStandaloneComponent(value)) {
605606
this.queueType(value, null);
606607
const def = getComponentDef(value);
608+
609+
if (processedDefs.has(def)) {
610+
continue;
611+
}
612+
processedDefs.add(def);
613+
607614
const dependencies = maybeUnwrapFn(def.dependencies ?? []);
608615
dependencies.forEach((dependency) => {
609616
// Note: in AOT, the `dependencies` might also contain regular

0 commit comments

Comments
 (0)