fix(ivy): check semantics of NgModule for consistency (#27604)
`NgModule` requires that `Component`s/`Directive`s/`Pipe`s are listed in declarations, and that each `Component`s/`Directive`s/`Pipe` is declared in exactly one `NgModule`. This change adds runtime checks to ensure that these sementics are true at runtime. There will need to be seperate set of checks for the AoT path of the codebase to verify that same set of semantics hold. Due to current design there does not seem to be an easy way to share the two checks because JIT deal with references where as AoT deals with AST nodes. PR Close #27604
This commit is contained in:
parent
d132baede3
commit
e94975d109
|
@ -128,6 +128,7 @@ export {
|
|||
compileNgModule as ɵcompileNgModule,
|
||||
compileNgModuleDefs as ɵcompileNgModuleDefs,
|
||||
patchComponentDefWithScope as ɵpatchComponentDefWithScope,
|
||||
resetCompiledComponents as ɵresetCompiledComponents,
|
||||
} from './render3/jit/module';
|
||||
export {
|
||||
compilePipe as ɵcompilePipe,
|
||||
|
@ -224,6 +225,8 @@ export {
|
|||
SWITCH_RENDERER2_FACTORY__POST_R3__ as ɵSWITCH_RENDERER2_FACTORY__POST_R3__,
|
||||
} from './render/api';
|
||||
|
||||
export {getModuleFactory__POST_R3__ as ɵgetModuleFactory__POST_R3__} from './linker/ng_module_factory_loader';
|
||||
|
||||
export {
|
||||
publishGlobalUtil as ɵpublishGlobalUtil,
|
||||
publishDefaultGlobalUtils as ɵpublishDefaultGlobalUtils
|
||||
|
|
|
@ -168,7 +168,7 @@ export function defineInjector(options: {factory: () => any, providers?: any[],
|
|||
* @param type type which may have `ngInjectableDef`
|
||||
*/
|
||||
export function getInjectableDef<T>(type: any): InjectableDef<T>|null {
|
||||
return type.hasOwnProperty(NG_INJECTABLE_DEF) ? (type as any)[NG_INJECTABLE_DEF] : null;
|
||||
return type && type.hasOwnProperty(NG_INJECTABLE_DEF) ? (type as any)[NG_INJECTABLE_DEF] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -177,5 +177,5 @@ export function getInjectableDef<T>(type: any): InjectableDef<T>|null {
|
|||
* @param type type which may have `ngInjectorDef`
|
||||
*/
|
||||
export function getInjectorDef<T>(type: any): InjectorDef<T>|null {
|
||||
return type.hasOwnProperty(NG_INJECTOR_DEF) ? (type as any)[NG_INJECTOR_DEF] : null;
|
||||
return type && type.hasOwnProperty(NG_INJECTOR_DEF) ? (type as any)[NG_INJECTOR_DEF] : null;
|
||||
}
|
|
@ -111,8 +111,9 @@ export class R3Injector {
|
|||
const dedupStack: InjectorType<any>[] = [];
|
||||
deepForEach([def], injectorDef => this.processInjectorType(injectorDef, [], dedupStack));
|
||||
|
||||
additionalProviders &&
|
||||
deepForEach(additionalProviders, provider => this.processProvider(provider));
|
||||
additionalProviders && deepForEach(
|
||||
additionalProviders, provider => this.processProvider(
|
||||
provider, def, additionalProviders));
|
||||
|
||||
|
||||
// Make sure the INJECTOR token provides this injector.
|
||||
|
@ -270,25 +271,31 @@ export class R3Injector {
|
|||
}
|
||||
|
||||
// Next, include providers listed on the definition itself.
|
||||
if (def.providers != null && !isDuplicate) {
|
||||
deepForEach(def.providers, provider => this.processProvider(provider));
|
||||
const defProviders = def.providers;
|
||||
if (defProviders != null && !isDuplicate) {
|
||||
const injectorType = defOrWrappedDef as InjectorType<any>;
|
||||
deepForEach(
|
||||
defProviders, provider => this.processProvider(provider, injectorType, defProviders));
|
||||
}
|
||||
|
||||
// Finally, include providers from an InjectorDefTypeWithProviders if there was one.
|
||||
deepForEach(providers, provider => this.processProvider(provider));
|
||||
const ngModuleType = (defOrWrappedDef as InjectorTypeWithProviders<any>).ngModule;
|
||||
deepForEach(providers, provider => this.processProvider(provider, ngModuleType, providers));
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a `SingleProvider` and add it.
|
||||
*/
|
||||
private processProvider(provider: SingleProvider): void {
|
||||
private processProvider(
|
||||
provider: SingleProvider, ngModuleType: InjectorType<any>, providers: any[]): void {
|
||||
// Determine the token from the provider. Either it's its own token, or has a {provide: ...}
|
||||
// property.
|
||||
provider = resolveForwardRef(provider);
|
||||
let token: any = isTypeProvider(provider) ? provider : resolveForwardRef(provider.provide);
|
||||
let token: any =
|
||||
isTypeProvider(provider) ? provider : resolveForwardRef(provider && provider.provide);
|
||||
|
||||
// Construct a `Record` for the provider.
|
||||
const record = providerToRecord(provider);
|
||||
const record = providerToRecord(provider, ngModuleType, providers);
|
||||
|
||||
if (!isTypeProvider(provider) && provider.multi === true) {
|
||||
// If the provider indicates that it's a multi-provider, process it specially.
|
||||
|
@ -317,7 +324,7 @@ export class R3Injector {
|
|||
|
||||
private hydrate<T>(token: Type<T>|InjectionToken<T>, record: Record<T>): T {
|
||||
if (record.value === CIRCULAR) {
|
||||
throw new Error(`Circular dep for ${stringify(token)}`);
|
||||
throw new Error(`Cannot instantiate cyclic dependency! ${stringify(token)}`);
|
||||
} else if (record.value === NOT_YET) {
|
||||
record.value = CIRCULAR;
|
||||
record.value = record.factory !();
|
||||
|
@ -345,20 +352,25 @@ function injectableDefOrInjectorDefFactory(token: Type<any>| InjectionToken<any>
|
|||
const injectorDef = getInjectorDef(token as InjectorType<any>);
|
||||
if (injectorDef !== null) {
|
||||
return injectorDef.factory;
|
||||
}
|
||||
|
||||
if (token instanceof InjectionToken) {
|
||||
} else if (token instanceof InjectionToken) {
|
||||
throw new Error(`Token ${stringify(token)} is missing an ngInjectableDef definition.`);
|
||||
} else if (token instanceof Function) {
|
||||
const paramLength = token.length;
|
||||
if (paramLength > 0) {
|
||||
const args: string[] = new Array(paramLength).fill('?');
|
||||
throw new Error(
|
||||
`Can't resolve all parameters for ${stringify(token)}: (${args.join(', ')}).`);
|
||||
}
|
||||
return () => new (token as Type<any>)();
|
||||
}
|
||||
// TODO(alxhub): there should probably be a strict mode which throws here instead of assuming a
|
||||
// no-args constructor.
|
||||
return () => new (token as Type<any>)();
|
||||
throw new Error('unreachable');
|
||||
}
|
||||
return injectableDef.factory;
|
||||
}
|
||||
|
||||
function providerToRecord(provider: SingleProvider): Record<any> {
|
||||
let factory: (() => any)|undefined = providerToFactory(provider);
|
||||
function providerToRecord(
|
||||
provider: SingleProvider, ngModuleType: InjectorType<any>, providers: any[]): Record<any> {
|
||||
let factory: (() => any)|undefined = providerToFactory(provider, ngModuleType, providers);
|
||||
if (isValueProvider(provider)) {
|
||||
return makeRecord(undefined, provider.useValue);
|
||||
} else {
|
||||
|
@ -371,7 +383,8 @@ function providerToRecord(provider: SingleProvider): Record<any> {
|
|||
*
|
||||
* @param provider provider to convert to factory
|
||||
*/
|
||||
export function providerToFactory(provider: SingleProvider): () => any {
|
||||
export function providerToFactory(
|
||||
provider: SingleProvider, ngModuleType?: InjectorType<any>, providers?: any[]): () => any {
|
||||
let factory: (() => any)|undefined = undefined;
|
||||
if (isTypeProvider(provider)) {
|
||||
return injectableDefOrInjectorDefFactory(resolveForwardRef(provider));
|
||||
|
@ -384,7 +397,18 @@ export function providerToFactory(provider: SingleProvider): () => any {
|
|||
factory = () => provider.useFactory(...injectArgs(provider.deps || []));
|
||||
} else {
|
||||
const classRef = resolveForwardRef(
|
||||
(provider as StaticClassProvider | ClassProvider).useClass || provider.provide);
|
||||
provider &&
|
||||
((provider as StaticClassProvider | ClassProvider).useClass || provider.provide));
|
||||
if (!classRef) {
|
||||
let ngModuleDetail = '';
|
||||
if (ngModuleType && providers) {
|
||||
const providerDetail = providers.map(v => v == provider ? '?' + provider + '?' : '...');
|
||||
ngModuleDetail =
|
||||
` - only instances of Provider and Type are allowed, got: [${providerDetail.join(', ')}]`;
|
||||
}
|
||||
throw new Error(
|
||||
`Invalid provider for the NgModule '${stringify(ngModuleType)}'` + ngModuleDetail);
|
||||
}
|
||||
if (hasDeps(provider)) {
|
||||
factory = () => new (classRef)(...injectArgs(provider.deps));
|
||||
} else {
|
||||
|
@ -409,15 +433,15 @@ function deepForEach<T>(input: (T | any[])[], fn: (value: T) => void): void {
|
|||
}
|
||||
|
||||
function isValueProvider(value: SingleProvider): value is ValueProvider {
|
||||
return USE_VALUE in value;
|
||||
return value && typeof value == 'object' && USE_VALUE in value;
|
||||
}
|
||||
|
||||
function isExistingProvider(value: SingleProvider): value is ExistingProvider {
|
||||
return !!(value as ExistingProvider).useExisting;
|
||||
return !!(value && (value as ExistingProvider).useExisting);
|
||||
}
|
||||
|
||||
function isFactoryProvider(value: SingleProvider): value is FactoryProvider {
|
||||
return !!(value as FactoryProvider).useFactory;
|
||||
return !!(value && (value as FactoryProvider).useFactory);
|
||||
}
|
||||
|
||||
export function isTypeProvider(value: SingleProvider): value is TypeProvider {
|
||||
|
|
|
@ -6,6 +6,9 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {NgModuleFactory as R3NgModuleFactory, NgModuleType} from '../render3/ng_module_ref';
|
||||
import {Type} from '../type';
|
||||
import {stringify} from '../util';
|
||||
import {NgModuleFactory} from './ng_module_factory';
|
||||
|
||||
/**
|
||||
|
@ -17,23 +20,50 @@ export abstract class NgModuleFactoryLoader {
|
|||
abstract load(path: string): Promise<NgModuleFactory<any>>;
|
||||
}
|
||||
|
||||
let moduleFactories = new Map<string, NgModuleFactory<any>>();
|
||||
/**
|
||||
* Map of module-id to the corresponding NgModule.
|
||||
* - In pre Ivy we track NgModuleFactory,
|
||||
* - In post Ivy we track the NgModuleType
|
||||
*/
|
||||
const modules = new Map<string, NgModuleFactory<any>|NgModuleType>();
|
||||
|
||||
/**
|
||||
* Registers a loaded module. Should only be called from generated NgModuleFactory code.
|
||||
* @publicApi
|
||||
*/
|
||||
export function registerModuleFactory(id: string, factory: NgModuleFactory<any>) {
|
||||
const existing = moduleFactories.get(id);
|
||||
if (existing) {
|
||||
throw new Error(`Duplicate module registered for ${id
|
||||
} - ${existing.moduleType.name} vs ${factory.moduleType.name}`);
|
||||
}
|
||||
moduleFactories.set(id, factory);
|
||||
const existing = modules.get(id) as NgModuleFactory<any>;
|
||||
assertNotExisting(id, existing && existing.moduleType);
|
||||
modules.set(id, factory);
|
||||
}
|
||||
|
||||
export function clearModulesForTest() {
|
||||
moduleFactories = new Map<string, NgModuleFactory<any>>();
|
||||
function assertNotExisting(id: string, type: Type<any>| null): void {
|
||||
if (type) {
|
||||
throw new Error(
|
||||
`Duplicate module registered for ${id} - ${stringify(type)} vs ${stringify(type.name)}`);
|
||||
}
|
||||
}
|
||||
|
||||
export function registerNgModuleType(id: string, ngModuleType: NgModuleType) {
|
||||
const existing = modules.get(id) as NgModuleType | null;
|
||||
assertNotExisting(id, existing);
|
||||
modules.set(id, ngModuleType);
|
||||
}
|
||||
|
||||
export function clearModulesForTest(): void {
|
||||
modules.clear();
|
||||
}
|
||||
|
||||
export function getModuleFactory__PRE_R3__(id: string): NgModuleFactory<any> {
|
||||
const factory = modules.get(id) as NgModuleFactory<any>| null;
|
||||
if (!factory) throw noModuleError(id);
|
||||
return factory;
|
||||
}
|
||||
|
||||
export function getModuleFactory__POST_R3__(id: string): NgModuleFactory<any> {
|
||||
const type = modules.get(id) as NgModuleType | null;
|
||||
if (!type) throw noModuleError(id);
|
||||
return new R3NgModuleFactory(type);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -42,8 +72,8 @@ export function clearModulesForTest() {
|
|||
* cannot be found.
|
||||
* @publicApi
|
||||
*/
|
||||
export function getModuleFactory(id: string): NgModuleFactory<any> {
|
||||
const factory = moduleFactories.get(id);
|
||||
if (!factory) throw new Error(`No module with ID ${id} loaded`);
|
||||
return factory;
|
||||
export const getModuleFactory: (id: string) => NgModuleFactory<any> = getModuleFactory__PRE_R3__;
|
||||
|
||||
function noModuleError(id: string, ): Error {
|
||||
return new Error(`No module with ID ${id} loaded`);
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import {ApplicationRef} from '../application_ref';
|
|||
import {InjectorType, defineInjector} from '../di/defs';
|
||||
import {Provider} from '../di/provider';
|
||||
import {convertInjectableProviderToFactory} from '../di/util';
|
||||
import {NgModuleType} from '../render3';
|
||||
import {compileNgModule as render3CompileNgModule} from '../render3/jit/module';
|
||||
import {Type} from '../type';
|
||||
import {TypeDecorator, makeDecorator} from '../util/decorators';
|
||||
|
@ -337,7 +338,7 @@ export const NgModule: NgModuleDecorator = makeDecorator(
|
|||
* * The `imports` and `exports` options bring in members from other modules, and make
|
||||
* this module's members available to others.
|
||||
*/
|
||||
(type: Type<any>, meta: NgModule) => SWITCH_COMPILE_NGMODULE(type, meta));
|
||||
(type: NgModuleType, meta: NgModule) => SWITCH_COMPILE_NGMODULE(type, meta));
|
||||
|
||||
/**
|
||||
* @description
|
||||
|
|
|
@ -13,7 +13,7 @@ import {Provider} from '../di/provider';
|
|||
import {NgModuleDef} from '../metadata/ng_module';
|
||||
import {ViewEncapsulation} from '../metadata/view';
|
||||
import {Mutable, Type} from '../type';
|
||||
import {noSideEffects} from '../util';
|
||||
import {noSideEffects, stringify} from '../util';
|
||||
|
||||
import {NG_COMPONENT_DEF, NG_DIRECTIVE_DEF, NG_MODULE_DEF, NG_PIPE_DEF} from './fields';
|
||||
import {BaseDef, ComponentDef, ComponentDefFeature, ComponentQuery, ComponentTemplate, ComponentType, DirectiveDef, DirectiveDefFeature, DirectiveType, DirectiveTypesOrFactory, HostBindingsFunction, PipeDef, PipeType, PipeTypesOrFactory} from './interfaces/definition';
|
||||
|
@ -651,6 +651,12 @@ export function getPipeDef<T>(type: any): PipeDef<T>|null {
|
|||
return (type as any)[NG_PIPE_DEF] || null;
|
||||
}
|
||||
|
||||
export function getNgModuleDef<T>(type: any): NgModuleDef<T>|null {
|
||||
return (type as any)[NG_MODULE_DEF] || null;
|
||||
export function getNgModuleDef<T>(type: any, throwNotFound: true): NgModuleDef<T>;
|
||||
export function getNgModuleDef<T>(type: any): NgModuleDef<T>|null;
|
||||
export function getNgModuleDef<T>(type: any, throwNotFound?: boolean): NgModuleDef<T>|null {
|
||||
const ngModuleDef = (type as any)[NG_MODULE_DEF] || null;
|
||||
if (!ngModuleDef && throwNotFound === true) {
|
||||
throw new Error(`Type ${stringify(type)} does not have 'ngModuleDef' property.`);
|
||||
}
|
||||
return ngModuleDef;
|
||||
}
|
||||
|
|
|
@ -101,8 +101,8 @@ export interface R3InjectorMetadataFacade {
|
|||
name: string;
|
||||
type: any;
|
||||
deps: R3DependencyMetadataFacade[]|null;
|
||||
providers: any;
|
||||
imports: any;
|
||||
providers: any[];
|
||||
imports: any[];
|
||||
}
|
||||
|
||||
export interface R3DirectiveMetadataFacade {
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {ComponentType} from '..';
|
||||
import {Query} from '../../metadata/di';
|
||||
import {Component, Directive} from '../../metadata/directives';
|
||||
import {componentNeedsResolution, maybeQueueResolutionOfComponentResources} from '../../metadata/resource_loading';
|
||||
|
@ -58,7 +59,7 @@ export function compileComponent(type: Type<any>, metadata: Component): void {
|
|||
preserveWhitespaces: metadata.preserveWhitespaces || false,
|
||||
styles: metadata.styles || EMPTY_ARRAY,
|
||||
animations: metadata.animations,
|
||||
viewQueries: extractQueriesMetadata(getReflect().propMetadata(type), isViewQuery),
|
||||
viewQueries: extractQueriesMetadata(type, getReflect().propMetadata(type), isViewQuery),
|
||||
directives: [],
|
||||
pipes: new Map(),
|
||||
encapsulation: metadata.encapsulation || ViewEncapsulation.Emulated,
|
||||
|
@ -108,7 +109,7 @@ export function compileDirective(type: Type<any>, directive: Directive): void {
|
|||
Object.defineProperty(type, NG_DIRECTIVE_DEF, {
|
||||
get: () => {
|
||||
if (ngDirectiveDef === null) {
|
||||
const facade = directiveMetadata(type, directive);
|
||||
const facade = directiveMetadata(type as ComponentType<any>, directive);
|
||||
ngDirectiveDef = getCompilerFacade().compileDirective(
|
||||
angularCoreEnv, `ng://${type && type.name}/ngDirectiveDef.js`, facade);
|
||||
}
|
||||
|
@ -141,7 +142,7 @@ function directiveMetadata(type: Type<any>, metadata: Directive): R3DirectiveMet
|
|||
propMetadata: propMetadata,
|
||||
inputs: metadata.inputs || EMPTY_ARRAY,
|
||||
outputs: metadata.outputs || EMPTY_ARRAY,
|
||||
queries: extractQueriesMetadata(propMetadata, isContentQuery),
|
||||
queries: extractQueriesMetadata(type, propMetadata, isContentQuery),
|
||||
lifecycle: {
|
||||
usesOnChanges: type.prototype.ngOnChanges !== undefined,
|
||||
},
|
||||
|
@ -168,13 +169,18 @@ export function convertToR3QueryMetadata(propertyName: string, ann: Query): R3Qu
|
|||
};
|
||||
}
|
||||
function extractQueriesMetadata(
|
||||
propMetadata: {[key: string]: any[]},
|
||||
type: Type<any>, propMetadata: {[key: string]: any[]},
|
||||
isQueryAnn: (ann: any) => ann is Query): R3QueryMetadataFacade[] {
|
||||
const queriesMeta: R3QueryMetadataFacade[] = [];
|
||||
for (const field in propMetadata) {
|
||||
if (propMetadata.hasOwnProperty(field)) {
|
||||
propMetadata[field].forEach(ann => {
|
||||
if (isQueryAnn(ann)) {
|
||||
if (!ann.selector) {
|
||||
throw new Error(
|
||||
`Can't construct a query for the property "${field}" of ` +
|
||||
`"${stringify(type)}" since the query selector wasn't defined.`);
|
||||
}
|
||||
queriesMeta.push(convertToR3QueryMetadata(field, ann));
|
||||
}
|
||||
});
|
||||
|
|
|
@ -7,12 +7,16 @@
|
|||
*/
|
||||
|
||||
import {resolveForwardRef} from '../../di/forward_ref';
|
||||
import {registerNgModuleType} from '../../linker/ng_module_factory_loader';
|
||||
import {Component} from '../../metadata';
|
||||
import {ModuleWithProviders, NgModule, NgModuleDef, NgModuleTransitiveScopes} from '../../metadata/ng_module';
|
||||
import {Type} from '../../type';
|
||||
import {assertDefined} from '../assert';
|
||||
import {getComponentDef, getDirectiveDef, getNgModuleDef, getPipeDef} from '../definition';
|
||||
import {NG_COMPONENT_DEF, NG_DIRECTIVE_DEF, NG_INJECTOR_DEF, NG_MODULE_DEF, NG_PIPE_DEF} from '../fields';
|
||||
import {ComponentDef} from '../interfaces/definition';
|
||||
import {NgModuleType} from '../ng_module_ref';
|
||||
import {stringify} from '../util';
|
||||
|
||||
import {R3InjectorMetadataFacade, getCompilerFacade} from './compiler_facade';
|
||||
import {angularCoreEnv} from './environment';
|
||||
|
@ -44,16 +48,19 @@ let flushingModuleQueue = false;
|
|||
export function flushModuleScopingQueueAsMuchAsPossible() {
|
||||
if (!flushingModuleQueue) {
|
||||
flushingModuleQueue = true;
|
||||
for (let i = moduleQueue.length - 1; i >= 0; i--) {
|
||||
const {moduleType, ngModule} = moduleQueue[i];
|
||||
try {
|
||||
for (let i = moduleQueue.length - 1; i >= 0; i--) {
|
||||
const {moduleType, ngModule} = moduleQueue[i];
|
||||
|
||||
if (ngModule.declarations && ngModule.declarations.every(isResolvedDeclaration)) {
|
||||
// dequeue
|
||||
moduleQueue.splice(i, 1);
|
||||
setScopeOnDeclaredComponents(moduleType, ngModule);
|
||||
if (ngModule.declarations && ngModule.declarations.every(isResolvedDeclaration)) {
|
||||
// dequeue
|
||||
moduleQueue.splice(i, 1);
|
||||
setScopeOnDeclaredComponents(moduleType, ngModule);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
flushingModuleQueue = false;
|
||||
}
|
||||
flushingModuleQueue = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -75,7 +82,7 @@ function isResolvedDeclaration(declaration: any[] | Type<any>): boolean {
|
|||
* This function automatically gets called when a class has a `@NgModule` decorator.
|
||||
*/
|
||||
export function compileNgModule(moduleType: Type<any>, ngModule: NgModule = {}): void {
|
||||
compileNgModuleDefs(moduleType, ngModule);
|
||||
compileNgModuleDefs(moduleType as NgModuleType, ngModule);
|
||||
|
||||
// Because we don't know if all declarations have resolved yet at the moment the
|
||||
// NgModule decorator is executing, we're enqueueing the setting of module scope
|
||||
|
@ -87,7 +94,7 @@ export function compileNgModule(moduleType: Type<any>, ngModule: NgModule = {}):
|
|||
/**
|
||||
* Compiles and adds the `ngModuleDef` and `ngInjectorDef` properties to the module class.
|
||||
*/
|
||||
export function compileNgModuleDefs(moduleType: Type<any>, ngModule: NgModule): void {
|
||||
export function compileNgModuleDefs(moduleType: NgModuleType, ngModule: NgModule): void {
|
||||
ngDevMode && assertDefined(moduleType, 'Required value moduleType');
|
||||
ngDevMode && assertDefined(ngModule, 'Required value ngModule');
|
||||
const declarations: Type<any>[] = flatten(ngModule.declarations || EMPTY_ARRAY);
|
||||
|
@ -110,11 +117,15 @@ export function compileNgModuleDefs(moduleType: Type<any>, ngModule: NgModule):
|
|||
return ngModuleDef;
|
||||
}
|
||||
});
|
||||
if (ngModule.id) {
|
||||
registerNgModuleType(ngModule.id, moduleType);
|
||||
}
|
||||
|
||||
let ngInjectorDef: any = null;
|
||||
Object.defineProperty(moduleType, NG_INJECTOR_DEF, {
|
||||
get: () => {
|
||||
if (ngInjectorDef === null) {
|
||||
ngDevMode && verifySemanticsOfNgModuleDef(moduleType as any as NgModuleType);
|
||||
const meta: R3InjectorMetadataFacade = {
|
||||
name: moduleType.name,
|
||||
type: moduleType,
|
||||
|
@ -135,6 +146,164 @@ export function compileNgModuleDefs(moduleType: Type<any>, ngModule: NgModule):
|
|||
});
|
||||
}
|
||||
|
||||
function verifySemanticsOfNgModuleDef(moduleType: NgModuleType): void {
|
||||
if (verifiedNgModule.get(moduleType)) return;
|
||||
verifiedNgModule.set(moduleType, true);
|
||||
moduleType = resolveForwardRef(moduleType);
|
||||
const ngModuleDef = getNgModuleDef(moduleType, true);
|
||||
const errors: string[] = [];
|
||||
ngModuleDef.declarations.forEach(verifyDeclarationsHaveDefinitions);
|
||||
const combinedDeclarations: Type<any>[] = [
|
||||
...ngModuleDef.declarations, //
|
||||
...flatten(ngModuleDef.imports.map(computeCombinedExports)),
|
||||
];
|
||||
ngModuleDef.exports.forEach(verifyExportsAreDeclaredOrReExported);
|
||||
ngModuleDef.declarations.forEach(verifyDeclarationIsUnique);
|
||||
ngModuleDef.declarations.forEach(verifyComponentEntryComponentsIsPartOfNgModule);
|
||||
|
||||
const ngModule = getAnnotation<NgModule>(moduleType, 'NgModule');
|
||||
if (ngModule) {
|
||||
ngModule.imports &&
|
||||
flatten(ngModule.imports, unwrapModuleWithProvidersImports)
|
||||
.forEach(verifySemanticsOfNgModuleDef);
|
||||
ngModule.bootstrap && ngModule.bootstrap.forEach(verifyComponentIsPartOfNgModule);
|
||||
ngModule.entryComponents && ngModule.entryComponents.forEach(verifyComponentIsPartOfNgModule);
|
||||
}
|
||||
|
||||
// Throw Error if any errors were detected.
|
||||
if (errors.length) {
|
||||
throw new Error(errors.join('\n'));
|
||||
}
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
function verifyDeclarationsHaveDefinitions(type: Type<any>): void {
|
||||
type = resolveForwardRef(type);
|
||||
const def = getComponentDef(type) || getDirectiveDef(type) || getPipeDef(type);
|
||||
if (!def) {
|
||||
errors.push(
|
||||
`Unexpected value '${stringify(type)}' declared by the module '${stringify(moduleType)}'. Please add a @Pipe/@Directive/@Component annotation.`);
|
||||
}
|
||||
}
|
||||
|
||||
function verifyExportsAreDeclaredOrReExported(type: Type<any>) {
|
||||
type = resolveForwardRef(type);
|
||||
const kind = getComponentDef(type) && 'component' || getDirectiveDef(type) && 'directive' ||
|
||||
getPipeDef(type) && 'pipe';
|
||||
if (kind) {
|
||||
// only checked if we are declared as Component, Directive, or Pipe
|
||||
// Modules don't need to be declared or imported.
|
||||
if (combinedDeclarations.lastIndexOf(type) === -1) {
|
||||
// We are exporting something which we don't explicitly declare or import.
|
||||
errors.push(
|
||||
`Can't export ${kind} ${stringify(type)} from ${stringify(moduleType)} as it was neither declared nor imported!`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function verifyDeclarationIsUnique(type: Type<any>) {
|
||||
type = resolveForwardRef(type);
|
||||
const existingModule = ownerNgModule.get(type);
|
||||
if (existingModule && existingModule !== moduleType) {
|
||||
const modules = [existingModule, moduleType].map(stringify).sort();
|
||||
errors.push(
|
||||
`Type ${stringify(type)} is part of the declarations of 2 modules: ${modules[0]} and ${modules[1]}! ` +
|
||||
`Please consider moving ${stringify(type)} to a higher module that imports ${modules[0]} and ${modules[1]}. ` +
|
||||
`You can also create a new NgModule that exports and includes ${stringify(type)} then import that NgModule in ${modules[0]} and ${modules[1]}.`);
|
||||
} else {
|
||||
// Mark type as having owner.
|
||||
ownerNgModule.set(type, moduleType);
|
||||
}
|
||||
}
|
||||
|
||||
function verifyComponentIsPartOfNgModule(type: Type<any>) {
|
||||
type = resolveForwardRef(type);
|
||||
const existingModule = ownerNgModule.get(type);
|
||||
if (!existingModule) {
|
||||
errors.push(
|
||||
`Component ${stringify(type)} is not part of any NgModule or the module has not been imported into your module.`);
|
||||
}
|
||||
}
|
||||
|
||||
function verifyComponentEntryComponentsIsPartOfNgModule(type: Type<any>) {
|
||||
type = resolveForwardRef(type);
|
||||
if (getComponentDef(type)) {
|
||||
// We know we are component
|
||||
const component = getAnnotation<Component>(type, 'Component');
|
||||
if (component && component.entryComponents) {
|
||||
component.entryComponents.forEach(verifyComponentIsPartOfNgModule);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function unwrapModuleWithProvidersImports(
|
||||
typeOrWithProviders: NgModuleType<any>| {ngModule: NgModuleType<any>}): NgModuleType<any> {
|
||||
typeOrWithProviders = resolveForwardRef(typeOrWithProviders);
|
||||
return (typeOrWithProviders as any).ngModule || typeOrWithProviders;
|
||||
}
|
||||
|
||||
function getAnnotation<T>(type: any, name: string): T|null {
|
||||
let annotation: T|null = null;
|
||||
collect(type.__annotations__);
|
||||
collect(type.decorators);
|
||||
return annotation;
|
||||
|
||||
function collect(annotations: any[] | null) {
|
||||
if (annotations) {
|
||||
annotations.forEach(readAnnotation);
|
||||
}
|
||||
}
|
||||
|
||||
function readAnnotation(
|
||||
decorator: {type: {prototype: {ngMetadataName: string}, args: any[]}, args: any}): void {
|
||||
if (!annotation) {
|
||||
const proto = Object.getPrototypeOf(decorator);
|
||||
if (proto.ngMetadataName == name) {
|
||||
annotation = decorator as any;
|
||||
} else if (decorator.type) {
|
||||
const proto = Object.getPrototypeOf(decorator.type);
|
||||
if (proto.ngMetadataName == name) {
|
||||
annotation = decorator.args[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Keep track of compiled components. This is needed because in tests we often want to compile the
|
||||
* same component with more than one NgModule. This would cause an error unless we reset which
|
||||
* NgModule the component belongs to. We keep the list of compiled components here so that the
|
||||
* TestBed can reset it later.
|
||||
*/
|
||||
let ownerNgModule = new Map<Type<any>, NgModuleType<any>>();
|
||||
let verifiedNgModule = new Map<NgModuleType<any>, boolean>();
|
||||
|
||||
export function resetCompiledComponents(): void {
|
||||
ownerNgModule = new Map<Type<any>, NgModuleType<any>>();
|
||||
verifiedNgModule = new Map<NgModuleType<any>, boolean>();
|
||||
moduleQueue.length = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the combined declarations of explicit declarations, as well as declarations inherited
|
||||
* by
|
||||
* traversing the exports of imported modules.
|
||||
* @param type
|
||||
*/
|
||||
function computeCombinedExports(type: Type<any>): Type<any>[] {
|
||||
type = resolveForwardRef(type);
|
||||
const ngModuleDef = getNgModuleDef(type, true);
|
||||
return [...flatten(ngModuleDef.exports.map((type) => {
|
||||
const ngModuleDef = getNgModuleDef(type);
|
||||
if (ngModuleDef) {
|
||||
verifySemanticsOfNgModuleDef(type as any as NgModuleType);
|
||||
return computeCombinedExports(type);
|
||||
} else {
|
||||
return type;
|
||||
}
|
||||
}))];
|
||||
}
|
||||
|
||||
/**
|
||||
* Some declared components may be compiled asynchronously, and thus may not have their
|
||||
* ngComponentDef set yet. If this is the case, then a reference to the module is written into
|
||||
|
@ -264,13 +433,13 @@ export function transitiveScopesFor<T>(moduleType: Type<T>): NgModuleTransitiveS
|
|||
return scopes;
|
||||
}
|
||||
|
||||
function flatten<T>(values: any[]): T[] {
|
||||
const out: T[] = [];
|
||||
function flatten<T>(values: any[], mapFn?: (value: T) => any): Type<T>[] {
|
||||
const out: Type<T>[] = [];
|
||||
values.forEach(value => {
|
||||
if (Array.isArray(value)) {
|
||||
out.push(...flatten<T>(value));
|
||||
out.push(...flatten<T>(value, mapFn));
|
||||
} else {
|
||||
out.push(value);
|
||||
out.push(mapFn ? mapFn(value) : value);
|
||||
}
|
||||
});
|
||||
return out;
|
||||
|
|
|
@ -20,7 +20,7 @@ import {assertDefined} from './assert';
|
|||
import {ComponentFactoryResolver} from './component_ref';
|
||||
import {getNgModuleDef} from './definition';
|
||||
|
||||
export interface NgModuleType { ngModuleDef: NgModuleDef<any>; }
|
||||
export interface NgModuleType<T = any> extends Type<T> { ngModuleDef: NgModuleDef<T>; }
|
||||
|
||||
const COMPONENT_FACTORY_RESOLVER: StaticProvider = {
|
||||
provide: viewEngine_ComponentFactoryResolver,
|
||||
|
|
|
@ -62,7 +62,7 @@ function getPipeDef(name: string, registry: PipeDefList | null): PipeDef<any> {
|
|||
}
|
||||
}
|
||||
}
|
||||
throw new Error(`Pipe with name '${name}' not found!`);
|
||||
throw new Error(`The pipe '${name}' could not be found!`);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -195,9 +195,6 @@ describe('InjectorDef-based createInjector()', () => {
|
|||
expect(injector.get(Service)).toBe(instance);
|
||||
});
|
||||
|
||||
it('throws an error when a token is not found',
|
||||
() => { expect(() => injector.get(ServiceTwo)).toThrow(); });
|
||||
|
||||
it('returns the default value if a provider isn\'t present',
|
||||
() => { expect(injector.get(ServiceTwo, null)).toBeNull(); });
|
||||
|
||||
|
@ -240,9 +237,6 @@ describe('InjectorDef-based createInjector()', () => {
|
|||
expect(instance.dep).toBe(injector.get(Service));
|
||||
});
|
||||
|
||||
it('throws an error on circular deps',
|
||||
() => { expect(() => injector.get(CircularA)).toThrow(); });
|
||||
|
||||
it('allows injecting itself via INJECTOR',
|
||||
() => { expect(injector.get(INJECTOR)).toBe(injector); });
|
||||
|
||||
|
@ -287,4 +281,24 @@ describe('InjectorDef-based createInjector()', () => {
|
|||
injector = createInjector(ImportsNotAModule);
|
||||
expect(injector.get(ImportsNotAModule)).toBeDefined();
|
||||
});
|
||||
|
||||
describe('error handling', () => {
|
||||
it('throws an error when a token is not found',
|
||||
() => { expect(() => injector.get(ServiceTwo)).toThrow(); });
|
||||
|
||||
it('throws an error on circular deps',
|
||||
() => { expect(() => injector.get(CircularA)).toThrow(); });
|
||||
|
||||
it('should throw when it can\'t resolve all arguments', () => {
|
||||
class MissingArgumentType {
|
||||
constructor(missingType: any) {}
|
||||
}
|
||||
class ErrorModule {
|
||||
static ngInjectorDef =
|
||||
defineInjector({factory: () => new ErrorModule(), providers: [MissingArgumentType]});
|
||||
}
|
||||
expect(() => createInjector(ErrorModule).get(MissingArgumentType))
|
||||
.toThrowError('Can\'t resolve all parameters for MissingArgumentType: (?).');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1492,28 +1492,23 @@ function declareTests(config?: {useJit: boolean}) {
|
|||
});
|
||||
|
||||
describe('error handling', () => {
|
||||
fixmeIvy('FW-682: TestBed: tests assert that compilation produces specific error')
|
||||
.it('should report a meaningful error when a directive is missing annotation', () => {
|
||||
TestBed.configureTestingModule(
|
||||
{declarations: [MyComp, SomeDirectiveMissingAnnotation]});
|
||||
it('should report a meaningful error when a directive is missing annotation', () => {
|
||||
TestBed.configureTestingModule({declarations: [MyComp, SomeDirectiveMissingAnnotation]});
|
||||
|
||||
expect(() => TestBed.createComponent(MyComp))
|
||||
.toThrowError(
|
||||
`Unexpected value '${stringify(SomeDirectiveMissingAnnotation)}' declared by the module 'DynamicTestModule'. Please add a @Pipe/@Directive/@Component annotation.`);
|
||||
});
|
||||
expect(() => TestBed.createComponent(MyComp))
|
||||
.toThrowError(
|
||||
`Unexpected value '${stringify(SomeDirectiveMissingAnnotation)}' declared by the module 'DynamicTestModule'. Please add a @Pipe/@Directive/@Component annotation.`);
|
||||
});
|
||||
|
||||
fixmeIvy('FW-682: TestBed: tests assert that compilation produces specific error')
|
||||
.it('should report a meaningful error when a component is missing view annotation',
|
||||
() => {
|
||||
TestBed.configureTestingModule({declarations: [MyComp, ComponentWithoutView]});
|
||||
try {
|
||||
TestBed.createComponent(ComponentWithoutView);
|
||||
expect(true).toBe(false);
|
||||
} catch (e) {
|
||||
expect(e.message).toContain(
|
||||
`No template specified for component ${stringify(ComponentWithoutView)}`);
|
||||
}
|
||||
});
|
||||
it('should report a meaningful error when a component is missing view annotation', () => {
|
||||
TestBed.configureTestingModule({declarations: [MyComp, ComponentWithoutView]});
|
||||
try {
|
||||
TestBed.createComponent(ComponentWithoutView);
|
||||
} catch (e) {
|
||||
expect(e.message).toContain(
|
||||
`No template specified for component ${stringify(ComponentWithoutView)}`);
|
||||
}
|
||||
});
|
||||
|
||||
fixmeIvy('FW-722: getDebugContext needs to be replaced / re-implemented')
|
||||
.it('should provide an error context when an error happens in DI', () => {
|
||||
|
|
|
@ -9,11 +9,12 @@
|
|||
import {ANALYZE_FOR_ENTRY_COMPONENTS, CUSTOM_ELEMENTS_SCHEMA, Compiler, Component, ComponentFactoryResolver, Directive, HostBinding, Inject, Injectable, InjectionToken, Injector, Input, NgModule, NgModuleRef, Optional, Pipe, Provider, Self, Type, forwardRef, getModuleFactory, ɵivyEnabled as ivyEnabled} from '@angular/core';
|
||||
import {Console} from '@angular/core/src/console';
|
||||
import {InjectableDef, defineInjectable} from '@angular/core/src/di/defs';
|
||||
import {getNgModuleDef} from '@angular/core/src/render3/definition';
|
||||
import {NgModuleData} from '@angular/core/src/view/types';
|
||||
import {tokenKey} from '@angular/core/src/view/util';
|
||||
import {ComponentFixture, TestBed, inject} from '@angular/core/testing';
|
||||
import {expect} from '@angular/platform-browser/testing/src/matchers';
|
||||
import {fixmeIvy} from '@angular/private/testing';
|
||||
import {fixmeIvy, modifiedInIvy, obsoleteInIvy} from '@angular/private/testing';
|
||||
|
||||
import {InternalNgModuleRef, NgModuleFactory} from '../../src/linker/ng_module_factory';
|
||||
import {clearModulesForTest} from '../../src/linker/ng_module_factory_loader';
|
||||
|
@ -129,6 +130,8 @@ function declareTests(config?: {useJit: boolean}) {
|
|||
|
||||
function createModule<T>(
|
||||
moduleType: Type<T>, parentInjector?: Injector | null): NgModuleRef<T> {
|
||||
// Read the `ngModuleDef` to cause it to be compiled and any errors thrown.
|
||||
getNgModuleDef(moduleType);
|
||||
return createModuleFactory(moduleType).create(parentInjector || null);
|
||||
}
|
||||
|
||||
|
@ -143,106 +146,100 @@ function declareTests(config?: {useJit: boolean}) {
|
|||
}
|
||||
|
||||
describe('errors', () => {
|
||||
fixmeIvy('FW-682: Compiler error handling')
|
||||
.it('should error when exporting a directive that was neither declared nor imported', () => {
|
||||
@NgModule({exports: [SomeDirective]})
|
||||
class SomeModule {
|
||||
}
|
||||
it('should error when exporting a directive that was neither declared nor imported', () => {
|
||||
@NgModule({exports: [SomeDirective]})
|
||||
class SomeModule {
|
||||
}
|
||||
|
||||
expect(() => createModule(SomeModule))
|
||||
.toThrowError(
|
||||
`Can't export directive ${stringify(SomeDirective)} from ${stringify(SomeModule)} as it was neither declared nor imported!`);
|
||||
});
|
||||
expect(() => createModule(SomeModule))
|
||||
.toThrowError(
|
||||
`Can't export directive ${stringify(SomeDirective)} from ${stringify(SomeModule)} as it was neither declared nor imported!`);
|
||||
});
|
||||
|
||||
fixmeIvy('FW-682: Compiler error handling')
|
||||
.it('should error when exporting a pipe that was neither declared nor imported', () => {
|
||||
@NgModule({exports: [SomePipe]})
|
||||
class SomeModule {
|
||||
}
|
||||
it('should error when exporting a pipe that was neither declared nor imported', () => {
|
||||
@NgModule({exports: [SomePipe]})
|
||||
class SomeModule {
|
||||
}
|
||||
|
||||
expect(() => createModule(SomeModule))
|
||||
.toThrowError(
|
||||
`Can't export pipe ${stringify(SomePipe)} from ${stringify(SomeModule)} as it was neither declared nor imported!`);
|
||||
});
|
||||
expect(() => createModule(SomeModule))
|
||||
.toThrowError(
|
||||
`Can't export pipe ${stringify(SomePipe)} from ${stringify(SomeModule)} as it was neither declared nor imported!`);
|
||||
});
|
||||
|
||||
fixmeIvy('FW-682: Compiler error handling')
|
||||
.it('should error if a directive is declared in more than 1 module', () => {
|
||||
@NgModule({declarations: [SomeDirective]})
|
||||
class Module1 {
|
||||
}
|
||||
it('should error if a directive is declared in more than 1 module', () => {
|
||||
@NgModule({declarations: [SomeDirective]})
|
||||
class Module1 {
|
||||
}
|
||||
|
||||
@NgModule({declarations: [SomeDirective]})
|
||||
class Module2 {
|
||||
}
|
||||
@NgModule({declarations: [SomeDirective]})
|
||||
class Module2 {
|
||||
}
|
||||
|
||||
createModule(Module1);
|
||||
createModule(Module1);
|
||||
|
||||
expect(() => createModule(Module2))
|
||||
.toThrowError(
|
||||
`Type ${stringify(SomeDirective)} is part of the declarations of 2 modules: ${stringify(Module1)} and ${stringify(Module2)}! ` +
|
||||
`Please consider moving ${stringify(SomeDirective)} to a higher module that imports ${stringify(Module1)} and ${stringify(Module2)}. ` +
|
||||
`You can also create a new NgModule that exports and includes ${stringify(SomeDirective)} then import that NgModule in ${stringify(Module1)} and ${stringify(Module2)}.`);
|
||||
});
|
||||
expect(() => createModule(Module2))
|
||||
.toThrowError(
|
||||
`Type ${stringify(SomeDirective)} is part of the declarations of 2 modules: ${stringify(Module1)} and ${stringify(Module2)}! ` +
|
||||
`Please consider moving ${stringify(SomeDirective)} to a higher module that imports ${stringify(Module1)} and ${stringify(Module2)}. ` +
|
||||
`You can also create a new NgModule that exports and includes ${stringify(SomeDirective)} then import that NgModule in ${stringify(Module1)} and ${stringify(Module2)}.`);
|
||||
});
|
||||
|
||||
fixmeIvy('FW-682: Compiler error handling')
|
||||
.it('should error if a directive is declared in more than 1 module also if the module declaring it is imported',
|
||||
() => {
|
||||
@NgModule({declarations: [SomeDirective], exports: [SomeDirective]})
|
||||
class Module1 {
|
||||
}
|
||||
it('should error if a directive is declared in more than 1 module also if the module declaring it is imported',
|
||||
() => {
|
||||
@NgModule({declarations: [SomeDirective], exports: [SomeDirective]})
|
||||
class Module1 {
|
||||
}
|
||||
|
||||
@NgModule({declarations: [SomeDirective], imports: [Module1]})
|
||||
class Module2 {
|
||||
}
|
||||
@NgModule({declarations: [SomeDirective], imports: [Module1]})
|
||||
class Module2 {
|
||||
}
|
||||
|
||||
expect(() => createModule(Module2))
|
||||
.toThrowError(
|
||||
`Type ${stringify(SomeDirective)} is part of the declarations of 2 modules: ${stringify(Module1)} and ${stringify(Module2)}! ` +
|
||||
`Please consider moving ${stringify(SomeDirective)} to a higher module that imports ${stringify(Module1)} and ${stringify(Module2)}. ` +
|
||||
`You can also create a new NgModule that exports and includes ${stringify(SomeDirective)} then import that NgModule in ${stringify(Module1)} and ${stringify(Module2)}.`);
|
||||
});
|
||||
expect(() => createModule(Module2))
|
||||
.toThrowError(
|
||||
`Type ${stringify(SomeDirective)} is part of the declarations of 2 modules: ${stringify(Module1)} and ${stringify(Module2)}! ` +
|
||||
`Please consider moving ${stringify(SomeDirective)} to a higher module that imports ${stringify(Module1)} and ${stringify(Module2)}. ` +
|
||||
`You can also create a new NgModule that exports and includes ${stringify(SomeDirective)} then import that NgModule in ${stringify(Module1)} and ${stringify(Module2)}.`);
|
||||
});
|
||||
|
||||
fixmeIvy('FW-682: Compiler error handling')
|
||||
.it('should error if a pipe is declared in more than 1 module', () => {
|
||||
@NgModule({declarations: [SomePipe]})
|
||||
class Module1 {
|
||||
}
|
||||
it('should error if a pipe is declared in more than 1 module', () => {
|
||||
@NgModule({declarations: [SomePipe]})
|
||||
class Module1 {
|
||||
}
|
||||
|
||||
@NgModule({declarations: [SomePipe]})
|
||||
class Module2 {
|
||||
}
|
||||
@NgModule({declarations: [SomePipe]})
|
||||
class Module2 {
|
||||
}
|
||||
|
||||
createModule(Module1);
|
||||
createModule(Module1);
|
||||
|
||||
expect(() => createModule(Module2))
|
||||
.toThrowError(
|
||||
`Type ${stringify(SomePipe)} is part of the declarations of 2 modules: ${stringify(Module1)} and ${stringify(Module2)}! ` +
|
||||
`Please consider moving ${stringify(SomePipe)} to a higher module that imports ${stringify(Module1)} and ${stringify(Module2)}. ` +
|
||||
`You can also create a new NgModule that exports and includes ${stringify(SomePipe)} then import that NgModule in ${stringify(Module1)} and ${stringify(Module2)}.`);
|
||||
});
|
||||
expect(() => createModule(Module2))
|
||||
.toThrowError(
|
||||
`Type ${stringify(SomePipe)} is part of the declarations of 2 modules: ${stringify(Module1)} and ${stringify(Module2)}! ` +
|
||||
`Please consider moving ${stringify(SomePipe)} to a higher module that imports ${stringify(Module1)} and ${stringify(Module2)}. ` +
|
||||
`You can also create a new NgModule that exports and includes ${stringify(SomePipe)} then import that NgModule in ${stringify(Module1)} and ${stringify(Module2)}.`);
|
||||
});
|
||||
|
||||
fixmeIvy('FW-682: Compiler error handling')
|
||||
.it('should error if a pipe is declared in more than 1 module also if the module declaring it is imported',
|
||||
() => {
|
||||
@NgModule({declarations: [SomePipe], exports: [SomePipe]})
|
||||
class Module1 {
|
||||
}
|
||||
it('should error if a pipe is declared in more than 1 module also if the module declaring it is imported',
|
||||
() => {
|
||||
@NgModule({declarations: [SomePipe], exports: [SomePipe]})
|
||||
class Module1 {
|
||||
}
|
||||
|
||||
@NgModule({declarations: [SomePipe], imports: [Module1]})
|
||||
class Module2 {
|
||||
}
|
||||
@NgModule({declarations: [SomePipe], imports: [Module1]})
|
||||
class Module2 {
|
||||
}
|
||||
|
||||
expect(() => createModule(Module2))
|
||||
.toThrowError(
|
||||
`Type ${stringify(SomePipe)} is part of the declarations of 2 modules: ${stringify(Module1)} and ${stringify(Module2)}! ` +
|
||||
`Please consider moving ${stringify(SomePipe)} to a higher module that imports ${stringify(Module1)} and ${stringify(Module2)}. ` +
|
||||
`You can also create a new NgModule that exports and includes ${stringify(SomePipe)} then import that NgModule in ${stringify(Module1)} and ${stringify(Module2)}.`);
|
||||
});
|
||||
expect(() => createModule(Module2))
|
||||
.toThrowError(
|
||||
`Type ${stringify(SomePipe)} is part of the declarations of 2 modules: ${stringify(Module1)} and ${stringify(Module2)}! ` +
|
||||
`Please consider moving ${stringify(SomePipe)} to a higher module that imports ${stringify(Module1)} and ${stringify(Module2)}. ` +
|
||||
`You can also create a new NgModule that exports and includes ${stringify(SomePipe)} then import that NgModule in ${stringify(Module1)} and ${stringify(Module2)}.`);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('schemas', () => {
|
||||
fixmeIvy('FW-682: Compiler error handling')
|
||||
fixmeIvy('FW-819: ngtsc compiler should support schemas')
|
||||
.it('should error on unknown bound properties on custom elements by default', () => {
|
||||
@Component({template: '<some-element [someUnknownProp]="true"></some-element>'})
|
||||
class ComponentUsingInvalidProperty {
|
||||
|
@ -272,28 +269,31 @@ function declareTests(config?: {useJit: boolean}) {
|
|||
|
||||
describe('id', () => {
|
||||
const token = 'myid';
|
||||
@NgModule({id: token})
|
||||
class SomeModule {
|
||||
}
|
||||
@NgModule({id: token})
|
||||
class SomeOtherModule {
|
||||
}
|
||||
|
||||
afterEach(() => clearModulesForTest());
|
||||
|
||||
fixmeIvy('FW-740: missing global registry of NgModules by id')
|
||||
.it('should register loaded modules', () => {
|
||||
createModule(SomeModule);
|
||||
const factory = getModuleFactory(token);
|
||||
expect(factory).toBeTruthy();
|
||||
expect(factory.moduleType).toBe(SomeModule);
|
||||
});
|
||||
it('should register loaded modules', () => {
|
||||
@NgModule({id: token})
|
||||
class SomeModule {
|
||||
}
|
||||
createModule(SomeModule);
|
||||
const factory = getModuleFactory(token);
|
||||
expect(factory).toBeTruthy();
|
||||
expect(factory.moduleType).toBe(SomeModule);
|
||||
});
|
||||
|
||||
fixmeIvy('FW-682: Compiler error handling')
|
||||
.it('should throw when registering a duplicate module', () => {
|
||||
createModule(SomeModule);
|
||||
expect(() => createModule(SomeOtherModule)).toThrowError(/Duplicate module registered/);
|
||||
});
|
||||
it('should throw when registering a duplicate module', () => {
|
||||
@NgModule({id: token})
|
||||
class SomeModule {
|
||||
}
|
||||
createModule(SomeModule);
|
||||
expect(() => {
|
||||
@NgModule({id: token})
|
||||
class SomeOtherModule {
|
||||
}
|
||||
createModule(SomeOtherModule);
|
||||
}).toThrowError(/Duplicate module registered/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('entryComponents', () => {
|
||||
|
@ -311,37 +311,34 @@ function declareTests(config?: {useJit: boolean}) {
|
|||
.toBe(SomeComp);
|
||||
});
|
||||
|
||||
fixmeIvy('FW-682: Compiler error handling')
|
||||
.it('should throw if we cannot find a module associated with a module-level entryComponent',
|
||||
() => {
|
||||
@Component({template: ''})
|
||||
class SomeCompWithEntryComponents {
|
||||
}
|
||||
it('should throw if we cannot find a module associated with a module-level entryComponent', () => {
|
||||
@Component({template: ''})
|
||||
class SomeCompWithEntryComponents {
|
||||
}
|
||||
|
||||
@NgModule({declarations: [], entryComponents: [SomeCompWithEntryComponents]})
|
||||
class SomeModule {
|
||||
}
|
||||
@NgModule({declarations: [], entryComponents: [SomeCompWithEntryComponents]})
|
||||
class SomeModule {
|
||||
}
|
||||
|
||||
expect(() => createModule(SomeModule))
|
||||
.toThrowError(
|
||||
'Component SomeCompWithEntryComponents is not part of any NgModule or the module has not been imported into your module.');
|
||||
});
|
||||
expect(() => createModule(SomeModule))
|
||||
.toThrowError(
|
||||
'Component SomeCompWithEntryComponents is not part of any NgModule or the module has not been imported into your module.');
|
||||
});
|
||||
|
||||
fixmeIvy('FW-682: Compiler error handling')
|
||||
.it('should throw if we cannot find a module associated with a component-level entryComponent',
|
||||
() => {
|
||||
@Component({template: '', entryComponents: [SomeComp]})
|
||||
class SomeCompWithEntryComponents {
|
||||
}
|
||||
it('should throw if we cannot find a module associated with a component-level entryComponent',
|
||||
() => {
|
||||
@Component({template: '', entryComponents: [SomeComp]})
|
||||
class SomeCompWithEntryComponents {
|
||||
}
|
||||
|
||||
@NgModule({declarations: [SomeCompWithEntryComponents]})
|
||||
class SomeModule {
|
||||
}
|
||||
@NgModule({declarations: [SomeCompWithEntryComponents]})
|
||||
class SomeModule {
|
||||
}
|
||||
|
||||
expect(() => createModule(SomeModule))
|
||||
.toThrowError(
|
||||
'Component SomeComp is not part of any NgModule or the module has not been imported into your module.');
|
||||
});
|
||||
expect(() => createModule(SomeModule))
|
||||
.toThrowError(
|
||||
'Component SomeComp is not part of any NgModule or the module has not been imported into your module.');
|
||||
});
|
||||
|
||||
it('should create ComponentFactories via ANALYZE_FOR_ENTRY_COMPONENTS', () => {
|
||||
@NgModule({
|
||||
|
@ -584,27 +581,26 @@ function declareTests(config?: {useJit: boolean}) {
|
|||
.toBe('transformed someValue');
|
||||
});
|
||||
|
||||
fixmeIvy('FW-682: Compiler error handling')
|
||||
.it('should not use non exported pipes of an imported module', () => {
|
||||
@NgModule({
|
||||
declarations: [SomePipe],
|
||||
})
|
||||
class SomeImportedModule {
|
||||
}
|
||||
it('should not use non exported pipes of an imported module', () => {
|
||||
@NgModule({
|
||||
declarations: [SomePipe],
|
||||
})
|
||||
class SomeImportedModule {
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
declarations: [CompUsingModuleDirectiveAndPipe],
|
||||
imports: [SomeImportedModule],
|
||||
entryComponents: [CompUsingModuleDirectiveAndPipe]
|
||||
})
|
||||
class SomeModule {
|
||||
}
|
||||
@NgModule({
|
||||
declarations: [CompUsingModuleDirectiveAndPipe],
|
||||
imports: [SomeImportedModule],
|
||||
entryComponents: [CompUsingModuleDirectiveAndPipe]
|
||||
})
|
||||
class SomeModule {
|
||||
}
|
||||
|
||||
expect(() => createComp(SomeComp, SomeModule))
|
||||
.toThrowError(/The pipe 'somePipe' could not be found/);
|
||||
});
|
||||
expect(() => createComp(CompUsingModuleDirectiveAndPipe, SomeModule))
|
||||
.toThrowError(/The pipe 'somePipe' could not be found/);
|
||||
});
|
||||
|
||||
fixmeIvy('FW-682: Compiler error handling')
|
||||
obsoleteInIvy('Ivy does not have a restriction on classes being exported')
|
||||
.it('should not use non exported directives of an imported module', () => {
|
||||
@NgModule({
|
||||
declarations: [SomeDirective],
|
||||
|
@ -667,13 +663,12 @@ function declareTests(config?: {useJit: boolean}) {
|
|||
expect(car.engine).toBeAnInstanceOf(TurboEngine);
|
||||
});
|
||||
|
||||
fixmeIvy('FW-682: Compiler error handling')
|
||||
.it('should throw when no type and not @Inject (class case)', () => {
|
||||
expect(() => createInjector([NoAnnotations]))
|
||||
.toThrowError('Can\'t resolve all parameters for NoAnnotations: (?).');
|
||||
});
|
||||
it('should throw when no type and not @Inject (class case)', () => {
|
||||
expect(() => createInjector([NoAnnotations]))
|
||||
.toThrowError('Can\'t resolve all parameters for NoAnnotations: (?).');
|
||||
});
|
||||
|
||||
fixmeIvy('FW-682: Compiler error handling')
|
||||
modifiedInIvy('Ivy does not use deps for factories as deps are inlined in generated code.')
|
||||
.it('should throw when no type and not @Inject (factory case)', () => {
|
||||
expect(() => createInjector([{provide: 'someToken', useFactory: factoryFn}]))
|
||||
.toThrowError('Can\'t resolve all parameters for factoryFn: (?).');
|
||||
|
@ -795,13 +790,13 @@ function declareTests(config?: {useJit: boolean}) {
|
|||
expect(injector.get('token')).toEqual('value');
|
||||
});
|
||||
|
||||
fixmeIvy('FW-682: Compiler error handling').it('should throw when given invalid providers', () => {
|
||||
it('should throw when given invalid providers', () => {
|
||||
expect(() => createInjector(<any>['blah']))
|
||||
.toThrowError(
|
||||
`Invalid provider for the NgModule 'SomeModule' - only instances of Provider and Type are allowed, got: [?blah?]`);
|
||||
});
|
||||
|
||||
fixmeIvy('FW-682: Compiler error handling').it('should throw when given blank providers', () => {
|
||||
it('should throw when given blank providers', () => {
|
||||
expect(() => createInjector(<any>[null, {provide: 'token', useValue: 'value'}]))
|
||||
.toThrowError(
|
||||
`Invalid provider for the NgModule 'SomeModule' - only instances of Provider and Type are allowed, got: [?null?, ...]`);
|
||||
|
@ -947,11 +942,10 @@ function declareTests(config?: {useJit: boolean}) {
|
|||
.toThrowError('NullInjectorError: No provider for NonExisting!');
|
||||
});
|
||||
|
||||
fixmeIvy('FW-682: Compiler error handling')
|
||||
.it('should throw when trying to instantiate a cyclic dependency', () => {
|
||||
expect(() => createInjector([Car, {provide: Engine, useClass: CyclicEngine}]))
|
||||
.toThrowError(/Cannot instantiate cyclic dependency! Car/g);
|
||||
});
|
||||
it('should throw when trying to instantiate a cyclic dependency', () => {
|
||||
expect(() => createInjector([Car, {provide: Engine, useClass: CyclicEngine}]).get(Car))
|
||||
.toThrowError(/Cannot instantiate cyclic dependency! Car/g);
|
||||
});
|
||||
|
||||
it('should support null values', () => {
|
||||
const injector = createInjector([{provide: 'null', useValue: null}]);
|
||||
|
@ -1316,20 +1310,19 @@ function declareTests(config?: {useJit: boolean}) {
|
|||
expect(injector.get('token1')).toBe('imported2');
|
||||
});
|
||||
|
||||
fixmeIvy('FW-682: Compiler error handling')
|
||||
.it('should throw when given invalid providers in an imported ModuleWithProviders', () => {
|
||||
@NgModule()
|
||||
class ImportedModule1 {
|
||||
}
|
||||
it('should throw when given invalid providers in an imported ModuleWithProviders', () => {
|
||||
@NgModule()
|
||||
class ImportedModule1 {
|
||||
}
|
||||
|
||||
@NgModule({imports: [{ngModule: ImportedModule1, providers: [<any>'broken']}]})
|
||||
class SomeModule {
|
||||
}
|
||||
@NgModule({imports: [{ngModule: ImportedModule1, providers: [<any>'broken']}]})
|
||||
class SomeModule {
|
||||
}
|
||||
|
||||
expect(() => createModule(SomeModule).injector)
|
||||
.toThrowError(
|
||||
`Invalid provider for the NgModule 'ImportedModule1' - only instances of Provider and Type are allowed, got: [?broken?]`);
|
||||
});
|
||||
expect(() => createModule(SomeModule).injector)
|
||||
.toThrowError(
|
||||
`Invalid provider for the NgModule 'ImportedModule1' - only instances of Provider and Type are allowed, got: [?broken?]`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('tree shakable providers', () => {
|
||||
|
|
|
@ -234,15 +234,14 @@ describe('Query API', () => {
|
|||
expect(asNativeElements(view.debugElement.children)).toHaveText('2|3d|2d|');
|
||||
});
|
||||
|
||||
fixmeIvy('FW-682 - TestBed: tests assert that compilation produces specific error')
|
||||
.it('should throw with descriptive error when query selectors are not present', () => {
|
||||
TestBed.configureTestingModule({declarations: [MyCompBroken0, HasNullQueryCondition]});
|
||||
const template = '<has-null-query-condition></has-null-query-condition>';
|
||||
TestBed.overrideComponent(MyCompBroken0, {set: {template}});
|
||||
expect(() => TestBed.createComponent(MyCompBroken0))
|
||||
.toThrowError(
|
||||
`Can't construct a query for the property "errorTrigger" of "${stringify(HasNullQueryCondition)}" since the query selector wasn't defined.`);
|
||||
});
|
||||
it('should throw with descriptive error when query selectors are not present', () => {
|
||||
TestBed.configureTestingModule({declarations: [MyCompBroken0, HasNullQueryCondition]});
|
||||
const template = '<has-null-query-condition></has-null-query-condition>';
|
||||
TestBed.overrideComponent(MyCompBroken0, {set: {template}});
|
||||
expect(() => TestBed.createComponent(MyCompBroken0))
|
||||
.toThrowError(
|
||||
`Can't construct a query for the property "errorTrigger" of "${stringify(HasNullQueryCondition)}" since the query selector wasn't defined.`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('query for TemplateRef', () => {
|
||||
|
|
|
@ -52,27 +52,25 @@ function declareTests(config?: {useJit: boolean}) {
|
|||
afterEach(() => { getDOM().log = originalLog; });
|
||||
|
||||
describe('events', () => {
|
||||
fixmeIvy('FW-787: Exception in template parsing leaves TestBed in corrupted state')
|
||||
.it('should disallow binding to attr.on*', () => {
|
||||
const template = `<div [attr.onclick]="ctxProp"></div>`;
|
||||
TestBed.overrideComponent(SecuredComponent, {set: {template}});
|
||||
it('should disallow binding to attr.on*', () => {
|
||||
const template = `<div [attr.onclick]="ctxProp"></div>`;
|
||||
TestBed.overrideComponent(SecuredComponent, {set: {template}});
|
||||
|
||||
expect(() => TestBed.createComponent(SecuredComponent))
|
||||
.toThrowError(
|
||||
/Binding to event attribute 'onclick' is disallowed for security reasons, please use \(click\)=.../);
|
||||
});
|
||||
expect(() => TestBed.createComponent(SecuredComponent))
|
||||
.toThrowError(
|
||||
/Binding to event attribute 'onclick' is disallowed for security reasons, please use \(click\)=.../);
|
||||
});
|
||||
|
||||
fixmeIvy('FW-787: Exception in template parsing leaves TestBed in corrupted state')
|
||||
.it('should disallow binding to on* with NO_ERRORS_SCHEMA', () => {
|
||||
const template = `<div [onclick]="ctxProp"></div>`;
|
||||
TestBed.overrideComponent(SecuredComponent, {set: {template}}).configureTestingModule({
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
});
|
||||
it('should disallow binding to on* with NO_ERRORS_SCHEMA', () => {
|
||||
const template = `<div [onclick]="ctxProp"></div>`;
|
||||
TestBed.overrideComponent(SecuredComponent, {set: {template}}).configureTestingModule({
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
});
|
||||
|
||||
expect(() => TestBed.createComponent(SecuredComponent))
|
||||
.toThrowError(
|
||||
/Binding to event property 'onclick' is disallowed for security reasons, please use \(click\)=.../);
|
||||
});
|
||||
expect(() => TestBed.createComponent(SecuredComponent))
|
||||
.toThrowError(
|
||||
/Binding to event property 'onclick' is disallowed for security reasons, please use \(click\)=.../);
|
||||
});
|
||||
|
||||
fixmeIvy(
|
||||
'FW-786: Element properties and directive inputs are not distinguished for sanitisation purposes')
|
||||
|
@ -227,7 +225,7 @@ function declareTests(config?: {useJit: boolean}) {
|
|||
expect(getDOM().getStyle(e, 'background')).not.toContain('javascript');
|
||||
});
|
||||
|
||||
fixmeIvy('FW-787: Exception in template parsing leaves TestBed in corrupted state')
|
||||
fixmeIvy('FW-850: Should throw on unsafe SVG attributes')
|
||||
.it('should escape unsafe SVG attributes', () => {
|
||||
const template = `<svg:circle [xlink:href]="ctxProp">Text</svg:circle>`;
|
||||
TestBed.overrideComponent(SecuredComponent, {set: {template}});
|
||||
|
|
|
@ -12,6 +12,8 @@ import {extractSourceMap, originalPositionFor} from '@angular/compiler/testing/s
|
|||
import {MockResourceLoader} from '@angular/compiler/testing/src/resource_loader_mock';
|
||||
import {Attribute, Component, Directive, ErrorHandler, ɵglobal} from '@angular/core';
|
||||
import {getErrorLogger} from '@angular/core/src/errors';
|
||||
import {ivyEnabled} from '@angular/core/src/ivy_switch';
|
||||
import {resolveComponentResources} from '@angular/core/src/metadata/resource_loading';
|
||||
import {TestBed, fakeAsync, tick} from '@angular/core/testing';
|
||||
import {fixmeIvy} from '@angular/private/testing';
|
||||
|
||||
|
@ -102,24 +104,30 @@ import {fixmeIvy} from '@angular/private/testing';
|
|||
function declareTests(
|
||||
{ngUrl, templateDecorator}:
|
||||
{ngUrl: string, templateDecorator: (template: string) => { [key: string]: any }}) {
|
||||
fixmeIvy('FW-682: Compiler error handling')
|
||||
fixmeIvy('FW-223: Generate source maps during template compilation')
|
||||
.it('should use the right source url in html parse errors', fakeAsync(() => {
|
||||
@Component({...templateDecorator('<div>\n </error>')})
|
||||
class MyComp {
|
||||
}
|
||||
|
||||
expect(() => compileAndCreateComponent(MyComp))
|
||||
expect(() => {
|
||||
ivyEnabled && resolveComponentResources(null !);
|
||||
compileAndCreateComponent(MyComp);
|
||||
})
|
||||
.toThrowError(new RegExp(
|
||||
`Template parse errors[\\s\\S]*${ngUrl.replace('$', '\\$')}@1:2`));
|
||||
}));
|
||||
|
||||
fixmeIvy('FW-682: Compiler error handling')
|
||||
fixmeIvy('FW-223: Generate source maps during template compilation')
|
||||
.it('should use the right source url in template parse errors', fakeAsync(() => {
|
||||
@Component({...templateDecorator('<div>\n <div unknown="{{ctxProp}}"></div>')})
|
||||
class MyComp {
|
||||
}
|
||||
|
||||
expect(() => compileAndCreateComponent(MyComp))
|
||||
expect(() => {
|
||||
ivyEnabled && resolveComponentResources(null !);
|
||||
compileAndCreateComponent(MyComp);
|
||||
})
|
||||
.toThrowError(new RegExp(
|
||||
`Template parse errors[\\s\\S]*${ngUrl.replace('$', '\\$')}@1:7`));
|
||||
}));
|
||||
|
|
|
@ -69,7 +69,7 @@ describe('pipe', () => {
|
|||
|
||||
expect(() => {
|
||||
const fixture = new ComponentFixture(App);
|
||||
}).toThrowError(/Pipe with name 'randomPipeName' not found!/);
|
||||
}).toThrowError(/The pipe 'randomPipeName' could not be found!/);
|
||||
});
|
||||
|
||||
it('should support bindings', () => {
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {ApplicationInitStatus, Component, Directive, Injector, NgModule, NgZone, Pipe, PlatformRef, Provider, SchemaMetadata, Type, ɵInjectableDef as InjectableDef, ɵNgModuleDef as NgModuleDef, ɵNgModuleTransitiveScopes as NgModuleTransitiveScopes, ɵRender3ComponentFactory as ComponentFactory, ɵRender3NgModuleRef as NgModuleRef, ɵcompileComponent as compileComponent, ɵcompileDirective as compileDirective, ɵcompileNgModuleDefs as compileNgModuleDefs, ɵcompilePipe as compilePipe, ɵgetInjectableDef as getInjectableDef, ɵpatchComponentDefWithScope as patchComponentDefWithScope, ɵstringify as stringify} from '@angular/core';
|
||||
import {ApplicationInitStatus, Component, Directive, Injector, NgModule, NgZone, Pipe, PlatformRef, Provider, SchemaMetadata, Type, ɵInjectableDef as InjectableDef, ɵNgModuleDef as NgModuleDef, ɵNgModuleTransitiveScopes as NgModuleTransitiveScopes, ɵNgModuleType as NgModuleType, ɵRender3ComponentFactory as ComponentFactory, ɵRender3NgModuleRef as NgModuleRef, ɵcompileComponent as compileComponent, ɵcompileDirective as compileDirective, ɵcompileNgModuleDefs as compileNgModuleDefs, ɵcompilePipe as compilePipe, ɵgetInjectableDef as getInjectableDef, ɵpatchComponentDefWithScope as patchComponentDefWithScope, ɵresetCompiledComponents as resetCompiledComponents, ɵstringify as stringify} from '@angular/core';
|
||||
|
||||
import {ComponentFixture} from './component_fixture';
|
||||
import {MetadataOverride} from './metadata_override';
|
||||
|
@ -221,6 +221,7 @@ export class TestBedRender3 implements Injector, TestBed {
|
|||
}
|
||||
|
||||
resetTestingModule(): void {
|
||||
resetCompiledComponents();
|
||||
// reset metadata overrides
|
||||
this._moduleOverrides = [];
|
||||
this._componentOverrides = [];
|
||||
|
@ -423,7 +424,7 @@ export class TestBedRender3 implements Injector, TestBed {
|
|||
}
|
||||
}
|
||||
|
||||
private _createTestModule(): Type<any> {
|
||||
private _createTestModule(): NgModuleType {
|
||||
const rootProviderOverrides = this._rootProviderOverrides;
|
||||
|
||||
@NgModule({
|
||||
|
@ -445,7 +446,7 @@ export class TestBedRender3 implements Injector, TestBed {
|
|||
class DynamicTestModule {
|
||||
}
|
||||
|
||||
return DynamicTestModule;
|
||||
return DynamicTestModule as NgModuleType;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -455,6 +456,18 @@ export function _getTestBedRender3(): TestBedRender3 {
|
|||
return testBed = testBed || new TestBedRender3();
|
||||
}
|
||||
|
||||
const OWNER_MODULE = '__NG_MODULE__';
|
||||
/**
|
||||
* This function clears the OWNER_MODULE property from the Types. This is set in r3/jit/modules.ts.
|
||||
* It is common for the same Type to be compiled in different tests. If we don't clear this we will
|
||||
* get errors which will complain that the same Component/Directive is in more than one NgModule.
|
||||
*/
|
||||
function clearNgModules(type: Type<any>) {
|
||||
if (type.hasOwnProperty(OWNER_MODULE)) {
|
||||
(type as any)[OWNER_MODULE] = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Module compiler
|
||||
|
||||
|
@ -468,7 +481,7 @@ type Resolvers = {
|
|||
pipe: Resolver<Pipe>,
|
||||
};
|
||||
|
||||
function compileNgModule(moduleType: Type<any>, resolvers: Resolvers): void {
|
||||
function compileNgModule(moduleType: NgModuleType, resolvers: Resolvers): void {
|
||||
const ngModule = resolvers.module.resolve(moduleType);
|
||||
|
||||
if (ngModule === null) {
|
||||
|
@ -548,7 +561,7 @@ function transitiveScopesFor<T>(
|
|||
}
|
||||
});
|
||||
|
||||
def.imports.forEach(<I>(imported: Type<I>) => {
|
||||
def.imports.forEach(<I>(imported: NgModuleType) => {
|
||||
const ngModule = resolvers.module.resolve(imported);
|
||||
|
||||
if (ngModule === null) {
|
||||
|
|
|
@ -617,14 +617,15 @@ class HiddenModule {
|
|||
});
|
||||
}));
|
||||
|
||||
it('should handle false values on attributes', async(() => {
|
||||
renderModule(FalseAttributesModule, {document: doc}).then(output => {
|
||||
expect(output).toBe(
|
||||
'<html><head></head><body><app ng-version="0.0.0-PLACEHOLDER">' +
|
||||
'<my-child ng-reflect-attr="false">Works!</my-child></app></body></html>');
|
||||
called = true;
|
||||
});
|
||||
}));
|
||||
fixmeIvy('unknown').it(
|
||||
'should handle false values on attributes', async(() => {
|
||||
renderModule(FalseAttributesModule, {document: doc}).then(output => {
|
||||
expect(output).toBe(
|
||||
'<html><head></head><body><app ng-version="0.0.0-PLACEHOLDER">' +
|
||||
'<my-child ng-reflect-attr="false">Works!</my-child></app></body></html>');
|
||||
called = true;
|
||||
});
|
||||
}));
|
||||
|
||||
it('should handle element property "name"', async(() => {
|
||||
renderModule(NameModule, {document: doc}).then(output => {
|
||||
|
|
|
@ -3991,26 +3991,27 @@ describe('Integration', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('should use the injector of the lazily-loaded configuration',
|
||||
fakeAsync(inject(
|
||||
[Router, Location, NgModuleFactoryLoader],
|
||||
(router: Router, location: Location, loader: SpyNgModuleFactoryLoader) => {
|
||||
loader.stubbedModules = {expected: LoadedModule};
|
||||
fixmeIvy('unknown').it(
|
||||
'should use the injector of the lazily-loaded configuration',
|
||||
fakeAsync(inject(
|
||||
[Router, Location, NgModuleFactoryLoader],
|
||||
(router: Router, location: Location, loader: SpyNgModuleFactoryLoader) => {
|
||||
loader.stubbedModules = {expected: LoadedModule};
|
||||
|
||||
const fixture = createRoot(router, RootCmp);
|
||||
const fixture = createRoot(router, RootCmp);
|
||||
|
||||
router.resetConfig([{
|
||||
path: 'eager-parent',
|
||||
component: EagerParentComponent,
|
||||
children: [{path: 'lazy', loadChildren: 'expected'}]
|
||||
}]);
|
||||
router.resetConfig([{
|
||||
path: 'eager-parent',
|
||||
component: EagerParentComponent,
|
||||
children: [{path: 'lazy', loadChildren: 'expected'}]
|
||||
}]);
|
||||
|
||||
router.navigateByUrl('/eager-parent/lazy/lazy-parent/lazy-child');
|
||||
advance(fixture);
|
||||
router.navigateByUrl('/eager-parent/lazy/lazy-parent/lazy-child');
|
||||
advance(fixture);
|
||||
|
||||
expect(location.path()).toEqual('/eager-parent/lazy/lazy-parent/lazy-child');
|
||||
expect(fixture.nativeElement).toHaveText('eager-parent lazy-parent lazy-child');
|
||||
})));
|
||||
expect(location.path()).toEqual('/eager-parent/lazy/lazy-parent/lazy-child');
|
||||
expect(fixture.nativeElement).toHaveText('eager-parent lazy-parent lazy-child');
|
||||
})));
|
||||
});
|
||||
|
||||
it('works when given a callback',
|
||||
|
@ -4333,41 +4334,43 @@ describe('Integration', () => {
|
|||
class LazyLoadedModule {
|
||||
}
|
||||
|
||||
it('should not ignore empty path when in legacy mode',
|
||||
fakeAsync(inject(
|
||||
[Router, NgModuleFactoryLoader],
|
||||
(router: Router, loader: SpyNgModuleFactoryLoader) => {
|
||||
router.relativeLinkResolution = 'legacy';
|
||||
loader.stubbedModules = {expected: LazyLoadedModule};
|
||||
fixmeIvy('unknown').it(
|
||||
'should not ignore empty path when in legacy mode',
|
||||
fakeAsync(inject(
|
||||
[Router, NgModuleFactoryLoader],
|
||||
(router: Router, loader: SpyNgModuleFactoryLoader) => {
|
||||
router.relativeLinkResolution = 'legacy';
|
||||
loader.stubbedModules = {expected: LazyLoadedModule};
|
||||
|
||||
const fixture = createRoot(router, RootCmp);
|
||||
const fixture = createRoot(router, RootCmp);
|
||||
|
||||
router.resetConfig([{path: 'lazy', loadChildren: 'expected'}]);
|
||||
router.resetConfig([{path: 'lazy', loadChildren: 'expected'}]);
|
||||
|
||||
router.navigateByUrl('/lazy/foo/bar');
|
||||
advance(fixture);
|
||||
router.navigateByUrl('/lazy/foo/bar');
|
||||
advance(fixture);
|
||||
|
||||
const link = fixture.nativeElement.querySelector('a');
|
||||
expect(link.getAttribute('href')).toEqual('/lazy/foo/bar/simple');
|
||||
})));
|
||||
const link = fixture.nativeElement.querySelector('a');
|
||||
expect(link.getAttribute('href')).toEqual('/lazy/foo/bar/simple');
|
||||
})));
|
||||
|
||||
it('should ignore empty path when in corrected mode',
|
||||
fakeAsync(inject(
|
||||
[Router, NgModuleFactoryLoader],
|
||||
(router: Router, loader: SpyNgModuleFactoryLoader) => {
|
||||
router.relativeLinkResolution = 'corrected';
|
||||
loader.stubbedModules = {expected: LazyLoadedModule};
|
||||
fixmeIvy('unknown').it(
|
||||
'should ignore empty path when in corrected mode',
|
||||
fakeAsync(inject(
|
||||
[Router, NgModuleFactoryLoader],
|
||||
(router: Router, loader: SpyNgModuleFactoryLoader) => {
|
||||
router.relativeLinkResolution = 'corrected';
|
||||
loader.stubbedModules = {expected: LazyLoadedModule};
|
||||
|
||||
const fixture = createRoot(router, RootCmp);
|
||||
const fixture = createRoot(router, RootCmp);
|
||||
|
||||
router.resetConfig([{path: 'lazy', loadChildren: 'expected'}]);
|
||||
router.resetConfig([{path: 'lazy', loadChildren: 'expected'}]);
|
||||
|
||||
router.navigateByUrl('/lazy/foo/bar');
|
||||
advance(fixture);
|
||||
router.navigateByUrl('/lazy/foo/bar');
|
||||
advance(fixture);
|
||||
|
||||
const link = fixture.nativeElement.querySelector('a');
|
||||
expect(link.getAttribute('href')).toEqual('/lazy/foo/simple');
|
||||
})));
|
||||
const link = fixture.nativeElement.querySelector('a');
|
||||
expect(link.getAttribute('href')).toEqual('/lazy/foo/simple');
|
||||
})));
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -149,30 +149,28 @@ withEachNg1Version(() => {
|
|||
adapter = new UpgradeAdapter(Ng2Module);
|
||||
});
|
||||
|
||||
fixmeIvy('FW-682: JIT compilation occurs at component definition time rather than bootstrap')
|
||||
.it('should throw an uncaught error', fakeAsync(() => {
|
||||
const resolveSpy = jasmine.createSpy('resolveSpy');
|
||||
spyOn(console, 'error');
|
||||
it('should throw an uncaught error', fakeAsync(() => {
|
||||
const resolveSpy = jasmine.createSpy('resolveSpy');
|
||||
spyOn(console, 'error');
|
||||
|
||||
expect(() => {
|
||||
adapter.bootstrap(html('<ng2></ng2>'), ['ng1']).ready(resolveSpy);
|
||||
flushMicrotasks();
|
||||
}).toThrowError();
|
||||
expect(resolveSpy).not.toHaveBeenCalled();
|
||||
}));
|
||||
expect(() => {
|
||||
adapter.bootstrap(html('<ng2></ng2>'), ['ng1']).ready(resolveSpy);
|
||||
flushMicrotasks();
|
||||
}).toThrowError();
|
||||
expect(resolveSpy).not.toHaveBeenCalled();
|
||||
}));
|
||||
|
||||
fixmeIvy('FW-682: JIT compilation occurs at component definition time rather than bootstrap')
|
||||
.it('should output an error message to the console and re-throw', fakeAsync(() => {
|
||||
const consoleErrorSpy: jasmine.Spy = spyOn(console, 'error');
|
||||
expect(() => {
|
||||
adapter.bootstrap(html('<ng2></ng2>'), ['ng1']);
|
||||
flushMicrotasks();
|
||||
}).toThrowError();
|
||||
const args: any[] = consoleErrorSpy.calls.mostRecent().args;
|
||||
expect(consoleErrorSpy).toHaveBeenCalled();
|
||||
expect(args.length).toBeGreaterThan(0);
|
||||
expect(args[0]).toEqual(jasmine.any(Error));
|
||||
}));
|
||||
it('should output an error message to the console and re-throw', fakeAsync(() => {
|
||||
const consoleErrorSpy: jasmine.Spy = spyOn(console, 'error');
|
||||
expect(() => {
|
||||
adapter.bootstrap(html('<ng2></ng2>'), ['ng1']);
|
||||
flushMicrotasks();
|
||||
}).toThrowError();
|
||||
const args: any[] = consoleErrorSpy.calls.mostRecent().args;
|
||||
expect(consoleErrorSpy).toHaveBeenCalled();
|
||||
expect(args.length).toBeGreaterThan(0);
|
||||
expect(args[0]).toEqual(jasmine.any(Error));
|
||||
}));
|
||||
});
|
||||
|
||||
describe('scope/component change-detection', () => {
|
||||
|
|
|
@ -334,7 +334,7 @@ export interface ForwardRefFn {
|
|||
|
||||
export declare const getDebugNode: (nativeNode: any) => DebugNode | null;
|
||||
|
||||
export declare function getModuleFactory(id: string): NgModuleFactory<any>;
|
||||
export declare const getModuleFactory: (id: string) => NgModuleFactory<any>;
|
||||
|
||||
export declare function getPlatform(): PlatformRef | null;
|
||||
|
||||
|
|
Loading…
Reference in New Issue