feat(Compiler): Multiple template per component

fixes #596

- TemplateConfig becomes Template
- introduce a TemplateResolver to pick the cmp template,
- @Component and @Template are disociated
This commit is contained in:
Victor Berchet
2015-02-12 14:44:59 +01:00
parent 52b062621d
commit e6c8bde808
35 changed files with 953 additions and 724 deletions

View File

@ -1,7 +1,8 @@
import {describe, beforeEach, it, expect, ddescribe, iit, el, IS_DARTIUM} from 'angular2/test_lib';
import {DOM, Element, TemplateElement} from 'angular2/src/facade/dom';
import {List, ListWrapper, Map, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
import {Type, isBlank, stringify} from 'angular2/src/facade/lang';
import {Type, isBlank, stringify, isPresent} from 'angular2/src/facade/lang';
import {PromiseWrapper} from 'angular2/src/facade/async';
import {Compiler, CompilerCache} from 'angular2/src/core/compiler/compiler';
@ -9,39 +10,39 @@ import {ProtoView} from 'angular2/src/core/compiler/view';
import {DirectiveMetadataReader} from 'angular2/src/core/compiler/directive_metadata_reader';
import {DirectiveMetadata} from 'angular2/src/core/compiler/directive_metadata';
import {Component} from 'angular2/src/core/annotations/annotations';
import {TemplateConfig} from 'angular2/src/core/annotations/template_config';
import {Template} from 'angular2/src/core/annotations/template';
import {CompileElement} from 'angular2/src/core/compiler/pipeline/compile_element';
import {CompileStep} from 'angular2/src/core/compiler/pipeline/compile_step'
import {CompileControl} from 'angular2/src/core/compiler/pipeline/compile_control';
import {TemplateLoader} from 'angular2/src/core/compiler/template_loader';
import {TemplateResolver} from 'angular2/src/core/compiler/template_resolver';
import {Lexer, Parser, dynamicChangeDetection} from 'angular2/change_detection';
import {ShadowDomStrategy, NativeShadowDomStrategy} from 'angular2/src/core/compiler/shadow_dom_strategy';
import {XHRMock} from 'angular2/src/mock/xhr_mock';
export function main() {
describe('compiler', function() {
var reader;
beforeEach( () => {
reader = new DirectiveMetadataReader();
});
var syncTemplateLoader = new FakeTemplateLoader();
syncTemplateLoader.forceSync();
var asyncTemplateLoader = new FakeTemplateLoader();
asyncTemplateLoader.forceAsync();
StringMapWrapper.forEach({
'(sync TemplateLoader)': syncTemplateLoader,
'(async TemplateLoader)': asyncTemplateLoader
}, (templateLoader, name) => {
'(sync TemplateLoader)': true,
'(async TemplateLoader)': false
}, (sync, name) => {
var reader, tplResolver;
beforeEach(() => {
reader = new DirectiveMetadataReader();
tplResolver = new FakeTemplateResolver();
if (sync) {
tplResolver.forceSync();
} else {
tplResolver.forceAsync();
}
});
describe(name, () => {
function createCompiler(processClosure) {
var steps = [new MockStep(processClosure)];
return new TestableCompiler(reader, steps, templateLoader);
return new TestableCompiler(reader, steps, new FakeTemplateLoader(), tplResolver);
}
it('should run the steps and return the ProtoView of the root element', (done) => {
@ -49,43 +50,35 @@ export function main() {
var compiler = createCompiler( (parent, current, control) => {
current.inheritedProtoView = rootProtoView;
});
compiler.compile(MainComponent, el('<div></div>')).then( (protoView) => {
tplResolver.setTemplate(MainComponent, new Template({inline: '<div></div>'}));
compiler.compile(MainComponent).then( (protoView) => {
expect(protoView).toBe(rootProtoView);
done();
});
});
it('should use the given element', (done) => {
var element = el('<div></div>');
it('should use the inline template', (done) => {
var compiler = createCompiler( (parent, current, control) => {
current.inheritedProtoView = new ProtoView(current.element, null, null);
});
compiler.compile(MainComponent, element).then( (protoView) => {
expect(protoView.element).toBe(element);
done();
});
});
it('should use the inline template if no element is given explicitly', (done) => {
var compiler = createCompiler( (parent, current, control) => {
current.inheritedProtoView = new ProtoView(current.element, null, null);
});
compiler.compile(MainComponent, null).then( (protoView) => {
compiler.compile(MainComponent).then( (protoView) => {
expect(DOM.getInnerHTML(protoView.element)).toEqual('inline component');
done();
});
});
it('should load nested components', (done) => {
var mainEl = el('<div></div>');
var compiler = createCompiler( (parent, current, control) => {
current.inheritedProtoView = new ProtoView(current.element, null, null);
current.inheritedElementBinder = current.inheritedProtoView.bindElement(null);
if (current.element === mainEl) {
if (DOM.hasClass(current.element, 'nested')) {
current.componentDirective = reader.read(NestedComponent);
current.inheritedProtoView = parent.inheritedProtoView;
current.inheritedElementBinder = current.inheritedProtoView.bindElement(null);
} else {
current.inheritedProtoView = new ProtoView(current.element, null, null);
}
});
compiler.compile(MainComponent, mainEl).then( (protoView) => {
tplResolver.setTemplate(MainComponent, new Template({inline: '<div class="nested"></div>'}));
compiler.compile(MainComponent).then( (protoView) => {
var nestedView = protoView.elementBinders[0].nestedProtoView;
expect(DOM.getInnerHTML(nestedView.element)).toEqual('nested component');
done();
@ -93,14 +86,14 @@ export function main() {
});
it('should cache compiled components', (done) => {
var element = el('<div></div>');
var compiler = createCompiler( (parent, current, control) => {
current.inheritedProtoView = new ProtoView(current.element, null, null);
});
var firstProtoView;
compiler.compile(MainComponent, element).then( (protoView) => {
tplResolver.setTemplate(MainComponent, new Template({inline: '<div></div>'}));
compiler.compile(MainComponent).then( (protoView) => {
firstProtoView = protoView;
return compiler.compile(MainComponent, element);
return compiler.compile(MainComponent);
}).then( (protoView) => {
expect(firstProtoView).toBe(protoView);
done();
@ -109,7 +102,6 @@ export function main() {
it('should re-use components being compiled', (done) => {
var nestedElBinders = [];
var mainEl = el('<div><div class="nested"></div><div class="nested"></div></div>');
var compiler = createCompiler( (parent, current, control) => {
if (DOM.hasClass(current.element, 'nested')) {
current.inheritedProtoView = new ProtoView(current.element, null, null);
@ -118,7 +110,9 @@ export function main() {
ListWrapper.push(nestedElBinders, current.inheritedElementBinder);
}
});
compiler.compile(MainComponent, mainEl).then( (protoView) => {
tplResolver.setTemplate(MainComponent,
new Template({inline: '<div><div class="nested"></div><div class="nested"></div></div>'}));
compiler.compile(MainComponent).then( (protoView) => {
expect(nestedElBinders[0].nestedProtoView).toBe(nestedElBinders[1].nestedProtoView);
done();
});
@ -130,7 +124,7 @@ export function main() {
current.inheritedElementBinder = current.inheritedProtoView.bindElement(null);
current.componentDirective = reader.read(RecursiveComponent);
});
compiler.compile(RecursiveComponent, null).then( (protoView) => {
compiler.compile(RecursiveComponent).then( (protoView) => {
expect(protoView.elementBinders[0].nestedProtoView).toBe(protoView);
done();
});
@ -139,12 +133,14 @@ export function main() {
});
describe('(mixed async, sync TemplateLoader)', () => {
function createCompiler(processClosure, templateLoader: TemplateLoader) {
var reader = new DirectiveMetadataReader();
function createCompiler(processClosure, resolver: TemplateResolver) {
var steps = [new MockStep(processClosure)];
return new TestableCompiler(reader, steps, templateLoader);
return new TestableCompiler(reader, steps, new FakeTemplateLoader(), resolver);
}
function createNestedComponentSpec(name, loader: TemplateLoader, error:string = null) {
function createNestedComponentSpec(name, resolver: TemplateResolver, error:string = null) {
it(`should load nested components ${name}`, (done) => {
var compiler = createCompiler((parent, current, control) => {
@ -155,7 +151,7 @@ export function main() {
} else {
current.inheritedProtoView = new ProtoView(current.element, null, null);
}
}, loader);
}, resolver);
PromiseWrapper.then(compiler.compile(ParentComponent),
function(protoView) {
@ -172,89 +168,77 @@ export function main() {
});
}
var loader = new FakeTemplateLoader();
loader.setSync(ParentComponent);
loader.setSync(NestedComponent);
createNestedComponentSpec('(sync -> sync)', loader);
var resolver = new FakeTemplateResolver();
resolver.setSync(ParentComponent);
resolver.setSync(NestedComponent);
createNestedComponentSpec('(sync -> sync)', resolver);
loader = new FakeTemplateLoader();
loader.setAsync(ParentComponent);
loader.setSync(NestedComponent);
createNestedComponentSpec('(async -> sync)', loader);
resolver = new FakeTemplateResolver();
resolver.setAsync(ParentComponent);
resolver.setSync(NestedComponent);
createNestedComponentSpec('(async -> sync)', resolver);
loader = new FakeTemplateLoader();
loader.setSync(ParentComponent);
loader.setAsync(NestedComponent);
createNestedComponentSpec('(sync -> async)', loader);
resolver = new FakeTemplateResolver();
resolver.setSync(ParentComponent);
resolver.setAsync(NestedComponent);
createNestedComponentSpec('(sync -> async)', resolver);
loader = new FakeTemplateLoader();
loader.setAsync(ParentComponent);
loader.setAsync(NestedComponent);
createNestedComponentSpec('(async -> async)', loader);
resolver = new FakeTemplateResolver();
resolver.setAsync(ParentComponent);
resolver.setAsync(NestedComponent);
createNestedComponentSpec('(async -> async)', resolver);
loader = new FakeTemplateLoader();
loader.setError(ParentComponent);
loader.setSync(NestedComponent);
createNestedComponentSpec('(error -> sync)', loader,
resolver = new FakeTemplateResolver();
resolver.setError(ParentComponent);
resolver.setSync(NestedComponent);
createNestedComponentSpec('(error -> sync)', resolver,
'Failed to load the template for ParentComponent');
// TODO(vicb): Check why errors this fails with Dart
// TODO(vicb): The Promise is rejected with the correct error but an exc is thrown before
//loader = new FakeTemplateLoader();
//loader.setSync(ParentComponent);
//loader.setError(NestedComponent);
//createNestedComponentSpec('(sync -> error)', loader,
//resolver = new FakeTemplateResolver();
//resolver.setSync(ParentComponent);
//resolver.setError(NestedComponent);
//createNestedComponentSpec('(sync -> error)', resolver,
// 'Failed to load the template for NestedComponent -> Failed to compile ParentComponent');
//
//loader = new FakeTemplateLoader();
//loader.setAsync(ParentComponent);
//loader.setError(NestedComponent);
//createNestedComponentSpec('(async -> error)', loader,
//resolver = new FakeTemplateResolver();
//resolver.setAsync(ParentComponent);
//resolver.setError(NestedComponent);
//createNestedComponentSpec('(async -> error)', resolver,
// 'Failed to load the template for NestedComponent -> Failed to compile ParentComponent');
});
});
}
@Component({
template: new TemplateConfig({
inline: '<div class="parent"></div>'
})
})
@Component()
@Template({inline: '<div class="parent"></div>'})
class ParentComponent {}
@Component({
template: new TemplateConfig({
inline: 'inline component'
})
})
@Component()
@Template({inline: 'inline component'})
class MainComponent {}
@Component({
template: new TemplateConfig({
inline: 'nested component'
})
})
@Component()
@Template({inline: 'nested component'})
class NestedComponent {}
@Component({
template: new TemplateConfig({
inline: '<div rec-comp></div>'
}),
selector: 'rec-comp'
})
@Component({selector: 'rec-comp'})
@Template({inline: '<div rec-comp></div>'})
class RecursiveComponent {}
class TestableCompiler extends Compiler {
steps:List;
constructor(reader:DirectiveMetadataReader, steps:List<CompileStep>, loader: TemplateLoader) {
constructor(reader:DirectiveMetadataReader, steps:List<CompileStep>, loader: TemplateLoader,
resolver: TemplateResolver) {
super(dynamicChangeDetection, loader, reader, new Parser(new Lexer()), new CompilerCache(),
new NativeShadowDomStrategy());
new NativeShadowDomStrategy(), resolver);
this.steps = steps;
}
createSteps(component):List<CompileStep> {
createSteps(component:Type, template: Template):List<CompileStep> {
return this.steps;
}
}
@ -271,19 +255,70 @@ class MockStep extends CompileStep {
}
class FakeTemplateLoader extends TemplateLoader {
constructor() {
super(null);
}
load(template: Template) {
if (isPresent(template.inline)) {
return DOM.createTemplate(template.inline);
}
if (isPresent(template.url)) {
var tplElement = DOM.createTemplate(template.url);
return PromiseWrapper.resolve(tplElement);
}
return PromiseWrapper.reject('Fail to load');
}
}
class FakeTemplateResolver extends TemplateResolver {
_forceSync: boolean;
_forceAsync: boolean;
_cmpTemplates: Map;
_syncCmp: List<Type>;
_asyncCmp: List<Type>;
_errorCmp: List<Type>;
constructor() {
super (new XHRMock());
super();
this._forceSync = false;
this._forceAsync = false;
this._syncCmp = [];
this._asyncCmp = [];
this._errorCmp = [];
this._cmpTemplates = MapWrapper.create();
}
resolve(component: Type): Template {
var template = MapWrapper.get(this._cmpTemplates, component);
if (isBlank(template)) {
template = super.resolve(component);
}
var html = template.inline;
if (isBlank(template.inline)) {
throw 'The tested component must define an inline template';
}
if (ListWrapper.contains(this._errorCmp, component)) {
return new Template({url: null, inline: null});
}
if (ListWrapper.contains(this._syncCmp, component)) {
return new Template({inline: html});
}
if (ListWrapper.contains(this._asyncCmp, component)) {
return new Template({url: html});
}
if (this._forceSync) return new Template({inline: html});
if (this._forceAsync) return new Template({url: html});
throw 'No template';
}
forceSync() {
@ -308,31 +343,7 @@ class FakeTemplateLoader extends TemplateLoader {
ListWrapper.push(this._errorCmp, component);
}
load(cmpMetadata: DirectiveMetadata) {
var annotation:Component = cmpMetadata.annotation;
var tplConfig:TemplateConfig = annotation.template;
if (isBlank(tplConfig.inline)) {
throw 'The component must define an inline template';
}
var template = DOM.createTemplate(tplConfig.inline);
if (ListWrapper.contains(this._errorCmp, cmpMetadata.type)) {
return PromiseWrapper.reject('Fail to load');
}
if (ListWrapper.contains(this._syncCmp, cmpMetadata.type)) {
return template;
}
if (ListWrapper.contains(this._asyncCmp, cmpMetadata.type)) {
return PromiseWrapper.resolve(template);
}
if (this._forceSync) return template;
if (this._forceAsync) return PromiseWrapper.resolve(template);
throw `No template configured for ${stringify(cmpMetadata.type)}`;
setTemplate(component: Type, template: Template) {
MapWrapper.set(this._cmpTemplates, component, template);
}
}

View File

@ -1,43 +1,25 @@
import {ddescribe, describe, it, iit, expect, beforeEach} from 'angular2/test_lib';
import {DirectiveMetadataReader} from 'angular2/src/core/compiler/directive_metadata_reader';
import {Decorator, Component} from 'angular2/src/core/annotations/annotations';
import {TemplateConfig} from 'angular2/src/core/annotations/template_config';
import {Decorator, Component, Viewport} from 'angular2/src/core/annotations/annotations';
import {Template} from 'angular2/src/core/annotations/template';
import {DirectiveMetadata} from 'angular2/src/core/compiler/directive_metadata';
import {ShadowDomStrategy, NativeShadowDomStrategy} from 'angular2/src/core/compiler/shadow_dom_strategy';
import {CONST} from 'angular2/src/facade/lang';
import {If, Foreach} from 'angular2/directives';
@Decorator({
selector: 'someSelector'
})
class SomeDirective {
}
@Decorator({selector: 'someDecorator'})
class SomeDecorator {}
@Component({selector: 'someComponent'})
class SomeComponent {}
@Viewport({selector: 'someViewport'})
class SomeViewport {}
class SomeDirectiveWithoutAnnotation {
}
@Component({
selector: 'withoutDirectives'
})
class ComponentWithoutDirectives {}
@Component({
selector: 'withDirectives',
template: new TemplateConfig({
directives: [ComponentWithoutDirectives]
})
})
class ComponentWithDirectives {}
@Component({
selector: 'withDirectivesTree',
template: new TemplateConfig({
directives: [[SomeDirective, [Foreach, If]], ComponentWithoutDirectives]
})
})
class ComponentWithDirectivesTree {}
export function main() {
describe("DirectiveMetadataReader", () => {
var reader;
@ -46,10 +28,22 @@ export function main() {
reader = new DirectiveMetadataReader();
});
it('should read out the annotation', () => {
var directiveMetadata = reader.read(SomeDirective);
it('should read out the Decorator annotation', () => {
var directiveMetadata = reader.read(SomeDecorator);
expect(directiveMetadata).toEqual(
new DirectiveMetadata(SomeDirective, new Decorator({selector: 'someSelector'}), null));
new DirectiveMetadata(SomeDecorator, new Decorator({selector: 'someDecorator'})));
});
it('should read out the Viewport annotation', () => {
var directiveMetadata = reader.read(SomeViewport);
expect(directiveMetadata).toEqual(
new DirectiveMetadata(SomeViewport, new Viewport({selector: 'someViewport'})));
});
it('should read out the Component annotation', () => {
var directiveMetadata = reader.read(SomeComponent);
expect(directiveMetadata).toEqual(
new DirectiveMetadata(SomeComponent, new Component({selector: 'someComponent'})));
});
it('should throw if not matching annotation is found', () => {
@ -57,22 +51,5 @@ export function main() {
reader.read(SomeDirectiveWithoutAnnotation);
}).toThrowError('No Directive annotation found on SomeDirectiveWithoutAnnotation');
});
describe("componentDirectives", () => {
it("should return an empty list when no directives specified", () => {
var cmp = reader.read(ComponentWithoutDirectives);
expect(cmp.componentDirectives).toEqual([]);
});
it("should return a list of directives specified in the template config", () => {
var cmp = reader.read(ComponentWithDirectives);
expect(cmp.componentDirectives).toEqual([ComponentWithoutDirectives]);
});
it("should return a list of directives specified in the template config as a tree", () => {
var cmp = reader.read(ComponentWithDirectivesTree);
expect(cmp.componentDirectives).toEqual([SomeDirective, Foreach, If, ComponentWithoutDirectives]);
});
});
});
}

View File

@ -1,6 +1,8 @@
import {describe, xit, it, expect, beforeEach, ddescribe, iit, el} from 'angular2/test_lib';
import {DOM} from 'angular2/src/facade/dom';
import {Map, MapWrapper} from 'angular2/src/facade/collection';
import {Type, isPresent} from 'angular2/src/facade/lang';
import {Injector} from 'angular2/di';
import {Lexer, Parser, ChangeDetector, dynamicChangeDetection} from 'angular2/change_detection';
@ -9,27 +11,27 @@ import {Compiler, CompilerCache} from 'angular2/src/core/compiler/compiler';
import {DirectiveMetadataReader} from 'angular2/src/core/compiler/directive_metadata_reader';
import {NativeShadowDomStrategy} from 'angular2/src/core/compiler/shadow_dom_strategy';
import {TemplateLoader} from 'angular2/src/core/compiler/template_loader';
import {TemplateResolver} from 'angular2/src/core/compiler/template_resolver';
import {BindingPropagationConfig} from 'angular2/src/core/compiler/binding_propagation_config';
import {Decorator, Component, Viewport} from 'angular2/src/core/annotations/annotations';
import {TemplateConfig} from 'angular2/src/core/annotations/template_config';
import {Template} from 'angular2/src/core/annotations/template';
import {ViewContainer} from 'angular2/src/core/compiler/view_container';
import {MapWrapper} from 'angular2/src/facade/collection';
import {XHRMock} from 'angular2/src/mock/xhr_mock';
export function main() {
describe('integration tests', function() {
var compiler;
var compiler, tplResolver;
beforeEach( () => {
tplResolver = new FakeTemplateResolver();
compiler = new Compiler(dynamicChangeDetection,
new TemplateLoader(new XHRMock()),
new TemplateLoader(null),
new DirectiveMetadataReader(),
new Parser(new Lexer()),
new CompilerCache(),
new NativeShadowDomStrategy()
new NativeShadowDomStrategy(),
tplResolver
);
});
@ -43,7 +45,9 @@ export function main() {
}
it('should consume text node changes', (done) => {
compiler.compile(MyComp, el('<div>{{ctxProp}}</div>')).then((pv) => {
tplResolver.setTemplate(MyComp, new Template({inline: '<div>{{ctxProp}}</div>'}));
compiler.compile(MyComp).then((pv) => {
createView(pv);
ctx.ctxProp = 'Hello World!';
@ -54,7 +58,9 @@ export function main() {
});
it('should consume element binding changes', (done) => {
compiler.compile(MyComp, el('<div [id]="ctxProp"></div>')).then((pv) => {
tplResolver.setTemplate(MyComp, new Template({inline: '<div [id]="ctxProp"></div>'}));
compiler.compile(MyComp).then((pv) => {
createView(pv);
ctx.ctxProp = 'Hello World!';
@ -73,7 +79,9 @@ export function main() {
'<div my-dir elprop="Hi {{\'there!\'}}"></div>' +
'<div my-dir elprop="One more {{ctxProp}}"></div>' +
'</div>'
compiler.compile(MyComp, el(tpl)).then((pv) => {
tplResolver.setTemplate(MyComp, new Template({inline: tpl, directives: [MyDir]}));
compiler.compile(MyComp).then((pv) => {
createView(pv);
ctx.ctxProp = 'Hello World!';
@ -88,7 +96,12 @@ export function main() {
});
it('should support nested components.', (done) => {
compiler.compile(MyComp, el('<child-cmp></child-cmp>')).then((pv) => {
tplResolver.setTemplate(MyComp, new Template({
inline: '<child-cmp></child-cmp>',
directives: [ChildComp]
}));
compiler.compile(MyComp).then((pv) => {
createView(pv);
cd.detectChanges();
@ -100,7 +113,13 @@ export function main() {
// GH issue 328 - https://github.com/angular/angular/issues/328
it('should support different directive types on a single node', (done) => {
compiler.compile(MyComp, el('<child-cmp my-dir [elprop]="ctxProp"></child-cmp>')).then((pv) => {
tplResolver.setTemplate(MyComp,
new Template({
inline: '<child-cmp my-dir [elprop]="ctxProp"></child-cmp>',
directives: [MyDir, ChildComp]
}));
compiler.compile(MyComp).then((pv) => {
createView(pv);
ctx.ctxProp = 'Hello World!';
@ -115,7 +134,13 @@ export function main() {
});
it('should support template directives via `<template>` elements.', (done) => {
compiler.compile(MyComp, el('<div><template some-tmplate var-greeting="some-tmpl"><copy-me>{{greeting}}</copy-me></template></div>')).then((pv) => {
tplResolver.setTemplate(MyComp,
new Template({
inline: '<div><template some-viewport var-greeting="some-tmpl"><copy-me>{{greeting}}</copy-me></template></div>',
directives: [SomeViewport]
}));
compiler.compile(MyComp).then((pv) => {
createView(pv);
cd.detectChanges();
@ -130,7 +155,12 @@ export function main() {
});
it('should support template directives via `template` attribute.', (done) => {
compiler.compile(MyComp, el('<div><copy-me template="some-tmplate: var greeting=some-tmpl">{{greeting}}</copy-me></div>')).then((pv) => {
tplResolver.setTemplate(MyComp, new Template({
inline: '<div><copy-me template="some-viewport: var greeting=some-tmpl">{{greeting}}</copy-me></div>',
directives: [SomeViewport]
}));
compiler.compile(MyComp).then((pv) => {
createView(pv);
cd.detectChanges();
@ -145,7 +175,12 @@ export function main() {
});
it('should assign the component instance to a var-', (done) => {
compiler.compile(MyComp, el('<p><child-cmp var-alice></child-cmp></p>')).then((pv) => {
tplResolver.setTemplate(MyComp, new Template({
inline: '<p><child-cmp var-alice></child-cmp></p>',
directives: [ChildComp]
}));
compiler.compile(MyComp).then((pv) => {
createView(pv);
expect(view.contextWithLocals).not.toBe(null);
@ -156,9 +191,12 @@ export function main() {
});
it('should assign two component instances each with a var-', (done) => {
var element = el('<p><child-cmp var-alice></child-cmp><child-cmp var-bob></p>');
tplResolver.setTemplate(MyComp, new Template({
inline: '<p><child-cmp var-alice></child-cmp><child-cmp var-bob></p>',
directives: [ChildComp]
}));
compiler.compile(MyComp, element).then((pv) => {
compiler.compile(MyComp).then((pv) => {
createView(pv);
expect(view.contextWithLocals).not.toBe(null);
@ -171,7 +209,12 @@ export function main() {
});
it('should assign the component instance to a var- with shorthand syntax', (done) => {
compiler.compile(MyComp, el('<child-cmp #alice></child-cmp>')).then((pv) => {
tplResolver.setTemplate(MyComp, new Template({
inline: '<child-cmp #alice></child-cmp>',
directives: [ChildComp]
}));
compiler.compile(MyComp).then((pv) => {
createView(pv);
expect(view.contextWithLocals).not.toBe(null);
@ -182,13 +225,10 @@ export function main() {
});
it('should assign the element instance to a user-defined variable', (done) => {
// How is this supposed to work?
var element = el('<p></p>');
var div = el('<div var-alice></div>');
DOM.appendChild(div, el('<i>Hello</i>'));
DOM.appendChild(element, div);
tplResolver.setTemplate(MyComp,
new Template({inline: '<p><div var-alice><i>Hello</i></div></p>'}));
compiler.compile(MyComp, element).then((pv) => {
compiler.compile(MyComp).then((pv) => {
createView(pv);
expect(view.contextWithLocals).not.toBe(null);
@ -201,7 +241,12 @@ export function main() {
});
it('should provide binding configuration config to the component', (done) => {
compiler.compile(MyComp, el('<push-cmp #cmp></push-cmp>')).then((pv) => {
tplResolver.setTemplate(MyComp, new Template({
inline: '<push-cmp #cmp></push-cmp>',
directives: [[[PushBasedComp]]]
}));
compiler.compile(MyComp).then((pv) => {
createView(pv);
var cmp = view.contextWithLocals.get('cmp');
@ -234,12 +279,8 @@ class MyDir {
}
}
@Component({
selector: 'push-cmp',
template: new TemplateConfig({
inline: '{{field}}'
})
})
@Component({selector: 'push-cmp'})
@Template({inline: '{{field}}'})
class PushBasedComp {
numberOfChecks:number;
bpc:BindingPropagationConfig;
@ -260,11 +301,7 @@ class PushBasedComp {
}
}
@Component({
template: new TemplateConfig({
directives: [MyDir, [[ChildComp], SomeViewport, PushBasedComp]]
})
})
@Component()
class MyComp {
ctxProp:string;
constructor() {
@ -274,11 +311,11 @@ class MyComp {
@Component({
selector: 'child-cmp',
componentServices: [MyService],
template: new TemplateConfig({
directives: [MyDir],
inline: '{{ctxProp}}'
})
componentServices: [MyService]
})
@Template({
directives: [MyDir],
inline: '{{ctxProp}}'
})
class ChildComp {
ctxProp:string;
@ -290,7 +327,7 @@ class ChildComp {
}
@Viewport({
selector: '[some-tmplate]'
selector: '[some-viewport]'
})
class SomeViewport {
constructor(container: ViewContainer) {
@ -305,3 +342,26 @@ class MyService {
this.greeting = 'hello';
}
}
class FakeTemplateResolver extends TemplateResolver {
_cmpTemplates: Map;
constructor() {
super();
this._cmpTemplates = MapWrapper.create();
}
setTemplate(component: Type, template: Template) {
MapWrapper.set(this._cmpTemplates, component, template);
}
resolve(component: Type): Template {
var override = MapWrapper.get(this._cmpTemplates, component);
if (isPresent(override)) {
return override;
}
return super.resolve(component);
}
}

View File

@ -9,7 +9,7 @@ import {CompileControl} from 'angular2/src/core/compiler/pipeline/compile_contro
import {DOM} from 'angular2/src/facade/dom';
import {NativeShadowDomStrategy, ShadowDomStrategy} from 'angular2/src/core/compiler/shadow_dom_strategy';
import {Component, Decorator, Viewport} from 'angular2/src/core/annotations/annotations';
import {TemplateConfig} from 'angular2/src/core/annotations/template_config';
import {Template} from 'angular2/src/core/annotations/template';
import {DirectiveMetadataReader} from 'angular2/src/core/compiler/directive_metadata_reader';
import {Lexer, Parser} from 'angular2/change_detection';
@ -235,19 +235,14 @@ class SomeViewport {}
})
class SomeViewport2 {}
@Component({
selector: '[some-comp]'
})
@Component({selector: '[some-comp]'})
class SomeComponent {}
@Component({
selector: '[some-comp2]'
})
@Component({selector: '[some-comp2]'})
class SomeComponent2 {}
@Component({
template: new TemplateConfig({
@Component()
@Template({
directives: [SomeDecorator, SomeViewport, SomeViewport2, SomeComponent, SomeComponent2]
})
})
class MyComp {}

View File

@ -405,9 +405,7 @@ class SomeViewportDirectiveWithBinding {
class SomeComponentDirective {
}
@Component({
bind: {'boundprop3': 'compProp'}
})
@Component({bind: {'boundprop3': 'compProp'}})
class SomeComponentDirectiveWithBinding {
compProp;
constructor() {

View File

@ -14,7 +14,7 @@ export function main() {
describe('ShadowDomTransformer', () => {
function createPipeline(selector, strategy:ShadowDomStrategy, styleHost) {
var component = new Component({selector: selector});
var meta = new DirectiveMetadata(null, component, null);
var meta = new DirectiveMetadata(null, component);
var transformer = new ShadowDomTransformer(meta, strategy, styleHost);
transformer.clearCache();
return new CompilePipeline([transformer]);

View File

@ -1,6 +1,8 @@
import {describe, xit, it, expect, beforeEach, ddescribe, iit, el} from 'angular2/test_lib';
import {DOM} from 'angular2/src/facade/dom';
import {StringMapWrapper, MapWrapper, List} from 'angular2/src/facade/collection';
import {isPresent, Type} from 'angular2/src/facade/lang';
import {Injector} from 'angular2/di';
import {Lexer, Parser, ChangeDetector, dynamicChangeDetection} from 'angular2/change_detection';
@ -12,14 +14,12 @@ import {ShadowDomStrategy,
NativeShadowDomStrategy,
EmulatedShadowDomStrategy} from 'angular2/src/core/compiler/shadow_dom_strategy';
import {TemplateLoader} from 'angular2/src/core/compiler/template_loader';
import {TemplateResolver} from 'angular2/src/core/compiler/template_resolver';
import {Decorator, Component, Viewport} from 'angular2/src/core/annotations/annotations';
import {TemplateConfig} from 'angular2/src/core/annotations/template_config';
import {Template} from 'angular2/src/core/annotations/template';
import {ViewContainer} from 'angular2/src/core/compiler/view_container';
import {StringMapWrapper, MapWrapper} from 'angular2/src/facade/collection';
import {XHRMock} from 'angular2/src/mock/xhr_mock';
export function main() {
describe('integration tests', function() {
@ -31,22 +31,28 @@ export function main() {
(strategy, name) => {
describe(`${name} shadow dom strategy`, () => {
var compiler;
var compiler, tplResolver;
beforeEach( () => {
beforeEach(() => {
tplResolver = new FakeTemplateResolver();
compiler = new Compiler(dynamicChangeDetection,
new TemplateLoader(new XHRMock()),
new TemplateLoader(null),
new DirectiveMetadataReader(),
new Parser(new Lexer()),
new CompilerCache(),
strategy
strategy,
tplResolver
);
});
function compile(template, assertions) {
compiler.compile(MyComp, el(template)).
then(createView).
then((view) => {
function compile(template, directives: List<Type>, assertions) {
tplResolver.setTemplate(MyComp, new Template({
inline: template,
directives: directives
}));
compiler.compile(MyComp)
.then(createView)
.then((view) => {
var lc = new LifeCycle(view.changeDetector, false);
assertions(view, lc);
});
@ -59,7 +65,7 @@ export function main() {
'<div class="left">A</div>' +
'</multiple-content-tags>';
compile(temp, (view, lc) => {
compile(temp, [MultipleContentTagsComponent], (view, lc) => {
expect(view.nodes).toHaveText('(A, BC)');
done();
});
@ -71,7 +77,7 @@ export function main() {
'<div>C</div>' +
'</multiple-content-tags>';
compile(temp, (view, lc) => {
compile(temp, [MultipleContentTagsComponent], (view, lc) => {
expect(view.nodes).toHaveText('(, BAC)');
done();
});
@ -83,7 +89,7 @@ export function main() {
'<div>B</div>' +
'</multiple-content-tags>';
compile(temp, (view, lc) => {
compile(temp, [MultipleContentTagsComponent, ManualViewportDirective], (view, lc) => {
var dir = view.elementInjectors[1].get(ManualViewportDirective);
expect(view.nodes).toHaveText('(, B)');
@ -108,7 +114,7 @@ export function main() {
'<div>B</div>' +
'</multiple-content-tags>';
compile(temp, (view, lc) => {
compile(temp, [MultipleContentTagsComponent, ManualViewportDirective], (view, lc) => {
var dir = view.elementInjectors[1].get(ManualViewportDirective);
expect(view.nodes).toHaveText('(, B)');
@ -133,7 +139,7 @@ export function main() {
'<div>B</div>' +
'</outer-with-indirect-nested>';
compile(temp, (view, lc) => {
compile(temp, [OuterWithIndirectNestedComponent], (view, lc) => {
expect(view.nodes).toHaveText('OUTER(SIMPLE(AB))');
done();
@ -147,7 +153,7 @@ export function main() {
'<div>C</div>' +
'</outer>';
compile(temp, (view, lc) => {
compile(temp, [OuterComponent, ManualViewportDirective], (view, lc) => {
var dir = view.elementInjectors[1].get(ManualViewportDirective);
expect(view.nodes).toHaveText('OUTER(INNER(INNERINNER(,BC)))');
@ -257,31 +263,23 @@ class AutoViewportDirective {
}
}
@Component({
selector: 'simple',
template: new TemplateConfig({
inline: 'SIMPLE(<content></content>)'
})
})
@Component({selector: 'simple'})
@Template({inline: 'SIMPLE(<content></content>)'})
class Simple {
}
@Component({
selector: 'multiple-content-tags',
template: new TemplateConfig({
inline: '(<content select=".left"></content>, <content></content>)'
})
@Component({selector: 'multiple-content-tags'})
@Template({
inline: '(<content select=".left"></content>, <content></content>)'
})
class MultipleContentTagsComponent {
}
@Component({
selector: 'conditional-content',
template: new TemplateConfig({
inline: '<div>(<div template="auto: cond"><content select=".left"></content></div>, <content></content>)</div>',
directives: [AutoViewportDirective]
})
@Component({selector: 'conditional-content'})
@Template({
inline: '<div>(<div template="auto: cond"><content select=".left"></content></div>, <content></content>)</div>',
directives: [AutoViewportDirective]
})
class ConditionalContentComponent {
cond:boolean;
@ -294,52 +292,42 @@ class ConditionalContentComponent {
hideLeft() { this.cond = false; }
}
@Component({
selector: 'outer-with-indirect-nested',
template: new TemplateConfig({
inline: 'OUTER(<simple><div><content></content></div></simple>)',
directives: [Simple]
})
@Component({selector: 'outer-with-indirect-nested'})
@Template({
inline: 'OUTER(<simple><div><content></content></div></simple>)',
directives: [Simple]
})
class OuterWithIndirectNestedComponent {
}
@Component({
selector: 'outer',
template: new TemplateConfig({
inline: 'OUTER(<inner><content></content></inner>)',
directives: [InnerComponent]
})
@Component({selector: 'outer'})
@Template({
inline: 'OUTER(<inner><content></content></inner>)',
directives: [InnerComponent]
})
class OuterComponent {
}
@Component({
selector: 'inner',
template: new TemplateConfig({
inline: 'INNER(<innerinner><content></content></innerinner>)',
directives: [InnerInnerComponent]
})
@Component({selector: 'inner'})
@Template({
inline: 'INNER(<innerinner><content></content></innerinner>)',
directives: [InnerInnerComponent]
})
class InnerComponent {
}
@Component({
selector: 'innerinner',
template: new TemplateConfig({
inline: 'INNERINNER(<content select=".left"></content>,<content></content>)'
})
@Component({selector: 'innerinner'})
@Template({
inline: 'INNERINNER(<content select=".left"></content>,<content></content>)'
})
class InnerInnerComponent {
}
@Component({
selector: 'my-comp',
template: new TemplateConfig({
directives: [MultipleContentTagsComponent, ManualViewportDirective,
ConditionalContentComponent, OuterWithIndirectNestedComponent, OuterComponent]
})
@Component({selector: 'my-comp'})
@Template({
directives: [MultipleContentTagsComponent, ManualViewportDirective,
ConditionalContentComponent, OuterWithIndirectNestedComponent, OuterComponent]
})
class MyComp {
}
@ -349,3 +337,26 @@ function createView(pv) {
view.hydrate(new Injector([]), null, {});
return view;
}
class FakeTemplateResolver extends TemplateResolver {
_cmpTemplates: Map;
constructor() {
super();
this._cmpTemplates = MapWrapper.create();
}
setTemplate(component: Type, template: Template) {
MapWrapper.set(this._cmpTemplates, component, template);
}
resolve(component: Type): Template {
var override = MapWrapper.get(this._cmpTemplates, component);
if (isPresent(override)) {
return override;
}
return super.resolve(component);
}
}

View File

@ -1,10 +1,15 @@
import {describe, it, expect, beforeEach, ddescribe, iit, xit, el} from 'angular2/test_lib';
import {TemplateLoader} from 'angular2/src/core/compiler/template_loader';
import {Component} from 'angular2/src/core/annotations/annotations';
import {TemplateConfig} from 'angular2/src/core/annotations/template_config';
import {TemplateResolver} from 'angular2/src/core/compiler/template_resolver';
import {DirectiveMetadata} from 'angular2/src/core/compiler/directive_metadata';
import {Component} from 'angular2/src/core/annotations/annotations';
import {Template} from 'angular2/src/core/annotations/template';
import {PromiseWrapper} from 'angular2/src/facade/async';
import {Type, stringify, isPresent} from 'angular2/src/facade/lang';
import {Map, MapWrapper} from 'angular2/src/facade/collection';
import {XHRMock} from 'angular2/src/mock/xhr_mock';
@ -17,25 +22,16 @@ export function main() {
loader = new TemplateLoader(xhr);
});
function createMetadata({inline = null, url = null}={}) {
var config = new TemplateConfig({url: url, inline: inline});
var component = new Component({template: config});
return new DirectiveMetadata(FakeComponent, component, null);
}
it('should load inline templates synchronously', () => {
var template = 'inline template';
var md = createMetadata({inline: template});
expect(loader.load(md).content).toHaveText(template);
var template = new Template({inline: 'inline template'});
expect(loader.load(template).content).toHaveText('inline template');
});
it('should load templates through XHR', (done) => {
var url = '/foo';
var template = 'xhr template';
xhr.expect(url, template);
var md = createMetadata({url: '/foo'});
loader.load(md).then((el) => {
expect(el.content).toHaveText(template);
xhr.expect('/foo', 'xhr template');
var template = new Template({url: '/foo'});
loader.load(template).then((el) => {
expect(el.content).toHaveText('xhr template');
done();
});
xhr.flush();
@ -43,34 +39,31 @@ export function main() {
it('should cache template loaded through XHR', (done) => {
var firstEl;
var url = '/foo';
var template = 'xhr template';
xhr.expect(url, template);
var md = createMetadata({url: '/foo'});
loader.load(md)
xhr.expect('/foo', 'xhr template');
var template = new Template({url: '/foo'});
loader.load(template)
.then((el) => {
firstEl = el;
return loader.load(md);
return loader.load(template);
})
.then((el) =>{
expect(el).toBe(firstEl);
expect(el.content).toHaveText(template);
expect(el.content).toHaveText('xhr template');
done();
});
xhr.flush();
});
it('should throw when no template is defined', () => {
var md = createMetadata();
expect(() => loader.load(md))
.toThrowError('No template configured for component FakeComponent');
var template = new Template({inline: null, url: null});
expect(() => loader.load(template))
.toThrowError('Templates should have either their url or inline property set');
});
it('should return a rejected Promise when xhr loading fails', (done) => {
var url = '/foo';
xhr.expect(url, null);
var md = createMetadata({url: '/foo'});
PromiseWrapper.then(loader.load(md),
xhr.expect('/foo', null);
var template = new Template({url: '/foo'});
PromiseWrapper.then(loader.load(template),
function(_) { throw 'Unexpected response'; },
function(error) {
expect(error).toEqual('Failed to load /foo');
@ -83,5 +76,5 @@ export function main() {
});
}
class FakeComponent {
class SomeComponent {
}

View File

@ -6,7 +6,7 @@ import {DirectiveMetadataReader} from 'angular2/src/core/compiler/directive_meta
import {Component, Decorator, Viewport, Directive, onChange} from 'angular2/src/core/annotations/annotations';
import {Lexer, Parser, DynamicProtoChangeDetector,
ChangeDetector} from 'angular2/change_detection';
import {TemplateConfig} from 'angular2/src/core/annotations/template_config';
import {Template} from 'angular2/src/core/annotations/template';
import {EventEmitter} from 'angular2/src/core/annotations/events';
import {List, MapWrapper} from 'angular2/src/facade/collection';
import {DOM, Element} from 'angular2/src/facade/dom';
@ -649,9 +649,7 @@ class DirectiveImplementingOnChange {
class SomeService {}
@Component({
componentServices: [SomeService]
})
@Component({componentServices: [SomeService]})
class SomeComponent {
service: SomeService;
constructor(service: SomeService) {