refactor(view_compiler): codegen DI and Queries
BREAKING CHANGE: - Renderer: * renderComponent method is removed form `Renderer`, only present on `RootRenderer` * Renderer.setDebugInfo is removed. Renderer.createElement / createText / createTemplateAnchor now take the DebugInfo directly. - Query semantics: * Queries don't work with dynamically loaded components. * e.g. for router-outlet: loaded components can't be queries via @ViewQuery, but router-outlet emits an event `activate` now that emits the activated component - Exception classes and the context inside changed (renamed fields) - DebugElement.attributes is an Object and not a Map in JS any more - ChangeDetectorGenConfig was renamed into CompilerConfig - AppViewManager.createEmbeddedViewInContainer / AppViewManager.createHostViewInContainer are removed, use the methods in ViewContainerRef instead - Change detection order changed: * 1. dirty check component inputs * 2. dirty check content children * 3. update render nodes Closes #6301 Closes #6567
This commit is contained in:
@ -13,7 +13,7 @@ import {
|
||||
import {provide} from 'angular2/src/core/di';
|
||||
|
||||
import {TEST_PROVIDERS} from './test_bindings';
|
||||
import {isPresent} from 'angular2/src/facade/lang';
|
||||
import {isPresent, CONST_EXPR} from 'angular2/src/facade/lang';
|
||||
import {
|
||||
TemplateParser,
|
||||
splitClasses,
|
||||
@ -23,8 +23,12 @@ import {
|
||||
CompileDirectiveMetadata,
|
||||
CompilePipeMetadata,
|
||||
CompileTypeMetadata,
|
||||
CompileTemplateMetadata
|
||||
} from 'angular2/src/compiler/directive_metadata';
|
||||
CompileTemplateMetadata,
|
||||
CompileProviderMetadata,
|
||||
CompileTokenMetadata,
|
||||
CompileDiDependencyMetadata,
|
||||
CompileQueryMetadata
|
||||
} from 'angular2/src/compiler/compile_metadata';
|
||||
import {
|
||||
templateVisitAll,
|
||||
TemplateAstVisitor,
|
||||
@ -40,16 +44,19 @@ import {
|
||||
BoundTextAst,
|
||||
TextAst,
|
||||
PropertyBindingType,
|
||||
DirectiveAst
|
||||
DirectiveAst,
|
||||
ProviderAstType
|
||||
} from 'angular2/src/compiler/template_ast';
|
||||
|
||||
import {ElementSchemaRegistry} from 'angular2/src/compiler/schema/element_schema_registry';
|
||||
import {MockSchemaRegistry} from './schema_registry_mock';
|
||||
|
||||
import {Unparser} from '../core/change_detection/parser/unparser';
|
||||
import {Unparser} from './expression_parser/unparser';
|
||||
|
||||
var expressionUnparser = new Unparser();
|
||||
|
||||
var someModuleUrl = 'package:someModule';
|
||||
|
||||
var MOCK_SCHEMA_REGISTRY = [
|
||||
provide(
|
||||
ElementSchemaRegistry,
|
||||
@ -62,15 +69,23 @@ export function main() {
|
||||
|
||||
function commonBeforeEach() {
|
||||
beforeEach(inject([TemplateParser], (parser) => {
|
||||
ngIf = CompileDirectiveMetadata.create(
|
||||
{selector: '[ngIf]', type: new CompileTypeMetadata({name: 'NgIf'}), inputs: ['ngIf']});
|
||||
var component = CompileDirectiveMetadata.create({
|
||||
selector: 'root',
|
||||
type: new CompileTypeMetadata({moduleUrl: someModuleUrl, name: 'Root'}),
|
||||
isComponent: true
|
||||
});
|
||||
ngIf = CompileDirectiveMetadata.create({
|
||||
selector: '[ngIf]',
|
||||
type: new CompileTypeMetadata({moduleUrl: someModuleUrl, name: 'NgIf'}),
|
||||
inputs: ['ngIf']
|
||||
});
|
||||
|
||||
parse = (template: string, directives: CompileDirectiveMetadata[],
|
||||
pipes: CompilePipeMetadata[] = null): TemplateAst[] => {
|
||||
if (pipes === null) {
|
||||
pipes = [];
|
||||
}
|
||||
return parser.parse(template, directives, pipes, 'TestComp');
|
||||
return parser.parse(component, template, directives, pipes, 'TestComp');
|
||||
};
|
||||
}));
|
||||
}
|
||||
@ -260,7 +275,7 @@ export function main() {
|
||||
var dirA = CompileDirectiveMetadata.create({
|
||||
selector: 'template',
|
||||
outputs: ['e'],
|
||||
type: new CompileTypeMetadata({name: 'DirA'})
|
||||
type: new CompileTypeMetadata({moduleUrl: someModuleUrl, name: 'DirA'})
|
||||
});
|
||||
expect(humanizeTplAst(parse('<template (e)="f"></template>', [dirA])))
|
||||
.toEqual([
|
||||
@ -297,16 +312,22 @@ export function main() {
|
||||
describe('directives', () => {
|
||||
it('should locate directives components first and ordered by the directives array in the View',
|
||||
() => {
|
||||
var dirA = CompileDirectiveMetadata.create(
|
||||
{selector: '[a]', type: new CompileTypeMetadata({name: 'DirA'})});
|
||||
var dirB = CompileDirectiveMetadata.create(
|
||||
{selector: '[b]', type: new CompileTypeMetadata({name: 'DirB'})});
|
||||
var dirC = CompileDirectiveMetadata.create(
|
||||
{selector: '[c]', type: new CompileTypeMetadata({name: 'DirC'})});
|
||||
var dirA = CompileDirectiveMetadata.create({
|
||||
selector: '[a]',
|
||||
type: new CompileTypeMetadata({moduleUrl: someModuleUrl, name: 'DirA'})
|
||||
});
|
||||
var dirB = CompileDirectiveMetadata.create({
|
||||
selector: '[b]',
|
||||
type: new CompileTypeMetadata({moduleUrl: someModuleUrl, name: 'DirB'})
|
||||
});
|
||||
var dirC = CompileDirectiveMetadata.create({
|
||||
selector: '[c]',
|
||||
type: new CompileTypeMetadata({moduleUrl: someModuleUrl, name: 'DirC'})
|
||||
});
|
||||
var comp = CompileDirectiveMetadata.create({
|
||||
selector: 'div',
|
||||
isComponent: true,
|
||||
type: new CompileTypeMetadata({name: 'ZComp'}),
|
||||
type: new CompileTypeMetadata({moduleUrl: someModuleUrl, name: 'ZComp'}),
|
||||
template: new CompileTemplateMetadata({ngContentSelectors: []})
|
||||
});
|
||||
expect(humanizeTplAst(parse('<div a c b>', [dirA, dirB, dirC, comp])))
|
||||
@ -323,10 +344,14 @@ export function main() {
|
||||
});
|
||||
|
||||
it('should locate directives in property bindings', () => {
|
||||
var dirA = CompileDirectiveMetadata.create(
|
||||
{selector: '[a=b]', type: new CompileTypeMetadata({name: 'DirA'})});
|
||||
var dirB = CompileDirectiveMetadata.create(
|
||||
{selector: '[b]', type: new CompileTypeMetadata({name: 'DirB'})});
|
||||
var dirA = CompileDirectiveMetadata.create({
|
||||
selector: '[a=b]',
|
||||
type: new CompileTypeMetadata({moduleUrl: someModuleUrl, name: 'DirA'})
|
||||
});
|
||||
var dirB = CompileDirectiveMetadata.create({
|
||||
selector: '[b]',
|
||||
type: new CompileTypeMetadata({moduleUrl: someModuleUrl, name: 'DirB'})
|
||||
});
|
||||
expect(humanizeTplAst(parse('<div [a]="b">', [dirA, dirB])))
|
||||
.toEqual([
|
||||
[ElementAst, 'div'],
|
||||
@ -336,8 +361,10 @@ export function main() {
|
||||
});
|
||||
|
||||
it('should locate directives in event bindings', () => {
|
||||
var dirA = CompileDirectiveMetadata.create(
|
||||
{selector: '[a]', type: new CompileTypeMetadata({name: 'DirB'})});
|
||||
var dirA = CompileDirectiveMetadata.create({
|
||||
selector: '[a]',
|
||||
type: new CompileTypeMetadata({moduleUrl: someModuleUrl, name: 'DirB'})
|
||||
});
|
||||
|
||||
expect(humanizeTplAst(parse('<div (a)="b">', [dirA])))
|
||||
.toEqual(
|
||||
@ -347,7 +374,7 @@ export function main() {
|
||||
it('should parse directive host properties', () => {
|
||||
var dirA = CompileDirectiveMetadata.create({
|
||||
selector: 'div',
|
||||
type: new CompileTypeMetadata({name: 'DirA'}),
|
||||
type: new CompileTypeMetadata({moduleUrl: someModuleUrl, name: 'DirA'}),
|
||||
host: {'[a]': 'expr'}
|
||||
});
|
||||
expect(humanizeTplAst(parse('<div></div>', [dirA])))
|
||||
@ -361,7 +388,7 @@ export function main() {
|
||||
it('should parse directive host listeners', () => {
|
||||
var dirA = CompileDirectiveMetadata.create({
|
||||
selector: 'div',
|
||||
type: new CompileTypeMetadata({name: 'DirA'}),
|
||||
type: new CompileTypeMetadata({moduleUrl: someModuleUrl, name: 'DirA'}),
|
||||
host: {'(a)': 'expr'}
|
||||
});
|
||||
expect(humanizeTplAst(parse('<div></div>', [dirA])))
|
||||
@ -370,8 +397,11 @@ export function main() {
|
||||
});
|
||||
|
||||
it('should parse directive properties', () => {
|
||||
var dirA = CompileDirectiveMetadata.create(
|
||||
{selector: 'div', type: new CompileTypeMetadata({name: 'DirA'}), inputs: ['aProp']});
|
||||
var dirA = CompileDirectiveMetadata.create({
|
||||
selector: 'div',
|
||||
type: new CompileTypeMetadata({moduleUrl: someModuleUrl, name: 'DirA'}),
|
||||
inputs: ['aProp']
|
||||
});
|
||||
expect(humanizeTplAst(parse('<div [aProp]="expr"></div>', [dirA])))
|
||||
.toEqual([
|
||||
[ElementAst, 'div'],
|
||||
@ -381,8 +411,11 @@ export function main() {
|
||||
});
|
||||
|
||||
it('should parse renamed directive properties', () => {
|
||||
var dirA = CompileDirectiveMetadata.create(
|
||||
{selector: 'div', type: new CompileTypeMetadata({name: 'DirA'}), inputs: ['b:a']});
|
||||
var dirA = CompileDirectiveMetadata.create({
|
||||
selector: 'div',
|
||||
type: new CompileTypeMetadata({moduleUrl: someModuleUrl, name: 'DirA'}),
|
||||
inputs: ['b:a']
|
||||
});
|
||||
expect(humanizeTplAst(parse('<div [a]="expr"></div>', [dirA])))
|
||||
.toEqual([
|
||||
[ElementAst, 'div'],
|
||||
@ -392,8 +425,11 @@ export function main() {
|
||||
});
|
||||
|
||||
it('should parse literal directive properties', () => {
|
||||
var dirA = CompileDirectiveMetadata.create(
|
||||
{selector: 'div', type: new CompileTypeMetadata({name: 'DirA'}), inputs: ['a']});
|
||||
var dirA = CompileDirectiveMetadata.create({
|
||||
selector: 'div',
|
||||
type: new CompileTypeMetadata({moduleUrl: someModuleUrl, name: 'DirA'}),
|
||||
inputs: ['a']
|
||||
});
|
||||
expect(humanizeTplAst(parse('<div a="literal"></div>', [dirA])))
|
||||
.toEqual([
|
||||
[ElementAst, 'div'],
|
||||
@ -404,8 +440,11 @@ export function main() {
|
||||
});
|
||||
|
||||
it('should favor explicit bound properties over literal properties', () => {
|
||||
var dirA = CompileDirectiveMetadata.create(
|
||||
{selector: 'div', type: new CompileTypeMetadata({name: 'DirA'}), inputs: ['a']});
|
||||
var dirA = CompileDirectiveMetadata.create({
|
||||
selector: 'div',
|
||||
type: new CompileTypeMetadata({moduleUrl: someModuleUrl, name: 'DirA'}),
|
||||
inputs: ['a']
|
||||
});
|
||||
expect(humanizeTplAst(parse('<div a="literal" [a]="\'literal2\'"></div>', [dirA])))
|
||||
.toEqual([
|
||||
[ElementAst, 'div'],
|
||||
@ -416,14 +455,284 @@ export function main() {
|
||||
});
|
||||
|
||||
it('should support optional directive properties', () => {
|
||||
var dirA = CompileDirectiveMetadata.create(
|
||||
{selector: 'div', type: new CompileTypeMetadata({name: 'DirA'}), inputs: ['a']});
|
||||
var dirA = CompileDirectiveMetadata.create({
|
||||
selector: 'div',
|
||||
type: new CompileTypeMetadata({moduleUrl: someModuleUrl, name: 'DirA'}),
|
||||
inputs: ['a']
|
||||
});
|
||||
expect(humanizeTplAst(parse('<div></div>', [dirA])))
|
||||
.toEqual([[ElementAst, 'div'], [DirectiveAst, dirA]]);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('providers', () => {
|
||||
var nextProviderId;
|
||||
|
||||
function createToken(value: string): CompileTokenMetadata {
|
||||
var token;
|
||||
if (value.startsWith('type:')) {
|
||||
token = new CompileTokenMetadata({
|
||||
identifier:
|
||||
new CompileTypeMetadata({moduleUrl: someModuleUrl, name: value.substring(5)})
|
||||
});
|
||||
} else {
|
||||
token = new CompileTokenMetadata({value: value});
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
function createDep(value: string): CompileDiDependencyMetadata {
|
||||
var isOptional = false;
|
||||
if (value.startsWith('optional:')) {
|
||||
isOptional = true;
|
||||
value = value.substring(9);
|
||||
}
|
||||
var isSelf = false;
|
||||
if (value.startsWith('self:')) {
|
||||
isSelf = true;
|
||||
value = value.substring(5);
|
||||
}
|
||||
var isHost = false;
|
||||
if (value.startsWith('host:')) {
|
||||
isHost = true;
|
||||
value = value.substring(5);
|
||||
}
|
||||
return new CompileDiDependencyMetadata(
|
||||
{token: createToken(value), isOptional: isOptional, isSelf: isSelf, isHost: isHost});
|
||||
}
|
||||
|
||||
function createProvider(
|
||||
token: string, {multi = false, deps = CONST_EXPR([])}:
|
||||
{multi?: boolean, deps?: string[]} = {}): CompileProviderMetadata {
|
||||
return new CompileProviderMetadata({
|
||||
token: createToken(token),
|
||||
multi: multi,
|
||||
useClass: new CompileTypeMetadata({name: `provider${nextProviderId++}`}),
|
||||
deps: deps.map(createDep)
|
||||
});
|
||||
}
|
||||
|
||||
function createDir(selector: string, {providers = null, viewProviders = null,
|
||||
deps = CONST_EXPR([]), queries = CONST_EXPR([])}: {
|
||||
providers?: CompileProviderMetadata[],
|
||||
viewProviders?: CompileProviderMetadata[],
|
||||
deps?: string[],
|
||||
queries?: string[]
|
||||
} = {}): CompileDirectiveMetadata {
|
||||
var isComponent = !selector.startsWith('[');
|
||||
return CompileDirectiveMetadata.create({
|
||||
selector: selector,
|
||||
type: new CompileTypeMetadata(
|
||||
{moduleUrl: someModuleUrl, name: selector, diDeps: deps.map(createDep)}),
|
||||
isComponent: isComponent,
|
||||
template: new CompileTemplateMetadata({ngContentSelectors: []}),
|
||||
providers: providers,
|
||||
viewProviders: viewProviders,
|
||||
queries: queries.map((value) =>
|
||||
new CompileQueryMetadata({selectors: [createToken(value)]}))
|
||||
});
|
||||
}
|
||||
|
||||
beforeEach(() => { nextProviderId = 0; });
|
||||
|
||||
it('should provide a component', () => {
|
||||
var comp = createDir('my-comp');
|
||||
var elAst: ElementAst = <ElementAst>parse('<my-comp>', [comp])[0];
|
||||
expect(elAst.providers.length).toBe(1);
|
||||
expect(elAst.providers[0].providerType).toBe(ProviderAstType.Component);
|
||||
expect(elAst.providers[0].providers[0].useClass).toBe(comp.type);
|
||||
});
|
||||
|
||||
it('should provide a directive', () => {
|
||||
var dirA = createDir('[dirA]');
|
||||
var elAst: ElementAst = <ElementAst>parse('<div dirA>', [dirA])[0];
|
||||
expect(elAst.providers.length).toBe(1);
|
||||
expect(elAst.providers[0].providerType).toBe(ProviderAstType.Directive);
|
||||
expect(elAst.providers[0].providers[0].useClass).toBe(dirA.type);
|
||||
});
|
||||
|
||||
it('should use the public providers of a directive', () => {
|
||||
var provider = createProvider('service');
|
||||
var dirA = createDir('[dirA]', {providers: [provider]});
|
||||
var elAst: ElementAst = <ElementAst>parse('<div dirA>', [dirA])[0];
|
||||
expect(elAst.providers.length).toBe(2);
|
||||
expect(elAst.providers[1].providerType).toBe(ProviderAstType.PublicService);
|
||||
expect(elAst.providers[1].providers).toEqual([provider]);
|
||||
});
|
||||
|
||||
it('should use the private providers of a component', () => {
|
||||
var provider = createProvider('service');
|
||||
var comp = createDir('my-comp', {viewProviders: [provider]});
|
||||
var elAst: ElementAst = <ElementAst>parse('<my-comp>', [comp])[0];
|
||||
expect(elAst.providers.length).toBe(2);
|
||||
expect(elAst.providers[1].providerType).toBe(ProviderAstType.PrivateService);
|
||||
expect(elAst.providers[1].providers).toEqual([provider]);
|
||||
});
|
||||
|
||||
it('should support multi providers', () => {
|
||||
var provider0 = createProvider('service0', {multi: true});
|
||||
var provider1 = createProvider('service1', {multi: true});
|
||||
var provider2 = createProvider('service0', {multi: true});
|
||||
var dirA = createDir('[dirA]', {providers: [provider0, provider1]});
|
||||
var dirB = createDir('[dirB]', {providers: [provider2]});
|
||||
var elAst: ElementAst = <ElementAst>parse('<div dirA dirB>', [dirA, dirB])[0];
|
||||
expect(elAst.providers.length).toBe(4);
|
||||
expect(elAst.providers[2].providers).toEqual([provider0, provider2]);
|
||||
expect(elAst.providers[3].providers).toEqual([provider1]);
|
||||
});
|
||||
|
||||
it('should overwrite non multi providers', () => {
|
||||
var provider1 = createProvider('service0');
|
||||
var provider2 = createProvider('service1');
|
||||
var provider3 = createProvider('service0');
|
||||
var dirA = createDir('[dirA]', {providers: [provider1, provider2]});
|
||||
var dirB = createDir('[dirB]', {providers: [provider3]});
|
||||
var elAst: ElementAst = <ElementAst>parse('<div dirA dirB>', [dirA, dirB])[0];
|
||||
expect(elAst.providers.length).toBe(4);
|
||||
expect(elAst.providers[2].providers).toEqual([provider3]);
|
||||
expect(elAst.providers[3].providers).toEqual([provider2]);
|
||||
});
|
||||
|
||||
it('should overwrite component providers by directive providers', () => {
|
||||
var compProvider = createProvider('service0');
|
||||
var dirProvider = createProvider('service0');
|
||||
var comp = createDir('my-comp', {providers: [compProvider]});
|
||||
var dirA = createDir('[dirA]', {providers: [dirProvider]});
|
||||
var elAst: ElementAst = <ElementAst>parse('<my-comp dirA>', [dirA, comp])[0];
|
||||
expect(elAst.providers.length).toBe(3);
|
||||
expect(elAst.providers[2].providers).toEqual([dirProvider]);
|
||||
});
|
||||
|
||||
it('should overwrite view providers by directive providers', () => {
|
||||
var viewProvider = createProvider('service0');
|
||||
var dirProvider = createProvider('service0');
|
||||
var comp = createDir('my-comp', {viewProviders: [viewProvider]});
|
||||
var dirA = createDir('[dirA]', {providers: [dirProvider]});
|
||||
var elAst: ElementAst = <ElementAst>parse('<my-comp dirA>', [dirA, comp])[0];
|
||||
expect(elAst.providers.length).toBe(3);
|
||||
expect(elAst.providers[2].providers).toEqual([dirProvider]);
|
||||
});
|
||||
|
||||
it('should overwrite directives by providers', () => {
|
||||
var dirProvider = createProvider('type:my-comp');
|
||||
var comp = createDir('my-comp', {providers: [dirProvider]});
|
||||
var elAst: ElementAst = <ElementAst>parse('<my-comp>', [comp])[0];
|
||||
expect(elAst.providers.length).toBe(1);
|
||||
expect(elAst.providers[0].providers).toEqual([dirProvider]);
|
||||
});
|
||||
|
||||
it('should throw if mixing multi and non multi providers', () => {
|
||||
var provider0 = createProvider('service0');
|
||||
var provider1 = createProvider('service0', {multi: true});
|
||||
var dirA = createDir('[dirA]', {providers: [provider0]});
|
||||
var dirB = createDir('[dirB]', {providers: [provider1]});
|
||||
expect(() => parse('<div dirA dirB>', [dirA, dirB]))
|
||||
.toThrowError(
|
||||
`Template parse errors:\n` +
|
||||
`Mixing multi and non multi provider is not possible for token service0 ("[ERROR ->]<div dirA dirB>"): TestComp@0:0`);
|
||||
});
|
||||
|
||||
it('should sort providers and directives by their DI order', () => {
|
||||
var provider0 = createProvider('service0', {deps: ['type:[dir2]']});
|
||||
var provider1 = createProvider('service1');
|
||||
var dir2 = createDir('[dir2]', {deps: ['service1']});
|
||||
var comp = createDir('my-comp', {providers: [provider0, provider1]});
|
||||
var elAst: ElementAst = <ElementAst>parse('<my-comp dir2>', [comp, dir2])[0];
|
||||
expect(elAst.providers.length).toBe(4);
|
||||
expect(elAst.providers[0].providers[0].useClass).toEqual(comp.type);
|
||||
expect(elAst.providers[1].providers).toEqual([provider1]);
|
||||
expect(elAst.providers[2].providers[0].useClass).toEqual(dir2.type);
|
||||
expect(elAst.providers[3].providers).toEqual([provider0]);
|
||||
});
|
||||
|
||||
it('should mark directives and dependencies of directives as eager', () => {
|
||||
var provider0 = createProvider('service0');
|
||||
var provider1 = createProvider('service1');
|
||||
var dirA = createDir('[dirA]', {providers: [provider0, provider1], deps: ['service0']});
|
||||
var elAst: ElementAst = <ElementAst>parse('<div dirA>', [dirA])[0];
|
||||
expect(elAst.providers.length).toBe(3);
|
||||
expect(elAst.providers[0].providers).toEqual([provider0]);
|
||||
expect(elAst.providers[0].eager).toBe(true);
|
||||
expect(elAst.providers[1].providers[0].useClass).toEqual(dirA.type);
|
||||
expect(elAst.providers[1].eager).toBe(true);
|
||||
expect(elAst.providers[2].providers).toEqual([provider1]);
|
||||
expect(elAst.providers[2].eager).toBe(false);
|
||||
});
|
||||
|
||||
it('should mark dependencies on parent elements as eager', () => {
|
||||
var provider0 = createProvider('service0');
|
||||
var provider1 = createProvider('service1');
|
||||
var dirA = createDir('[dirA]', {providers: [provider0, provider1]});
|
||||
var dirB = createDir('[dirB]', {deps: ['service0']});
|
||||
var elAst: ElementAst =
|
||||
<ElementAst>parse('<div dirA><div dirB></div></div>', [dirA, dirB])[0];
|
||||
expect(elAst.providers.length).toBe(3);
|
||||
expect(elAst.providers[0].providers[0].useClass).toEqual(dirA.type);
|
||||
expect(elAst.providers[0].eager).toBe(true);
|
||||
expect(elAst.providers[1].providers).toEqual([provider0]);
|
||||
expect(elAst.providers[1].eager).toBe(true);
|
||||
expect(elAst.providers[2].providers).toEqual([provider1]);
|
||||
expect(elAst.providers[2].eager).toBe(false);
|
||||
});
|
||||
|
||||
it('should mark queried providers as eager', () => {
|
||||
var provider0 = createProvider('service0');
|
||||
var provider1 = createProvider('service1');
|
||||
var dirA =
|
||||
createDir('[dirA]', {providers: [provider0, provider1], queries: ['service0']});
|
||||
var elAst: ElementAst = <ElementAst>parse('<div dirA></div>', [dirA])[0];
|
||||
expect(elAst.providers.length).toBe(3);
|
||||
expect(elAst.providers[0].providers[0].useClass).toEqual(dirA.type);
|
||||
expect(elAst.providers[0].eager).toBe(true);
|
||||
expect(elAst.providers[1].providers).toEqual([provider0]);
|
||||
expect(elAst.providers[1].eager).toBe(true);
|
||||
expect(elAst.providers[2].providers).toEqual([provider1]);
|
||||
expect(elAst.providers[2].eager).toBe(false);
|
||||
});
|
||||
|
||||
it('should not mark dependencies accross embedded views as eager', () => {
|
||||
var provider0 = createProvider('service0');
|
||||
var dirA = createDir('[dirA]', {providers: [provider0]});
|
||||
var dirB = createDir('[dirB]', {deps: ['service0']});
|
||||
var elAst: ElementAst =
|
||||
<ElementAst>parse('<div dirA><div *ngIf dirB></div></div>', [dirA, dirB])[0];
|
||||
expect(elAst.providers.length).toBe(2);
|
||||
expect(elAst.providers[0].providers[0].useClass).toEqual(dirA.type);
|
||||
expect(elAst.providers[0].eager).toBe(true);
|
||||
expect(elAst.providers[1].providers).toEqual([provider0]);
|
||||
expect(elAst.providers[1].eager).toBe(false);
|
||||
});
|
||||
|
||||
it('should report missing @Self() deps as errors', () => {
|
||||
var dirA = createDir('[dirA]', {deps: ['self:provider0']});
|
||||
expect(() => parse('<div dirA></div>', [dirA]))
|
||||
.toThrowErrorWith(
|
||||
'No provider for provider0 ("[ERROR ->]<div dirA></div>"): TestComp@0:0');
|
||||
});
|
||||
|
||||
it('should change missing @Self() that are optional to nulls', () => {
|
||||
var dirA = createDir('[dirA]', {deps: ['optional:self:provider0']});
|
||||
var elAst: ElementAst = <ElementAst>parse('<div dirA></div>', [dirA])[0];
|
||||
expect(elAst.providers[0].providers[0].deps[0].isValue).toBe(true);
|
||||
expect(elAst.providers[0].providers[0].deps[0].value).toBe(null);
|
||||
});
|
||||
|
||||
it('should report missing @Host() deps as errors', () => {
|
||||
var dirA = createDir('[dirA]', {deps: ['host:provider0']});
|
||||
expect(() => parse('<div dirA></div>', [dirA]))
|
||||
.toThrowErrorWith(
|
||||
'No provider for provider0 ("[ERROR ->]<div dirA></div>"): TestComp@0:0');
|
||||
});
|
||||
|
||||
it('should change missing @Host() that are optional to nulls', () => {
|
||||
var dirA = createDir('[dirA]', {deps: ['optional:host:provider0']});
|
||||
var elAst: ElementAst = <ElementAst>parse('<div dirA></div>', [dirA])[0];
|
||||
expect(elAst.providers[0].providers[0].deps[0].isValue).toBe(true);
|
||||
expect(elAst.providers[0].providers[0].deps[0].value).toBe(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('variables', () => {
|
||||
|
||||
it('should parse variables via #... and not report them as attributes', () => {
|
||||
@ -447,8 +756,11 @@ export function main() {
|
||||
});
|
||||
|
||||
it('should assign variables to directives via exportAs', () => {
|
||||
var dirA = CompileDirectiveMetadata.create(
|
||||
{selector: '[a]', type: new CompileTypeMetadata({name: 'DirA'}), exportAs: 'dirA'});
|
||||
var dirA = CompileDirectiveMetadata.create({
|
||||
selector: '[a]',
|
||||
type: new CompileTypeMetadata({moduleUrl: someModuleUrl, name: 'DirA'}),
|
||||
exportAs: 'dirA'
|
||||
});
|
||||
expect(humanizeTplAst(parse('<div a #a="dirA"></div>', [dirA])))
|
||||
.toEqual([
|
||||
[ElementAst, 'div'],
|
||||
@ -478,7 +790,7 @@ There is no directive with "exportAs" set to "dirA" ("<div [ERROR ->]#a="dirA"><
|
||||
var dirA = CompileDirectiveMetadata.create({
|
||||
selector: '[a]',
|
||||
isComponent: true,
|
||||
type: new CompileTypeMetadata({name: 'DirA'}),
|
||||
type: new CompileTypeMetadata({moduleUrl: someModuleUrl, name: 'DirA'}),
|
||||
exportAs: 'dirA',
|
||||
template: new CompileTemplateMetadata({ngContentSelectors: []})
|
||||
});
|
||||
@ -540,10 +852,15 @@ There is no directive with "exportAs" set to "dirA" ("<div [ERROR ->]#a="dirA"><
|
||||
|
||||
describe('directives', () => {
|
||||
it('should locate directives in property bindings', () => {
|
||||
var dirA = CompileDirectiveMetadata.create(
|
||||
{selector: '[a=b]', type: new CompileTypeMetadata({name: 'DirA'}), inputs: ['a']});
|
||||
var dirB = CompileDirectiveMetadata.create(
|
||||
{selector: '[b]', type: new CompileTypeMetadata({name: 'DirB'})});
|
||||
var dirA = CompileDirectiveMetadata.create({
|
||||
selector: '[a=b]',
|
||||
type: new CompileTypeMetadata({moduleUrl: someModuleUrl, name: 'DirA'}),
|
||||
inputs: ['a']
|
||||
});
|
||||
var dirB = CompileDirectiveMetadata.create({
|
||||
selector: '[b]',
|
||||
type: new CompileTypeMetadata({moduleUrl: someModuleUrl, name: 'DirB'})
|
||||
});
|
||||
expect(humanizeTplAst(parse('<div template="a b" b>', [dirA, dirB])))
|
||||
.toEqual([
|
||||
[EmbeddedTemplateAst],
|
||||
@ -556,10 +873,14 @@ There is no directive with "exportAs" set to "dirA" ("<div [ERROR ->]#a="dirA"><
|
||||
});
|
||||
|
||||
it('should locate directives in variable bindings', () => {
|
||||
var dirA = CompileDirectiveMetadata.create(
|
||||
{selector: '[a=b]', type: new CompileTypeMetadata({name: 'DirA'})});
|
||||
var dirB = CompileDirectiveMetadata.create(
|
||||
{selector: '[b]', type: new CompileTypeMetadata({name: 'DirB'})});
|
||||
var dirA = CompileDirectiveMetadata.create({
|
||||
selector: '[a=b]',
|
||||
type: new CompileTypeMetadata({moduleUrl: someModuleUrl, name: 'DirA'})
|
||||
});
|
||||
var dirB = CompileDirectiveMetadata.create({
|
||||
selector: '[b]',
|
||||
type: new CompileTypeMetadata({moduleUrl: someModuleUrl, name: 'DirB'})
|
||||
});
|
||||
expect(humanizeTplAst(parse('<div template="#a=b" b>', [dirA, dirB])))
|
||||
.toEqual([
|
||||
[EmbeddedTemplateAst],
|
||||
@ -596,12 +917,16 @@ There is no directive with "exportAs" set to "dirA" ("<div [ERROR ->]#a="dirA"><
|
||||
});
|
||||
|
||||
describe('content projection', () => {
|
||||
var compCounter;
|
||||
beforeEach(() => { compCounter = 0; });
|
||||
|
||||
function createComp(selector: string,
|
||||
ngContentSelectors: string[]): CompileDirectiveMetadata {
|
||||
return CompileDirectiveMetadata.create({
|
||||
selector: selector,
|
||||
isComponent: true,
|
||||
type: new CompileTypeMetadata({name: 'SomeComp'}),
|
||||
type:
|
||||
new CompileTypeMetadata({moduleUrl: someModuleUrl, name: `SomeComp${compCounter++}`}),
|
||||
template: new CompileTemplateMetadata({ngContentSelectors: ngContentSelectors})
|
||||
})
|
||||
}
|
||||
@ -750,7 +1075,7 @@ Parser Error: Unexpected token 'b' at column 3 in [a b] in TestComp@0:5 ("<div [
|
||||
() => {
|
||||
var dirA = CompileDirectiveMetadata.create({
|
||||
selector: 'div',
|
||||
type: new CompileTypeMetadata({name: 'DirA'}),
|
||||
type: new CompileTypeMetadata({moduleUrl: someModuleUrl, name: 'DirA'}),
|
||||
inputs: ['invalidProp']
|
||||
});
|
||||
expect(() => parse('<div [invalid-prop]></div>', [dirA])).not.toThrow();
|
||||
@ -760,13 +1085,13 @@ Parser Error: Unexpected token 'b' at column 3 in [a b] in TestComp@0:5 ("<div [
|
||||
var dirA = CompileDirectiveMetadata.create({
|
||||
selector: 'div',
|
||||
isComponent: true,
|
||||
type: new CompileTypeMetadata({name: 'DirA'}),
|
||||
type: new CompileTypeMetadata({moduleUrl: someModuleUrl, name: 'DirA'}),
|
||||
template: new CompileTemplateMetadata({ngContentSelectors: []})
|
||||
});
|
||||
var dirB = CompileDirectiveMetadata.create({
|
||||
selector: 'div',
|
||||
isComponent: true,
|
||||
type: new CompileTypeMetadata({name: 'DirB'}),
|
||||
type: new CompileTypeMetadata({moduleUrl: someModuleUrl, name: 'DirB'}),
|
||||
template: new CompileTemplateMetadata({ngContentSelectors: []})
|
||||
});
|
||||
expect(() => parse('<div>', [dirB, dirA])).toThrowError(`Template parse errors:
|
||||
@ -778,7 +1103,7 @@ More than one component: DirB,DirA ("[ERROR ->]<div>"): TestComp@0:0`);
|
||||
var dirA = CompileDirectiveMetadata.create({
|
||||
selector: '[a]',
|
||||
isComponent: true,
|
||||
type: new CompileTypeMetadata({name: 'DirA'}),
|
||||
type: new CompileTypeMetadata({moduleUrl: someModuleUrl, name: 'DirA'}),
|
||||
template: new CompileTemplateMetadata({ngContentSelectors: []})
|
||||
});
|
||||
expect(() => parse('<template [a]="b" (e)="f"></template>', [dirA]))
|
||||
@ -792,7 +1117,7 @@ Property binding a not used by any directive on an embedded template ("[ERROR ->
|
||||
var dirA = CompileDirectiveMetadata.create({
|
||||
selector: '[a]',
|
||||
isComponent: true,
|
||||
type: new CompileTypeMetadata({name: 'DirA'}),
|
||||
type: new CompileTypeMetadata({moduleUrl: someModuleUrl, name: 'DirA'}),
|
||||
template: new CompileTemplateMetadata({ngContentSelectors: []})
|
||||
});
|
||||
expect(() => parse('<div *a="b"></div>', [dirA])).toThrowError(`Template parse errors:
|
||||
@ -910,8 +1235,11 @@ Property binding a not used by any directive on an embedded template ("[ERROR ->
|
||||
});
|
||||
|
||||
it('should support variables', () => {
|
||||
var dirA = CompileDirectiveMetadata.create(
|
||||
{selector: '[a]', type: new CompileTypeMetadata({name: 'DirA'}), exportAs: 'dirA'});
|
||||
var dirA = CompileDirectiveMetadata.create({
|
||||
selector: '[a]',
|
||||
type: new CompileTypeMetadata({moduleUrl: someModuleUrl, name: 'DirA'}),
|
||||
exportAs: 'dirA'
|
||||
});
|
||||
expect(humanizeTplAstSourceSpans(parse('<div a #a="dirA"></div>', [dirA])))
|
||||
.toEqual([
|
||||
[ElementAst, 'div', '<div a #a="dirA">'],
|
||||
@ -955,12 +1283,14 @@ Property binding a not used by any directive on an embedded template ("[ERROR ->
|
||||
});
|
||||
|
||||
it('should support directive', () => {
|
||||
var dirA = CompileDirectiveMetadata.create(
|
||||
{selector: '[a]', type: new CompileTypeMetadata({name: 'DirA'})});
|
||||
var dirA = CompileDirectiveMetadata.create({
|
||||
selector: '[a]',
|
||||
type: new CompileTypeMetadata({moduleUrl: someModuleUrl, name: 'DirA'})
|
||||
});
|
||||
var comp = CompileDirectiveMetadata.create({
|
||||
selector: 'div',
|
||||
isComponent: true,
|
||||
type: new CompileTypeMetadata({name: 'ZComp'}),
|
||||
type: new CompileTypeMetadata({moduleUrl: someModuleUrl, name: 'ZComp'}),
|
||||
template: new CompileTemplateMetadata({ngContentSelectors: []})
|
||||
});
|
||||
expect(humanizeTplAstSourceSpans(parse('<div a>', [dirA, comp])))
|
||||
@ -973,10 +1303,14 @@ Property binding a not used by any directive on an embedded template ("[ERROR ->
|
||||
});
|
||||
|
||||
it('should support directive in namespace', () => {
|
||||
var tagSel = CompileDirectiveMetadata.create(
|
||||
{selector: 'circle', type: new CompileTypeMetadata({name: 'elDir'})});
|
||||
var attrSel = CompileDirectiveMetadata.create(
|
||||
{selector: '[href]', type: new CompileTypeMetadata({name: 'attrDir'})});
|
||||
var tagSel = CompileDirectiveMetadata.create({
|
||||
selector: 'circle',
|
||||
type: new CompileTypeMetadata({moduleUrl: someModuleUrl, name: 'elDir'})
|
||||
});
|
||||
var attrSel = CompileDirectiveMetadata.create({
|
||||
selector: '[href]',
|
||||
type: new CompileTypeMetadata({moduleUrl: someModuleUrl, name: 'attrDir'})
|
||||
});
|
||||
|
||||
expect(humanizeTplAstSourceSpans(
|
||||
parse('<svg><circle /><use xlink:href="Port" /></svg>', [tagSel, attrSel])))
|
||||
@ -991,8 +1325,11 @@ Property binding a not used by any directive on an embedded template ("[ERROR ->
|
||||
});
|
||||
|
||||
it('should support directive property', () => {
|
||||
var dirA = CompileDirectiveMetadata.create(
|
||||
{selector: 'div', type: new CompileTypeMetadata({name: 'DirA'}), inputs: ['aProp']});
|
||||
var dirA = CompileDirectiveMetadata.create({
|
||||
selector: 'div',
|
||||
type: new CompileTypeMetadata({moduleUrl: someModuleUrl, name: 'DirA'}),
|
||||
inputs: ['aProp']
|
||||
});
|
||||
expect(humanizeTplAstSourceSpans(parse('<div [aProp]="foo"></div>', [dirA])))
|
||||
.toEqual([
|
||||
[ElementAst, 'div', '<div [aProp]="foo">'],
|
||||
@ -1005,8 +1342,10 @@ Property binding a not used by any directive on an embedded template ("[ERROR ->
|
||||
|
||||
describe('pipes', () => {
|
||||
it('should allow pipes that have been defined as dependencies', () => {
|
||||
var testPipe =
|
||||
new CompilePipeMetadata({name: 'test', type: new CompileTypeMetadata({name: 'DirA'})});
|
||||
var testPipe = new CompilePipeMetadata({
|
||||
name: 'test',
|
||||
type: new CompileTypeMetadata({moduleUrl: someModuleUrl, name: 'DirA'})
|
||||
});
|
||||
expect(() => parse('{{a | test}}', [], [testPipe])).not.toThrow();
|
||||
});
|
||||
|
||||
@ -1167,7 +1506,7 @@ class FooAstTransformer implements TemplateAstVisitor {
|
||||
visitEmbeddedTemplate(ast: EmbeddedTemplateAst, context: any): any { throw 'not implemented'; }
|
||||
visitElement(ast: ElementAst, context: any): any {
|
||||
if (ast.name != 'div') return ast;
|
||||
return new ElementAst('foo', [], [], [], [], [], [], ast.ngContentIndex, ast.sourceSpan);
|
||||
return new ElementAst('foo', [], [], [], [], [], [], [], ast.ngContentIndex, ast.sourceSpan);
|
||||
}
|
||||
visitVariable(ast: VariableAst, context: any): any { throw 'not implemented'; }
|
||||
visitEvent(ast: BoundEventAst, context: any): any { throw 'not implemented'; }
|
||||
@ -1184,6 +1523,6 @@ class FooAstTransformer implements TemplateAstVisitor {
|
||||
class BarAstTransformer extends FooAstTransformer {
|
||||
visitElement(ast: ElementAst, context: any): any {
|
||||
if (ast.name != 'foo') return ast;
|
||||
return new ElementAst('bar', [], [], [], [], [], [], ast.ngContentIndex, ast.sourceSpan);
|
||||
return new ElementAst('bar', [], [], [], [], [], [], [], ast.ngContentIndex, ast.sourceSpan);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user