chore(packaging): move files to match target file structure
This commit is contained in:
153
modules/angular2/test/core/compiler/compiler_spec.js
vendored
Normal file
153
modules/angular2/test/core/compiler/compiler_spec.js
vendored
Normal file
@ -0,0 +1,153 @@
|
||||
import {describe, beforeEach, it, expect, ddescribe, iit, el} from 'test_lib/test_lib';
|
||||
import {DOM} from 'facade/src/dom';
|
||||
import {List} from 'facade/src/collection';
|
||||
|
||||
import {Compiler, CompilerCache} from 'core/src/compiler/compiler';
|
||||
import {ProtoView} from 'core/src/compiler/view';
|
||||
import {DirectiveMetadataReader} from 'core/src/compiler/directive_metadata_reader';
|
||||
import {TemplateLoader} from 'core/src/compiler/template_loader';
|
||||
import {Component} from 'core/src/annotations/annotations';
|
||||
import {TemplateConfig} from 'core/src/annotations/template_config';
|
||||
import {CompileElement} from 'core/src/compiler/pipeline/compile_element';
|
||||
import {CompileStep} from 'core/src/compiler/pipeline/compile_step'
|
||||
import {CompileControl} from 'core/src/compiler/pipeline/compile_control';
|
||||
|
||||
import {Lexer, Parser, dynamicChangeDetection} from 'change_detection/change_detection';
|
||||
|
||||
export function main() {
|
||||
describe('compiler', function() {
|
||||
var reader;
|
||||
|
||||
beforeEach( () => {
|
||||
reader = new DirectiveMetadataReader();
|
||||
});
|
||||
|
||||
function createCompiler(processClosure) {
|
||||
var steps = [new MockStep(processClosure)];
|
||||
return new TestableCompiler(reader, steps);
|
||||
}
|
||||
|
||||
it('should run the steps and return the ProtoView of the root element', (done) => {
|
||||
var rootProtoView = new ProtoView(null, null);
|
||||
var compiler = createCompiler( (parent, current, control) => {
|
||||
current.inheritedProtoView = rootProtoView;
|
||||
});
|
||||
compiler.compile(MainComponent, el('<div></div>')).then( (protoView) => {
|
||||
expect(protoView).toBe(rootProtoView);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should use the given element', (done) => {
|
||||
var element = el('<div></div>');
|
||||
var compiler = createCompiler( (parent, current, control) => {
|
||||
current.inheritedProtoView = new ProtoView(current.element, 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);
|
||||
});
|
||||
compiler.compile(MainComponent, null).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);
|
||||
current.inheritedElementBinder = current.inheritedProtoView.bindElement(null);
|
||||
if (current.element === mainEl) {
|
||||
current.componentDirective = reader.read(NestedComponent);
|
||||
}
|
||||
});
|
||||
compiler.compile(MainComponent, mainEl).then( (protoView) => {
|
||||
var nestedView = protoView.elementBinders[0].nestedProtoView;
|
||||
expect(DOM.getInnerHTML(nestedView.element)).toEqual('nested component');
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it('should cache components', (done) => {
|
||||
var element = el('<div></div>');
|
||||
var compiler = createCompiler( (parent, current, control) => {
|
||||
current.inheritedProtoView = new ProtoView(current.element, null);
|
||||
});
|
||||
var firstProtoView;
|
||||
compiler.compile(MainComponent, element).then( (protoView) => {
|
||||
firstProtoView = protoView;
|
||||
return compiler.compile(MainComponent, element);
|
||||
}).then( (protoView) => {
|
||||
expect(firstProtoView).toBe(protoView);
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it('should allow recursive components', (done) => {
|
||||
var compiler = createCompiler( (parent, current, control) => {
|
||||
current.inheritedProtoView = new ProtoView(current.element, null);
|
||||
current.inheritedElementBinder = current.inheritedProtoView.bindElement(null);
|
||||
current.componentDirective = reader.read(RecursiveComponent);
|
||||
});
|
||||
compiler.compile(RecursiveComponent, null).then( (protoView) => {
|
||||
expect(protoView.elementBinders[0].nestedProtoView).toBe(protoView);
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@Component({
|
||||
template: new TemplateConfig({
|
||||
inline: 'inline component'
|
||||
})
|
||||
})
|
||||
class MainComponent {}
|
||||
|
||||
@Component({
|
||||
template: new TemplateConfig({
|
||||
inline: 'nested component'
|
||||
})
|
||||
})
|
||||
class NestedComponent {}
|
||||
|
||||
@Component({
|
||||
template: new TemplateConfig({
|
||||
inline: '<div rec-comp></div>'
|
||||
}),
|
||||
selector: 'rec-comp'
|
||||
})
|
||||
class RecursiveComponent {}
|
||||
|
||||
class TestableCompiler extends Compiler {
|
||||
steps:List;
|
||||
constructor(reader:DirectiveMetadataReader, steps:List<CompileStep>) {
|
||||
super(dynamicChangeDetection, null, reader, new Parser(new Lexer()), new CompilerCache());
|
||||
this.steps = steps;
|
||||
}
|
||||
createSteps(component):List<CompileStep> {
|
||||
return this.steps;
|
||||
}
|
||||
}
|
||||
|
||||
class MockStep extends CompileStep {
|
||||
processClosure:Function;
|
||||
constructor(process) {
|
||||
this.processClosure = process;
|
||||
}
|
||||
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
|
||||
this.processClosure(parent, current, control);
|
||||
}
|
||||
}
|
103
modules/angular2/test/core/compiler/directive_metadata_reader_spec.js
vendored
Normal file
103
modules/angular2/test/core/compiler/directive_metadata_reader_spec.js
vendored
Normal file
@ -0,0 +1,103 @@
|
||||
import {ddescribe, describe, it, iit, expect, beforeEach} from 'test_lib/test_lib';
|
||||
import {DirectiveMetadataReader} from 'core/src/compiler/directive_metadata_reader';
|
||||
import {Decorator, Component} from 'core/src/annotations/annotations';
|
||||
import {TemplateConfig} from 'core/src/annotations/template_config';
|
||||
import {DirectiveMetadata} from 'core/src/compiler/directive_metadata';
|
||||
import {ShadowDomStrategy, ShadowDomNative} from 'core/src/compiler/shadow_dom';
|
||||
import {CONST} from 'facade/src/lang';
|
||||
|
||||
|
||||
class FakeShadowDomStrategy extends ShadowDomStrategy {
|
||||
@CONST()
|
||||
constructor() {}
|
||||
|
||||
polyfillDirectives() {
|
||||
return [SomeDirective];
|
||||
}
|
||||
}
|
||||
|
||||
@Decorator({
|
||||
selector: 'someSelector'
|
||||
})
|
||||
class SomeDirective {
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'someSelector'
|
||||
})
|
||||
class ComponentWithoutExplicitShadowDomStrategy {}
|
||||
|
||||
@Component({
|
||||
selector: 'someSelector',
|
||||
shadowDom: new FakeShadowDomStrategy()
|
||||
})
|
||||
class ComponentWithExplicitShadowDomStrategy {}
|
||||
|
||||
class SomeDirectiveWithoutAnnotation {
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'withoutDirectives'
|
||||
})
|
||||
class ComponentWithoutDirectives {}
|
||||
|
||||
@Component({
|
||||
selector: 'withDirectives',
|
||||
template: new TemplateConfig({
|
||||
directives: [ComponentWithoutDirectives]
|
||||
})
|
||||
})
|
||||
class ComponentWithDirectives {}
|
||||
|
||||
|
||||
|
||||
export function main() {
|
||||
describe("DirectiveMetadataReader", () => {
|
||||
var reader;
|
||||
|
||||
beforeEach( () => {
|
||||
reader = new DirectiveMetadataReader();
|
||||
});
|
||||
|
||||
it('should read out the annotation', () => {
|
||||
var directiveMetadata = reader.read(SomeDirective);
|
||||
expect(directiveMetadata).toEqual(
|
||||
new DirectiveMetadata(SomeDirective, new Decorator({selector: 'someSelector'}), null, null));
|
||||
});
|
||||
|
||||
it('should throw if not matching annotation is found', () => {
|
||||
expect(() => {
|
||||
reader.read(SomeDirectiveWithoutAnnotation);
|
||||
}).toThrowError('No Directive annotation found on SomeDirectiveWithoutAnnotation');
|
||||
});
|
||||
|
||||
describe("shadow dom strategy", () => {
|
||||
it('should return the provided shadow dom strategy when it is present', () => {
|
||||
var directiveMetadata = reader.read(ComponentWithExplicitShadowDomStrategy);
|
||||
expect(directiveMetadata.shadowDomStrategy).toBeAnInstanceOf(FakeShadowDomStrategy);
|
||||
});
|
||||
|
||||
it('should return Native otherwise', () => {
|
||||
var directiveMetadata = reader.read(ComponentWithoutExplicitShadowDomStrategy);
|
||||
expect(directiveMetadata.shadowDomStrategy).toEqual(ShadowDomNative);
|
||||
});
|
||||
});
|
||||
|
||||
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 include directives required by the shadow DOM strategy", () => {
|
||||
var cmp = reader.read(ComponentWithExplicitShadowDomStrategy);
|
||||
expect(cmp.componentDirectives).toEqual([SomeDirective]);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
436
modules/angular2/test/core/compiler/element_injector_spec.js
vendored
Normal file
436
modules/angular2/test/core/compiler/element_injector_spec.js
vendored
Normal file
@ -0,0 +1,436 @@
|
||||
import {describe, ddescribe, it, iit, xit, xdescribe, expect, beforeEach, SpyObject} from 'test_lib/test_lib';
|
||||
import {isBlank, isPresent, FIELD, IMPLEMENTS, proxy} from 'facade/src/lang';
|
||||
import {ListWrapper, MapWrapper, List} from 'facade/src/collection';
|
||||
import {ProtoElementInjector, PreBuiltObjects, DirectiveBinding} from 'core/src/compiler/element_injector';
|
||||
import {Parent, Ancestor} from 'core/src/annotations/visibility';
|
||||
import {EventEmitter} from 'core/src/annotations/events';
|
||||
import {onDestroy} from 'core/src/annotations/annotations';
|
||||
import {Injector, Inject, bind} from 'di/di';
|
||||
import {View} from 'core/src/compiler/view';
|
||||
import {ProtoRecordRange} from 'change_detection/change_detection';
|
||||
import {ViewPort} from 'core/src/compiler/viewport';
|
||||
import {NgElement} from 'core/src/dom/element';
|
||||
import {LightDom, SourceLightDom, DestinationLightDom} from 'core/src/compiler/shadow_dom_emulation/light_dom';
|
||||
import {Directive} from 'core/src/annotations/annotations';
|
||||
import {BindingPropagationConfig} from 'core/src/compiler/binding_propagation_config';
|
||||
|
||||
@proxy
|
||||
@IMPLEMENTS(View)
|
||||
class DummyView extends SpyObject {noSuchMethod(m){super.noSuchMethod(m)}}
|
||||
|
||||
@proxy
|
||||
@IMPLEMENTS(LightDom)
|
||||
class DummyLightDom extends SpyObject {noSuchMethod(m){super.noSuchMethod(m)}}
|
||||
|
||||
|
||||
class SimpleDirective {
|
||||
}
|
||||
|
||||
|
||||
class SomeOtherDirective {
|
||||
}
|
||||
|
||||
class NeedsDirective {
|
||||
dependency:SimpleDirective;
|
||||
constructor(dependency:SimpleDirective){
|
||||
this.dependency = dependency;
|
||||
}
|
||||
}
|
||||
|
||||
class NeedDirectiveFromParent {
|
||||
dependency:SimpleDirective;
|
||||
constructor(@Parent() dependency:SimpleDirective){
|
||||
this.dependency = dependency;
|
||||
}
|
||||
}
|
||||
|
||||
class NeedDirectiveFromAncestor {
|
||||
dependency:SimpleDirective;
|
||||
constructor(@Ancestor() dependency:SimpleDirective){
|
||||
this.dependency = dependency;
|
||||
}
|
||||
}
|
||||
|
||||
class NeedsService {
|
||||
service:any;
|
||||
constructor(@Inject("service") service) {
|
||||
this.service = service;
|
||||
}
|
||||
}
|
||||
|
||||
class NeedsEventEmitter {
|
||||
clickEmitter;
|
||||
constructor(@EventEmitter('click') clickEmitter:Function) {
|
||||
this.clickEmitter = clickEmitter;
|
||||
}
|
||||
click() {
|
||||
this.clickEmitter(null);
|
||||
}
|
||||
}
|
||||
|
||||
class A_Needs_B {
|
||||
constructor(dep){}
|
||||
}
|
||||
|
||||
class B_Needs_A {
|
||||
constructor(dep){}
|
||||
}
|
||||
|
||||
class NeedsView {
|
||||
view:any;
|
||||
constructor(@Inject(View) view) {
|
||||
this.view = view;
|
||||
}
|
||||
}
|
||||
|
||||
class DirectiveWithDestroy {
|
||||
onDestroyCounter:number;
|
||||
|
||||
constructor(){
|
||||
this.onDestroyCounter = 0;
|
||||
}
|
||||
|
||||
onDestroy() {
|
||||
this.onDestroyCounter ++;
|
||||
}
|
||||
}
|
||||
|
||||
export function main() {
|
||||
var defaultPreBuiltObjects = new PreBuiltObjects(null, null, null, null, null);
|
||||
|
||||
function humanize(tree, names:List) {
|
||||
var lookupName = (item) =>
|
||||
ListWrapper.last(
|
||||
ListWrapper.find(names, (pair) => pair[0] === item));
|
||||
|
||||
if (tree.children.length == 0) return lookupName(tree);
|
||||
var children = tree.children.map(m => humanize(m, names));
|
||||
return [lookupName(tree), children];
|
||||
}
|
||||
|
||||
function injector(bindings, lightDomAppInjector = null, shadowDomAppInjector = null, preBuiltObjects = null) {
|
||||
if (isBlank(lightDomAppInjector)) lightDomAppInjector = new Injector([]);
|
||||
|
||||
var proto = new ProtoElementInjector(null, 0, bindings, isPresent(shadowDomAppInjector));
|
||||
var inj = proto.instantiate(null, null, null);
|
||||
var preBuilt = isPresent(preBuiltObjects) ? preBuiltObjects : defaultPreBuiltObjects;
|
||||
|
||||
inj.instantiateDirectives(lightDomAppInjector, shadowDomAppInjector, preBuilt);
|
||||
return inj;
|
||||
}
|
||||
|
||||
function parentChildInjectors(parentBindings, childBindings, parentPreBuildObjects = null) {
|
||||
if (isBlank(parentPreBuildObjects)) parentPreBuildObjects = defaultPreBuiltObjects;
|
||||
|
||||
var inj = new Injector([]);
|
||||
|
||||
var protoParent = new ProtoElementInjector(null, 0, parentBindings);
|
||||
var parent = protoParent.instantiate(null, null, null);
|
||||
|
||||
parent.instantiateDirectives(inj, null, parentPreBuildObjects);
|
||||
|
||||
var protoChild = new ProtoElementInjector(protoParent, 1, childBindings, false, 1);
|
||||
var child = protoChild.instantiate(parent, null, null);
|
||||
child.instantiateDirectives(inj, null, defaultPreBuiltObjects);
|
||||
|
||||
return child;
|
||||
}
|
||||
|
||||
function hostShadowInjectors(hostBindings, shadowBindings, hostPreBuildObjects = null) {
|
||||
if (isBlank(hostPreBuildObjects)) hostPreBuildObjects = defaultPreBuiltObjects;
|
||||
|
||||
var inj = new Injector([]);
|
||||
var shadowInj = inj.createChild([]);
|
||||
|
||||
var protoParent = new ProtoElementInjector(null, 0, hostBindings, true);
|
||||
var host = protoParent.instantiate(null, null, null);
|
||||
host.instantiateDirectives(inj, shadowInj, hostPreBuildObjects);
|
||||
|
||||
var protoChild = new ProtoElementInjector(protoParent, 0, shadowBindings, false, 1);
|
||||
var shadow = protoChild.instantiate(null, host, null);
|
||||
shadow.instantiateDirectives(shadowInj, null, null);
|
||||
|
||||
return shadow;
|
||||
}
|
||||
|
||||
describe("ElementInjector", function () {
|
||||
describe("instantiate", function () {
|
||||
it("should create an element injector", function () {
|
||||
var protoParent = new ProtoElementInjector(null, 0, []);
|
||||
var protoChild1 = new ProtoElementInjector(protoParent, 1, []);
|
||||
var protoChild2 = new ProtoElementInjector(protoParent, 2, []);
|
||||
|
||||
var p = protoParent.instantiate(null, null, null);
|
||||
var c1 = protoChild1.instantiate(p, null, null);
|
||||
var c2 = protoChild2.instantiate(p, null, null);
|
||||
|
||||
expect(humanize(p, [
|
||||
[p, 'parent'],
|
||||
[c1, 'child1'],
|
||||
[c2, 'child2']
|
||||
])).toEqual(["parent", ["child1", "child2"]]);
|
||||
});
|
||||
|
||||
describe("direct parent", () => {
|
||||
it("should return parent injector when distance is 1", () => {
|
||||
var distance = 1;
|
||||
var protoParent = new ProtoElementInjector(null, 0, []);
|
||||
var protoChild = new ProtoElementInjector(protoParent, 1, [], false, distance);
|
||||
|
||||
var p = protoParent.instantiate(null, null, null);
|
||||
var c = protoChild.instantiate(p, null, null);
|
||||
|
||||
expect(c.directParent()).toEqual(p);
|
||||
});
|
||||
|
||||
it("should return null otherwise", () => {
|
||||
var distance = 2;
|
||||
var protoParent = new ProtoElementInjector(null, 0, []);
|
||||
var protoChild = new ProtoElementInjector(protoParent, 1, [], false, distance);
|
||||
|
||||
var p = protoParent.instantiate(null, null, null);
|
||||
var c = protoChild.instantiate(p, null, null);
|
||||
|
||||
expect(c.directParent()).toEqual(null);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("hasBindings", function () {
|
||||
it("should be true when there are bindings", function () {
|
||||
var p = new ProtoElementInjector(null, 0, [SimpleDirective]);
|
||||
expect(p.hasBindings).toBeTruthy();
|
||||
});
|
||||
|
||||
it("should be false otherwise", function () {
|
||||
var p = new ProtoElementInjector(null, 0, []);
|
||||
expect(p.hasBindings).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
describe("hasInstances", function () {
|
||||
it("should be false when no directives are instantiated", function () {
|
||||
expect(injector([]).hasInstances()).toBe(false);
|
||||
});
|
||||
|
||||
it("should be true when directives are instantiated", function () {
|
||||
expect(injector([SimpleDirective]).hasInstances()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("instantiateDirectives", function () {
|
||||
it("should instantiate directives that have no dependencies", function () {
|
||||
var inj = injector([SimpleDirective]);
|
||||
expect(inj.get(SimpleDirective)).toBeAnInstanceOf(SimpleDirective);
|
||||
});
|
||||
|
||||
it("should instantiate directives that depend on other directives", function () {
|
||||
var inj = injector([SimpleDirective, NeedsDirective]);
|
||||
|
||||
var d = inj.get(NeedsDirective);
|
||||
|
||||
expect(d).toBeAnInstanceOf(NeedsDirective);
|
||||
expect(d.dependency).toBeAnInstanceOf(SimpleDirective);
|
||||
});
|
||||
|
||||
it("should instantiate directives that depend on app services", function () {
|
||||
var appInjector = new Injector([
|
||||
bind("service").toValue("service")
|
||||
]);
|
||||
var inj = injector([NeedsService], appInjector);
|
||||
|
||||
var d = inj.get(NeedsService);
|
||||
expect(d).toBeAnInstanceOf(NeedsService);
|
||||
expect(d.service).toEqual("service");
|
||||
});
|
||||
|
||||
it("should instantiate directives that depend on pre built objects", function () {
|
||||
var view = new DummyView();
|
||||
var inj = injector([NeedsView], null, null, new PreBuiltObjects(view, null, null, null, null));
|
||||
|
||||
expect(inj.get(NeedsView).view).toBe(view);
|
||||
});
|
||||
|
||||
it("should instantiate directives that depend on the containing component", function () {
|
||||
var shadow = hostShadowInjectors([SimpleDirective], [NeedsDirective]);
|
||||
|
||||
var d = shadow.get(NeedsDirective);
|
||||
expect(d).toBeAnInstanceOf(NeedsDirective);
|
||||
expect(d.dependency).toBeAnInstanceOf(SimpleDirective);
|
||||
});
|
||||
|
||||
it("should not instantiate directives that depend on other directives in the containing component's ElementInjector", () => {
|
||||
expect( () => {
|
||||
hostShadowInjectors([SomeOtherDirective, SimpleDirective], [NeedsDirective]);
|
||||
}).toThrowError('No provider for SimpleDirective! (NeedsDirective -> SimpleDirective)')
|
||||
});
|
||||
|
||||
it("should instantiate component directives that depend on app services in the shadow app injector", () => {
|
||||
var shadowAppInjector = new Injector([
|
||||
bind("service").toValue("service")
|
||||
]);
|
||||
var inj = injector([NeedsService], null, shadowAppInjector);
|
||||
|
||||
var d = inj.get(NeedsService);
|
||||
expect(d).toBeAnInstanceOf(NeedsService);
|
||||
expect(d.service).toEqual("service");
|
||||
});
|
||||
|
||||
it("should not instantiate other directives that depend on app services in the shadow app injector", () => {
|
||||
var shadowAppInjector = new Injector([
|
||||
bind("service").toValue("service")
|
||||
]);
|
||||
expect( () => {
|
||||
injector([SomeOtherDirective, NeedsService], null, shadowAppInjector);
|
||||
}).toThrowError('No provider for service! (NeedsService -> service)');
|
||||
});
|
||||
|
||||
it("should return app services", function () {
|
||||
var appInjector = new Injector([
|
||||
bind("service").toValue("service")
|
||||
]);
|
||||
var inj = injector([], appInjector);
|
||||
|
||||
expect(inj.get('service')).toEqual('service');
|
||||
});
|
||||
|
||||
it("should get directives from parent", function () {
|
||||
var child = parentChildInjectors([SimpleDirective], [NeedDirectiveFromParent]);
|
||||
|
||||
var d = child.get(NeedDirectiveFromParent);
|
||||
|
||||
expect(d).toBeAnInstanceOf(NeedDirectiveFromParent);
|
||||
expect(d.dependency).toBeAnInstanceOf(SimpleDirective);
|
||||
});
|
||||
|
||||
it("should not return parent's directives on self", function () {
|
||||
expect(() => {
|
||||
injector([SimpleDirective, NeedDirectiveFromParent]);
|
||||
}).toThrowError();
|
||||
});
|
||||
|
||||
it("should get directives from ancestor", function () {
|
||||
var child = parentChildInjectors([SimpleDirective], [NeedDirectiveFromAncestor]);
|
||||
|
||||
var d = child.get(NeedDirectiveFromAncestor);
|
||||
|
||||
expect(d).toBeAnInstanceOf(NeedDirectiveFromAncestor);
|
||||
expect(d.dependency).toBeAnInstanceOf(SimpleDirective);
|
||||
});
|
||||
|
||||
it("should throw when no SimpleDirective found", function () {
|
||||
expect(() => injector([NeedDirectiveFromParent])).
|
||||
toThrowError('No provider for SimpleDirective! (NeedDirectiveFromParent -> SimpleDirective)');
|
||||
});
|
||||
|
||||
it("should accept SimpleDirective bindings instead of SimpleDirective types", function () {
|
||||
var inj = injector([
|
||||
DirectiveBinding.createFromBinding(bind(SimpleDirective).toClass(SimpleDirective), null)
|
||||
]);
|
||||
expect(inj.get(SimpleDirective)).toBeAnInstanceOf(SimpleDirective);
|
||||
});
|
||||
|
||||
it("should allow for direct access using getAtIndex", function () {
|
||||
var inj = injector([
|
||||
DirectiveBinding.createFromBinding(bind(SimpleDirective).toClass(SimpleDirective), null)
|
||||
]);
|
||||
expect(inj.getAtIndex(0)).toBeAnInstanceOf(SimpleDirective);
|
||||
expect(() => inj.getAtIndex(-1)).toThrowError(
|
||||
'Index -1 is out-of-bounds.');
|
||||
expect(() => inj.getAtIndex(10)).toThrowError(
|
||||
'Index 10 is out-of-bounds.');
|
||||
});
|
||||
|
||||
it("should handle cyclic dependencies", function () {
|
||||
expect(() => {
|
||||
var bAneedsB = bind(A_Needs_B).toFactory((a) => new A_Needs_B(a), [B_Needs_A]);
|
||||
var bBneedsA = bind(B_Needs_A).toFactory((a) => new B_Needs_A(a), [A_Needs_B]);
|
||||
injector([
|
||||
DirectiveBinding.createFromBinding(bAneedsB, null),
|
||||
DirectiveBinding.createFromBinding(bBneedsA, null)
|
||||
]);
|
||||
}).toThrowError('Cannot instantiate cyclic dependency! ' +
|
||||
'(A_Needs_B -> B_Needs_A -> A_Needs_B)');
|
||||
});
|
||||
|
||||
it("should call onDestroy on directives subscribed to this event", function () {
|
||||
var inj = injector([
|
||||
DirectiveBinding.createFromType(DirectiveWithDestroy, new Directive({lifecycle: [onDestroy]}))
|
||||
]);
|
||||
var destroy = inj.get(DirectiveWithDestroy);
|
||||
inj.clearDirectives();
|
||||
expect(destroy.onDestroyCounter).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("pre built objects", function () {
|
||||
it("should return view", function () {
|
||||
var view = new DummyView();
|
||||
var inj = injector([], null, null, new PreBuiltObjects(view, null, null, null, null));
|
||||
|
||||
expect(inj.get(View)).toEqual(view);
|
||||
});
|
||||
|
||||
it("should return element", function () {
|
||||
var element = new NgElement(null);
|
||||
var inj = injector([], null, null, new PreBuiltObjects(null, element, null, null, null));
|
||||
|
||||
expect(inj.get(NgElement)).toEqual(element);
|
||||
});
|
||||
|
||||
it('should return viewPort', function () {
|
||||
var viewPort = new ViewPort(null, null, null, null);
|
||||
var inj = injector([], null, null, new PreBuiltObjects(null, null, viewPort, null, null));
|
||||
|
||||
expect(inj.get(ViewPort)).toEqual(viewPort);
|
||||
});
|
||||
|
||||
it('should return bindingPropagationConfig', function () {
|
||||
var config = new BindingPropagationConfig(null);
|
||||
var inj = injector([], null, null, new PreBuiltObjects(null, null, null, null, config));
|
||||
|
||||
expect(inj.get(BindingPropagationConfig)).toEqual(config);
|
||||
});
|
||||
|
||||
describe("light DOM", () => {
|
||||
var lightDom, parentPreBuiltObjects;
|
||||
|
||||
beforeEach(() => {
|
||||
lightDom = new DummyLightDom();
|
||||
parentPreBuiltObjects = new PreBuiltObjects(null, null, null, lightDom, null);
|
||||
});
|
||||
|
||||
it("should return destination light DOM from the parent's injector", function () {
|
||||
var child = parentChildInjectors([], [], parentPreBuiltObjects);
|
||||
|
||||
expect(child.get(DestinationLightDom)).toEqual(lightDom);
|
||||
});
|
||||
|
||||
it("should return null when parent's injector is a component boundary", function () {
|
||||
var child = hostShadowInjectors([], [], parentPreBuiltObjects);
|
||||
|
||||
expect(child.get(DestinationLightDom)).toBeNull();
|
||||
});
|
||||
|
||||
it("should return source light DOM from the closest component boundary", function () {
|
||||
var child = hostShadowInjectors([], [], parentPreBuiltObjects);
|
||||
|
||||
expect(child.get(SourceLightDom)).toEqual(lightDom);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('event emitters', () => {
|
||||
it('should be injectable and callable', () => {
|
||||
var inj = injector([NeedsEventEmitter]);
|
||||
inj.get(NeedsEventEmitter).click();
|
||||
});
|
||||
|
||||
it('should be queryable through hasEventEmitter', () => {
|
||||
var inj = injector([NeedsEventEmitter]);
|
||||
expect(inj.hasEventEmitter('click')).toBe(true);
|
||||
expect(inj.hasEventEmitter('move')).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
205
modules/angular2/test/core/compiler/integration_spec.js
vendored
Normal file
205
modules/angular2/test/core/compiler/integration_spec.js
vendored
Normal file
@ -0,0 +1,205 @@
|
||||
import {describe, xit, it, expect, beforeEach, ddescribe, iit, el} from 'test_lib/test_lib';
|
||||
|
||||
import {DOM} from 'facade/src/dom';
|
||||
|
||||
import {Injector} from 'di/di';
|
||||
import {Lexer, Parser, ChangeDetector, dynamicChangeDetection} from 'change_detection/change_detection';
|
||||
|
||||
import {Compiler, CompilerCache} from 'core/src/compiler/compiler';
|
||||
import {DirectiveMetadataReader} from 'core/src/compiler/directive_metadata_reader';
|
||||
import {ShadowDomEmulated} from 'core/src/compiler/shadow_dom';
|
||||
|
||||
import {Decorator, Component, Template} from 'core/src/annotations/annotations';
|
||||
import {TemplateConfig} from 'core/src/annotations/template_config';
|
||||
|
||||
import {ViewPort} from 'core/src/compiler/viewport';
|
||||
import {MapWrapper} from 'facade/src/collection';
|
||||
|
||||
export function main() {
|
||||
describe('integration tests', function() {
|
||||
var compiler;
|
||||
|
||||
beforeEach( () => {
|
||||
compiler = new Compiler(dynamicChangeDetection, null, new DirectiveMetadataReader(),
|
||||
new Parser(new Lexer()), new CompilerCache());
|
||||
});
|
||||
|
||||
describe('react to record changes', function() {
|
||||
var view, ctx, cd;
|
||||
function createView(pv) {
|
||||
ctx = new MyComp();
|
||||
view = pv.instantiate(null);
|
||||
view.hydrate(new Injector([]), null, ctx);
|
||||
cd = view.changeDetector;
|
||||
}
|
||||
|
||||
it('should consume text node changes', (done) => {
|
||||
compiler.compile(MyComp, el('<div>{{ctxProp}}</div>')).then((pv) => {
|
||||
createView(pv);
|
||||
ctx.ctxProp = 'Hello World!';
|
||||
|
||||
cd.detectChanges();
|
||||
expect(DOM.getInnerHTML(view.nodes[0])).toEqual('Hello World!');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should consume element binding changes', (done) => {
|
||||
compiler.compile(MyComp, el('<div [id]="ctxProp"></div>')).then((pv) => {
|
||||
createView(pv);
|
||||
|
||||
ctx.ctxProp = 'Hello World!';
|
||||
cd.detectChanges();
|
||||
|
||||
expect(view.nodes[0].id).toEqual('Hello World!');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should consume directive watch expression change.', (done) => {
|
||||
compiler.compile(MyComp, el('<div my-dir [elprop]="ctxProp"></div>')).then((pv) => {
|
||||
createView(pv);
|
||||
|
||||
ctx.ctxProp = 'Hello World!';
|
||||
cd.detectChanges();
|
||||
|
||||
var elInj = view.elementInjectors[0];
|
||||
expect(elInj.get(MyDir).dirProp).toEqual('Hello World!');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should consume element binding for class attribute', (done) => {
|
||||
compiler.compile(MyComp, el('<div class="foo" [class.bar]="boolProp"></div>')).then((pv) => {
|
||||
createView(pv);
|
||||
|
||||
ctx.boolProp = true;
|
||||
cd.detectChanges();
|
||||
expect(view.nodes[0].className).toEqual('foo ng-binding bar');
|
||||
|
||||
ctx.boolProp = false;
|
||||
cd.detectChanges();
|
||||
expect(view.nodes[0].className).toEqual('foo ng-binding');
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should support nested components.', (done) => {
|
||||
compiler.compile(MyComp, el('<child-cmp></child-cmp>')).then((pv) => {
|
||||
createView(pv);
|
||||
|
||||
cd.detectChanges();
|
||||
|
||||
expect(view.nodes[0].shadowRoot.childNodes[0].nodeValue).toEqual('hello');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
// 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) => {
|
||||
createView(pv);
|
||||
|
||||
ctx.ctxProp = 'Hello World!';
|
||||
cd.detectChanges();
|
||||
|
||||
var elInj = view.elementInjectors[0];
|
||||
expect(elInj.get(MyDir).dirProp).toEqual('Hello World!');
|
||||
expect(elInj.get(ChildComp).dirProp).toEqual(null);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
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) => {
|
||||
createView(pv);
|
||||
|
||||
cd.detectChanges();
|
||||
|
||||
var childNodesOfWrapper = view.nodes[0].childNodes;
|
||||
// 1 template + 2 copies.
|
||||
expect(childNodesOfWrapper.length).toBe(3);
|
||||
expect(childNodesOfWrapper[1].childNodes[0].nodeValue).toEqual('hello');
|
||||
expect(childNodesOfWrapper[2].childNodes[0].nodeValue).toEqual('again');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
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) => {
|
||||
createView(pv);
|
||||
|
||||
cd.detectChanges();
|
||||
|
||||
var childNodesOfWrapper = view.nodes[0].childNodes;
|
||||
// 1 template + 2 copies.
|
||||
expect(childNodesOfWrapper.length).toBe(3);
|
||||
expect(childNodesOfWrapper[1].childNodes[0].nodeValue).toEqual('hello');
|
||||
expect(childNodesOfWrapper[2].childNodes[0].nodeValue).toEqual('again');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@Decorator({
|
||||
selector: '[my-dir]',
|
||||
bind: {'elprop':'dirProp'}
|
||||
})
|
||||
class MyDir {
|
||||
dirProp:string;
|
||||
constructor() {
|
||||
this.dirProp = '';
|
||||
}
|
||||
}
|
||||
|
||||
@Component({
|
||||
template: new TemplateConfig({
|
||||
directives: [MyDir, ChildComp, SomeTemplate]
|
||||
})
|
||||
})
|
||||
class MyComp {
|
||||
ctxProp:string;
|
||||
boolProp:boolean;
|
||||
constructor() {
|
||||
this.ctxProp = 'initial value';
|
||||
}
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'child-cmp',
|
||||
componentServices: [MyService],
|
||||
template: new TemplateConfig({
|
||||
directives: [MyDir],
|
||||
inline: '{{ctxProp}}'
|
||||
})
|
||||
})
|
||||
class ChildComp {
|
||||
ctxProp:string;
|
||||
dirProp:string;
|
||||
constructor(service: MyService) {
|
||||
this.ctxProp = service.greeting;
|
||||
this.dirProp = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Template({
|
||||
selector: '[some-tmplate]'
|
||||
})
|
||||
class SomeTemplate {
|
||||
constructor(viewPort: ViewPort) {
|
||||
viewPort.create().setLocal('some-tmpl', 'hello');
|
||||
viewPort.create().setLocal('some-tmpl', 'again');
|
||||
}
|
||||
}
|
||||
|
||||
class MyService {
|
||||
greeting:string;
|
||||
constructor() {
|
||||
this.greeting = 'hello';
|
||||
}
|
||||
}
|
||||
|
236
modules/angular2/test/core/compiler/pipeline/directive_parser_spec.js
vendored
Normal file
236
modules/angular2/test/core/compiler/pipeline/directive_parser_spec.js
vendored
Normal file
@ -0,0 +1,236 @@
|
||||
import {describe, beforeEach, it, expect, iit, ddescribe, el} from 'test_lib/test_lib';
|
||||
import {isPresent} from 'facade/src/lang';
|
||||
import {ListWrapper, MapWrapper, StringMapWrapper} from 'facade/src/collection';
|
||||
import {DirectiveParser} from 'core/src/compiler/pipeline/directive_parser';
|
||||
import {CompilePipeline} from 'core/src/compiler/pipeline/compile_pipeline';
|
||||
import {CompileStep} from 'core/src/compiler/pipeline/compile_step';
|
||||
import {CompileElement} from 'core/src/compiler/pipeline/compile_element';
|
||||
import {CompileControl} from 'core/src/compiler/pipeline/compile_control';
|
||||
import {DOM} from 'facade/src/dom';
|
||||
import {Component} from 'core/src/annotations/annotations';
|
||||
import {Decorator} from 'core/src/annotations/annotations';
|
||||
import {Template} from 'core/src/annotations/annotations';
|
||||
import {TemplateConfig} from 'core/src/annotations/template_config';
|
||||
import {DirectiveMetadataReader} from 'core/src/compiler/directive_metadata_reader';
|
||||
import {Lexer, Parser} from 'change_detection/change_detection';
|
||||
|
||||
export function main() {
|
||||
describe('DirectiveParser', () => {
|
||||
var reader, directives;
|
||||
|
||||
beforeEach( () => {
|
||||
reader = new DirectiveMetadataReader();
|
||||
directives = [
|
||||
SomeDecorator,
|
||||
SomeDecoratorIgnoringChildren,
|
||||
SomeTemplate,
|
||||
SomeTemplate2,
|
||||
SomeComponent,
|
||||
SomeComponent2
|
||||
];
|
||||
});
|
||||
|
||||
function createPipeline({propertyBindings, variableBindings}={}) {
|
||||
var parser = new Parser(new Lexer());
|
||||
var annotatedDirectives = ListWrapper.create();
|
||||
for (var i=0; i<directives.length; i++) {
|
||||
ListWrapper.push(annotatedDirectives, reader.read(directives[i]));
|
||||
}
|
||||
|
||||
return new CompilePipeline([new MockStep((parent, current, control) => {
|
||||
if (isPresent(propertyBindings)) {
|
||||
StringMapWrapper.forEach(propertyBindings, (v, k) => {
|
||||
current.addPropertyBinding(k, parser.parseBinding(v, null));
|
||||
});
|
||||
}
|
||||
if (isPresent(variableBindings)) {
|
||||
current.variableBindings = MapWrapper.createFromStringMap(variableBindings);
|
||||
}
|
||||
}), new DirectiveParser(annotatedDirectives)]);
|
||||
}
|
||||
|
||||
it('should not add directives if they are not used', () => {
|
||||
var results = createPipeline().process(el('<div></div>'));
|
||||
expect(results[0].decoratorDirectives).toBe(null);
|
||||
expect(results[0].componentDirective).toBe(null);
|
||||
expect(results[0].templateDirective).toBe(null);
|
||||
});
|
||||
|
||||
describe('component directives', () => {
|
||||
it('should detect them in attributes', () => {
|
||||
var results = createPipeline().process(el('<div some-comp></div>'));
|
||||
expect(results[0].componentDirective).toEqual(reader.read(SomeComponent));
|
||||
});
|
||||
|
||||
it('component directives must be first in collected directives', () => {
|
||||
var results = createPipeline().process(el('<div some-comp some-decor></div>'));
|
||||
var dirs = results[0].getAllDirectives();
|
||||
expect(dirs.length).toEqual(2);
|
||||
expect(dirs[0]).toEqual(reader.read(SomeComponent));
|
||||
expect(dirs[1]).toEqual(reader.read(SomeDecorator));
|
||||
});
|
||||
|
||||
it('should detect them in property bindings', () => {
|
||||
var pipeline = createPipeline({propertyBindings: {
|
||||
'some-comp': 'someExpr'
|
||||
}});
|
||||
var results = pipeline.process(el('<div></div>'));
|
||||
expect(results[0].componentDirective).toEqual(reader.read(SomeComponent));
|
||||
});
|
||||
|
||||
it('should detect them in variable bindings', () => {
|
||||
var pipeline = createPipeline({variableBindings: {
|
||||
'some-comp': 'someExpr'
|
||||
}});
|
||||
var results = pipeline.process(el('<div></div>'));
|
||||
expect(results[0].componentDirective).toEqual(reader.read(SomeComponent));
|
||||
});
|
||||
|
||||
it('should not allow multiple component directives on the same element', () => {
|
||||
expect( () => {
|
||||
createPipeline().process(
|
||||
el('<div some-comp some-comp2></div>')
|
||||
);
|
||||
}).toThrowError('Only one component directive per element is allowed!');
|
||||
});
|
||||
|
||||
it('should not allow component directives on <template> elements', () => {
|
||||
expect( () => {
|
||||
createPipeline().process(
|
||||
el('<template some-comp></template>')
|
||||
);
|
||||
}).toThrowError('Only template directives are allowed on <template> elements!');
|
||||
});
|
||||
});
|
||||
|
||||
describe('template directives', () => {
|
||||
it('should detect them in attributes', () => {
|
||||
var results = createPipeline().process(el('<template some-templ></template>'));
|
||||
expect(results[0].templateDirective).toEqual(reader.read(SomeTemplate));
|
||||
});
|
||||
|
||||
it('should detect them in property bindings', () => {
|
||||
var pipeline = createPipeline({propertyBindings: {
|
||||
'some-templ': 'someExpr'
|
||||
}});
|
||||
var results = pipeline.process(el('<template></template>'));
|
||||
expect(results[0].templateDirective).toEqual(reader.read(SomeTemplate));
|
||||
});
|
||||
|
||||
it('should detect them in variable bindings', () => {
|
||||
var pipeline = createPipeline({variableBindings: {
|
||||
'some-templ': 'someExpr'
|
||||
}});
|
||||
var results = pipeline.process(el('<template></template>'));
|
||||
expect(results[0].templateDirective).toEqual(reader.read(SomeTemplate));
|
||||
});
|
||||
|
||||
it('should not allow multiple template directives on the same element', () => {
|
||||
expect( () => {
|
||||
createPipeline().process(
|
||||
el('<template some-templ some-templ2></template>')
|
||||
);
|
||||
}).toThrowError('Only one template directive per element is allowed!');
|
||||
});
|
||||
|
||||
it('should not allow template directives on non <template> elements', () => {
|
||||
expect( () => {
|
||||
createPipeline().process(
|
||||
el('<div some-templ></div>')
|
||||
);
|
||||
}).toThrowError('Template directives need to be placed on <template> elements or elements with template attribute!');
|
||||
});
|
||||
});
|
||||
|
||||
describe('decorator directives', () => {
|
||||
it('should detect them in attributes', () => {
|
||||
var results = createPipeline().process(el('<div some-decor></div>'));
|
||||
expect(results[0].decoratorDirectives).toEqual([reader.read(SomeDecorator)]);
|
||||
});
|
||||
|
||||
it('should detect them in property bindings', () => {
|
||||
var pipeline = createPipeline({propertyBindings: {
|
||||
'some-decor': 'someExpr'
|
||||
}});
|
||||
var results = pipeline.process(el('<div></div>'));
|
||||
expect(results[0].decoratorDirectives).toEqual([reader.read(SomeDecorator)]);
|
||||
});
|
||||
|
||||
it('should compile children by default', () => {
|
||||
var results = createPipeline().process(el('<div some-decor></div>'));
|
||||
expect(results[0].compileChildren).toEqual(true);
|
||||
});
|
||||
|
||||
it('should stop compiling children when specified in the decorator config', () => {
|
||||
var results = createPipeline().process(el('<div some-decor-ignoring-children></div>'));
|
||||
expect(results[0].compileChildren).toEqual(false);
|
||||
});
|
||||
|
||||
it('should detect them in variable bindings', () => {
|
||||
var pipeline = createPipeline({variableBindings: {
|
||||
'some-decor': 'someExpr'
|
||||
}});
|
||||
var results = pipeline.process(el('<div></div>'));
|
||||
expect(results[0].decoratorDirectives).toEqual([reader.read(SomeDecorator)]);
|
||||
});
|
||||
|
||||
it('should not allow decorator directives on <template> elements', () => {
|
||||
expect( () => {
|
||||
createPipeline().process(
|
||||
el('<template some-decor></template>')
|
||||
);
|
||||
}).toThrowError('Only template directives are allowed on <template> elements!');
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
class MockStep extends CompileStep {
|
||||
processClosure:Function;
|
||||
constructor(process) {
|
||||
this.processClosure = process;
|
||||
}
|
||||
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
|
||||
this.processClosure(parent, current, control);
|
||||
}
|
||||
}
|
||||
|
||||
@Decorator({
|
||||
selector: '[some-decor]'
|
||||
})
|
||||
class SomeDecorator {}
|
||||
|
||||
@Decorator({
|
||||
selector: '[some-decor-ignoring-children]',
|
||||
compileChildren: false
|
||||
})
|
||||
class SomeDecoratorIgnoringChildren {
|
||||
}
|
||||
|
||||
@Template({
|
||||
selector: '[some-templ]'
|
||||
})
|
||||
class SomeTemplate {}
|
||||
|
||||
@Template({
|
||||
selector: '[some-templ2]'
|
||||
})
|
||||
class SomeTemplate2 {}
|
||||
|
||||
@Component({
|
||||
selector: '[some-comp]'
|
||||
})
|
||||
class SomeComponent {}
|
||||
|
||||
@Component({
|
||||
selector: '[some-comp2]'
|
||||
})
|
||||
class SomeComponent2 {}
|
||||
|
||||
@Component({
|
||||
template: new TemplateConfig({
|
||||
directives: [SomeDecorator, SomeTemplate, SomeTemplate2, SomeComponent, SomeComponent2]
|
||||
})
|
||||
})
|
||||
class MyComp {}
|
366
modules/angular2/test/core/compiler/pipeline/element_binder_builder_spec.js
vendored
Normal file
366
modules/angular2/test/core/compiler/pipeline/element_binder_builder_spec.js
vendored
Normal file
@ -0,0 +1,366 @@
|
||||
import {describe, beforeEach, it, expect, iit, ddescribe, el} from 'test_lib/test_lib';
|
||||
import {isPresent} from 'facade/src/lang';
|
||||
import {DOM} from 'facade/src/dom';
|
||||
import {ListWrapper, MapWrapper} from 'facade/src/collection';
|
||||
|
||||
import {ElementBinderBuilder} from 'core/src/compiler/pipeline/element_binder_builder';
|
||||
import {CompilePipeline} from 'core/src/compiler/pipeline/compile_pipeline';
|
||||
import {CompileElement} from 'core/src/compiler/pipeline/compile_element';
|
||||
import {CompileStep} from 'core/src/compiler/pipeline/compile_step'
|
||||
import {CompileControl} from 'core/src/compiler/pipeline/compile_control';
|
||||
|
||||
import {Decorator} from 'core/src/annotations/annotations';
|
||||
import {Template} from 'core/src/annotations/annotations';
|
||||
import {Component} from 'core/src/annotations/annotations';
|
||||
import {ProtoView, ElementPropertyMemento, DirectivePropertyMemento} from 'core/src/compiler/view';
|
||||
import {ProtoElementInjector} from 'core/src/compiler/element_injector';
|
||||
import {DirectiveMetadataReader} from 'core/src/compiler/directive_metadata_reader';
|
||||
|
||||
import {ChangeDetector, Lexer, Parser, DynamicProtoChangeDetector,
|
||||
} from 'change_detection/change_detection';
|
||||
import {Injector} from 'di/di';
|
||||
|
||||
export function main() {
|
||||
describe('ElementBinderBuilder', () => {
|
||||
var evalContext, view, changeDetector;
|
||||
|
||||
function createPipeline({textNodeBindings, propertyBindings, eventBindings, directives, protoElementInjector
|
||||
}={}) {
|
||||
var reflector = new DirectiveMetadataReader();
|
||||
var parser = new Parser(new Lexer());
|
||||
return new CompilePipeline([
|
||||
new MockStep((parent, current, control) => {
|
||||
var hasBinding = false;
|
||||
if (isPresent(current.element.getAttribute('text-binding'))) {
|
||||
MapWrapper.forEach(textNodeBindings, (v,k) => {
|
||||
current.addTextNodeBinding(k, parser.parseBinding(v, null));
|
||||
});
|
||||
hasBinding = true;
|
||||
}
|
||||
if (isPresent(current.element.getAttribute('prop-binding'))) {
|
||||
if (isPresent(propertyBindings)) {
|
||||
MapWrapper.forEach(propertyBindings, (v,k) => {
|
||||
current.addPropertyBinding(k, parser.parseBinding(v, null));
|
||||
});
|
||||
}
|
||||
hasBinding = true;
|
||||
}
|
||||
if (isPresent(current.element.getAttribute('event-binding'))) {
|
||||
MapWrapper.forEach(eventBindings, (v,k) => {
|
||||
current.addEventBinding(k, parser.parseAction(v, null));
|
||||
});
|
||||
hasBinding = true;
|
||||
}
|
||||
if (isPresent(protoElementInjector)) {
|
||||
current.inheritedProtoElementInjector = protoElementInjector;
|
||||
}
|
||||
if (isPresent(current.element.getAttribute('directives'))) {
|
||||
hasBinding = true;
|
||||
for (var i=0; i<directives.length; i++) {
|
||||
var dirMetadata = reflector.read(directives[i]);
|
||||
current.addDirective(dirMetadata);
|
||||
}
|
||||
}
|
||||
if (hasBinding) {
|
||||
current.hasBindings = true;
|
||||
DOM.addClass(current.element, 'ng-binding');
|
||||
}
|
||||
if (isPresent(current.element.getAttribute('viewroot'))) {
|
||||
current.isViewRoot = true;
|
||||
current.inheritedProtoView = new ProtoView(current.element, new DynamicProtoChangeDetector());
|
||||
} else if (isPresent(parent)) {
|
||||
current.inheritedProtoView = parent.inheritedProtoView;
|
||||
}
|
||||
}), new ElementBinderBuilder()
|
||||
]);
|
||||
}
|
||||
|
||||
function instantiateView(protoView) {
|
||||
evalContext = new Context();
|
||||
view = protoView.instantiate(null);
|
||||
view.hydrate(new Injector([]), null, evalContext);
|
||||
changeDetector = view.changeDetector;
|
||||
}
|
||||
|
||||
it('should not create an ElementBinder for elements that have no bindings', () => {
|
||||
var pipeline = createPipeline();
|
||||
var results = pipeline.process(el('<div viewroot><span></span></div>'));
|
||||
var pv = results[0].inheritedProtoView;
|
||||
|
||||
expect(pv.elementBinders.length).toBe(0);
|
||||
});
|
||||
|
||||
it('should create an ElementBinder for elements that have bindings', () => {
|
||||
var pipeline = createPipeline();
|
||||
var results = pipeline.process(el('<div viewroot prop-binding><span prop-binding></span></div>'));
|
||||
var pv = results[0].inheritedProtoView;
|
||||
|
||||
expect(pv.elementBinders.length).toBe(2);
|
||||
expect(pv.elementBinders[1]).not.toBe(pv.elementBinders[0]);
|
||||
});
|
||||
|
||||
it('should inherit ElementBinders to children that have no bindings', () => {
|
||||
var pipeline = createPipeline();
|
||||
var results = pipeline.process(el('<div viewroot prop-binding><span></span></div>'));
|
||||
var pv = results[0].inheritedProtoView;
|
||||
|
||||
expect(pv.elementBinders.length).toBe(1);
|
||||
expect(results[0].inheritedElementBinder).toBe(results[1].inheritedElementBinder);
|
||||
});
|
||||
|
||||
it('should store the current protoElementInjector', () => {
|
||||
var directives = [SomeDecoratorDirective];
|
||||
var protoElementInjector = new ProtoElementInjector(null, 0, directives);
|
||||
|
||||
var pipeline = createPipeline({protoElementInjector: protoElementInjector, directives: directives});
|
||||
var results = pipeline.process(el('<div viewroot directives></div>'));
|
||||
var pv = results[0].inheritedProtoView;
|
||||
|
||||
expect(pv.elementBinders[0].protoElementInjector).toBe(protoElementInjector);
|
||||
});
|
||||
|
||||
it('should store the component directive', () => {
|
||||
var directives = [SomeComponentDirective];
|
||||
var pipeline = createPipeline({protoElementInjector: null, directives: directives});
|
||||
var results = pipeline.process(el('<div viewroot directives></div>'));
|
||||
var pv = results[0].inheritedProtoView;
|
||||
|
||||
expect(pv.elementBinders[0].componentDirective.type).toBe(SomeComponentDirective);
|
||||
});
|
||||
|
||||
it('should store the template directive', () => {
|
||||
var directives = [SomeTemplateDirective];
|
||||
var pipeline = createPipeline({protoElementInjector: null, directives: directives});
|
||||
var results = pipeline.process(el('<div viewroot directives></div>'));
|
||||
var pv = results[0].inheritedProtoView;
|
||||
|
||||
expect(pv.elementBinders[0].templateDirective.type).toBe(SomeTemplateDirective);
|
||||
});
|
||||
|
||||
it('should bind text nodes', () => {
|
||||
var textNodeBindings = MapWrapper.create();
|
||||
MapWrapper.set(textNodeBindings, 0, 'prop1');
|
||||
MapWrapper.set(textNodeBindings, 2, 'prop2');
|
||||
var pipeline = createPipeline({textNodeBindings: textNodeBindings});
|
||||
var results = pipeline.process(el('<div viewroot text-binding>{{}}<span></span>{{}}</div>'));
|
||||
var pv = results[0].inheritedProtoView;
|
||||
|
||||
expect(sortArr(pv.elementBinders[0].textNodeIndices)).toEqual([0, 2]);
|
||||
|
||||
instantiateView(pv);
|
||||
evalContext.prop1 = 'a';
|
||||
evalContext.prop2 = 'b';
|
||||
changeDetector.detectChanges();
|
||||
|
||||
expect(view.nodes[0].childNodes[0].nodeValue).toEqual('a');
|
||||
expect(view.nodes[0].childNodes[2].nodeValue).toEqual('b');
|
||||
});
|
||||
|
||||
it('should bind element properties', () => {
|
||||
var propertyBindings = MapWrapper.createFromStringMap({
|
||||
'value': 'prop1',
|
||||
'hidden': 'prop2'
|
||||
});
|
||||
var pipeline = createPipeline({propertyBindings: propertyBindings});
|
||||
var results = pipeline.process(el('<input viewroot prop-binding>'));
|
||||
var pv = results[0].inheritedProtoView;
|
||||
|
||||
expect(pv.elementBinders[0].hasElementPropertyBindings).toBe(true);
|
||||
|
||||
instantiateView(pv);
|
||||
evalContext.prop1 = 'a';
|
||||
evalContext.prop2 = false;
|
||||
changeDetector.detectChanges();
|
||||
|
||||
expect(view.nodes[0].value).toEqual('a');
|
||||
expect(view.nodes[0].hidden).toEqual(false);
|
||||
});
|
||||
|
||||
it('should bind class with a dot', () => {
|
||||
var propertyBindings = MapWrapper.createFromStringMap({
|
||||
'class.bar': 'prop1',
|
||||
});
|
||||
var pipeline = createPipeline({propertyBindings: propertyBindings});
|
||||
var results = pipeline.process(el('<input class="foo" viewroot prop-binding>'));
|
||||
var pv = results[0].inheritedProtoView;
|
||||
|
||||
expect(pv.elementBinders[0].hasElementPropertyBindings).toBe(true);
|
||||
|
||||
instantiateView(pv);
|
||||
|
||||
evalContext.prop1 = true;
|
||||
changeDetector.detectChanges();
|
||||
expect(view.nodes[0].className).toEqual('foo ng-binding bar');
|
||||
|
||||
evalContext.prop1 = false;
|
||||
changeDetector.detectChanges();
|
||||
expect(view.nodes[0].className).toEqual('foo ng-binding');
|
||||
});
|
||||
|
||||
it('should bind events', () => {
|
||||
var eventBindings = MapWrapper.createFromStringMap({
|
||||
'event1': '1+1'
|
||||
});
|
||||
var pipeline = createPipeline({eventBindings: eventBindings});
|
||||
var results = pipeline.process(el('<div viewroot event-binding></div>'));
|
||||
var pv = results[0].inheritedProtoView;
|
||||
|
||||
var ast = MapWrapper.get(pv.elementBinders[0].events, 'event1');
|
||||
expect(ast.eval(null)).toBe(2);
|
||||
});
|
||||
|
||||
it('should bind directive properties', () => {
|
||||
var propertyBindings = MapWrapper.createFromStringMap({
|
||||
'boundprop1': 'prop1',
|
||||
'boundprop2': 'prop2',
|
||||
'boundprop3': 'prop3'
|
||||
});
|
||||
var directives = [SomeComponentDirectiveWithBinding,
|
||||
SomeTemplateDirectiveWithBinding,
|
||||
SomeDecoratorDirectiveWith2Bindings];
|
||||
var protoElementInjector = new ProtoElementInjector(null, 0, directives, true);
|
||||
var pipeline = createPipeline({
|
||||
propertyBindings: propertyBindings,
|
||||
directives: directives,
|
||||
protoElementInjector: protoElementInjector
|
||||
});
|
||||
var results = pipeline.process(el('<div viewroot prop-binding directives></div>'));
|
||||
var pv = results[0].inheritedProtoView;
|
||||
results[0].inheritedElementBinder.nestedProtoView = new ProtoView(
|
||||
el('<div></div>'), new DynamicProtoChangeDetector());
|
||||
|
||||
instantiateView(pv);
|
||||
evalContext.prop1 = 'a';
|
||||
evalContext.prop2 = 'b';
|
||||
evalContext.prop3 = 'c';
|
||||
changeDetector.detectChanges();
|
||||
|
||||
expect(view.elementInjectors[0].get(SomeDecoratorDirectiveWith2Bindings).decorProp).toBe('a');
|
||||
expect(view.elementInjectors[0].get(SomeDecoratorDirectiveWith2Bindings).decorProp2).toBe('b');
|
||||
expect(view.elementInjectors[0].get(SomeTemplateDirectiveWithBinding).templProp).toBe('b');
|
||||
expect(view.elementInjectors[0].get(SomeComponentDirectiveWithBinding).compProp).toBe('c');
|
||||
});
|
||||
|
||||
it('should bind directive properties for sibling elements', () => {
|
||||
var propertyBindings = MapWrapper.createFromStringMap({
|
||||
'boundprop1': 'prop1'
|
||||
});
|
||||
var directives = [SomeDecoratorDirectiveWithBinding];
|
||||
var protoElementInjector = new ProtoElementInjector(null, 0, directives);
|
||||
var pipeline = createPipeline({
|
||||
propertyBindings: propertyBindings,
|
||||
directives: directives,
|
||||
protoElementInjector: protoElementInjector
|
||||
});
|
||||
var results = pipeline.process(
|
||||
el('<div viewroot><div prop-binding directives>'+
|
||||
'</div><div prop-binding directives></div></div>'));
|
||||
var pv = results[0].inheritedProtoView;
|
||||
|
||||
instantiateView(pv);
|
||||
evalContext.prop1 = 'a';
|
||||
changeDetector.detectChanges();
|
||||
|
||||
expect(view.elementInjectors[1].get(SomeDecoratorDirectiveWithBinding).decorProp).toBe('a');
|
||||
});
|
||||
|
||||
describe('errors', () => {
|
||||
|
||||
it('should throw if there is no element property bindings for a directive property binding', () => {
|
||||
var pipeline = createPipeline({propertyBindings: MapWrapper.create(), directives: [SomeDecoratorDirectiveWithBinding]});
|
||||
expect( () => {
|
||||
pipeline.process(el('<div viewroot prop-binding directives>'));
|
||||
}).toThrowError("No element binding found for property 'boundprop1' which is required by directive 'SomeDecoratorDirectiveWithBinding'");
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@Decorator()
|
||||
class SomeDecoratorDirective {
|
||||
}
|
||||
|
||||
@Decorator({
|
||||
bind: {'boundprop1': 'decorProp'}
|
||||
})
|
||||
class SomeDecoratorDirectiveWithBinding {
|
||||
decorProp;
|
||||
decorProp2;
|
||||
constructor() {
|
||||
this.decorProp = null;
|
||||
this.decorProp2 = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Decorator({
|
||||
bind: {
|
||||
'boundprop1': 'decorProp',
|
||||
'boundprop2': 'decorProp2'
|
||||
}
|
||||
})
|
||||
class SomeDecoratorDirectiveWith2Bindings {
|
||||
decorProp;
|
||||
decorProp2;
|
||||
constructor() {
|
||||
this.decorProp = null;
|
||||
this.decorProp2 = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Template()
|
||||
class SomeTemplateDirective {
|
||||
}
|
||||
|
||||
@Template({
|
||||
bind: {'boundprop2': 'templProp'}
|
||||
})
|
||||
class SomeTemplateDirectiveWithBinding {
|
||||
templProp;
|
||||
constructor() {
|
||||
this.templProp = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Component()
|
||||
class SomeComponentDirective {
|
||||
}
|
||||
|
||||
@Component({
|
||||
bind: {'boundprop3': 'compProp'}
|
||||
})
|
||||
class SomeComponentDirectiveWithBinding {
|
||||
compProp;
|
||||
constructor() {
|
||||
this.compProp = null;
|
||||
}
|
||||
}
|
||||
|
||||
class Context {
|
||||
prop1;
|
||||
prop2;
|
||||
prop3;
|
||||
constructor() {
|
||||
this.prop1 = null;
|
||||
this.prop2 = null;
|
||||
this.prop3 = null;
|
||||
}
|
||||
}
|
||||
|
||||
class MockStep extends CompileStep {
|
||||
processClosure:Function;
|
||||
constructor(process) {
|
||||
this.processClosure = process;
|
||||
}
|
||||
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
|
||||
this.processClosure(parent, current, control);
|
||||
}
|
||||
}
|
||||
|
||||
function sortArr(arr) {
|
||||
var arr2 = ListWrapper.clone(arr);
|
||||
arr2.sort();
|
||||
return arr2;
|
||||
}
|
118
modules/angular2/test/core/compiler/pipeline/element_binding_marker_spec.js
vendored
Normal file
118
modules/angular2/test/core/compiler/pipeline/element_binding_marker_spec.js
vendored
Normal file
@ -0,0 +1,118 @@
|
||||
import {describe, beforeEach, it, expect, iit, ddescribe, el} from 'test_lib/test_lib';
|
||||
import {isPresent} from 'facade/src/lang';
|
||||
import {DOM} from 'facade/src/dom';
|
||||
import {MapWrapper} from 'facade/src/collection';
|
||||
|
||||
import {ElementBindingMarker} from 'core/src/compiler/pipeline/element_binding_marker';
|
||||
import {CompilePipeline} from 'core/src/compiler/pipeline/compile_pipeline';
|
||||
import {CompileElement} from 'core/src/compiler/pipeline/compile_element';
|
||||
import {CompileStep} from 'core/src/compiler/pipeline/compile_step'
|
||||
import {CompileControl} from 'core/src/compiler/pipeline/compile_control';
|
||||
import {DirectiveMetadataReader} from 'core/src/compiler/directive_metadata_reader';
|
||||
import {Template, Decorator, Component} from 'core/src/annotations/annotations';
|
||||
|
||||
export function main() {
|
||||
describe('ElementBindingMarker', () => {
|
||||
|
||||
function createPipeline({textNodeBindings, propertyBindings, variableBindings, eventBindings, directives}={}) {
|
||||
var reader = new DirectiveMetadataReader();
|
||||
return new CompilePipeline([
|
||||
new MockStep((parent, current, control) => {
|
||||
if (isPresent(textNodeBindings)) {
|
||||
current.textNodeBindings = textNodeBindings;
|
||||
}
|
||||
if (isPresent(propertyBindings)) {
|
||||
current.propertyBindings = propertyBindings;
|
||||
}
|
||||
if (isPresent(variableBindings)) {
|
||||
current.variableBindings = variableBindings;
|
||||
}
|
||||
if (isPresent(eventBindings)) {
|
||||
current.eventBindings = eventBindings;
|
||||
}
|
||||
if (isPresent(directives)) {
|
||||
for (var i=0; i<directives.length; i++) {
|
||||
current.addDirective(reader.read(directives[i]));
|
||||
}
|
||||
}
|
||||
}), new ElementBindingMarker()
|
||||
]);
|
||||
}
|
||||
|
||||
it('should not mark empty elements', () => {
|
||||
var results = createPipeline().process(el('<div></div>'));
|
||||
assertBinding(results[0], false);
|
||||
});
|
||||
|
||||
it('should mark elements with text node bindings', () => {
|
||||
var textNodeBindings = MapWrapper.create();
|
||||
MapWrapper.set(textNodeBindings, 0, 'expr');
|
||||
var results = createPipeline({textNodeBindings: textNodeBindings}).process(el('<div></div>'));
|
||||
assertBinding(results[0], true);
|
||||
});
|
||||
|
||||
it('should mark elements with property bindings', () => {
|
||||
var propertyBindings = MapWrapper.createFromStringMap({'a': 'expr'});
|
||||
var results = createPipeline({propertyBindings: propertyBindings}).process(el('<div></div>'));
|
||||
assertBinding(results[0], true);
|
||||
});
|
||||
|
||||
it('should mark elements with variable bindings', () => {
|
||||
var variableBindings = MapWrapper.createFromStringMap({'a': 'expr'});
|
||||
var results = createPipeline({variableBindings: variableBindings}).process(el('<div></div>'));
|
||||
assertBinding(results[0], true);
|
||||
});
|
||||
|
||||
it('should mark elements with event bindings', () => {
|
||||
var eventBindings = MapWrapper.createFromStringMap({'click': 'expr'});
|
||||
var results = createPipeline({eventBindings: eventBindings}).process(el('<div></div>'));
|
||||
assertBinding(results[0], true);
|
||||
});
|
||||
|
||||
it('should mark elements with decorator directives', () => {
|
||||
var results = createPipeline({
|
||||
directives: [SomeDecoratorDirective]
|
||||
}).process(el('<div></div>'));
|
||||
assertBinding(results[0], true);
|
||||
});
|
||||
|
||||
it('should mark elements with template directives', () => {
|
||||
var results = createPipeline({
|
||||
directives: [SomeTemplateDirective]
|
||||
}).process(el('<div></div>'));
|
||||
assertBinding(results[0], true);
|
||||
});
|
||||
|
||||
it('should mark elements with component directives', () => {
|
||||
var results = createPipeline({
|
||||
directives: [SomeComponentDirective]
|
||||
}).process(el('<div></div>'));
|
||||
assertBinding(results[0], true);
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
function assertBinding(pipelineElement, shouldBePresent) {
|
||||
expect(pipelineElement.hasBindings).toBe(shouldBePresent);
|
||||
expect(DOM.hasClass(pipelineElement.element, 'ng-binding')).toBe(shouldBePresent);
|
||||
}
|
||||
|
||||
class MockStep extends CompileStep {
|
||||
processClosure:Function;
|
||||
constructor(process) {
|
||||
this.processClosure = process;
|
||||
}
|
||||
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
|
||||
this.processClosure(parent, current, control);
|
||||
}
|
||||
}
|
||||
|
||||
@Template()
|
||||
class SomeTemplateDirective {}
|
||||
|
||||
@Component()
|
||||
class SomeComponentDirective {}
|
||||
|
||||
@Decorator()
|
||||
class SomeDecoratorDirective {}
|
178
modules/angular2/test/core/compiler/pipeline/pipeline_spec.js
vendored
Normal file
178
modules/angular2/test/core/compiler/pipeline/pipeline_spec.js
vendored
Normal file
@ -0,0 +1,178 @@
|
||||
import {describe, beforeEach, it, expect, iit, ddescribe, el} from 'test_lib/test_lib';
|
||||
import {ListWrapper, List, MapWrapper} from 'facade/src/collection';
|
||||
import {DOM} from 'facade/src/dom';
|
||||
import {isPresent, NumberWrapper, StringWrapper} from 'facade/src/lang';
|
||||
|
||||
import {CompilePipeline} from 'core/src/compiler/pipeline/compile_pipeline';
|
||||
import {CompileElement} from 'core/src/compiler/pipeline/compile_element';
|
||||
import {CompileStep} from 'core/src/compiler/pipeline/compile_step'
|
||||
import {CompileControl} from 'core/src/compiler/pipeline/compile_control';
|
||||
|
||||
export function main() {
|
||||
describe('compile_pipeline', () => {
|
||||
describe('children compilation', () => {
|
||||
it('should walk the tree in depth first order including template contents', () => {
|
||||
var element = el('<div id="1"><template id="2"><span id="3"></span></template></div>');
|
||||
|
||||
var step0Log = [];
|
||||
var results = new CompilePipeline([createLoggerStep(step0Log)]).process(element);
|
||||
|
||||
expect(step0Log).toEqual(['1', '1<2', '2<3']);
|
||||
expect(resultIdLog(results)).toEqual(['1', '2', '3']);
|
||||
});
|
||||
|
||||
it('should stop walking the tree when compileChildren is false', () => {
|
||||
var element = el('<div id="1"><template id="2" ignore-children><span id="3"></span></template></div>');
|
||||
|
||||
var step0Log = [];
|
||||
var pipeline = new CompilePipeline([new IgnoreChildrenStep(), createLoggerStep(step0Log)]);
|
||||
var results = pipeline.process(element);
|
||||
|
||||
expect(step0Log).toEqual(['1', '1<2']);
|
||||
expect(resultIdLog(results)).toEqual(['1', '2']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('control.addParent', () => {
|
||||
it('should report the new parent to the following processor and the result', () => {
|
||||
var element = el('<div id="1"><span wrap0="1" id="2"><b id="3"></b></span></div>');
|
||||
var step0Log = [];
|
||||
var step1Log = [];
|
||||
var pipeline = new CompilePipeline([
|
||||
createWrapperStep('wrap0', step0Log),
|
||||
createLoggerStep(step1Log)
|
||||
]);
|
||||
var result = pipeline.process(element);
|
||||
expect(step0Log).toEqual(['1', '1<2', '2<3']);
|
||||
expect(step1Log).toEqual(['1', '1<wrap0#0', 'wrap0#0<2', '2<3']);
|
||||
expect(resultIdLog(result)).toEqual(['1', 'wrap0#0', '2', '3']);
|
||||
});
|
||||
|
||||
it('should allow to add a parent by multiple processors to the same element', () => {
|
||||
var element = el('<div id="1"><span wrap0="1" wrap1="1" id="2"><b id="3"></b></span></div>');
|
||||
var step0Log = [];
|
||||
var step1Log = [];
|
||||
var step2Log = [];
|
||||
var pipeline = new CompilePipeline([
|
||||
createWrapperStep('wrap0', step0Log),
|
||||
createWrapperStep('wrap1', step1Log),
|
||||
createLoggerStep(step2Log)
|
||||
]);
|
||||
var result = pipeline.process(element);
|
||||
expect(step0Log).toEqual(['1', '1<2', '2<3']);
|
||||
expect(step1Log).toEqual(['1', '1<wrap0#0', 'wrap0#0<2', '2<3']);
|
||||
expect(step2Log).toEqual(['1', '1<wrap0#0', 'wrap0#0<wrap1#0', 'wrap1#0<2', '2<3']);
|
||||
expect(resultIdLog(result)).toEqual(['1', 'wrap0#0', 'wrap1#0', '2', '3']);
|
||||
});
|
||||
|
||||
it('should allow to add a parent by multiple processors to different elements', () => {
|
||||
var element = el('<div id="1"><span wrap0="1" id="2"><b id="3" wrap1="1"></b></span></div>');
|
||||
var step0Log = [];
|
||||
var step1Log = [];
|
||||
var step2Log = [];
|
||||
var pipeline = new CompilePipeline([
|
||||
createWrapperStep('wrap0', step0Log),
|
||||
createWrapperStep('wrap1', step1Log),
|
||||
createLoggerStep(step2Log)
|
||||
]);
|
||||
var result = pipeline.process(element);
|
||||
expect(step0Log).toEqual(['1', '1<2', '2<3']);
|
||||
expect(step1Log).toEqual(['1', '1<wrap0#0', 'wrap0#0<2', '2<3']);
|
||||
expect(step2Log).toEqual(['1', '1<wrap0#0', 'wrap0#0<2', '2<wrap1#0', 'wrap1#0<3']);
|
||||
expect(resultIdLog(result)).toEqual(['1', 'wrap0#0', '2', 'wrap1#0', '3']);
|
||||
});
|
||||
|
||||
it('should allow to add multiple parents by the same processor', () => {
|
||||
var element = el('<div id="1"><span wrap0="2" id="2"><b id="3"></b></span></div>');
|
||||
var step0Log = [];
|
||||
var step1Log = [];
|
||||
var pipeline = new CompilePipeline([
|
||||
createWrapperStep('wrap0', step0Log),
|
||||
createLoggerStep(step1Log)
|
||||
]);
|
||||
var result = pipeline.process(element);
|
||||
expect(step0Log).toEqual(['1', '1<2', '2<3']);
|
||||
expect(step1Log).toEqual(['1', '1<wrap0#0', 'wrap0#0<wrap0#1', 'wrap0#1<2', '2<3']);
|
||||
expect(resultIdLog(result)).toEqual(['1', 'wrap0#0', 'wrap0#1', '2', '3']);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('control.addChild', () => {
|
||||
it('should report the new child to all processors and the result', () => {
|
||||
var element = el('<div id="1"><div id="2"></div></div>');
|
||||
var resultLog = [];
|
||||
var newChild = new CompileElement(el('<div id="3"></div>'));
|
||||
var pipeline = new CompilePipeline([
|
||||
new MockStep((parent, current, control) => {
|
||||
if (StringWrapper.equals(current.element.id, '1')) {
|
||||
control.addChild(newChild);
|
||||
}
|
||||
}),
|
||||
createLoggerStep(resultLog)
|
||||
]);
|
||||
var result = pipeline.process(element);
|
||||
expect(result[2]).toBe(newChild);
|
||||
expect(resultLog).toEqual(['1', '1<2', '1<3']);
|
||||
expect(resultIdLog(result)).toEqual(['1', '2', '3']);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
class MockStep extends CompileStep {
|
||||
processClosure:Function;
|
||||
constructor(process) {
|
||||
this.processClosure = process;
|
||||
}
|
||||
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
|
||||
this.processClosure(parent, current, control);
|
||||
}
|
||||
}
|
||||
|
||||
export class IgnoreChildrenStep extends CompileStep {
|
||||
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
|
||||
var attributeMap = DOM.attributeMap(current.element);
|
||||
if (MapWrapper.contains(attributeMap, 'ignore-children')) {
|
||||
current.compileChildren = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function logEntry(log, parent, current) {
|
||||
var parentId = '';
|
||||
if (isPresent(parent)) {
|
||||
parentId = parent.element.getAttribute('id')+'<';
|
||||
}
|
||||
ListWrapper.push(log, parentId+current.element.getAttribute('id'));
|
||||
}
|
||||
|
||||
function createLoggerStep(log) {
|
||||
return new MockStep((parent, current, control) => {
|
||||
logEntry(log, parent, current);
|
||||
});
|
||||
}
|
||||
|
||||
function createWrapperStep(wrapperId, log) {
|
||||
var nextElementId = 0;
|
||||
return new MockStep((parent, current, control) => {
|
||||
var parentCountStr = current.element.getAttribute(wrapperId);
|
||||
if (isPresent(parentCountStr)) {
|
||||
var parentCount = NumberWrapper.parseInt(parentCountStr, 10);
|
||||
while (parentCount > 0) {
|
||||
control.addParent(new CompileElement(el(`<a id="${wrapperId}#${nextElementId++}"></a>`)));
|
||||
parentCount--;
|
||||
}
|
||||
}
|
||||
logEntry(log, parent, current);
|
||||
});
|
||||
}
|
||||
|
||||
function resultIdLog(result) {
|
||||
var idLog = [];
|
||||
ListWrapper.forEach(result, (current) => {
|
||||
logEntry(idLog, null, current);
|
||||
});
|
||||
return idLog;
|
||||
}
|
57
modules/angular2/test/core/compiler/pipeline/property_binding_parser_spec.js
vendored
Normal file
57
modules/angular2/test/core/compiler/pipeline/property_binding_parser_spec.js
vendored
Normal file
@ -0,0 +1,57 @@
|
||||
import {describe, beforeEach, it, expect, iit, ddescribe, el} from 'test_lib/test_lib';
|
||||
import {PropertyBindingParser} from 'core/src/compiler/pipeline/property_binding_parser';
|
||||
import {CompilePipeline} from 'core/src/compiler/pipeline/compile_pipeline';
|
||||
import {DOM} from 'facade/src/dom';
|
||||
import {MapWrapper} from 'facade/src/collection';
|
||||
|
||||
import {Lexer, Parser} from 'change_detection/change_detection';
|
||||
|
||||
export function main() {
|
||||
describe('PropertyBindingParser', () => {
|
||||
function createPipeline() {
|
||||
return new CompilePipeline([new PropertyBindingParser(new Parser(new Lexer()), null)]);
|
||||
}
|
||||
|
||||
it('should detect [] syntax', () => {
|
||||
var results = createPipeline().process(el('<div [a]="b"></div>'));
|
||||
expect(MapWrapper.get(results[0].propertyBindings, 'a').source).toEqual('b');
|
||||
});
|
||||
|
||||
it('should detect bind- syntax', () => {
|
||||
var results = createPipeline().process(el('<div bind-a="b"></div>'));
|
||||
expect(MapWrapper.get(results[0].propertyBindings, 'a').source).toEqual('b');
|
||||
});
|
||||
|
||||
it('should detect interpolation syntax', () => {
|
||||
// Note: we don't test all corner cases of interpolation as we assume shared functionality between text interpolation
|
||||
// and attribute interpolation.
|
||||
var results = createPipeline().process(el('<div a="{{b}}"></div>'));
|
||||
expect(MapWrapper.get(results[0].propertyBindings, 'a').source).toEqual('{{b}}');
|
||||
});
|
||||
|
||||
it('should detect var- syntax', () => {
|
||||
var results = createPipeline().process(el('<template var-a="b"></template>'));
|
||||
expect(MapWrapper.get(results[0].variableBindings, 'b')).toEqual('a');
|
||||
});
|
||||
|
||||
it('should not allow var- syntax on non template elements', () => {
|
||||
expect( () => {
|
||||
createPipeline().process(el('<div var-a="b"></div>'))
|
||||
}).toThrowError('var-* is only allowed on <template> elements!');
|
||||
});
|
||||
|
||||
it('should detect () syntax', () => {
|
||||
var results = createPipeline().process(el('<div (click)="b()"></div>'));
|
||||
expect(MapWrapper.get(results[0].eventBindings, 'click').source).toEqual('b()');
|
||||
// "(click[])" is not an expected syntax and is only used to validate the regexp
|
||||
results = createPipeline().process(el('<div (click[])="b()"></div>'));
|
||||
expect(MapWrapper.get(results[0].eventBindings, 'click[]').source).toEqual('b()');
|
||||
|
||||
});
|
||||
|
||||
it('should detect on- syntax', () => {
|
||||
var results = createPipeline().process(el('<div on-click="b()"></div>'));
|
||||
expect(MapWrapper.get(results[0].eventBindings, 'click').source).toEqual('b()');
|
||||
});
|
||||
});
|
||||
}
|
180
modules/angular2/test/core/compiler/pipeline/proto_element_injector_builder_spec.js
vendored
Normal file
180
modules/angular2/test/core/compiler/pipeline/proto_element_injector_builder_spec.js
vendored
Normal file
@ -0,0 +1,180 @@
|
||||
import {describe, beforeEach, it, expect, iit, ddescribe, el} from 'test_lib/test_lib';
|
||||
import {isPresent, isBlank} from 'facade/src/lang';
|
||||
import {DOM} from 'facade/src/dom';
|
||||
import {List, ListWrapper} from 'facade/src/collection';
|
||||
|
||||
import {ProtoElementInjectorBuilder} from 'core/src/compiler/pipeline/proto_element_injector_builder';
|
||||
import {CompilePipeline} from 'core/src/compiler/pipeline/compile_pipeline';
|
||||
import {CompileElement} from 'core/src/compiler/pipeline/compile_element';
|
||||
import {CompileStep} from 'core/src/compiler/pipeline/compile_step'
|
||||
import {CompileControl} from 'core/src/compiler/pipeline/compile_control';
|
||||
import {ProtoView} from 'core/src/compiler/view';
|
||||
import {DirectiveMetadataReader} from 'core/src/compiler/directive_metadata_reader';
|
||||
import {Template, Decorator, Component} from 'core/src/annotations/annotations';
|
||||
import {ProtoElementInjector} from 'core/src/compiler/element_injector';
|
||||
|
||||
export function main() {
|
||||
describe('ProtoElementInjectorBuilder', () => {
|
||||
var protoElementInjectorBuilder, protoView;
|
||||
beforeEach( () => {
|
||||
protoElementInjectorBuilder = new TestableProtoElementInjectorBuilder();
|
||||
protoView = new ProtoView(null, null);
|
||||
});
|
||||
|
||||
function createPipeline(directives = null) {
|
||||
if (isBlank(directives)) {
|
||||
directives = [];
|
||||
}
|
||||
var reader = new DirectiveMetadataReader();
|
||||
return new CompilePipeline([new MockStep((parent, current, control) => {
|
||||
if (isPresent(current.element.getAttribute('viewroot'))) {
|
||||
current.isViewRoot = true;
|
||||
}
|
||||
if (isPresent(current.element.getAttribute('directives'))) {
|
||||
for (var i=0; i<directives.length; i++) {
|
||||
var dirMetadata = reader.read(directives[i]);
|
||||
current.addDirective(dirMetadata);
|
||||
}
|
||||
}
|
||||
current.inheritedProtoView = protoView;
|
||||
}), protoElementInjectorBuilder]);
|
||||
}
|
||||
|
||||
function getCreationArgs(protoElementInjector) {
|
||||
return protoElementInjectorBuilder.findArgsFor(protoElementInjector);
|
||||
}
|
||||
|
||||
it('should not create a ProtoElementInjector for elements without directives', () => {
|
||||
var results = createPipeline().process(el('<div></div>'));
|
||||
expect(results[0].inheritedProtoElementInjector).toBe(null);
|
||||
});
|
||||
|
||||
it('should create a ProtoElementInjector for elements directives', () => {
|
||||
var directives = [SomeComponentDirective, SomeTemplateDirective, SomeDecoratorDirective];
|
||||
var results = createPipeline(directives).process(el('<div directives></div>'));
|
||||
var creationArgs = getCreationArgs(results[0].inheritedProtoElementInjector);
|
||||
var boundDirectives = creationArgs['bindings'].map((b) => b.key.token);
|
||||
expect(boundDirectives).toEqual(directives);
|
||||
});
|
||||
|
||||
it('should mark ProtoElementInjector for elements with component directives and use the ComponentDirective as first binding', () => {
|
||||
var directives = [SomeDecoratorDirective, SomeComponentDirective];
|
||||
var results = createPipeline(directives).process(el('<div directives></div>'));
|
||||
var creationArgs = getCreationArgs(results[0].inheritedProtoElementInjector);
|
||||
expect(creationArgs['firstBindingIsComponent']).toBe(true);
|
||||
var boundDirectives = creationArgs['bindings'].map((b) => b.key.token);
|
||||
expect(boundDirectives).toEqual([SomeComponentDirective, SomeDecoratorDirective]);
|
||||
});
|
||||
|
||||
it('should use the next ElementBinder index as index of the ProtoElementInjector', () => {
|
||||
// just adding some indices..
|
||||
ListWrapper.push(protoView.elementBinders, null);
|
||||
ListWrapper.push(protoView.elementBinders, null);
|
||||
var directives = [SomeDecoratorDirective];
|
||||
var results = createPipeline(directives).process(el('<div directives></div>'));
|
||||
var creationArgs = getCreationArgs(results[0].inheritedProtoElementInjector);
|
||||
expect(creationArgs['index']).toBe(protoView.elementBinders.length);
|
||||
});
|
||||
|
||||
describe("inheritedProtoElementInjector", () => {
|
||||
it('should inherit the ProtoElementInjector down to children without directives', () => {
|
||||
var directives = [SomeDecoratorDirective];
|
||||
var results = createPipeline(directives).process(el('<div directives><span></span></div>'));
|
||||
expect(results[1].inheritedProtoElementInjector).toBe(results[0].inheritedProtoElementInjector);
|
||||
});
|
||||
|
||||
it('should use the ProtoElementInjector of the parent element as parent', () => {
|
||||
var element = el('<div directives><span><a directives></a></span></div>');
|
||||
var directives = [SomeDecoratorDirective];
|
||||
var results = createPipeline(directives).process(element);
|
||||
expect(results[2].inheritedProtoElementInjector.parent).toBe(
|
||||
results[0].inheritedProtoElementInjector);
|
||||
});
|
||||
|
||||
it('should use a null parent for viewRoots', () => {
|
||||
var element = el('<div directives><span viewroot directives></span></div>');
|
||||
var directives = [SomeDecoratorDirective];
|
||||
var results = createPipeline(directives).process(element);
|
||||
expect(results[1].inheritedProtoElementInjector.parent).toBe(null);
|
||||
});
|
||||
|
||||
it('should use a null parent if there is an intermediate viewRoot', () => {
|
||||
var element = el('<div directives><span viewroot><a directives></a></span></div>');
|
||||
var directives = [SomeDecoratorDirective];
|
||||
var results = createPipeline(directives).process(element);
|
||||
expect(results[2].inheritedProtoElementInjector.parent).toBe(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe("distanceToParentInjector", () => {
|
||||
it("should be 0 for root elements", () => {
|
||||
var element = el('<div directives></div>');
|
||||
var directives = [SomeDecoratorDirective];
|
||||
var results = createPipeline(directives).process(element);
|
||||
expect(results[0].inheritedProtoElementInjector.distanceToParent).toBe(0);
|
||||
});
|
||||
|
||||
it("should be 1 when a parent element has an injector", () => {
|
||||
var element = el('<div directives><span directives></span></div>');
|
||||
var directives = [SomeDecoratorDirective];
|
||||
var results = createPipeline(directives).process(element);
|
||||
expect(results[1].inheritedProtoElementInjector.distanceToParent).toBe(1);
|
||||
});
|
||||
|
||||
it("should add 1 for every element that does not have an injector", () => {
|
||||
var element = el('<div directives><a><b><span directives></span></b></a></div>');
|
||||
var directives = [SomeDecoratorDirective];
|
||||
var results = createPipeline(directives).process(element);
|
||||
expect(results[3].inheritedProtoElementInjector.distanceToParent).toBe(3);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
class TestableProtoElementInjectorBuilder extends ProtoElementInjectorBuilder {
|
||||
debugObjects:List;
|
||||
|
||||
constructor() {
|
||||
this.debugObjects = [];
|
||||
}
|
||||
|
||||
findArgsFor(protoElementInjector:ProtoElementInjector) {
|
||||
for (var i=0; i<this.debugObjects.length; i+=2) {
|
||||
if (this.debugObjects[i] === protoElementInjector) {
|
||||
return this.debugObjects[i+1];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
internalCreateProtoElementInjector(parent, index, bindings, firstBindingIsComponent, distance) {
|
||||
var result = new ProtoElementInjector(parent, index, bindings, firstBindingIsComponent, distance);
|
||||
ListWrapper.push(this.debugObjects, result);
|
||||
ListWrapper.push(this.debugObjects, {'parent': parent, 'index': index, 'bindings': bindings, 'firstBindingIsComponent': firstBindingIsComponent});
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
class MockStep extends CompileStep {
|
||||
processClosure:Function;
|
||||
constructor(process) {
|
||||
this.processClosure = process;
|
||||
}
|
||||
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
|
||||
this.processClosure(parent, current, control);
|
||||
}
|
||||
}
|
||||
|
||||
class SomeComponentService {}
|
||||
|
||||
@Template()
|
||||
class SomeTemplateDirective {}
|
||||
|
||||
@Component({
|
||||
componentServices: [SomeComponentService]
|
||||
})
|
||||
class SomeComponentDirective {}
|
||||
|
||||
@Decorator()
|
||||
class SomeDecoratorDirective {}
|
86
modules/angular2/test/core/compiler/pipeline/proto_view_builder_spec.js
vendored
Normal file
86
modules/angular2/test/core/compiler/pipeline/proto_view_builder_spec.js
vendored
Normal file
@ -0,0 +1,86 @@
|
||||
import {describe, beforeEach, it, expect, iit, ddescribe, el} from 'test_lib/test_lib';
|
||||
import {isPresent} from 'facade/src/lang';
|
||||
import {dynamicChangeDetection} from 'change_detection/change_detection';
|
||||
import {ElementBinder} from 'core/src/compiler/element_binder';
|
||||
import {ProtoViewBuilder} from 'core/src/compiler/pipeline/proto_view_builder';
|
||||
import {CompilePipeline} from 'core/src/compiler/pipeline/compile_pipeline';
|
||||
import {CompileElement} from 'core/src/compiler/pipeline/compile_element';
|
||||
import {CompileStep} from 'core/src/compiler/pipeline/compile_step'
|
||||
import {CompileControl} from 'core/src/compiler/pipeline/compile_control';
|
||||
import {DOM} from 'facade/src/dom';
|
||||
import {MapWrapper} from 'facade/src/collection';
|
||||
|
||||
export function main() {
|
||||
describe('ProtoViewBuilder', () => {
|
||||
function createPipeline(variableBindings=null) {
|
||||
return new CompilePipeline([new MockStep((parent, current, control) => {
|
||||
if (isPresent(current.element.getAttribute('viewroot'))) {
|
||||
current.isViewRoot = true;
|
||||
}
|
||||
if (isPresent(current.element.getAttribute('var-binding'))) {
|
||||
current.variableBindings = MapWrapper.createFromStringMap(variableBindings);
|
||||
}
|
||||
current.inheritedElementBinder = new ElementBinder(null, null, null);
|
||||
}), new ProtoViewBuilder(dynamicChangeDetection)]);
|
||||
}
|
||||
|
||||
it('should not create a ProtoView when the isViewRoot flag is not set', () => {
|
||||
var results = createPipeline().process(el('<div></div>'));
|
||||
expect(results[0].inheritedProtoView).toBe(null);
|
||||
});
|
||||
|
||||
it('should create a ProtoView when the isViewRoot flag is set', () => {
|
||||
var viewRootElement = el('<div viewroot></div>');
|
||||
var results = createPipeline().process(viewRootElement);
|
||||
expect(results[0].inheritedProtoView.element).toBe(viewRootElement);
|
||||
});
|
||||
|
||||
it('should inherit the ProtoView down to children that have no isViewRoot set', () => {
|
||||
var viewRootElement = el('<div viewroot><span></span></div>');
|
||||
var results = createPipeline().process(viewRootElement);
|
||||
expect(results[0].inheritedProtoView.element).toBe(viewRootElement);
|
||||
expect(results[1].inheritedProtoView.element).toBe(viewRootElement);
|
||||
});
|
||||
|
||||
it('should save ProtoView into the elementBinder of parent element', () => {
|
||||
var element = el('<div viewroot><template><a viewroot></a></template></div>');
|
||||
var results = createPipeline().process(element);
|
||||
expect(results[1].inheritedElementBinder.nestedProtoView).toBe(results[2].inheritedProtoView);
|
||||
});
|
||||
|
||||
it('should bind variables to the nested ProtoView', () => {
|
||||
var element = el('<div viewroot><template var-binding><a viewroot></a></template></div>');
|
||||
var results = createPipeline({
|
||||
'var1': 'map1',
|
||||
'var2': 'map2'
|
||||
}).process(element);
|
||||
var npv = results[1].inheritedElementBinder.nestedProtoView;
|
||||
expect(npv.variableBindings).toEqual(MapWrapper.createFromStringMap({
|
||||
'var1': 'map1',
|
||||
'var2': 'map2'
|
||||
}));
|
||||
});
|
||||
|
||||
describe('errors', () => {
|
||||
|
||||
it('should not allow multiple nested ProtoViews for the same parent element', () => {
|
||||
var element = el('<div viewroot><template><a viewroot></a><a viewroot></a></template></div>');
|
||||
expect( () => {
|
||||
createPipeline().process(element);
|
||||
}).toThrowError('Only one nested view per element is allowed');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
class MockStep extends CompileStep {
|
||||
processClosure:Function;
|
||||
constructor(process) {
|
||||
this.processClosure = process;
|
||||
}
|
||||
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
|
||||
this.processClosure(parent, current, control);
|
||||
}
|
||||
}
|
57
modules/angular2/test/core/compiler/pipeline/text_interpolation_parser_spec.js
vendored
Normal file
57
modules/angular2/test/core/compiler/pipeline/text_interpolation_parser_spec.js
vendored
Normal file
@ -0,0 +1,57 @@
|
||||
import {describe, beforeEach, expect, it, iit, ddescribe, el} from 'test_lib/test_lib';
|
||||
import {TextInterpolationParser} from 'core/src/compiler/pipeline/text_interpolation_parser';
|
||||
import {CompilePipeline} from 'core/src/compiler/pipeline/compile_pipeline';
|
||||
import {DOM} from 'facade/src/dom';
|
||||
import {MapWrapper} from 'facade/src/collection';
|
||||
|
||||
import {Lexer, Parser} from 'change_detection/change_detection';
|
||||
import {IgnoreChildrenStep} from './pipeline_spec';
|
||||
|
||||
export function main() {
|
||||
describe('TextInterpolationParser', () => {
|
||||
function createPipeline() {
|
||||
return new CompilePipeline([
|
||||
new IgnoreChildrenStep(),
|
||||
new TextInterpolationParser(new Parser(new Lexer()), null)
|
||||
]);
|
||||
}
|
||||
|
||||
it('should find text interpolation in normal elements', () => {
|
||||
var results = createPipeline().process(el('<div>{{expr1}}<span></span>{{expr2}}</div>'));
|
||||
var bindings = results[0].textNodeBindings;
|
||||
expect(MapWrapper.get(bindings, 0).source).toEqual("{{expr1}}");
|
||||
expect(MapWrapper.get(bindings, 2).source).toEqual("{{expr2}}");
|
||||
});
|
||||
|
||||
it('should find text interpolation in template elements', () => {
|
||||
var results = createPipeline().process(el('<template>{{expr1}}<span></span>{{expr2}}</template>'));
|
||||
var bindings = results[0].textNodeBindings;
|
||||
expect(MapWrapper.get(bindings, 0).source).toEqual("{{expr1}}");
|
||||
expect(MapWrapper.get(bindings, 2).source).toEqual("{{expr2}}");
|
||||
});
|
||||
|
||||
it('should allow multiple expressions', () => {
|
||||
var results = createPipeline().process(el('<div>{{expr1}}{{expr2}}</div>'));
|
||||
var bindings = results[0].textNodeBindings;
|
||||
expect(MapWrapper.get(bindings, 0).source).toEqual("{{expr1}}{{expr2}}");
|
||||
});
|
||||
|
||||
it('should not interpolate when compileChildren is false', () => {
|
||||
var results = createPipeline().process(el('<div>{{included}}<span ignore-children>{{excluded}}</span></div>'));
|
||||
var bindings = results[0].textNodeBindings;
|
||||
expect(MapWrapper.get(bindings, 0).source).toEqual("{{included}}");
|
||||
expect(results[1].textNodeBindings).toBe(null);
|
||||
});
|
||||
|
||||
it('should allow fixed text before, in between and after expressions', () => {
|
||||
var results = createPipeline().process(el('<div>a{{expr1}}b{{expr2}}c</div>'));
|
||||
var bindings = results[0].textNodeBindings;
|
||||
expect(MapWrapper.get(bindings, 0).source).toEqual("a{{expr1}}b{{expr2}}c");
|
||||
});
|
||||
|
||||
it('should escape quotes in fixed parts', () => {
|
||||
var results = createPipeline().process(el("<div>'\"a{{expr1}}</div>"));
|
||||
expect(MapWrapper.get(results[0].textNodeBindings, 0).source).toEqual("'\"a{{expr1}}");
|
||||
});
|
||||
});
|
||||
}
|
177
modules/angular2/test/core/compiler/pipeline/view_splitter_spec.js
vendored
Normal file
177
modules/angular2/test/core/compiler/pipeline/view_splitter_spec.js
vendored
Normal file
@ -0,0 +1,177 @@
|
||||
import {describe, beforeEach, it, expect, iit, ddescribe, el} from 'test_lib/test_lib';
|
||||
import {isPresent} from 'facade/src/lang';
|
||||
import {MapWrapper} from 'facade/src/collection';
|
||||
|
||||
import {ViewSplitter} from 'core/src/compiler/pipeline/view_splitter';
|
||||
import {CompilePipeline} from 'core/src/compiler/pipeline/compile_pipeline';
|
||||
import {DOM, TemplateElement} from 'facade/src/dom';
|
||||
|
||||
import {Lexer, Parser} from 'change_detection/change_detection';
|
||||
|
||||
export function main() {
|
||||
describe('ViewSplitter', () => {
|
||||
|
||||
function createPipeline() {
|
||||
return new CompilePipeline([new ViewSplitter(new Parser(new Lexer()), null)]);
|
||||
}
|
||||
|
||||
it('should mark root elements as viewRoot', () => {
|
||||
var rootElement = el('<div></div>');
|
||||
var results = createPipeline().process(rootElement);
|
||||
expect(results[0].isViewRoot).toBe(true);
|
||||
});
|
||||
|
||||
describe('<template> elements', () => {
|
||||
|
||||
it('should move the content into a new <template> element and mark that as viewRoot', () => {
|
||||
var rootElement = el('<div><template if="true">a</template></div>');
|
||||
var results = createPipeline().process(rootElement);
|
||||
expect(DOM.getOuterHTML(results[1].element)).toEqual('<template if="true"></template>');
|
||||
expect(results[1].isViewRoot).toBe(false);
|
||||
expect(DOM.getOuterHTML(results[2].element)).toEqual('<template>a</template>');
|
||||
expect(results[2].isViewRoot).toBe(true);
|
||||
});
|
||||
|
||||
it('should not wrap a root <template> element', () => {
|
||||
var rootElement = el('<div></div>');
|
||||
var results = createPipeline().process(rootElement);
|
||||
expect(results.length).toBe(1);
|
||||
expect(DOM.getOuterHTML(rootElement)).toEqual('<div></div>');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('elements with template attribute', () => {
|
||||
|
||||
it('should replace the element with an empty <template> element', () => {
|
||||
var rootElement = el('<div><span template=""></span></div>');
|
||||
var originalChild = rootElement.childNodes[0];
|
||||
var results = createPipeline().process(rootElement);
|
||||
expect(results[0].element).toBe(rootElement);
|
||||
expect(DOM.getOuterHTML(results[0].element)).toEqual('<div><template></template></div>');
|
||||
expect(DOM.getOuterHTML(results[2].element)).toEqual('<span template=""></span>')
|
||||
expect(results[2].element).toBe(originalChild);
|
||||
});
|
||||
|
||||
it('should mark the element as viewRoot', () => {
|
||||
var rootElement = el('<div><div template></div></div>');
|
||||
var results = createPipeline().process(rootElement);
|
||||
expect(results[2].isViewRoot).toBe(true);
|
||||
});
|
||||
|
||||
it('should work with top-level template node', () => {
|
||||
var rootElement = DOM.createTemplate('<div template>x</div>');
|
||||
var originalChild = rootElement.content.childNodes[0];
|
||||
var results = createPipeline().process(rootElement);
|
||||
|
||||
expect(results[0].element).toBe(rootElement);
|
||||
expect(results[0].isViewRoot).toBe(true);
|
||||
expect(results[2].isViewRoot).toBe(true);
|
||||
expect(DOM.getOuterHTML(results[0].element)).toEqual('<template><template></template></template>');
|
||||
expect(results[2].element).toBe(originalChild);
|
||||
});
|
||||
|
||||
it('should add property bindings from the template attribute', () => {
|
||||
var rootElement = el('<div><div template="prop:expr"></div></div>');
|
||||
var results = createPipeline().process(rootElement);
|
||||
expect(MapWrapper.get(results[1].propertyBindings, 'prop').source).toEqual('expr');
|
||||
});
|
||||
|
||||
it('should add variable mappings from the template attribute', () => {
|
||||
var rootElement = el('<div><div template="var varName=mapName"></div></div>');
|
||||
var results = createPipeline().process(rootElement);
|
||||
expect(results[1].variableBindings).toEqual(MapWrapper.createFromStringMap({'mapName': 'varName'}));
|
||||
});
|
||||
|
||||
it('should add entries without value as attribute to the element', () => {
|
||||
var rootElement = el('<div><div template="varname"></div></div>');
|
||||
var results = createPipeline().process(rootElement);
|
||||
expect(results[1].attrs()).toEqual(MapWrapper.createFromStringMap({'varname': ''}));
|
||||
expect(results[1].propertyBindings).toBe(null);
|
||||
expect(results[1].variableBindings).toBe(null);
|
||||
});
|
||||
|
||||
it('should iterate properly after a template dom modification', () => {
|
||||
var rootElement = el('<div><div template></div><after></after></div>');
|
||||
var results = createPipeline().process(rootElement);
|
||||
// 1 root + 2 initial + 1 generated template elements
|
||||
expect(results.length).toEqual(4);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('elements with !directive_name attribute', () => {
|
||||
|
||||
it('should replace the element with an empty <template> element', () => {
|
||||
var rootElement = el('<div><span !if></span></div>');
|
||||
var originalChild = rootElement.childNodes[0];
|
||||
var results = createPipeline().process(rootElement);
|
||||
expect(results[0].element).toBe(rootElement);
|
||||
expect(DOM.getOuterHTML(results[0].element)).toEqual('<div><template if=""></template></div>');
|
||||
expect(DOM.getOuterHTML(results[2].element)).toEqual('<span !if=""></span>')
|
||||
expect(results[2].element).toBe(originalChild);
|
||||
});
|
||||
|
||||
it('should mark the element as viewRoot', () => {
|
||||
var rootElement = el('<div><div !foo="bar"></div></div>');
|
||||
var results = createPipeline().process(rootElement);
|
||||
expect(results[2].isViewRoot).toBe(true);
|
||||
});
|
||||
|
||||
it('should work with top-level template node', () => {
|
||||
var rootElement = DOM.createTemplate('<div !foo>x</div>');
|
||||
var originalChild = rootElement.content.childNodes[0];
|
||||
var results = createPipeline().process(rootElement);
|
||||
|
||||
expect(results[0].element).toBe(rootElement);
|
||||
expect(results[0].isViewRoot).toBe(true);
|
||||
expect(results[2].isViewRoot).toBe(true);
|
||||
expect(DOM.getOuterHTML(results[0].element)).toEqual('<template><template foo=""></template></template>');
|
||||
expect(results[2].element).toBe(originalChild);
|
||||
});
|
||||
|
||||
it('should add property bindings from the template attribute', () => {
|
||||
var rootElement = el('<div><div !prop="expr"></div></div>');
|
||||
var results = createPipeline().process(rootElement);
|
||||
expect(MapWrapper.get(results[1].propertyBindings, 'prop').source).toEqual('expr');
|
||||
});
|
||||
|
||||
it('should add variable mappings from the template attribute', () => {
|
||||
var rootElement = el('<div><div !foreach="var varName=mapName"></div></div>');
|
||||
var results = createPipeline().process(rootElement);
|
||||
expect(results[1].variableBindings).toEqual(MapWrapper.createFromStringMap({'mapName': 'varName'}));
|
||||
});
|
||||
|
||||
it('should add entries without value as attribute to the element', () => {
|
||||
var rootElement = el('<div><div !varname></div></div>');
|
||||
var results = createPipeline().process(rootElement);
|
||||
expect(results[1].attrs()).toEqual(MapWrapper.createFromStringMap({'varname': ''}));
|
||||
expect(results[1].propertyBindings).toBe(null);
|
||||
expect(results[1].variableBindings).toBe(null);
|
||||
});
|
||||
|
||||
it('should iterate properly after a template dom modification', () => {
|
||||
var rootElement = el('<div><div !foo></div><after></after></div>');
|
||||
var results = createPipeline().process(rootElement);
|
||||
// 1 root + 2 initial + 1 generated template elements
|
||||
expect(results.length).toEqual(4);
|
||||
});
|
||||
|
||||
it('should not allow multiple template directives on the same element', () => {
|
||||
expect( () => {
|
||||
var rootElement = el('<div><div !foo !bar="blah"></div></div>');
|
||||
createPipeline().process(rootElement);
|
||||
}).toThrowError('Only one template directive per element is allowed: foo and bar cannot be used simultaneously!');
|
||||
});
|
||||
|
||||
it('should not allow template and bang directives on the same element', () => {
|
||||
expect( () => {
|
||||
var rootElement = el('<div><div !foo template="blah"></div></div>');
|
||||
createPipeline().process(rootElement);
|
||||
}).toThrowError('Only one template directive per element is allowed: blah and foo cannot be used simultaneously!');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
}
|
166
modules/angular2/test/core/compiler/selector_spec.js
vendored
Normal file
166
modules/angular2/test/core/compiler/selector_spec.js
vendored
Normal file
@ -0,0 +1,166 @@
|
||||
import {describe, it, expect, beforeEach, ddescribe, iit, xit, el} from 'test_lib/test_lib';
|
||||
import {SelectorMatcher} from 'core/src/compiler/selector';
|
||||
import {CssSelector} from 'core/src/compiler/selector';
|
||||
import {List, ListWrapper, MapWrapper} from 'facade/src/collection';
|
||||
import {DOM} from 'facade/src/dom';
|
||||
|
||||
export function main() {
|
||||
describe('SelectorMatcher', () => {
|
||||
var matcher, matched, selectableCollector;
|
||||
|
||||
function reset() {
|
||||
matched = ListWrapper.create();
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
reset();
|
||||
selectableCollector = (selectable) => {
|
||||
ListWrapper.push(matched, selectable);
|
||||
}
|
||||
matcher = new SelectorMatcher();
|
||||
});
|
||||
|
||||
it('should select by element name case insensitive', () => {
|
||||
matcher.addSelectable(CssSelector.parse('someTag'), 1);
|
||||
|
||||
matcher.match(CssSelector.parse('SOMEOTHERTAG'), selectableCollector);
|
||||
expect(matched).toEqual([]);
|
||||
|
||||
matcher.match(CssSelector.parse('SOMETAG'), selectableCollector);
|
||||
expect(matched).toEqual([1]);
|
||||
});
|
||||
|
||||
it('should select by class name case insensitive', () => {
|
||||
matcher.addSelectable(CssSelector.parse('.someClass'), 1);
|
||||
matcher.addSelectable(CssSelector.parse('.someClass.class2'), 2);
|
||||
|
||||
matcher.match(CssSelector.parse('.SOMEOTHERCLASS'), selectableCollector);
|
||||
expect(matched).toEqual([]);
|
||||
|
||||
matcher.match(CssSelector.parse('.SOMECLASS'), selectableCollector);
|
||||
expect(matched).toEqual([1]);
|
||||
|
||||
reset();
|
||||
matcher.match(CssSelector.parse('.someClass.class2'), selectableCollector);
|
||||
expect(matched).toEqual([1,2]);
|
||||
});
|
||||
|
||||
it('should select by attr name case insensitive independent of the value', () => {
|
||||
matcher.addSelectable(CssSelector.parse('[someAttr]'), 1);
|
||||
matcher.addSelectable(CssSelector.parse('[someAttr][someAttr2]'), 2);
|
||||
|
||||
matcher.match(CssSelector.parse('[SOMEOTHERATTR]'), selectableCollector);
|
||||
expect(matched).toEqual([]);
|
||||
|
||||
matcher.match(CssSelector.parse('[SOMEATTR]'), selectableCollector);
|
||||
expect(matched).toEqual([1]);
|
||||
|
||||
reset();
|
||||
matcher.match(CssSelector.parse('[SOMEATTR=someValue]'), selectableCollector);
|
||||
expect(matched).toEqual([1]);
|
||||
|
||||
reset();
|
||||
matcher.match(CssSelector.parse('[someAttr][someAttr2]'), selectableCollector);
|
||||
expect(matched).toEqual([1,2]);
|
||||
});
|
||||
|
||||
it('should select by attr name only once if the value is from the DOM', () => {
|
||||
matcher.addSelectable(CssSelector.parse('[some-decor]'), 1);
|
||||
|
||||
var elementSelector = new CssSelector();
|
||||
var element = el('<div attr></div>');
|
||||
var empty = element.getAttribute('attr');
|
||||
elementSelector.addAttribute('some-decor', empty);
|
||||
matcher.match(elementSelector, selectableCollector);
|
||||
expect(matched).toEqual([1]);
|
||||
});
|
||||
|
||||
it('should select by attr name and value case insensitive', () => {
|
||||
matcher.addSelectable(CssSelector.parse('[someAttr=someValue]'), 1);
|
||||
|
||||
matcher.match(CssSelector.parse('[SOMEATTR=SOMEOTHERATTR]'), selectableCollector);
|
||||
expect(matched).toEqual([]);
|
||||
|
||||
matcher.match(CssSelector.parse('[SOMEATTR=SOMEVALUE]'), selectableCollector);
|
||||
expect(matched).toEqual([1]);
|
||||
});
|
||||
|
||||
it('should select by element name, class name and attribute name with value', () => {
|
||||
matcher.addSelectable(CssSelector.parse('someTag.someClass[someAttr=someValue]'), 1);
|
||||
|
||||
matcher.match(CssSelector.parse('someOtherTag.someOtherClass[someOtherAttr]'), selectableCollector);
|
||||
expect(matched).toEqual([]);
|
||||
|
||||
matcher.match(CssSelector.parse('someTag.someOtherClass[someOtherAttr]'), selectableCollector);
|
||||
expect(matched).toEqual([]);
|
||||
|
||||
matcher.match(CssSelector.parse('someTag.someClass[someOtherAttr]'), selectableCollector);
|
||||
expect(matched).toEqual([]);
|
||||
|
||||
matcher.match(CssSelector.parse('someTag.someClass[someAttr]'), selectableCollector);
|
||||
expect(matched).toEqual([]);
|
||||
|
||||
matcher.match(CssSelector.parse('someTag.someClass[someAttr=someValue]'), selectableCollector);
|
||||
expect(matched).toEqual([1]);
|
||||
});
|
||||
|
||||
it('should select independent of the order in the css selector', () => {
|
||||
matcher.addSelectable(CssSelector.parse('[someAttr].someClass'), 1);
|
||||
matcher.addSelectable(CssSelector.parse('.someClass[someAttr]'), 2);
|
||||
matcher.addSelectable(CssSelector.parse('.class1.class2'), 3);
|
||||
matcher.addSelectable(CssSelector.parse('.class2.class1'), 4);
|
||||
|
||||
matcher.match(CssSelector.parse('[someAttr].someClass'), selectableCollector);
|
||||
expect(matched).toEqual([1,2]);
|
||||
|
||||
reset();
|
||||
matcher.match(CssSelector.parse('.someClass[someAttr]'), selectableCollector);
|
||||
expect(matched).toEqual([1,2]);
|
||||
|
||||
reset();
|
||||
matcher.match(CssSelector.parse('.class1.class2'), selectableCollector);
|
||||
expect(matched).toEqual([3,4]);
|
||||
|
||||
reset();
|
||||
matcher.match(CssSelector.parse('.class2.class1'), selectableCollector);
|
||||
expect(matched).toEqual([4,3]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('CssSelector.parse', () => {
|
||||
it('should detect element names', () => {
|
||||
var cssSelector = CssSelector.parse('sometag');
|
||||
expect(cssSelector.element).toEqual('sometag');
|
||||
expect(cssSelector.toString()).toEqual('sometag');
|
||||
});
|
||||
|
||||
it('should detect class names', () => {
|
||||
var cssSelector = CssSelector.parse('.someClass');
|
||||
expect(cssSelector.classNames).toEqual(['someclass']);
|
||||
|
||||
expect(cssSelector.toString()).toEqual('.someclass');
|
||||
});
|
||||
|
||||
it('should detect attr names', () => {
|
||||
var cssSelector = CssSelector.parse('[attrname]');
|
||||
expect(cssSelector.attrs).toEqual(['attrname', '']);
|
||||
|
||||
expect(cssSelector.toString()).toEqual('[attrname]');
|
||||
});
|
||||
|
||||
it('should detect attr values', () => {
|
||||
var cssSelector = CssSelector.parse('[attrname=attrvalue]');
|
||||
expect(cssSelector.attrs).toEqual(['attrname', 'attrvalue']);
|
||||
expect(cssSelector.toString()).toEqual('[attrname=attrvalue]');
|
||||
});
|
||||
|
||||
it('should detect multiple parts', () => {
|
||||
var cssSelector = CssSelector.parse('sometag[attrname=attrvalue].someclass');
|
||||
expect(cssSelector.element).toEqual('sometag');
|
||||
expect(cssSelector.attrs).toEqual(['attrname', 'attrvalue']);
|
||||
expect(cssSelector.classNames).toEqual(['someclass']);
|
||||
|
||||
expect(cssSelector.toString()).toEqual('sometag.someclass[attrname=attrvalue]');
|
||||
});
|
||||
});
|
||||
}
|
48
modules/angular2/test/core/compiler/shadow_dom/content_tag_spec.js
vendored
Normal file
48
modules/angular2/test/core/compiler/shadow_dom/content_tag_spec.js
vendored
Normal file
@ -0,0 +1,48 @@
|
||||
import {describe, beforeEach, it, expect, ddescribe, iit, SpyObject, el} from 'test_lib/test_lib';
|
||||
import {proxy, IMPLEMENTS} from 'facade/src/lang';
|
||||
import {DOM} from 'facade/src/dom';
|
||||
import {Content} from 'core/src/compiler/shadow_dom_emulation/content_tag';
|
||||
import {NgElement} from 'core/src/dom/element';
|
||||
import {LightDom} from 'core/src/compiler/shadow_dom_emulation/light_dom';
|
||||
|
||||
@proxy
|
||||
@IMPLEMENTS(LightDom)
|
||||
class DummyLightDom extends SpyObject {noSuchMethod(m){super.noSuchMethod(m)}}
|
||||
|
||||
var _script = `<script type="ng/content"></script>`;
|
||||
|
||||
export function main() {
|
||||
describe('Content', function() {
|
||||
it("should insert the nodes", () => {
|
||||
var parent = el("<div><content></content></div>");
|
||||
var content = DOM.firstChild(parent);
|
||||
|
||||
var c = new Content(null, new NgElement(content));
|
||||
c.insert([el("<a></a>"), el("<b></b>")])
|
||||
|
||||
expect(DOM.getInnerHTML(parent)).toEqual(`${_script}<a></a><b></b>${_script}`);
|
||||
});
|
||||
|
||||
it("should remove the nodes from the previous insertion", () => {
|
||||
var parent = el("<div><content></content></div>");
|
||||
var content = DOM.firstChild(parent);
|
||||
|
||||
var c = new Content(null, new NgElement(content));
|
||||
c.insert([el("<a></a>")]);
|
||||
c.insert([el("<b></b>")]);
|
||||
|
||||
expect(DOM.getInnerHTML(parent)).toEqual(`${_script}<b></b>${_script}`);
|
||||
});
|
||||
|
||||
it("should insert empty list", () => {
|
||||
var parent = el("<div><content></content></div>");
|
||||
var content = DOM.firstChild(parent);
|
||||
|
||||
var c = new Content(null, new NgElement(content));
|
||||
c.insert([el("<a></a>")]);
|
||||
c.insert([]);
|
||||
|
||||
expect(DOM.getInnerHTML(parent)).toEqual(`${_script}${_script}`);
|
||||
});
|
||||
});
|
||||
}
|
228
modules/angular2/test/core/compiler/shadow_dom/light_dom_spec.js
vendored
Normal file
228
modules/angular2/test/core/compiler/shadow_dom/light_dom_spec.js
vendored
Normal file
@ -0,0 +1,228 @@
|
||||
import {describe, beforeEach, it, expect, ddescribe, iit, SpyObject, el} from 'test_lib/test_lib';
|
||||
import {proxy, IMPLEMENTS, isBlank} from 'facade/src/lang';
|
||||
import {ListWrapper, MapWrapper} from 'facade/src/collection';
|
||||
import {DOM} from 'facade/src/dom';
|
||||
import {Content} from 'core/src/compiler/shadow_dom_emulation/content_tag';
|
||||
import {NgElement} from 'core/src/dom/element';
|
||||
import {LightDom} from 'core/src/compiler/shadow_dom_emulation/light_dom';
|
||||
import {View} from 'core/src/compiler/view';
|
||||
import {ViewPort} from 'core/src/compiler/viewport';
|
||||
import {ElementInjector} from 'core/src/compiler/element_injector';
|
||||
import {ProtoRecordRange} from 'change_detection/change_detection';
|
||||
|
||||
@proxy
|
||||
@IMPLEMENTS(ElementInjector)
|
||||
class FakeElementInjector {
|
||||
content;
|
||||
viewPort;
|
||||
element;
|
||||
|
||||
constructor(content = null, viewPort = null, element = null) {
|
||||
this.content = content;
|
||||
this.viewPort = viewPort;
|
||||
this.element = element;
|
||||
}
|
||||
|
||||
hasDirective(type) {
|
||||
return this.content != null;
|
||||
}
|
||||
|
||||
hasPreBuiltObject(type) {
|
||||
return this.viewPort != null;
|
||||
}
|
||||
|
||||
forElement(n) {
|
||||
return this.element == n;
|
||||
}
|
||||
|
||||
get(t) {
|
||||
if (t === Content) return this.content;
|
||||
if (t === ViewPort) return this.viewPort;
|
||||
return null;
|
||||
}
|
||||
|
||||
noSuchMethod(i) {
|
||||
super.noSuchMethod(i);
|
||||
}
|
||||
}
|
||||
|
||||
@proxy
|
||||
@IMPLEMENTS(View)
|
||||
class FakeView {
|
||||
elementInjectors;
|
||||
|
||||
constructor(elementInjectors = null) {
|
||||
this.elementInjectors = elementInjectors;
|
||||
}
|
||||
|
||||
noSuchMethod(i) {
|
||||
super.noSuchMethod(i);
|
||||
}
|
||||
}
|
||||
|
||||
@proxy
|
||||
@IMPLEMENTS(ViewPort)
|
||||
class FakeViewPort {
|
||||
_nodes;
|
||||
_contentTagContainers;
|
||||
|
||||
constructor(nodes = null, views = null) {
|
||||
this._nodes = nodes;
|
||||
this._contentTagContainers = views;
|
||||
}
|
||||
|
||||
nodes(){
|
||||
return this._nodes;
|
||||
}
|
||||
|
||||
contentTagContainers(){
|
||||
return this._contentTagContainers;
|
||||
}
|
||||
|
||||
noSuchMethod(i) {
|
||||
super.noSuchMethod(i);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@proxy
|
||||
@IMPLEMENTS(Content)
|
||||
class FakeContentTag {
|
||||
select;
|
||||
_nodes;
|
||||
|
||||
constructor(select = null, nodes = null) {
|
||||
this.select = select;
|
||||
this._nodes = nodes;
|
||||
}
|
||||
|
||||
insert(nodes){
|
||||
this._nodes = ListWrapper.clone(nodes);
|
||||
}
|
||||
|
||||
nodes() {
|
||||
return this._nodes;
|
||||
}
|
||||
|
||||
noSuchMethod(i) {
|
||||
super.noSuchMethod(i);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export function main() {
|
||||
describe('LightDom', function() {
|
||||
var lightDomView;
|
||||
|
||||
beforeEach(() => {
|
||||
lightDomView = new FakeView([]);
|
||||
});
|
||||
|
||||
describe("contentTags", () => {
|
||||
it("should collect content tags from element injectors", () => {
|
||||
var tag = new FakeContentTag();
|
||||
var shadowDomView = new FakeView([new FakeElementInjector(tag)]);
|
||||
|
||||
var lightDom = new LightDom(lightDomView, shadowDomView, el("<div></div>"));
|
||||
|
||||
expect(lightDom.contentTags()).toEqual([tag]);
|
||||
});
|
||||
|
||||
it("should collect content tags from view ports", () => {
|
||||
var tag = new FakeContentTag();
|
||||
var vp = new FakeViewPort(null, [
|
||||
new FakeView([new FakeElementInjector(tag, null)])
|
||||
]);
|
||||
|
||||
var shadowDomView = new FakeView([new FakeElementInjector(null, vp)]);
|
||||
|
||||
var lightDom = new LightDom(lightDomView, shadowDomView, el("<div></div>"));
|
||||
|
||||
expect(lightDom.contentTags()).toEqual([tag]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("expanded roots", () => {
|
||||
it("should contain root nodes", () => {
|
||||
var lightDomEl = el("<div><a></a></div>")
|
||||
var lightDom = new LightDom(lightDomView, new FakeView(), lightDomEl);
|
||||
expect(toHtml(lightDom.expandedDomNodes())).toEqual(["<a></a>"]);
|
||||
});
|
||||
|
||||
it("should include view port nodes", () => {
|
||||
var lightDomEl = el("<div><template></template></div>")
|
||||
|
||||
var lightDomView = new FakeView([
|
||||
new FakeElementInjector(
|
||||
null,
|
||||
new FakeViewPort([el("<a></a>")]),
|
||||
DOM.firstChild(lightDomEl))]);
|
||||
|
||||
var lightDom = new LightDom(
|
||||
lightDomView,
|
||||
new FakeView(),
|
||||
lightDomEl);
|
||||
|
||||
expect(toHtml(lightDom.expandedDomNodes())).toEqual(["<a></a>"]);
|
||||
});
|
||||
|
||||
it("should include content nodes", () => {
|
||||
var lightDomEl = el("<div><content></content></div>")
|
||||
|
||||
var lightDomView = new FakeView([
|
||||
new FakeElementInjector(
|
||||
new FakeContentTag(null, [el("<a></a>")]),
|
||||
null,
|
||||
DOM.firstChild(lightDomEl))]);
|
||||
|
||||
var lightDom = new LightDom(
|
||||
lightDomView,
|
||||
new FakeView(),
|
||||
lightDomEl);
|
||||
|
||||
expect(toHtml(lightDom.expandedDomNodes())).toEqual(["<a></a>"]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("redistribute", () => {
|
||||
it("should redistribute nodes between content tags with select property set", () => {
|
||||
var contentA = new FakeContentTag("a");
|
||||
var contentB = new FakeContentTag("b");
|
||||
|
||||
var lightDomEl = el("<div><a>1</a><b>2</b><a>3</a></div>")
|
||||
|
||||
var lightDom = new LightDom(lightDomView, new FakeView([
|
||||
new FakeElementInjector(contentA, null),
|
||||
new FakeElementInjector(contentB, null)
|
||||
]), lightDomEl);
|
||||
|
||||
lightDom.redistribute();
|
||||
|
||||
expect(toHtml(contentA.nodes())).toEqual(["<a>1</a>", "<a>3</a>"]);
|
||||
expect(toHtml(contentB.nodes())).toEqual(["<b>2</b>"]);
|
||||
});
|
||||
|
||||
it("should support wildcard content tags", () => {
|
||||
var wildcard = new FakeContentTag(null);
|
||||
var contentB = new FakeContentTag("b");
|
||||
|
||||
var lightDomEl = el("<div><a>1</a><b>2</b><a>3</a></div>")
|
||||
|
||||
var lightDom = new LightDom(lightDomView, new FakeView([
|
||||
new FakeElementInjector(wildcard, null),
|
||||
new FakeElementInjector(contentB, null)
|
||||
]), lightDomEl);
|
||||
|
||||
lightDom.redistribute();
|
||||
|
||||
expect(toHtml(wildcard.nodes())).toEqual(["<a>1</a>", "<b>2</b>", "<a>3</a>"]);
|
||||
expect(toHtml(contentB.nodes())).toEqual([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function toHtml(nodes) {
|
||||
if (isBlank(nodes)) return [];
|
||||
return ListWrapper.map(nodes, DOM.getOuterHTML);
|
||||
}
|
312
modules/angular2/test/core/compiler/shadow_dom/shadow_dom_emulation_integration_spec.js
vendored
Normal file
312
modules/angular2/test/core/compiler/shadow_dom/shadow_dom_emulation_integration_spec.js
vendored
Normal file
@ -0,0 +1,312 @@
|
||||
import {describe, xit, it, expect, beforeEach, ddescribe, iit, el} from 'test_lib/test_lib';
|
||||
|
||||
import {DOM} from 'facade/src/dom';
|
||||
|
||||
import {Injector} from 'di/di';
|
||||
import {Lexer, Parser, ChangeDetector, dynamicChangeDetection} from 'change_detection/change_detection';
|
||||
|
||||
import {Compiler, CompilerCache} from 'core/src/compiler/compiler';
|
||||
import {LifeCycle} from 'core/src/life_cycle/life_cycle';
|
||||
import {DirectiveMetadataReader} from 'core/src/compiler/directive_metadata_reader';
|
||||
import {ShadowDomStrategy, ShadowDomNative, ShadowDomEmulated} from 'core/src/compiler/shadow_dom';
|
||||
|
||||
import {Decorator, Component, Template} from 'core/src/annotations/annotations';
|
||||
import {TemplateConfig} from 'core/src/annotations/template_config';
|
||||
|
||||
import {ViewPort} from 'core/src/compiler/viewport';
|
||||
import {StringMapWrapper, MapWrapper} from 'facade/src/collection';
|
||||
|
||||
export function main() {
|
||||
describe('integration tests', function() {
|
||||
|
||||
StringMapWrapper.forEach(
|
||||
{"native" : ShadowDomNative, "emulated" : ShadowDomEmulated}, (strategy, name) => {
|
||||
|
||||
describe(`${name} shadow dom strategy`, () => {
|
||||
var compiler;
|
||||
|
||||
beforeEach( () => {
|
||||
compiler = new Compiler(dynamicChangeDetection, null,
|
||||
new TestDirectiveMetadataReader(strategy),
|
||||
new Parser(new Lexer()), new CompilerCache());
|
||||
});
|
||||
|
||||
function compile(template, assertions) {
|
||||
compiler.compile(MyComp, el(template)).
|
||||
then(createView).
|
||||
then((view) => {
|
||||
var lc = new LifeCycle(view.changeDetector, false);
|
||||
assertions(view, lc);
|
||||
});
|
||||
}
|
||||
|
||||
it('should support multiple content tags', (done) => {
|
||||
var temp = '<multiple-content-tags>' +
|
||||
'<div>B</div>' +
|
||||
'<div>C</div>' +
|
||||
'<div class="left">A</div>' +
|
||||
'</multiple-content-tags>';
|
||||
|
||||
compile(temp, (view, lc) => {
|
||||
expect(view.nodes).toHaveText('(A, BC)');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should redistribute only direct children', (done) => {
|
||||
var temp = '<multiple-content-tags>' +
|
||||
'<div>B<div class="left">A</div></div>' +
|
||||
'<div>C</div>' +
|
||||
'</multiple-content-tags>';
|
||||
|
||||
compile(temp, (view, lc) => {
|
||||
expect(view.nodes).toHaveText('(, BAC)');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("should redistribute when the light dom changes", (done) => {
|
||||
var temp = '<multiple-content-tags>' +
|
||||
'<div template="manual" class="left">A</div>' +
|
||||
'<div>B</div>' +
|
||||
'</multiple-content-tags>';
|
||||
|
||||
compile(temp, (view, lc) => {
|
||||
var dir = view.elementInjectors[1].get(ManualTemplateDirective);
|
||||
|
||||
expect(view.nodes).toHaveText('(, B)');
|
||||
|
||||
dir.show();
|
||||
lc.tick();
|
||||
|
||||
expect(view.nodes).toHaveText('(A, B)');
|
||||
|
||||
dir.hide();
|
||||
lc.tick();
|
||||
|
||||
expect(view.nodes).toHaveText('(, B)');
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("should support nested components", (done) => {
|
||||
var temp = '<outer-with-indirect-nested>' +
|
||||
'<div>A</div>' +
|
||||
'<div>B</div>' +
|
||||
'</outer-with-indirect-nested>';
|
||||
|
||||
compile(temp, (view, lc) => {
|
||||
expect(view.nodes).toHaveText('OUTER(SIMPLE(AB))');
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("should support nesting with content being direct child of a nested component", (done) => {
|
||||
var temp = '<outer>' +
|
||||
'<div template="manual" class="left">A</div>' +
|
||||
'<div>B</div>' +
|
||||
'<div>C</div>' +
|
||||
'</outer>';
|
||||
|
||||
compile(temp, (view, lc) => {
|
||||
var dir = view.elementInjectors[1].get(ManualTemplateDirective);
|
||||
|
||||
expect(view.nodes).toHaveText('OUTER(INNER(INNERINNER(,BC)))');
|
||||
|
||||
dir.show();
|
||||
lc.tick();
|
||||
|
||||
expect(view.nodes).toHaveText('OUTER(INNER(INNERINNER(A,BC)))');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
// Enable once dom-write queue is implemented and onDehydrate is implemented
|
||||
//it('should redistribute when the shadow dom changes', (done) => {
|
||||
// var temp = '<conditional-content>' +
|
||||
// '<div class="left">A</div>' +
|
||||
// '<div>B</div>' +
|
||||
// '<div>C</div>' +
|
||||
// '</conditional-content>';
|
||||
//
|
||||
//
|
||||
// compile(temp, (view, lc) => {
|
||||
// var cmp = view.elementInjectors[0].get(ConditionalContentComponent);
|
||||
//
|
||||
// expect(view.nodes).toHaveText('(, ABC)');
|
||||
//
|
||||
// cmp.showLeft();
|
||||
// lc.tick();
|
||||
//
|
||||
// expect(view.nodes).toHaveText('(A, BC)');
|
||||
//
|
||||
// cmp.hideLeft()
|
||||
// lc.tick();
|
||||
//
|
||||
// expect(view.nodes).toHaveText('(, ABC)');
|
||||
//
|
||||
// done();
|
||||
// });
|
||||
//});
|
||||
|
||||
//Implement once NgElement support changing a class
|
||||
//it("should redistribute when a class has been added or removed");
|
||||
//it('should not lose focus', () => {
|
||||
// var temp = `<simple>aaa<input type="text" id="focused-input" ng-class="{'aClass' : showClass}"> bbb</simple>`;
|
||||
//
|
||||
// compile(temp, (view, lc) => {
|
||||
// var input = view.nodes[1];
|
||||
// input.focus();
|
||||
//
|
||||
// expect(document.activeElement.id).toEqual("focused-input");
|
||||
//
|
||||
// // update class of input
|
||||
//
|
||||
// expect(document.activeElement.id).toEqual("focused-input");
|
||||
// });
|
||||
//});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
class TestDirectiveMetadataReader extends DirectiveMetadataReader {
|
||||
shadowDomStrategy;
|
||||
|
||||
constructor(shadowDomStrategy) {
|
||||
this.shadowDomStrategy = shadowDomStrategy;
|
||||
}
|
||||
|
||||
parseShadowDomStrategy(annotation:Component):ShadowDomStrategy{
|
||||
return this.shadowDomStrategy;
|
||||
}
|
||||
}
|
||||
|
||||
@Template({
|
||||
selector: '[manual]'
|
||||
})
|
||||
class ManualTemplateDirective {
|
||||
viewPort;
|
||||
constructor(viewPort:ViewPort) {
|
||||
this.viewPort = viewPort;
|
||||
}
|
||||
|
||||
show() { this.viewPort.create(); }
|
||||
hide() { this.viewPort.remove(0); }
|
||||
}
|
||||
|
||||
@Template({
|
||||
selector: '[auto]',
|
||||
bind: {
|
||||
'auto': 'auto'
|
||||
}
|
||||
})
|
||||
class AutoTemplateDirective {
|
||||
viewPort;
|
||||
constructor(viewPort:ViewPort) {
|
||||
this.viewPort = viewPort;
|
||||
}
|
||||
|
||||
set auto(newValue:boolean) {
|
||||
if (newValue) {
|
||||
this.viewPort.create();
|
||||
} else {
|
||||
this.viewPort.remove(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'simple',
|
||||
template: new TemplateConfig({
|
||||
inline: 'SIMPLE(<content></content>)'
|
||||
})
|
||||
})
|
||||
class Simple {
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'multiple-content-tags',
|
||||
template: new TemplateConfig({
|
||||
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: [AutoTemplateDirective]
|
||||
})
|
||||
})
|
||||
class ConditionalContentComponent {
|
||||
cond:boolean;
|
||||
|
||||
constructor() {
|
||||
this.cond = false;
|
||||
}
|
||||
|
||||
showLeft() { this.cond = true; }
|
||||
hideLeft() { this.cond = false; }
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'outer-with-indirect-nested',
|
||||
template: new TemplateConfig({
|
||||
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]
|
||||
})
|
||||
})
|
||||
class OuterComponent {
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'inner',
|
||||
template: new TemplateConfig({
|
||||
inline: 'INNER(<innerinner><content></content></innerinner>)',
|
||||
directives: [InnerInnerComponent]
|
||||
})
|
||||
})
|
||||
class InnerComponent {
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'innerinner',
|
||||
template: new TemplateConfig({
|
||||
inline: 'INNERINNER(<content select=".left"></content>,<content></content>)'
|
||||
})
|
||||
})
|
||||
class InnerInnerComponent {
|
||||
}
|
||||
|
||||
|
||||
@Component({
|
||||
template: new TemplateConfig({
|
||||
directives: [MultipleContentTagsComponent, ManualTemplateDirective,
|
||||
ConditionalContentComponent, OuterWithIndirectNestedComponent, OuterComponent]
|
||||
})
|
||||
})
|
||||
class MyComp {
|
||||
}
|
||||
|
||||
function createView(pv) {
|
||||
var view = pv.instantiate(null);
|
||||
view.hydrate(new Injector([]), null, {});
|
||||
return view;
|
||||
}
|
696
modules/angular2/test/core/compiler/view_spec.js
vendored
Normal file
696
modules/angular2/test/core/compiler/view_spec.js
vendored
Normal file
@ -0,0 +1,696 @@
|
||||
import {describe, xit, it, expect, beforeEach, ddescribe, iit, el} from 'test_lib/test_lib';
|
||||
import {ProtoView, ElementPropertyMemento, DirectivePropertyMemento} from 'core/src/compiler/view';
|
||||
import {ProtoElementInjector, ElementInjector} from 'core/src/compiler/element_injector';
|
||||
import {ShadowDomEmulated} from 'core/src/compiler/shadow_dom';
|
||||
import {DirectiveMetadataReader} from 'core/src/compiler/directive_metadata_reader';
|
||||
import {Component, Decorator, Template} from 'core/src/annotations/annotations';
|
||||
import {OnChange} from 'core/core';
|
||||
import {Lexer, Parser, DynamicProtoChangeDetector,
|
||||
ChangeDetector} from 'change_detection/change_detection';
|
||||
import {TemplateConfig} from 'core/src/annotations/template_config';
|
||||
import {EventEmitter} from 'core/src/annotations/events';
|
||||
import {List, MapWrapper} from 'facade/src/collection';
|
||||
import {DOM, Element} from 'facade/src/dom';
|
||||
import {int, proxy, IMPLEMENTS} from 'facade/src/lang';
|
||||
import {Injector} from 'di/di';
|
||||
import {View} from 'core/src/compiler/view';
|
||||
import {ViewPort} from 'core/src/compiler/viewport';
|
||||
import {reflector} from 'reflection/src/reflection';
|
||||
|
||||
|
||||
@proxy
|
||||
@IMPLEMENTS(ViewPort)
|
||||
class FakeViewPort {
|
||||
templateElement;
|
||||
|
||||
constructor(templateElement) {
|
||||
this.templateElement = templateElement;
|
||||
}
|
||||
|
||||
noSuchMethod(i) {
|
||||
super.noSuchMethod(i);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export function main() {
|
||||
describe('view', function() {
|
||||
var parser, someComponentDirective, someTemplateDirective;
|
||||
|
||||
function createView(protoView) {
|
||||
var ctx = new MyEvaluationContext();
|
||||
var view = protoView.instantiate(null);
|
||||
view.hydrate(null, null, ctx);
|
||||
return view;
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
parser = new Parser(new Lexer());
|
||||
someComponentDirective = new DirectiveMetadataReader().read(SomeComponent);
|
||||
someTemplateDirective = new DirectiveMetadataReader().read(SomeTemplate);
|
||||
});
|
||||
|
||||
describe('instantiated from protoView', () => {
|
||||
var view;
|
||||
beforeEach(() => {
|
||||
var pv = new ProtoView(el('<div id="1"></div>'), new DynamicProtoChangeDetector());
|
||||
view = pv.instantiate(null);
|
||||
});
|
||||
|
||||
it('should be dehydrated by default', () => {
|
||||
expect(view.hydrated()).toBe(false);
|
||||
});
|
||||
|
||||
it('should be able to be hydrated and dehydrated', () => {
|
||||
var ctx = new Object();
|
||||
view.hydrate(null, null, ctx);
|
||||
expect(view.hydrated()).toBe(true);
|
||||
|
||||
view.dehydrate();
|
||||
expect(view.hydrated()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with locals', function() {
|
||||
var view;
|
||||
beforeEach(() => {
|
||||
var pv = new ProtoView(el('<div id="1"></div>'), new DynamicProtoChangeDetector());
|
||||
pv.bindVariable('context-foo', 'template-foo');
|
||||
view = createView(pv);
|
||||
});
|
||||
|
||||
it('should support setting of declared locals', () => {
|
||||
view.setLocal('context-foo', 'bar');
|
||||
expect(view.context.get('template-foo')).toBe('bar');
|
||||
});
|
||||
|
||||
it('should not throw on undeclared locals', () => {
|
||||
expect(() => view.setLocal('setMePlease', 'bar')).not.toThrow();
|
||||
});
|
||||
|
||||
it('when dehydrated should set locals to null', () => {
|
||||
view.setLocal('context-foo', 'bar');
|
||||
view.dehydrate();
|
||||
view.hydrate(null, null, new Object());
|
||||
expect(view.context.get('template-foo')).toBe(null);
|
||||
});
|
||||
|
||||
it('should throw when trying to set on dehydrated view', () => {
|
||||
view.dehydrate();
|
||||
expect(() => view.setLocal('context-foo', 'bar')).toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
describe('instatiated and hydrated', function() {
|
||||
|
||||
function createCollectDomNodesTestCases(useTemplateElement:boolean) {
|
||||
|
||||
function templateAwareCreateElement(html) {
|
||||
return el(useTemplateElement ? `<template>${html}</template>` : html);
|
||||
}
|
||||
|
||||
it('should collect the root node in the ProtoView element', () => {
|
||||
var pv = new ProtoView(templateAwareCreateElement('<div id="1"></div>'), new DynamicProtoChangeDetector());
|
||||
var view = pv.instantiate(null);
|
||||
view.hydrate(null, null, null);
|
||||
expect(view.nodes.length).toBe(1);
|
||||
expect(view.nodes[0].getAttribute('id')).toEqual('1');
|
||||
});
|
||||
|
||||
describe('collect elements with property bindings', () => {
|
||||
|
||||
it('should collect property bindings on the root element if it has the ng-binding class', () => {
|
||||
var pv = new ProtoView(templateAwareCreateElement('<div [prop]="a" class="ng-binding"></div>'), new DynamicProtoChangeDetector());
|
||||
pv.bindElement(null);
|
||||
pv.bindElementProperty(parser.parseBinding('a', null), 'prop', reflector.setter('prop'));
|
||||
|
||||
var view = pv.instantiate(null);
|
||||
view.hydrate(null, null, null);
|
||||
expect(view.bindElements.length).toEqual(1);
|
||||
expect(view.bindElements[0]).toBe(view.nodes[0]);
|
||||
});
|
||||
|
||||
it('should collect property bindings on child elements with ng-binding class', () => {
|
||||
var pv = new ProtoView(templateAwareCreateElement('<div><span></span><span class="ng-binding"></span></div>'),
|
||||
new DynamicProtoChangeDetector());
|
||||
pv.bindElement(null);
|
||||
pv.bindElementProperty(parser.parseBinding('b', null), 'a', reflector.setter('a'));
|
||||
|
||||
var view = pv.instantiate(null);
|
||||
view.hydrate(null, null, null);
|
||||
expect(view.bindElements.length).toEqual(1);
|
||||
expect(view.bindElements[0]).toBe(view.nodes[0].childNodes[1]);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('collect text nodes with bindings', () => {
|
||||
|
||||
it('should collect text nodes under the root element', () => {
|
||||
var pv = new ProtoView(templateAwareCreateElement('<div class="ng-binding">{{}}<span></span>{{}}</div>'), new DynamicProtoChangeDetector());
|
||||
pv.bindElement(null);
|
||||
pv.bindTextNode(0, parser.parseBinding('a', null));
|
||||
pv.bindTextNode(2, parser.parseBinding('b', null));
|
||||
|
||||
var view = pv.instantiate(null);
|
||||
view.hydrate(null, null, null);
|
||||
expect(view.textNodes.length).toEqual(2);
|
||||
expect(view.textNodes[0]).toBe(view.nodes[0].childNodes[0]);
|
||||
expect(view.textNodes[1]).toBe(view.nodes[0].childNodes[2]);
|
||||
});
|
||||
|
||||
it('should collect text nodes with bindings on child elements with ng-binding class', () => {
|
||||
var pv = new ProtoView(templateAwareCreateElement('<div><span> </span><span class="ng-binding">{{}}</span></div>'),
|
||||
new DynamicProtoChangeDetector());
|
||||
pv.bindElement(null);
|
||||
pv.bindTextNode(0, parser.parseBinding('b', null));
|
||||
|
||||
var view = pv.instantiate(null);
|
||||
view.hydrate(null, null, null);
|
||||
expect(view.textNodes.length).toEqual(1);
|
||||
expect(view.textNodes[0]).toBe(view.nodes[0].childNodes[1].childNodes[0]);
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
describe('inplace instantiation', () => {
|
||||
it('should be supported.', () => {
|
||||
var template = el('<div></div>');
|
||||
var pv = new ProtoView(template, new DynamicProtoChangeDetector());
|
||||
pv.instantiateInPlace = true;
|
||||
var view = pv.instantiate(null);
|
||||
view.hydrate(null, null, null);
|
||||
expect(view.nodes[0]).toBe(template);
|
||||
});
|
||||
|
||||
it('should be off by default.', () => {
|
||||
var template = el('<div></div>')
|
||||
var view = new ProtoView(template, new DynamicProtoChangeDetector())
|
||||
.instantiate(null);
|
||||
view.hydrate(null, null, null);
|
||||
expect(view.nodes[0]).not.toBe(template);
|
||||
});
|
||||
});
|
||||
|
||||
describe('collect dom nodes with a regular element as root', () => {
|
||||
createCollectDomNodesTestCases(false);
|
||||
});
|
||||
|
||||
describe('collect dom nodes with a template element as root', () => {
|
||||
createCollectDomNodesTestCases(true);
|
||||
});
|
||||
|
||||
describe('create ElementInjectors', () => {
|
||||
it('should use the directives of the ProtoElementInjector', () => {
|
||||
var pv = new ProtoView(el('<div class="ng-binding"></div>'), new DynamicProtoChangeDetector());
|
||||
pv.bindElement(new ProtoElementInjector(null, 1, [SomeDirective]));
|
||||
|
||||
var view = pv.instantiate(null);
|
||||
view.hydrate(null, null, null);
|
||||
expect(view.elementInjectors.length).toBe(1);
|
||||
expect(view.elementInjectors[0].get(SomeDirective) instanceof SomeDirective).toBe(true);
|
||||
});
|
||||
|
||||
it('should use the correct parent', () => {
|
||||
var pv = new ProtoView(el('<div class="ng-binding"><span class="ng-binding"></span></div>'),
|
||||
new DynamicProtoChangeDetector());
|
||||
var protoParent = new ProtoElementInjector(null, 0, [SomeDirective]);
|
||||
pv.bindElement(protoParent);
|
||||
pv.bindElement(new ProtoElementInjector(protoParent, 1, [AnotherDirective]));
|
||||
|
||||
var view = pv.instantiate(null);
|
||||
view.hydrate(null, null, null);
|
||||
expect(view.elementInjectors.length).toBe(2);
|
||||
expect(view.elementInjectors[0].get(SomeDirective) instanceof SomeDirective).toBe(true);
|
||||
expect(view.elementInjectors[1].parent).toBe(view.elementInjectors[0]);
|
||||
});
|
||||
|
||||
it('should not pass the host injector when a parent injector exists', () => {
|
||||
var pv = new ProtoView(el('<div class="ng-binding"><span class="ng-binding"></span></div>'),
|
||||
new DynamicProtoChangeDetector());
|
||||
var protoParent = new ProtoElementInjector(null, 0, [SomeDirective]);
|
||||
pv.bindElement(protoParent);
|
||||
var testProtoElementInjector = new TestProtoElementInjector(protoParent, 1, [AnotherDirective]);
|
||||
pv.bindElement(testProtoElementInjector);
|
||||
|
||||
var hostProtoInjector = new ProtoElementInjector(null, 0, []);
|
||||
var hostInjector = hostProtoInjector.instantiate(null, null, null);
|
||||
var view;
|
||||
expect(() => view = pv.instantiate(hostInjector)).not.toThrow();
|
||||
expect(testProtoElementInjector.parentElementInjector).toBe(view.elementInjectors[0]);
|
||||
expect(testProtoElementInjector.hostElementInjector).toBeNull();
|
||||
});
|
||||
|
||||
it('should pass the host injector when there is no parent injector', () => {
|
||||
var pv = new ProtoView(el('<div class="ng-binding"><span class="ng-binding"></span></div>'),
|
||||
new DynamicProtoChangeDetector());
|
||||
pv.bindElement(new ProtoElementInjector(null, 0, [SomeDirective]));
|
||||
var testProtoElementInjector = new TestProtoElementInjector(null, 1, [AnotherDirective]);
|
||||
pv.bindElement(testProtoElementInjector);
|
||||
|
||||
var hostProtoInjector = new ProtoElementInjector(null, 0, []);
|
||||
var hostInjector = hostProtoInjector.instantiate(null, null, null);
|
||||
expect(() => pv.instantiate(hostInjector)).not.toThrow();
|
||||
expect(testProtoElementInjector.parentElementInjector).toBeNull();
|
||||
expect(testProtoElementInjector.hostElementInjector).toBe(hostInjector);
|
||||
});
|
||||
});
|
||||
|
||||
describe('collect root element injectors', () => {
|
||||
|
||||
it('should collect a single root element injector', () => {
|
||||
var pv = new ProtoView(el('<div class="ng-binding"><span class="ng-binding"></span></div>'),
|
||||
new DynamicProtoChangeDetector());
|
||||
var protoParent = new ProtoElementInjector(null, 0, [SomeDirective]);
|
||||
pv.bindElement(protoParent);
|
||||
pv.bindElement(new ProtoElementInjector(protoParent, 1, [AnotherDirective]));
|
||||
|
||||
var view = pv.instantiate(null);
|
||||
view.hydrate(null, null, null);
|
||||
expect(view.rootElementInjectors.length).toBe(1);
|
||||
expect(view.rootElementInjectors[0].get(SomeDirective) instanceof SomeDirective).toBe(true);
|
||||
});
|
||||
|
||||
it('should collect multiple root element injectors', () => {
|
||||
var pv = new ProtoView(el('<div><span class="ng-binding"></span><span class="ng-binding"></span></div>'),
|
||||
new DynamicProtoChangeDetector());
|
||||
pv.bindElement(new ProtoElementInjector(null, 1, [SomeDirective]));
|
||||
pv.bindElement(new ProtoElementInjector(null, 2, [AnotherDirective]));
|
||||
|
||||
var view = pv.instantiate(null);
|
||||
view.hydrate(null, null, null);
|
||||
expect(view.rootElementInjectors.length).toBe(2)
|
||||
expect(view.rootElementInjectors[0].get(SomeDirective) instanceof SomeDirective).toBe(true);
|
||||
expect(view.rootElementInjectors[1].get(AnotherDirective) instanceof AnotherDirective).toBe(true);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('with component views', () => {
|
||||
var ctx;
|
||||
|
||||
function createComponentWithSubPV(subProtoView) {
|
||||
var pv = new ProtoView(el('<cmp class="ng-binding"></cmp>'), new DynamicProtoChangeDetector());
|
||||
var binder = pv.bindElement(new ProtoElementInjector(null, 0, [SomeComponent], true));
|
||||
binder.componentDirective = someComponentDirective;
|
||||
binder.nestedProtoView = subProtoView;
|
||||
return pv;
|
||||
}
|
||||
|
||||
function createNestedView(protoView) {
|
||||
ctx = new MyEvaluationContext();
|
||||
var view = protoView.instantiate(null);
|
||||
view.hydrate(new Injector([]), null, ctx);
|
||||
return view;
|
||||
}
|
||||
|
||||
it('should expose component services to the component', () => {
|
||||
var subpv = new ProtoView(el('<span></span>'), new DynamicProtoChangeDetector());
|
||||
var pv = createComponentWithSubPV(subpv);
|
||||
|
||||
var view = createNestedView(pv);
|
||||
|
||||
var comp = view.rootElementInjectors[0].get(SomeComponent);
|
||||
expect(comp.service).toBeAnInstanceOf(SomeService);
|
||||
});
|
||||
|
||||
it('should expose component services and component instance to directives in the shadow Dom',
|
||||
() => {
|
||||
var subpv = new ProtoView(
|
||||
el('<div dec class="ng-binding">hello shadow dom</div>'), new DynamicProtoChangeDetector());
|
||||
subpv.bindElement(
|
||||
new ProtoElementInjector(null, 0, [ServiceDependentDecorator]));
|
||||
var pv = createComponentWithSubPV(subpv);
|
||||
|
||||
var view = createNestedView(pv);
|
||||
|
||||
var subView = view.componentChildViews[0];
|
||||
var subInj = subView.rootElementInjectors[0];
|
||||
var subDecorator = subInj.get(ServiceDependentDecorator);
|
||||
var comp = view.rootElementInjectors[0].get(SomeComponent);
|
||||
|
||||
expect(subDecorator).toBeAnInstanceOf(ServiceDependentDecorator);
|
||||
expect(subDecorator.service).toBe(comp.service);
|
||||
expect(subDecorator.component).toBe(comp);
|
||||
});
|
||||
|
||||
function expectViewHasNoDirectiveInstances(view) {
|
||||
view.elementInjectors.forEach((inj) => expect(inj.hasInstances()).toBe(false));
|
||||
}
|
||||
|
||||
it('dehydration should dehydrate child component views too', () => {
|
||||
var subpv = new ProtoView(
|
||||
el('<div dec class="ng-binding">hello shadow dom</div>'), new DynamicProtoChangeDetector());
|
||||
subpv.bindElement(
|
||||
new ProtoElementInjector(null, 0, [ServiceDependentDecorator]));
|
||||
var pv = createComponentWithSubPV(subpv);
|
||||
|
||||
var view = createNestedView(pv);
|
||||
view.dehydrate();
|
||||
|
||||
expect(view.hydrated()).toBe(false);
|
||||
expectViewHasNoDirectiveInstances(view);
|
||||
view.componentChildViews.forEach(
|
||||
(view) => expectViewHasNoDirectiveInstances(view));
|
||||
});
|
||||
|
||||
it('should create shadow dom', () => {
|
||||
var subpv = new ProtoView(el('<span>hello shadow dom</span>'), new DynamicProtoChangeDetector());
|
||||
var pv = createComponentWithSubPV(subpv);
|
||||
|
||||
var view = createNestedView(pv);
|
||||
|
||||
expect(view.nodes[0].shadowRoot.childNodes[0].childNodes[0].nodeValue).toEqual('hello shadow dom');
|
||||
});
|
||||
|
||||
it('should use the provided shadow DOM strategy', () => {
|
||||
var subpv = new ProtoView(el('<span>hello shadow dom</span>'), new DynamicProtoChangeDetector());
|
||||
|
||||
var pv = new ProtoView(el('<cmp class="ng-binding"></cmp>'), new DynamicProtoChangeDetector());
|
||||
var binder = pv.bindElement(new ProtoElementInjector(null, 0, [SomeComponentWithEmulatedShadowDom], true));
|
||||
binder.componentDirective = new DirectiveMetadataReader().read(SomeComponentWithEmulatedShadowDom);
|
||||
binder.nestedProtoView = subpv;
|
||||
|
||||
var view = createNestedView(pv);
|
||||
expect(view.nodes[0].childNodes[0].childNodes[0].nodeValue).toEqual('hello shadow dom');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('with template views', () => {
|
||||
function createViewWithTemplate() {
|
||||
var templateProtoView = new ProtoView(
|
||||
el('<div id="1"></div>'), new DynamicProtoChangeDetector());
|
||||
var pv = new ProtoView(el('<someTmpl class="ng-binding"></someTmpl>'), new DynamicProtoChangeDetector());
|
||||
var binder = pv.bindElement(new ProtoElementInjector(null, 0, [SomeTemplate]));
|
||||
binder.templateDirective = someTemplateDirective;
|
||||
binder.nestedProtoView = templateProtoView;
|
||||
|
||||
return createView(pv);
|
||||
}
|
||||
|
||||
it('should create a viewPort for the template directive', () => {
|
||||
var view = createViewWithTemplate();
|
||||
|
||||
var tmplComp = view.rootElementInjectors[0].get(SomeTemplate);
|
||||
expect(tmplComp.viewPort).not.toBe(null);
|
||||
});
|
||||
|
||||
it('dehydration should dehydrate viewports', () => {
|
||||
var view = createViewWithTemplate();
|
||||
|
||||
var tmplComp = view.rootElementInjectors[0].get(SomeTemplate);
|
||||
expect(tmplComp.viewPort.hydrated()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('event handlers', () => {
|
||||
var view, ctx, called, receivedEvent, dispatchedEvent;
|
||||
|
||||
function createViewAndContext(protoView) {
|
||||
view = createView(protoView);
|
||||
ctx = view.context;
|
||||
called = 0;
|
||||
receivedEvent = null;
|
||||
ctx.callMe = (event) => {
|
||||
called += 1;
|
||||
receivedEvent = event;
|
||||
}
|
||||
}
|
||||
|
||||
function dispatchClick(el) {
|
||||
dispatchedEvent = DOM.createMouseEvent('click');
|
||||
DOM.dispatchEvent(el, dispatchedEvent);
|
||||
}
|
||||
|
||||
function createProtoView() {
|
||||
var pv = new ProtoView(el('<div class="ng-binding"><div></div></div>'),
|
||||
new DynamicProtoChangeDetector());
|
||||
pv.bindElement(new TestProtoElementInjector(null, 0, []));
|
||||
pv.bindEvent('click', parser.parseBinding('callMe(\$event)', null));
|
||||
return pv;
|
||||
}
|
||||
|
||||
it('should fire on non-bubbling native events', () => {
|
||||
createViewAndContext(createProtoView());
|
||||
|
||||
dispatchClick(view.nodes[0]);
|
||||
|
||||
expect(called).toEqual(1);
|
||||
expect(receivedEvent).toBe(dispatchedEvent);
|
||||
});
|
||||
|
||||
it('should not fire on a bubbled native events', () => {
|
||||
createViewAndContext(createProtoView());
|
||||
|
||||
dispatchClick(view.nodes[0].firstChild);
|
||||
|
||||
// This test passes trivially on webkit browsers due to
|
||||
// https://bugs.webkit.org/show_bug.cgi?id=122755
|
||||
expect(called).toEqual(0);
|
||||
});
|
||||
|
||||
it('should not throw if the view is dehydrated', () => {
|
||||
createViewAndContext(createProtoView());
|
||||
|
||||
view.dehydrate();
|
||||
expect(() => dispatchClick(view.nodes[0])).not.toThrow();
|
||||
expect(called).toEqual(0);
|
||||
});
|
||||
|
||||
it('should support custom event emitters', () => {
|
||||
var pv = new ProtoView(el('<div class="ng-binding"><div></div></div>'),
|
||||
new DynamicProtoChangeDetector());
|
||||
pv.bindElement(new TestProtoElementInjector(null, 0, [EventEmitterDirective]));
|
||||
pv.bindEvent('click', parser.parseBinding('callMe(\$event)', null));
|
||||
|
||||
createViewAndContext(pv);
|
||||
var dir = view.elementInjectors[0].get(EventEmitterDirective);
|
||||
|
||||
var dispatchedEvent = new Object();
|
||||
|
||||
dir.click(dispatchedEvent);
|
||||
expect(receivedEvent).toBe(dispatchedEvent);
|
||||
expect(called).toEqual(1);
|
||||
|
||||
// Should not eval the binding, because custom emitter takes over.
|
||||
dispatchClick(view.nodes[0]);
|
||||
|
||||
expect(called).toEqual(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('react to record changes', () => {
|
||||
var view, cd, ctx;
|
||||
|
||||
function createViewAndChangeDetector(protoView) {
|
||||
view = createView(protoView);
|
||||
ctx = view.context;
|
||||
cd = view.changeDetector;
|
||||
}
|
||||
|
||||
it('should consume text node changes', () => {
|
||||
var pv = new ProtoView(el('<div class="ng-binding">{{}}</div>'),
|
||||
new DynamicProtoChangeDetector());
|
||||
pv.bindElement(null);
|
||||
pv.bindTextNode(0, parser.parseBinding('foo', null));
|
||||
createViewAndChangeDetector(pv);
|
||||
|
||||
ctx.foo = 'buz';
|
||||
cd.detectChanges();
|
||||
expect(view.textNodes[0].nodeValue).toEqual('buz');
|
||||
});
|
||||
|
||||
it('should consume element binding changes', () => {
|
||||
var pv = new ProtoView(el('<div class="ng-binding"></div>'),
|
||||
new DynamicProtoChangeDetector());
|
||||
pv.bindElement(null);
|
||||
pv.bindElementProperty(parser.parseBinding('foo', null), 'id', reflector.setter('id'));
|
||||
createViewAndChangeDetector(pv);
|
||||
|
||||
ctx.foo = 'buz';
|
||||
cd.detectChanges();
|
||||
expect(view.bindElements[0].id).toEqual('buz');
|
||||
});
|
||||
|
||||
it('should consume directive watch expression change', () => {
|
||||
var pv = new ProtoView(el('<div class="ng-binding"></div>'),
|
||||
new DynamicProtoChangeDetector());
|
||||
pv.bindElement(new ProtoElementInjector(null, 0, [SomeDirective]));
|
||||
pv.bindDirectiveProperty(0, parser.parseBinding('foo', null), 'prop', reflector.setter('prop'), false);
|
||||
createViewAndChangeDetector(pv);
|
||||
|
||||
ctx.foo = 'buz';
|
||||
cd.detectChanges();
|
||||
expect(view.elementInjectors[0].get(SomeDirective).prop).toEqual('buz');
|
||||
});
|
||||
|
||||
it('should notify a directive about changes after all its properties have been set', () => {
|
||||
var pv = new ProtoView(el('<div class="ng-binding"></div>'),
|
||||
new DynamicProtoChangeDetector());
|
||||
|
||||
pv.bindElement(new ProtoElementInjector(null, 0, [DirectiveImplementingOnChange]));
|
||||
pv.bindDirectiveProperty( 0, parser.parseBinding('a', null), 'a', reflector.setter('a'), false);
|
||||
pv.bindDirectiveProperty( 0, parser.parseBinding('b', null), 'b', reflector.setter('b'), false);
|
||||
createViewAndChangeDetector(pv);
|
||||
|
||||
ctx.a = 100;
|
||||
ctx.b = 200;
|
||||
cd.detectChanges();
|
||||
|
||||
var directive = view.elementInjectors[0].get(DirectiveImplementingOnChange);
|
||||
expect(directive.c).toEqual(300);
|
||||
});
|
||||
|
||||
it('should provide a map of updated properties', () => {
|
||||
var pv = new ProtoView(el('<div class="ng-binding"></div>'),
|
||||
new DynamicProtoChangeDetector());
|
||||
|
||||
pv.bindElement(new ProtoElementInjector(null, 0, [DirectiveImplementingOnChange]));
|
||||
pv.bindDirectiveProperty( 0, parser.parseBinding('a', null), 'a', reflector.setter('a'), false);
|
||||
pv.bindDirectiveProperty( 0, parser.parseBinding('b', null), 'b', reflector.setter('b'), false);
|
||||
createViewAndChangeDetector(pv);
|
||||
|
||||
ctx.a = 0;
|
||||
ctx.b = 0;
|
||||
cd.detectChanges();
|
||||
|
||||
ctx.a = 100;
|
||||
cd.detectChanges();
|
||||
|
||||
var directive = view.elementInjectors[0].get(DirectiveImplementingOnChange);
|
||||
expect(directive.changes["a"].currentValue).toEqual(100);
|
||||
expect(directive.changes["b"]).not.toBeDefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('protoView createRootProtoView', () => {
|
||||
var element, pv;
|
||||
beforeEach(() => {
|
||||
element = DOM.createElement('div');
|
||||
pv = new ProtoView(el('<div>hi</div>'), new DynamicProtoChangeDetector());
|
||||
});
|
||||
|
||||
it('should create the root component when instantiated', () => {
|
||||
var rootProtoView = ProtoView.createRootProtoView(pv, element,
|
||||
someComponentDirective, new DynamicProtoChangeDetector());
|
||||
var view = rootProtoView.instantiate(null);
|
||||
view.hydrate(new Injector([]), null, null);
|
||||
expect(view.rootElementInjectors[0].get(SomeComponent)).not.toBe(null);
|
||||
});
|
||||
|
||||
it('should inject the protoView into the shadowDom', () => {
|
||||
var rootProtoView = ProtoView.createRootProtoView(pv, element,
|
||||
someComponentDirective, new DynamicProtoChangeDetector());
|
||||
var view = rootProtoView.instantiate(null);
|
||||
view.hydrate(new Injector([]), null, null);
|
||||
expect(element.shadowRoot.childNodes[0].childNodes[0].nodeValue).toEqual('hi');
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
class SomeDirective {
|
||||
prop;
|
||||
constructor() {
|
||||
this.prop = 'foo';
|
||||
}
|
||||
}
|
||||
|
||||
class DirectiveImplementingOnChange extends OnChange {
|
||||
a;
|
||||
b;
|
||||
c;
|
||||
changes;
|
||||
|
||||
onChange(changes) {
|
||||
this.c = this.a + this.b;
|
||||
this.changes = changes;
|
||||
}
|
||||
}
|
||||
|
||||
class SomeService {}
|
||||
|
||||
@Component({
|
||||
componentServices: [SomeService]
|
||||
})
|
||||
class SomeComponent {
|
||||
service: SomeService;
|
||||
constructor(service: SomeService) {
|
||||
this.service = service;
|
||||
}
|
||||
}
|
||||
|
||||
@Component({
|
||||
shadowDom: ShadowDomEmulated
|
||||
})
|
||||
class SomeComponentWithEmulatedShadowDom {
|
||||
}
|
||||
|
||||
@Decorator({
|
||||
selector: '[dec]'
|
||||
})
|
||||
class ServiceDependentDecorator {
|
||||
component: SomeComponent;
|
||||
service: SomeService;
|
||||
constructor(component: SomeComponent, service: SomeService) {
|
||||
this.component = component;
|
||||
this.service = service;
|
||||
}
|
||||
}
|
||||
|
||||
@Template({
|
||||
selector: 'someTmpl'
|
||||
})
|
||||
class SomeTemplate {
|
||||
viewPort: ViewPort;
|
||||
constructor(viewPort: ViewPort) {
|
||||
this.viewPort = viewPort;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class AnotherDirective {
|
||||
prop:string;
|
||||
constructor() {
|
||||
this.prop = 'anotherFoo';
|
||||
}
|
||||
}
|
||||
|
||||
class EventEmitterDirective {
|
||||
_clicker:Function;
|
||||
constructor(@EventEmitter('click') clicker:Function) {
|
||||
this._clicker = clicker;
|
||||
}
|
||||
click(eventData) {
|
||||
this._clicker(eventData);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class MyEvaluationContext {
|
||||
foo:string;
|
||||
a;
|
||||
b;
|
||||
callMe;
|
||||
constructor() {
|
||||
this.foo = 'bar';
|
||||
};
|
||||
}
|
||||
|
||||
class TestProtoElementInjector extends ProtoElementInjector {
|
||||
parentElementInjector: ElementInjector;
|
||||
hostElementInjector: ElementInjector;
|
||||
|
||||
constructor(parent:ProtoElementInjector, index:int, bindings:List, firstBindingIsComponent:boolean = false) {
|
||||
super(parent, index, bindings, firstBindingIsComponent);
|
||||
}
|
||||
|
||||
instantiate(parent:ElementInjector, host:ElementInjector, events):ElementInjector {
|
||||
this.parentElementInjector = parent;
|
||||
this.hostElementInjector = host;
|
||||
return super.instantiate(parent, host, events);
|
||||
}
|
||||
}
|
246
modules/angular2/test/core/compiler/viewport_spec.js
vendored
Normal file
246
modules/angular2/test/core/compiler/viewport_spec.js
vendored
Normal file
@ -0,0 +1,246 @@
|
||||
import {describe, xit, it, expect, beforeEach, ddescribe, iit, el} from 'test_lib/test_lib';
|
||||
import {View, ProtoView} from 'core/src/compiler/view';
|
||||
import {ViewPort} from 'core/src/compiler/viewport';
|
||||
import {proxy, IMPLEMENTS} from 'facade/src/lang';
|
||||
import {DOM} from 'facade/src/dom';
|
||||
import {ListWrapper, MapWrapper} from 'facade/src/collection';
|
||||
import {Injector} from 'di/di';
|
||||
import {ProtoElementInjector, ElementInjector} from 'core/src/compiler/element_injector';
|
||||
import {DynamicProtoChangeDetector, ChangeDetector, Lexer, Parser} from 'change_detection/change_detection';
|
||||
|
||||
function createView(nodes) {
|
||||
var view = new View(null, nodes, new DynamicProtoChangeDetector(), MapWrapper.create());
|
||||
view.init([], [], [], [], [], [], []);
|
||||
return view;
|
||||
}
|
||||
|
||||
@proxy
|
||||
@IMPLEMENTS(ChangeDetector)
|
||||
class AttachableChangeDetector {
|
||||
parent;
|
||||
constructor() {
|
||||
}
|
||||
remove() {
|
||||
this.parent = null;
|
||||
}
|
||||
noSuchMethod(i) {
|
||||
super.noSuchMethod(i);
|
||||
}
|
||||
}
|
||||
|
||||
@proxy
|
||||
@IMPLEMENTS(View)
|
||||
class HydrateAwareFakeView {
|
||||
isHydrated: boolean;
|
||||
nodes: List<Nodes>;
|
||||
changeDetector: ChangeDetector;
|
||||
rootElementInjectors;
|
||||
constructor(isHydrated) {
|
||||
this.isHydrated = isHydrated;
|
||||
this.nodes = [DOM.createElement('div')];
|
||||
this.rootElementInjectors = [];
|
||||
this.changeDetector = new AttachableChangeDetector();
|
||||
}
|
||||
|
||||
hydrated() {
|
||||
return this.isHydrated;
|
||||
}
|
||||
|
||||
hydrate(_, __, ___) {
|
||||
this.isHydrated = true;
|
||||
}
|
||||
|
||||
dehydrate() {
|
||||
this.isHydrated = false;
|
||||
}
|
||||
|
||||
noSuchMethod(i) {
|
||||
super.noSuchMethod(i);
|
||||
}
|
||||
}
|
||||
|
||||
export function main() {
|
||||
describe('viewport', () => {
|
||||
var viewPort, parentView, protoView, dom, customViewWithOneNode,
|
||||
customViewWithTwoNodes, elementInjector;
|
||||
|
||||
beforeEach(() => {
|
||||
dom = el(`<div><stuff></stuff><div insert-after-me></div><stuff></stuff></div>`);
|
||||
var insertionElement = dom.childNodes[1];
|
||||
parentView = createView([dom.childNodes[0]]);
|
||||
protoView = new ProtoView(el('<div>hi</div>'), new DynamicProtoChangeDetector());
|
||||
elementInjector = new ElementInjector(null, null, null, null);
|
||||
viewPort = new ViewPort(parentView, insertionElement, protoView, elementInjector);
|
||||
customViewWithOneNode = createView([el('<div>single</div>')]);
|
||||
customViewWithTwoNodes = createView([el('<div>one</div>'), el('<div>two</div>')]);
|
||||
});
|
||||
|
||||
describe('when dehydrated', () => {
|
||||
it('should throw if create is called', () => {
|
||||
expect(() => viewPort.create()).toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when hydrated', () => {
|
||||
function textInViewPort() {
|
||||
var out = '';
|
||||
// skipping starting filler, insert-me and final filler.
|
||||
for (var i = 2; i < dom.childNodes.length - 1; i++) {
|
||||
if (i != 2) out += ' ';
|
||||
out += DOM.getInnerHTML(dom.childNodes[i]);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
viewPort.hydrate(new Injector([]), null);
|
||||
var fillerView = createView([el('<filler>filler</filler>')]);
|
||||
viewPort.insert(fillerView);
|
||||
});
|
||||
|
||||
it('should create new views from protoView', () => {
|
||||
viewPort.create();
|
||||
expect(textInViewPort()).toEqual('filler hi');
|
||||
expect(viewPort.length).toBe(2);
|
||||
});
|
||||
|
||||
it('should create new views from protoView at index', () => {
|
||||
viewPort.create(0);
|
||||
expect(textInViewPort()).toEqual('hi filler');
|
||||
expect(viewPort.length).toBe(2);
|
||||
});
|
||||
|
||||
it('should insert new views at the end by default', () => {
|
||||
viewPort.insert(customViewWithOneNode);
|
||||
expect(textInViewPort()).toEqual('filler single');
|
||||
expect(viewPort.get(1)).toBe(customViewWithOneNode);
|
||||
expect(viewPort.length).toBe(2);
|
||||
});
|
||||
|
||||
it('should insert new views at the given index', () => {
|
||||
viewPort.insert(customViewWithOneNode, 0);
|
||||
expect(textInViewPort()).toEqual('single filler');
|
||||
expect(viewPort.get(0)).toBe(customViewWithOneNode);
|
||||
expect(viewPort.length).toBe(2);
|
||||
});
|
||||
|
||||
it('should remove the last view by default', () => {
|
||||
viewPort.insert(customViewWithOneNode);
|
||||
|
||||
viewPort.remove();
|
||||
|
||||
expect(textInViewPort()).toEqual('filler');
|
||||
expect(viewPort.length).toBe(1);
|
||||
});
|
||||
|
||||
it('should remove the view at a given index', () => {
|
||||
viewPort.insert(customViewWithOneNode);
|
||||
viewPort.insert(customViewWithTwoNodes);
|
||||
|
||||
viewPort.remove(1);
|
||||
|
||||
expect(textInViewPort()).toEqual('filler one two');
|
||||
expect(viewPort.get(1)).toBe(customViewWithTwoNodes);
|
||||
expect(viewPort.length).toBe(2);
|
||||
});
|
||||
|
||||
it('should detach the last view by default', () => {
|
||||
viewPort.insert(customViewWithOneNode);
|
||||
expect(viewPort.length).toBe(2);
|
||||
|
||||
var detachedView = viewPort.detach();
|
||||
|
||||
expect(detachedView).toBe(customViewWithOneNode);
|
||||
expect(textInViewPort()).toEqual('filler');
|
||||
expect(viewPort.length).toBe(1);
|
||||
});
|
||||
|
||||
it('should detach the view at a given index', () => {
|
||||
viewPort.insert(customViewWithOneNode);
|
||||
viewPort.insert(customViewWithTwoNodes);
|
||||
expect(viewPort.length).toBe(3);
|
||||
|
||||
var detachedView = viewPort.detach(1);
|
||||
expect(detachedView).toBe(customViewWithOneNode);
|
||||
expect(textInViewPort()).toEqual('filler one two');
|
||||
expect(viewPort.length).toBe(2);
|
||||
});
|
||||
|
||||
it('should keep views hydration state during insert', () => {
|
||||
var hydratedView = new HydrateAwareFakeView(true);
|
||||
var dehydratedView = new HydrateAwareFakeView(false);
|
||||
viewPort.insert(hydratedView);
|
||||
viewPort.insert(dehydratedView);
|
||||
|
||||
expect(hydratedView.hydrated()).toBe(true);
|
||||
expect(dehydratedView.hydrated()).toBe(false);
|
||||
});
|
||||
|
||||
it('should dehydrate on remove', () => {
|
||||
var hydratedView = new HydrateAwareFakeView(true);
|
||||
viewPort.insert(hydratedView);
|
||||
viewPort.remove();
|
||||
|
||||
expect(hydratedView.hydrated()).toBe(false);
|
||||
});
|
||||
|
||||
it('should keep views hydration state during detach', () => {
|
||||
var hydratedView = new HydrateAwareFakeView(true);
|
||||
var dehydratedView = new HydrateAwareFakeView(false);
|
||||
viewPort.insert(hydratedView);
|
||||
viewPort.insert(dehydratedView);
|
||||
|
||||
expect(viewPort.detach().hydrated()).toBe(false);
|
||||
expect(viewPort.detach().hydrated()).toBe(true);
|
||||
});
|
||||
|
||||
it('should support adding/removing views with more than one node', () => {
|
||||
viewPort.insert(customViewWithTwoNodes);
|
||||
viewPort.insert(customViewWithOneNode);
|
||||
|
||||
expect(textInViewPort()).toEqual('filler one two single');
|
||||
|
||||
viewPort.remove(1);
|
||||
expect(textInViewPort()).toEqual('filler single');
|
||||
});
|
||||
});
|
||||
|
||||
describe('should update injectors and parent views.', () => {
|
||||
var fancyView;
|
||||
beforeEach(() => {
|
||||
var parser = new Parser(new Lexer());
|
||||
viewPort.hydrate(new Injector([]), null);
|
||||
|
||||
var pv = new ProtoView(el('<div class="ng-binding">{{}}</div>'),
|
||||
new DynamicProtoChangeDetector());
|
||||
pv.bindElement(new ProtoElementInjector(null, 1, [SomeDirective]));
|
||||
pv.bindTextNode(0, parser.parseBinding('foo', null));
|
||||
fancyView = pv.instantiate(null);
|
||||
});
|
||||
|
||||
it('hydrating should update rootElementInjectors and parent change detector', () => {
|
||||
viewPort.insert(fancyView);
|
||||
ListWrapper.forEach(fancyView.rootElementInjectors, (inj) =>
|
||||
expect(inj.parent).toBe(elementInjector));
|
||||
|
||||
expect(parentView.changeDetector.children.length).toBe(1);
|
||||
});
|
||||
|
||||
it('dehydrating should update rootElementInjectors and parent change detector', () => {
|
||||
viewPort.insert(fancyView);
|
||||
viewPort.remove();
|
||||
ListWrapper.forEach(fancyView.rootElementInjectors, (inj) =>
|
||||
expect(inj.parent).toBe(null));
|
||||
expect(parentView.changeDetector.children.length).toBe(0);
|
||||
expect(viewPort.length).toBe(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
class SomeDirective {
|
||||
prop;
|
||||
constructor() {
|
||||
this.prop = 'foo';
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user