Skip to content

Commit

Permalink
fix(core): improve TestBed declarations standalone error message (#45999
Browse files Browse the repository at this point in the history
)

improve the error message developers get when adding a standalone
component in the TestBed.configureTestingModule's declarations array,
by making more clear the fact that this error originated from the
TestBed call

resolves #45923

PR Close #45999
  • Loading branch information
dario-piotrowicz authored and alxhub committed May 25, 2022
1 parent ce66cfc commit ddce357
Show file tree
Hide file tree
Showing 3 changed files with 44 additions and 7 deletions.
13 changes: 9 additions & 4 deletions packages/core/src/render3/jit/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,13 @@ export function isStandalone<T>(type: Type<T>) {
return def !== null ? def.standalone : false;
}

export function generateStandaloneInDeclarationsError(type: Type<any>, location: string) {
const prefix = `Unexpected "${stringifyForError(type)}" found in the "declarations" array of the`;
const suffix = `"${stringifyForError(type)}" is marked as standalone and can't be declared ` +
'in any NgModule - did you intend to import it instead (by adding it to the "imports" array)?';
return `${prefix} ${location}, ${suffix}`;
}

function verifySemanticsOfNgModuleDef(
moduleType: NgModuleType, allowDuplicateDeclarationsInRoot: boolean,
importingModule?: NgModuleType): void {
Expand Down Expand Up @@ -280,10 +287,8 @@ function verifySemanticsOfNgModuleDef(
type = resolveForwardRef(type);
const def = getComponentDef(type) || getDirectiveDef(type) || getPipeDef(type);
if (def?.standalone) {
errors.push(`Unexpected "${stringifyForError(type)}" declaration in "${
stringifyForError(moduleType)}" NgModule. "${
stringifyForError(
type)}" is marked as standalone and can't be declared in any NgModule - did you intend to import it?`);
const location = `"${stringifyForError(moduleType)}" NgModule`;
errors.push(generateStandaloneInDeclarationsError(type, location));
}
}

Expand Down
23 changes: 20 additions & 3 deletions packages/core/test/acceptance/ng_module_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ describe('NgModule', () => {
TestBed.createComponent(MyComp);
})
.toThrowError(
`Unexpected "MyComp" declaration in "MyModule" NgModule. "MyComp" is marked as standalone and can't be declared in any NgModule - did you intend to import it?`);
`Unexpected "MyComp" found in the "declarations" array of the "MyModule" NgModule, "MyComp" is marked as standalone and can't be declared in any NgModule - did you intend to import it instead (by adding it to the "imports" array)?`);
});

it('should throw when a standalone directive is added to NgModule declarations', () => {
Expand Down Expand Up @@ -154,7 +154,7 @@ describe('NgModule', () => {
TestBed.createComponent(MyComp);
})
.toThrowError(
`Unexpected "MyDir" declaration in "MyModule" NgModule. "MyDir" is marked as standalone and can't be declared in any NgModule - did you intend to import it?`);
`Unexpected "MyDir" found in the "declarations" array of the "MyModule" NgModule, "MyDir" is marked as standalone and can't be declared in any NgModule - did you intend to import it instead (by adding it to the "imports" array)?`);
});

it('should throw when a standalone pipe is added to NgModule declarations', () => {
Expand Down Expand Up @@ -184,8 +184,25 @@ describe('NgModule', () => {
TestBed.createComponent(MyComp);
})
.toThrowError(
`Unexpected "MyPipe" declaration in "MyModule" NgModule. "MyPipe" is marked as standalone and can't be declared in any NgModule - did you intend to import it?`);
`Unexpected "MyPipe" found in the "declarations" array of the "MyModule" NgModule, "MyPipe" is marked as standalone and can't be declared in any NgModule - did you intend to import it instead (by adding it to the "imports" array)?`);
});

it('should throw a testing specific error when a standalone component is added to the configureTestingModule declarations',
() => {
@Component({
selector: 'my-comp',
template: '',
standalone: true,
})
class MyComp {
}

expect(() => {
TestBed.configureTestingModule({declarations: [MyComp]});
})
.toThrowError(
`Unexpected "MyComp" found in the "declarations" array of the "TestBed.configureTestingModule" call, "MyComp" is marked as standalone and can't be declared in any NgModule - did you intend to import it instead (by adding it to the "imports" array)?`);
});
});

describe('destroy', () => {
Expand Down
15 changes: 15 additions & 0 deletions packages/core/testing/src/r3_test_bed_compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {ApplicationInitStatus, Compiler, COMPILER_OPTIONS, Component, Directive,

import {clearResolutionOfComponentResourcesQueue, isComponentDefPendingResolution, resolveComponentResources, restoreComponentResolutionQueue} from '../../src/metadata/resource_loading';
import {ComponentDef, ComponentType} from '../../src/render3';
import {generateStandaloneInDeclarationsError} from '../../src/render3/jit/module';

import {MetadataOverride} from './metadata_override';
import {ComponentResolver, DirectiveResolver, NgModuleResolver, PipeResolver, Resolver} from './resolvers';
Expand All @@ -26,6 +27,16 @@ function isTestingModuleOverride(value: unknown): value is TestingModuleOverride
value === TestingModuleOverride.OVERRIDE_TEMPLATE;
}

function assertNoStandaloneComponents(
types: Type<any>[], resolver: Resolver<any>, location: string) {
types.forEach(type => {
const component = resolver.resolve(type);
if (component && component.standalone) {
throw new Error(generateStandaloneInDeclarationsError(type, location));
}
});
}

// Resolvers for Angular decorators
type Resolvers = {
module: Resolver<NgModule>,
Expand Down Expand Up @@ -107,6 +118,10 @@ export class R3TestBedCompiler {
configureTestingModule(moduleDef: TestModuleMetadata): void {
// Enqueue any compilation tasks for the directly declared component.
if (moduleDef.declarations !== undefined) {
// Verify that there are no standalone components
assertNoStandaloneComponents(
moduleDef.declarations, this.resolvers.component,
'"TestBed.configureTestingModule" call');
this.queueTypeArray(moduleDef.declarations, TestingModuleOverride.DECLARATION);
this.declarations.push(...moduleDef.declarations);
}
Expand Down

0 comments on commit ddce357

Please sign in to comment.