Skip to content

Commit 5d3b97e

Browse files
committed
fix(core): handle NgModules with standalone pipes in TestBed correctly (#46407)
Prior to this commit, the TestBed logic erroneously tried to apply provider overrides to standalone pipes that were imported in an NgModule. This commit updates the logic to recognize types that may have a scope (an NgModule or a standalone component) and skip other types while applying provider overrides recursively. PR Close #46407
1 parent c79bb14 commit 5d3b97e

File tree

2 files changed

+54
-9
lines changed

2 files changed

+54
-9
lines changed

packages/core/test/test_bed_spec.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,48 @@ describe('TestBed with Standalone types', () => {
367367
expect(rootElement.innerHTML).toBe('Overridden MyStandaloneComponent');
368368
});
369369

370+
it('should make overridden providers available in pipes', () => {
371+
const TOKEN_A = new InjectionToken('TOKEN_A');
372+
@Pipe({
373+
name: 'testPipe',
374+
standalone: true,
375+
})
376+
class TestPipe {
377+
constructor(@Inject(TOKEN_A) private token: string) {}
378+
379+
transform(value: string): string {
380+
return `transformed ${value} using ${this.token} token`;
381+
}
382+
}
383+
@NgModule({
384+
imports: [TestPipe],
385+
exports: [TestPipe],
386+
providers: [{provide: TOKEN_A, useValue: 'A'}],
387+
})
388+
class TestNgModule {
389+
}
390+
391+
@Component({
392+
selector: 'test-component',
393+
standalone: true,
394+
imports: [TestNgModule],
395+
template: `{{ 'original value' | testPipe }}`
396+
})
397+
class TestComponent {
398+
}
399+
400+
TestBed.configureTestingModule({
401+
imports: [TestComponent],
402+
});
403+
TestBed.overrideProvider(TOKEN_A, {useValue: 'Overridden A'});
404+
405+
const fixture = TestBed.createComponent(TestComponent);
406+
fixture.detectChanges();
407+
408+
const hostElement = fixture.nativeElement.firstChild;
409+
expect(hostElement.textContent).toBe('transformed original value using Overridden A token');
410+
});
411+
370412
describe('NgModules as dependencies', () => {
371413
@Component({
372414
selector: 'test-cmp',

packages/core/testing/src/r3_test_bed_compiler.ts

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -441,7 +441,13 @@ export class R3TestBedCompiler {
441441
* and all imported NgModules and standalone components recursively.
442442
*/
443443
private applyProviderOverridesInScope(type: Type<any>): void {
444-
if (this.scopesWithOverriddenProviders.has(type)) {
444+
const hasScope = isStandaloneComponent(type) || isNgModule(type);
445+
446+
// The function can be re-entered recursively while inspecting dependencies
447+
// of an NgModule or a standalone component. Exit early if we come across a
448+
// type that can not have a scope (directive or pipe) or the type is already
449+
// processed earlier.
450+
if (!hasScope || this.scopesWithOverriddenProviders.has(type)) {
445451
return;
446452
}
447453
this.scopesWithOverriddenProviders.add(type);
@@ -461,14 +467,7 @@ export class R3TestBedCompiler {
461467
const def = getComponentDef(type);
462468
const dependencies = maybeUnwrapFn(def.dependencies ?? []);
463469
for (const dependency of dependencies) {
464-
// Proceed with examining dependencies recursively
465-
// when a dependency is a standalone component or an NgModule.
466-
// In AOT, the `dependencies` might also contain regular (NgModule-based)
467-
// Component, Directive and Pipes. Skip them here, they are handled in a
468-
// different location (in the `configureTestingModule` function).
469-
if (isStandaloneComponent(dependency) || hasNgModuleDef(dependency)) {
470-
this.applyProviderOverridesInScope(dependency);
471-
}
470+
this.applyProviderOverridesInScope(dependency);
472471
}
473472
} else {
474473
const providers = [
@@ -880,6 +879,10 @@ function hasNgModuleDef<T>(value: Type<T>): value is NgModuleType<T> {
880879
return value.hasOwnProperty('ɵmod');
881880
}
882881

882+
function isNgModule<T>(value: Type<T>): boolean {
883+
return hasNgModuleDef(value);
884+
}
885+
883886
function maybeUnwrapFn<T>(maybeFn: (() => T)|T): T {
884887
return maybeFn instanceof Function ? maybeFn() : maybeFn;
885888
}

0 commit comments

Comments
 (0)