2016-06-23 12:47:54 -04:00
|
|
|
/**
|
|
|
|
* @license
|
|
|
|
* Copyright Google Inc. All Rights Reserved.
|
|
|
|
*
|
|
|
|
* Use of this source code is governed by an MIT-style license that can be
|
|
|
|
* found in the LICENSE file at https://angular.io/license
|
|
|
|
*/
|
|
|
|
|
2016-08-17 19:37:31 -04:00
|
|
|
import {TEST_COMPILER_PROVIDERS} from '@angular/compiler/testing/test_bindings';
|
2016-09-14 18:58:18 -04:00
|
|
|
import {AfterContentChecked, AfterContentInit, AfterViewChecked, AfterViewInit, ChangeDetectionStrategy, Component, DoCheck, Injectable, NgModule, OnChanges, OnDestroy, OnInit, Pipe, SimpleChanges, ViewEncapsulation} from '@angular/core';
|
2016-06-08 19:38:52 -04:00
|
|
|
import {LIFECYCLE_HOOKS_VALUES} from '@angular/core/src/metadata/lifecycle_hooks';
|
2016-08-19 15:51:01 -04:00
|
|
|
import {TestBed, inject} from '@angular/core/testing';
|
2015-09-14 18:59:09 -04:00
|
|
|
|
2016-07-30 22:31:25 -04:00
|
|
|
import {stringify} from '../src/facade/lang';
|
2016-04-28 20:50:03 -04:00
|
|
|
import {CompileMetadataResolver} from '../src/metadata_resolver';
|
2016-06-08 19:38:52 -04:00
|
|
|
|
2016-02-18 13:53:21 -05:00
|
|
|
import {MalformedStylesComponent} from './metadata_resolver_fixture';
|
2015-09-14 18:59:09 -04:00
|
|
|
|
|
|
|
export function main() {
|
2016-02-18 13:53:21 -05:00
|
|
|
describe('CompileMetadataResolver', () => {
|
2016-07-28 07:54:49 -04:00
|
|
|
beforeEach(() => { TestBed.configureCompiler({providers: TEST_COMPILER_PROVIDERS}); });
|
2015-09-14 18:59:09 -04:00
|
|
|
|
2016-07-18 06:50:31 -04:00
|
|
|
describe('getDirectiveMetadata', () => {
|
2015-09-14 18:59:09 -04:00
|
|
|
it('should read metadata',
|
2016-02-18 13:53:21 -05:00
|
|
|
inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => {
|
2016-09-14 18:58:18 -04:00
|
|
|
const meta = resolver.getDirectiveMetadata(ComponentWithEverything);
|
2015-09-14 18:59:09 -04:00
|
|
|
expect(meta.selector).toEqual('someSelector');
|
2015-09-18 13:33:23 -04:00
|
|
|
expect(meta.exportAs).toEqual('someExportAs');
|
2015-09-14 18:59:09 -04:00
|
|
|
expect(meta.isComponent).toBe(true);
|
2016-08-29 11:52:25 -04:00
|
|
|
expect(meta.type.reference).toBe(ComponentWithEverything);
|
2015-09-14 18:59:09 -04:00
|
|
|
expect(meta.type.name).toEqual(stringify(ComponentWithEverything));
|
2016-08-02 04:12:24 -04:00
|
|
|
expect(meta.type.lifecycleHooks).toEqual(LIFECYCLE_HOOKS_VALUES);
|
2016-06-27 23:00:30 -04:00
|
|
|
expect(meta.changeDetection).toBe(ChangeDetectionStrategy.Default);
|
2015-09-30 23:59:23 -04:00
|
|
|
expect(meta.inputs).toEqual({'someProp': 'someProp'});
|
|
|
|
expect(meta.outputs).toEqual({'someEvent': 'someEvent'});
|
2015-09-18 13:33:23 -04:00
|
|
|
expect(meta.hostListeners).toEqual({'someHostListener': 'someHostListenerExpr'});
|
|
|
|
expect(meta.hostProperties).toEqual({'someHostProp': 'someHostPropExpr'});
|
|
|
|
expect(meta.hostAttributes).toEqual({'someHostAttr': 'someHostAttrValue'});
|
2015-09-14 18:59:09 -04:00
|
|
|
expect(meta.template.encapsulation).toBe(ViewEncapsulation.Emulated);
|
|
|
|
expect(meta.template.styles).toEqual(['someStyle']);
|
|
|
|
expect(meta.template.styleUrls).toEqual(['someStyleUrl']);
|
|
|
|
expect(meta.template.template).toEqual('someTemplate');
|
|
|
|
expect(meta.template.templateUrl).toEqual('someTemplateUrl');
|
2016-06-20 12:52:41 -04:00
|
|
|
expect(meta.template.interpolation).toEqual(['{{', '}}']);
|
2015-09-14 18:59:09 -04:00
|
|
|
}));
|
|
|
|
|
2015-10-01 13:07:49 -04:00
|
|
|
it('should use the moduleUrl from the reflector if none is given',
|
2016-02-18 13:53:21 -05:00
|
|
|
inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => {
|
2016-07-30 22:31:25 -04:00
|
|
|
const value: string =
|
2016-05-02 12:38:46 -04:00
|
|
|
resolver.getDirectiveMetadata(ComponentWithoutModuleId).type.moduleUrl;
|
2016-07-30 22:31:25 -04:00
|
|
|
const expectedEndValue = './ComponentWithoutModuleId';
|
2015-12-13 20:35:33 -05:00
|
|
|
expect(value.endsWith(expectedEndValue)).toBe(true);
|
2015-09-14 18:59:09 -04:00
|
|
|
}));
|
2016-03-09 17:55:27 -05:00
|
|
|
|
2016-09-14 19:49:13 -04:00
|
|
|
it('should throw when the moduleId is not a string',
|
|
|
|
inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => {
|
|
|
|
expect(() => resolver.getDirectiveMetadata(ComponentWithInvalidModuleId))
|
|
|
|
.toThrowError(
|
|
|
|
`moduleId should be a string in "ComponentWithInvalidModuleId". See` +
|
|
|
|
` https://goo.gl/wIDDiL for more information.\n` +
|
|
|
|
`If you're using Webpack you should inline the template and the styles, see` +
|
|
|
|
` https://goo.gl/X2J8zc.`);
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
2016-03-09 17:55:27 -05:00
|
|
|
it('should throw when metadata is incorrectly typed',
|
2016-02-18 13:53:21 -05:00
|
|
|
inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => {
|
2016-06-18 12:42:34 -04:00
|
|
|
expect(() => resolver.getDirectiveMetadata(MalformedStylesComponent))
|
|
|
|
.toThrowError(`Expected 'styles' to be an array of strings.`);
|
2016-03-09 17:55:27 -05:00
|
|
|
}));
|
2016-06-09 19:07:06 -04:00
|
|
|
|
|
|
|
it('should throw with descriptive error message when provider token can not be resolved',
|
|
|
|
inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => {
|
2016-06-18 12:42:34 -04:00
|
|
|
expect(() => resolver.getDirectiveMetadata(MyBrokenComp1))
|
|
|
|
.toThrowError(`Can't resolve all parameters for MyBrokenComp1: (?).`);
|
|
|
|
}));
|
2016-08-17 18:57:02 -04:00
|
|
|
it('should throw with descriptive error message when a directive is passed to imports',
|
|
|
|
inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => {
|
|
|
|
@NgModule({imports: [ComponentWithoutModuleId]})
|
|
|
|
class ModuleWithImportedComponent {
|
|
|
|
}
|
|
|
|
expect(() => resolver.getNgModuleMetadata(ModuleWithImportedComponent))
|
|
|
|
.toThrowError(
|
|
|
|
`Unexpected directive 'ComponentWithoutModuleId' imported by the module 'ModuleWithImportedComponent'`);
|
|
|
|
}));
|
|
|
|
|
|
|
|
it('should throw with descriptive error message when a pipe is passed to imports',
|
|
|
|
inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => {
|
|
|
|
@Pipe({name: 'somePipe'})
|
|
|
|
class SomePipe {
|
|
|
|
}
|
|
|
|
@NgModule({imports: [SomePipe]})
|
|
|
|
class ModuleWithImportedPipe {
|
|
|
|
}
|
|
|
|
expect(() => resolver.getNgModuleMetadata(ModuleWithImportedPipe))
|
|
|
|
.toThrowError(
|
|
|
|
`Unexpected pipe 'SomePipe' imported by the module 'ModuleWithImportedPipe'`);
|
|
|
|
}));
|
|
|
|
|
|
|
|
it('should throw with descriptive error message when a module is passed to declarations',
|
|
|
|
inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => {
|
|
|
|
@NgModule({})
|
|
|
|
class SomeModule {
|
|
|
|
}
|
|
|
|
@NgModule({declarations: [SomeModule]})
|
|
|
|
class ModuleWithDeclaredModule {
|
|
|
|
}
|
|
|
|
expect(() => resolver.getNgModuleMetadata(ModuleWithDeclaredModule))
|
|
|
|
.toThrowError(
|
|
|
|
`Unexpected module 'SomeModule' declared by the module 'ModuleWithDeclaredModule'`);
|
|
|
|
}));
|
|
|
|
|
|
|
|
it('should throw with descriptive error message when null is passed to declarations',
|
|
|
|
inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => {
|
|
|
|
@NgModule({declarations: [null]})
|
|
|
|
class ModuleWithNullDeclared {
|
|
|
|
}
|
|
|
|
expect(() => resolver.getNgModuleMetadata(ModuleWithNullDeclared))
|
|
|
|
.toThrowError(
|
|
|
|
`Unexpected value 'null' declared by the module 'ModuleWithNullDeclared'`);
|
|
|
|
}));
|
|
|
|
|
|
|
|
it('should throw with descriptive error message when null is passed to imports',
|
|
|
|
inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => {
|
|
|
|
@NgModule({imports: [null]})
|
|
|
|
class ModuleWithNullImported {
|
|
|
|
}
|
|
|
|
expect(() => resolver.getNgModuleMetadata(ModuleWithNullImported))
|
|
|
|
.toThrowError(
|
|
|
|
`Unexpected value 'null' imported by the module 'ModuleWithNullImported'`);
|
|
|
|
}));
|
|
|
|
|
2016-06-18 12:42:34 -04:00
|
|
|
|
|
|
|
it('should throw with descriptive error message when a param token of a dependency is undefined',
|
|
|
|
inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => {
|
|
|
|
expect(() => resolver.getDirectiveMetadata(MyBrokenComp2))
|
|
|
|
.toThrowError(`Can't resolve all parameters for NonAnnotatedService: (?).`);
|
2016-06-09 19:07:06 -04:00
|
|
|
}));
|
2016-06-20 12:52:41 -04:00
|
|
|
|
2016-06-21 20:27:27 -04:00
|
|
|
it('should throw with descriptive error message when one of providers is not present',
|
|
|
|
inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => {
|
|
|
|
expect(() => resolver.getDirectiveMetadata(MyBrokenComp3))
|
|
|
|
.toThrowError(
|
2016-08-23 19:18:41 -04:00
|
|
|
`Invalid providers for "MyBrokenComp3" - only instances of Provider and Type are allowed, got: [SimpleService, ?null?, ...]`);
|
2016-06-21 20:27:27 -04:00
|
|
|
}));
|
|
|
|
|
|
|
|
it('should throw with descriptive error message when one of viewProviders is not present',
|
|
|
|
inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => {
|
|
|
|
expect(() => resolver.getDirectiveMetadata(MyBrokenComp4))
|
|
|
|
.toThrowError(
|
2016-08-23 19:18:41 -04:00
|
|
|
`Invalid viewProviders for "MyBrokenComp4" - only instances of Provider and Type are allowed, got: [?null?, ...]`);
|
2016-06-21 20:27:27 -04:00
|
|
|
}));
|
|
|
|
|
2016-09-06 12:55:48 -04:00
|
|
|
it('should throw with descriptive error message when null or undefined is passed to module bootstrap',
|
|
|
|
inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => {
|
|
|
|
@NgModule({bootstrap: [null]})
|
|
|
|
class ModuleWithNullBootstrap {
|
|
|
|
}
|
|
|
|
@NgModule({bootstrap: [undefined]})
|
|
|
|
class ModuleWithUndefinedBootstrap {
|
|
|
|
}
|
|
|
|
|
|
|
|
expect(() => resolver.getNgModuleMetadata(ModuleWithNullBootstrap))
|
|
|
|
.toThrowError(
|
|
|
|
`Unexpected value 'null' used in the bootstrap property of module 'ModuleWithNullBootstrap'`);
|
|
|
|
expect(() => resolver.getNgModuleMetadata(ModuleWithUndefinedBootstrap))
|
|
|
|
.toThrowError(
|
|
|
|
`Unexpected value 'undefined' used in the bootstrap property of module 'ModuleWithUndefinedBootstrap'`);
|
|
|
|
}));
|
|
|
|
|
2016-06-20 12:52:41 -04:00
|
|
|
it('should throw an error when the interpolation config has invalid symbols',
|
|
|
|
inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => {
|
|
|
|
expect(() => resolver.getDirectiveMetadata(ComponentWithInvalidInterpolation1))
|
|
|
|
.toThrowError(`[' ', ' '] contains unusable interpolation symbol.`);
|
|
|
|
expect(() => resolver.getDirectiveMetadata(ComponentWithInvalidInterpolation2))
|
|
|
|
.toThrowError(`['{', '}'] contains unusable interpolation symbol.`);
|
|
|
|
expect(() => resolver.getDirectiveMetadata(ComponentWithInvalidInterpolation3))
|
|
|
|
.toThrowError(`['<%', '%>'] contains unusable interpolation symbol.`);
|
2016-06-21 19:55:17 -04:00
|
|
|
expect(() => resolver.getDirectiveMetadata(ComponentWithInvalidInterpolation4))
|
|
|
|
.toThrowError(`['&#', '}}'] contains unusable interpolation symbol.`);
|
|
|
|
expect(() => resolver.getDirectiveMetadata(ComponentWithInvalidInterpolation5))
|
|
|
|
.toThrowError(`['{', '}}'] contains unusable interpolation symbol.`);
|
2016-06-20 12:52:41 -04:00
|
|
|
}));
|
2015-09-14 18:59:09 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2015-12-13 20:35:33 -05:00
|
|
|
@Component({selector: 'someComponent', template: ''})
|
|
|
|
class ComponentWithoutModuleId {
|
2015-09-14 18:59:09 -04:00
|
|
|
}
|
|
|
|
|
2016-09-14 19:49:13 -04:00
|
|
|
@Component({selector: 'someComponent', template: '', moduleId: <any>0})
|
|
|
|
class ComponentWithInvalidModuleId {
|
|
|
|
}
|
|
|
|
|
2015-09-14 18:59:09 -04:00
|
|
|
@Component({
|
|
|
|
selector: 'someSelector',
|
2015-09-30 23:59:23 -04:00
|
|
|
inputs: ['someProp'],
|
|
|
|
outputs: ['someEvent'],
|
2015-09-14 18:59:09 -04:00
|
|
|
host: {
|
|
|
|
'[someHostProp]': 'someHostPropExpr',
|
|
|
|
'(someHostListener)': 'someHostListenerExpr',
|
|
|
|
'someHostAttr': 'someHostAttrValue'
|
|
|
|
},
|
2015-09-18 13:33:23 -04:00
|
|
|
exportAs: 'someExportAs',
|
2015-09-14 18:59:09 -04:00
|
|
|
moduleId: 'someModuleId',
|
2016-06-27 23:00:30 -04:00
|
|
|
changeDetection: ChangeDetectionStrategy.Default,
|
2015-09-14 18:59:09 -04:00
|
|
|
template: 'someTemplate',
|
|
|
|
templateUrl: 'someTemplateUrl',
|
|
|
|
encapsulation: ViewEncapsulation.Emulated,
|
|
|
|
styles: ['someStyle'],
|
|
|
|
styleUrls: ['someStyleUrl'],
|
2016-06-20 12:52:41 -04:00
|
|
|
interpolation: ['{{', '}}']
|
2015-09-14 18:59:09 -04:00
|
|
|
})
|
|
|
|
class ComponentWithEverything implements OnChanges,
|
|
|
|
OnInit, DoCheck, OnDestroy, AfterContentInit, AfterContentChecked, AfterViewInit,
|
|
|
|
AfterViewChecked {
|
2016-05-09 18:45:04 -04:00
|
|
|
ngOnChanges(changes: SimpleChanges): void {}
|
refactor(lifecycle): prefix lifecycle methods with "ng"
BREAKING CHANGE:
Previously, components that would implement lifecycle interfaces would include methods
like "onChanges" or "afterViewInit." Given that components were at risk of using such
names without realizing that Angular would call the methods at different points of
the component lifecycle. This change adds an "ng" prefix to all lifecycle hook methods,
far reducing the risk of an accidental name collision.
To fix, just rename these methods:
* onInit
* onDestroy
* doCheck
* onChanges
* afterContentInit
* afterContentChecked
* afterViewInit
* afterViewChecked
* _Router Hooks_
* onActivate
* onReuse
* onDeactivate
* canReuse
* canDeactivate
To:
* ngOnInit,
* ngOnDestroy,
* ngDoCheck,
* ngOnChanges,
* ngAfterContentInit,
* ngAfterContentChecked,
* ngAfterViewInit,
* ngAfterViewChecked
* _Router Hooks_
* routerOnActivate
* routerOnReuse
* routerOnDeactivate
* routerCanReuse
* routerCanDeactivate
The names of lifecycle interfaces and enums have not changed, though interfaces
have been updated to reflect the new method names.
Closes #5036
2015-11-16 20:04:36 -05:00
|
|
|
ngOnInit(): void {}
|
|
|
|
ngDoCheck(): void {}
|
|
|
|
ngOnDestroy(): void {}
|
|
|
|
ngAfterContentInit(): void {}
|
|
|
|
ngAfterContentChecked(): void {}
|
|
|
|
ngAfterViewInit(): void {}
|
|
|
|
ngAfterViewChecked(): void {}
|
2015-09-14 18:59:09 -04:00
|
|
|
}
|
2016-06-09 19:07:06 -04:00
|
|
|
|
|
|
|
@Component({selector: 'my-broken-comp', template: ''})
|
|
|
|
class MyBrokenComp1 {
|
|
|
|
constructor(public dependency: any) {}
|
|
|
|
}
|
2016-06-20 12:52:41 -04:00
|
|
|
|
2016-06-18 12:42:34 -04:00
|
|
|
class NonAnnotatedService {
|
|
|
|
constructor(dep: any) {}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Component({selector: 'my-broken-comp', template: '', providers: [NonAnnotatedService]})
|
|
|
|
class MyBrokenComp2 {
|
|
|
|
constructor(dependency: NonAnnotatedService) {}
|
|
|
|
}
|
|
|
|
|
2016-06-21 20:27:27 -04:00
|
|
|
@Injectable()
|
|
|
|
class SimpleService {
|
|
|
|
}
|
|
|
|
|
2016-08-23 19:18:41 -04:00
|
|
|
@Component({selector: 'my-broken-comp', template: '', providers: [SimpleService, null, [null]]})
|
2016-06-21 20:27:27 -04:00
|
|
|
class MyBrokenComp3 {
|
|
|
|
}
|
|
|
|
|
|
|
|
@Component({selector: 'my-broken-comp', template: '', viewProviders: [null, SimpleService, [null]]})
|
|
|
|
class MyBrokenComp4 {
|
|
|
|
}
|
|
|
|
|
2016-06-20 12:52:41 -04:00
|
|
|
@Component({selector: 'someSelector', template: '', interpolation: [' ', ' ']})
|
|
|
|
class ComponentWithInvalidInterpolation1 {
|
|
|
|
}
|
|
|
|
|
|
|
|
@Component({selector: 'someSelector', template: '', interpolation: ['{', '}']})
|
|
|
|
class ComponentWithInvalidInterpolation2 {
|
|
|
|
}
|
|
|
|
|
|
|
|
@Component({selector: 'someSelector', template: '', interpolation: ['<%', '%>']})
|
|
|
|
class ComponentWithInvalidInterpolation3 {
|
|
|
|
}
|
2016-06-21 19:55:17 -04:00
|
|
|
|
|
|
|
@Component({selector: 'someSelector', template: '', interpolation: ['&#', '}}']})
|
|
|
|
class ComponentWithInvalidInterpolation4 {
|
|
|
|
}
|
|
|
|
|
|
|
|
@Component({selector: 'someSelector', template: '', interpolation: ['{', '}}']})
|
|
|
|
class ComponentWithInvalidInterpolation5 {
|
|
|
|
}
|