feat(view): add support for instantiation of nested component views.
Include shadowDom creation and integration tests for nested components. Fix accidentally clobbered modules/core/test/compiler/view_spec.js by previous commit.
This commit is contained in:
parent
b07ea6b90e
commit
be4cb2db3a
|
@ -15,7 +15,7 @@ export class Directive {
|
|||
bind:Object,
|
||||
lightDomServices:List,
|
||||
implementsTypes:List
|
||||
})
|
||||
}={})
|
||||
{
|
||||
this.selector = selector;
|
||||
this.lightDomServices = lightDomServices;
|
||||
|
|
|
@ -129,6 +129,7 @@ export class ProtoElementInjector {
|
|||
@FIELD('_key9:int')
|
||||
@FIELD('final parent:ProtoElementInjector')
|
||||
@FIELD('final index:int')
|
||||
@FIELD('view:View')
|
||||
constructor(parent:ProtoElementInjector, index:int, bindings:List, firstBindingIsComponent:boolean = false) {
|
||||
this.parent = parent;
|
||||
this.index = index;
|
||||
|
|
|
@ -30,6 +30,7 @@ export class View {
|
|||
/// to keep track of the nodes.
|
||||
@FIELD('final nodes:List<Node>')
|
||||
@FIELD('final onChangeDispatcher:OnChangeDispatcher')
|
||||
@FIELD('childViews: List<View>')
|
||||
constructor(nodes:List<Node>, elementInjectors:List,
|
||||
rootElementInjectors:List, textNodes:List, bindElements:List,
|
||||
protoWatchGroup:ProtoWatchGroup, context) {
|
||||
|
@ -41,6 +42,9 @@ export class View {
|
|||
this.bindElements = bindElements;
|
||||
this.watchGroup = protoWatchGroup.instantiate(this, MapWrapper.create());
|
||||
this.watchGroup.setContext(context);
|
||||
// TODO(rado): Since this is only used in tests for now, investigate whether
|
||||
// we can remove it.
|
||||
this.childViews = [];
|
||||
}
|
||||
|
||||
onRecordChange(record:Record, target) {
|
||||
|
@ -58,6 +62,10 @@ export class View {
|
|||
DOM.setText(this.textNodes[textNodeIndex], record.currentValue);
|
||||
}
|
||||
}
|
||||
|
||||
addChild(childView: View) {
|
||||
ListWrapper.push(this.childViews, childView);
|
||||
}
|
||||
}
|
||||
|
||||
export class ProtoView {
|
||||
|
@ -74,7 +82,7 @@ export class ProtoView {
|
|||
this.elementsWithBindingCount = 0;
|
||||
}
|
||||
|
||||
instantiate(context, appInjector:Injector):View {
|
||||
instantiate(context, lightDomAppInjector:Injector, hostElementInjector: ElementInjector):View {
|
||||
var clone = DOM.clone(this.element);
|
||||
var elements;
|
||||
if (clone instanceof TemplateElement) {
|
||||
|
@ -89,14 +97,15 @@ export class ProtoView {
|
|||
|
||||
/**
|
||||
* TODO: vsavkin: benchmark
|
||||
* If this performs poorly, the five loops can be collapsed into one.
|
||||
* If this performs poorly, the seven loops can be collapsed into one.
|
||||
*/
|
||||
var elementInjectors = ProtoView._createElementInjectors(elements, binders);
|
||||
var elementInjectors = ProtoView._createElementInjectors(elements, binders, hostElementInjector);
|
||||
var rootElementInjectors = ProtoView._rootElementInjectors(elementInjectors);
|
||||
var textNodes = ProtoView._textNodes(elements, binders);
|
||||
var bindElements = ProtoView._bindElements(elements, binders);
|
||||
|
||||
var shadowAppInjectors = ProtoView._createShadowAppInjectors(binders, lightDomAppInjector);
|
||||
var viewNodes;
|
||||
|
||||
if (clone instanceof TemplateElement) {
|
||||
viewNodes = ListWrapper.clone(clone.content.childNodes);
|
||||
} else {
|
||||
|
@ -105,7 +114,10 @@ export class ProtoView {
|
|||
var view = new View(viewNodes, elementInjectors, rootElementInjectors, textNodes,
|
||||
bindElements, this.protoWatchGroup, context);
|
||||
|
||||
ProtoView._instantiateDirectives(view, elements, elementInjectors, appInjector);
|
||||
ProtoView._instantiateDirectives(
|
||||
view, elements, elementInjectors, lightDomAppInjector, shadowAppInjectors);
|
||||
ProtoView._instantiateChildComponentViews(
|
||||
elements, binders, elementInjectors, shadowAppInjectors, view);
|
||||
|
||||
return view;
|
||||
}
|
||||
|
@ -162,13 +174,13 @@ export class ProtoView {
|
|||
);
|
||||
}
|
||||
|
||||
static _createElementInjectors(elements, binders) {
|
||||
static _createElementInjectors(elements, binders, hostElementInjector) {
|
||||
var injectors = ListWrapper.createFixedSize(binders.length);
|
||||
for (var i = 0; i < binders.length; ++i) {
|
||||
var proto = binders[i].protoElementInjector;
|
||||
if (isPresent(proto)) {
|
||||
var parentElementInjector = isPresent(proto.parent) ? injectors[proto.parent.index] : null;
|
||||
injectors[i] = ProtoView._createElementInjector(elements[i], parentElementInjector, proto);
|
||||
injectors[i] = proto.instantiate(parentElementInjector, hostElementInjector);
|
||||
} else {
|
||||
injectors[i] = null;
|
||||
}
|
||||
|
@ -177,17 +189,15 @@ export class ProtoView {
|
|||
}
|
||||
|
||||
static _instantiateDirectives(
|
||||
view: View, elements:List, injectors:List<ElementInjectors>, appInjector:Injector) {
|
||||
view: View, elements:List, injectors:List<ElementInjectors>, lightDomAppInjector: Injector,
|
||||
shadowDomAppInjectors:List<Injectors>) {
|
||||
for (var i = 0; i < injectors.length; ++i) {
|
||||
var preBuiltObjs = new PreBuiltObjects(view, new NgElement(elements[i]));
|
||||
if (injectors[i] != null) injectors[i].instantiateDirectives(appInjector, null, preBuiltObjs);
|
||||
if (injectors[i] != null) injectors[i].instantiateDirectives(
|
||||
lightDomAppInjector, shadowDomAppInjectors[i], preBuiltObjs);
|
||||
}
|
||||
}
|
||||
|
||||
static _createElementInjector(element, parent:ElementInjector, proto:ProtoElementInjector) {
|
||||
return proto.instantiate(parent, null);
|
||||
}
|
||||
|
||||
static _rootElementInjectors(injectors) {
|
||||
return ListWrapper.filter(injectors, inj => isPresent(inj) && isBlank(inj.parent));
|
||||
}
|
||||
|
@ -216,6 +226,39 @@ export class ProtoView {
|
|||
ListWrapper.push(allTextNodes, childNodes[indices[i]]);
|
||||
}
|
||||
}
|
||||
|
||||
static _instantiateChildComponentViews(elements, binders, injectors,
|
||||
shadowDomAppInjectors: List<Injector>, view: View) {
|
||||
for (var i = 0; i < binders.length; ++i) {
|
||||
var binder = binders[i];
|
||||
if (isPresent(binder.componentDirective)) {
|
||||
var injector = injectors[i];
|
||||
var childView = binder.nestedProtoView.instantiate(
|
||||
injector.getComponent(), shadowDomAppInjectors[i], injector);
|
||||
view.addChild(childView);
|
||||
var shadowRoot = elements[i].createShadowRoot();
|
||||
// TODO(rado): reuse utility from ViewPort/View.
|
||||
for (var j = 0; j < childView.nodes.length; ++j) {
|
||||
DOM.appendChild(shadowRoot, childView.nodes[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static _createShadowAppInjectors(binders: List<ElementBinders>, lightDomAppInjector: Injector): List<Injectors> {
|
||||
var injectors = ListWrapper.createFixedSize(binders.length);
|
||||
for (var i = 0; i < binders.length; ++i) {
|
||||
var componentDirective = binders[i].componentDirective;
|
||||
if (isPresent(componentDirective)) {
|
||||
var services = componentDirective.annotation.componentServices;
|
||||
injectors[i] = isPresent(services) ?
|
||||
lightDomAppInjector.createChild(services) : lightDomAppInjector;
|
||||
} else {
|
||||
injectors[i] = null;
|
||||
}
|
||||
}
|
||||
return injectors;
|
||||
}
|
||||
}
|
||||
|
||||
export class ElementPropertyMemento {
|
||||
|
|
|
@ -2,6 +2,7 @@ import {describe, xit, it, expect, beforeEach, ddescribe, iit} from 'test_lib/te
|
|||
|
||||
import {DOM} from 'facade/dom';
|
||||
|
||||
import {Injector} from 'di/di';
|
||||
import {ChangeDetector} from 'change_detection/change_detector';
|
||||
import {Parser} from 'change_detection/parser/parser';
|
||||
import {ClosureMap} from 'change_detection/parser/closure_map';
|
||||
|
@ -27,7 +28,7 @@ export function main() {
|
|||
var view, ctx, cd;
|
||||
function createView(pv) {
|
||||
ctx = new MyComp();
|
||||
view = pv.instantiate(ctx, null);
|
||||
view = pv.instantiate(ctx, new Injector([]), null);
|
||||
cd = new ChangeDetector(view.watchGroup);
|
||||
}
|
||||
|
||||
|
@ -66,6 +67,21 @@ export function main() {
|
|||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should support nested components.', (done) => {
|
||||
compiler.compile(MyComp, createElement('<child-cmp></child-cmp>')).then((pv) => {
|
||||
createView(pv);
|
||||
|
||||
cd.detectChanges();
|
||||
|
||||
// TODO(rado): this should be removed once watchgroups addChild is implemented.
|
||||
var childWatchGroup = view.childViews[0].watchGroup;
|
||||
new ChangeDetector(childWatchGroup).detectChanges();
|
||||
|
||||
expect(view.nodes[0].shadowRoot.childNodes[0].nodeValue).toEqual('hello');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -82,7 +98,7 @@ class MyDir {
|
|||
|
||||
@Component({
|
||||
template: new TemplateConfig({
|
||||
directives: [MyDir]
|
||||
directives: [MyDir, ChildComp]
|
||||
})
|
||||
})
|
||||
class MyComp {
|
||||
|
@ -91,6 +107,26 @@ class MyComp {
|
|||
}
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'child-cmp',
|
||||
componentServices: [MyService],
|
||||
template: new TemplateConfig({
|
||||
directives: [MyDir],
|
||||
inline: '{{ctxProp}}'
|
||||
})
|
||||
})
|
||||
class ChildComp {
|
||||
constructor(service: MyService) {
|
||||
this.ctxProp = service.greeting;
|
||||
}
|
||||
}
|
||||
|
||||
class MyService {
|
||||
constructor() {
|
||||
this.greeting = 'hello';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function createElement(html) {
|
||||
return DOM.createTemplate(html).content.firstChild;
|
||||
|
|
|
@ -21,12 +21,14 @@ import {Parser} from 'change_detection/parser/parser';
|
|||
import {Lexer} from 'change_detection/parser/lexer';
|
||||
import {ClosureMap} from 'change_detection/parser/closure_map';
|
||||
import {ChangeDetector} from 'change_detection/change_detector';
|
||||
import {Injector} from 'di/di';
|
||||
|
||||
export function main() {
|
||||
describe('ElementBinderBuilder', () => {
|
||||
var evalContext, view, changeDetector;
|
||||
|
||||
function createPipeline({textNodeBindings, propertyBindings, directives, protoElementInjector}={}) {
|
||||
function createPipeline({textNodeBindings, propertyBindings, directives, protoElementInjector
|
||||
}={}) {
|
||||
var reflector = new Reflector();
|
||||
var closureMap = new ClosureMap();
|
||||
return new CompilePipeline([
|
||||
|
@ -69,7 +71,7 @@ export function main() {
|
|||
|
||||
function instantiateView(protoView) {
|
||||
evalContext = new Context();
|
||||
view = protoView.instantiate(evalContext, null);
|
||||
view = protoView.instantiate(evalContext, new Injector([]), null);
|
||||
changeDetector = new ChangeDetector(view.watchGroup);
|
||||
}
|
||||
|
||||
|
@ -174,7 +176,7 @@ export function main() {
|
|||
'boundprop3': 'prop3'
|
||||
});
|
||||
var directives = [SomeDecoratorDirectiveWithBinding, SomeTemplateDirectiveWithBinding, SomeComponentDirectiveWithBinding];
|
||||
var protoElementInjector = new ProtoElementInjector(null, 0, directives);
|
||||
var protoElementInjector = new ProtoElementInjector(null, 0, directives, true);
|
||||
var pipeline = createPipeline({
|
||||
propertyBindings: propertyBindings,
|
||||
directives: directives,
|
||||
|
@ -182,6 +184,8 @@ export function main() {
|
|||
});
|
||||
var results = pipeline.process(createElement('<div viewroot prop-binding directives></div>'));
|
||||
var pv = results[0].inheritedProtoView;
|
||||
results[0].inheritedElementBinder.nestedProtoView = new ProtoView(
|
||||
createElement('<div></div>'), new ProtoWatchGroup());
|
||||
|
||||
instantiateView(pv);
|
||||
evalContext.prop1 = 'a';
|
||||
|
|
|
@ -1,14 +1,19 @@
|
|||
import {describe, xit, it, expect, beforeEach} from 'test_lib/test_lib';
|
||||
import {describe, xit, it, expect, beforeEach, ddescribe, iit} from 'test_lib/test_lib';
|
||||
import {ProtoView, ElementPropertyMemento, DirectivePropertyMemento} from 'core/compiler/view';
|
||||
import {Record, ProtoRecord} from 'change_detection/record';
|
||||
import {ProtoElementInjector, ElementInjector} from 'core/compiler/element_injector';
|
||||
import {Reflector} from 'core/compiler/reflector';
|
||||
import {Component} from 'core/annotations/component';
|
||||
import {Decorator} from 'core/annotations/decorator';
|
||||
import {ProtoWatchGroup} from 'change_detection/watch_group';
|
||||
import {ChangeDetector} from 'change_detection/change_detector';
|
||||
import {TemplateConfig} from 'core/annotations/template_config';
|
||||
import {Parser} from 'change_detection/parser/parser';
|
||||
import {ClosureMap} from 'change_detection/parser/closure_map';
|
||||
import {Lexer} from 'change_detection/parser/lexer';
|
||||
import {DOM, Element} from 'facade/dom';
|
||||
import {FIELD} from 'facade/lang';
|
||||
import {Injector} from 'di/di';
|
||||
import {View} from 'core/compiler/view';
|
||||
|
||||
export function main() {
|
||||
describe('view', function() {
|
||||
|
@ -21,120 +26,185 @@ export function main() {
|
|||
|
||||
describe('ProtoView.instantiate', function() {
|
||||
|
||||
describe('collect root nodes', () => {
|
||||
function createCollectDomNodesTestCases(useTemplateElement:boolean) {
|
||||
|
||||
it('should use the ProtoView element if it is no TemplateElement', () => {
|
||||
var pv = new ProtoView(createElement('<div id="1"></div>'), new ProtoWatchGroup());
|
||||
var view = pv.instantiate(null, null);
|
||||
expect(view.nodes.length).toBe(1);
|
||||
expect(view.nodes[0].getAttribute('id')).toEqual('1');
|
||||
});
|
||||
|
||||
it('should use the ProtoView elements children if it is a TemplateElement', () => {
|
||||
var pv = new ProtoView(createElement('<template><div id="1"></div></template>'),
|
||||
new ProtoWatchGroup());
|
||||
var view = pv.instantiate(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(createElement('<div [prop]="a" class="ng-binding"></div>'), new ProtoWatchGroup());
|
||||
pv.bindElement(null);
|
||||
pv.bindElementProperty('prop', parser.parseBinding('a'));
|
||||
|
||||
var view = pv.instantiate(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(createElement('<div><span></span><span class="ng-binding"></span></div>'),
|
||||
new ProtoWatchGroup());
|
||||
pv.bindElement(null);
|
||||
pv.bindElementProperty('a', parser.parseBinding('b'));
|
||||
|
||||
var view = pv.instantiate(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(createElement('<div class="ng-binding">{{}}<span></span>{{}}</div>'), new ProtoWatchGroup());
|
||||
pv.bindElement(null);
|
||||
pv.bindTextNode(0, parser.parseBinding('a'));
|
||||
pv.bindTextNode(2, parser.parseBinding('b'));
|
||||
|
||||
var view = pv.instantiate(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(createElement('<div><span> </span><span class="ng-binding">{{}}</span></div>'),
|
||||
new ProtoWatchGroup());
|
||||
pv.bindElement(null);
|
||||
pv.bindTextNode(0, parser.parseBinding('b'));
|
||||
|
||||
var view = pv.instantiate(null, null);
|
||||
expect(view.textNodes.length).toEqual(1);
|
||||
expect(view.textNodes[0]).toBe(view.nodes[0].childNodes[1].childNodes[0]);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('react to watch group changes', function() {
|
||||
var ctx, view, cd;
|
||||
|
||||
function createView(protoView) {
|
||||
ctx = new MyEvaluationContext();
|
||||
view = protoView.instantiate(ctx, null);
|
||||
cd = new ChangeDetector(view.watchGroup);
|
||||
function templateAwareCreateElement(html) {
|
||||
return createElement(useTemplateElement ? `<template>${html}</template>` : html);
|
||||
}
|
||||
|
||||
it('should consume text node changes', () => {
|
||||
var pv = new ProtoView(createElement('<div class="ng-binding">{{}}</div>'),
|
||||
new ProtoWatchGroup());
|
||||
pv.bindElement(null);
|
||||
pv.bindTextNode(0, parser.parseBinding('foo'));
|
||||
createView(pv);
|
||||
|
||||
ctx.foo = 'buz';
|
||||
cd.detectChanges();
|
||||
expect(view.textNodes[0].nodeValue).toEqual('buz');
|
||||
it('should collect the root node in the ProtoView element', () => {
|
||||
var pv = new ProtoView(templateAwareCreateElement('<div id="1"></div>'), new ProtoWatchGroup());
|
||||
var view = pv.instantiate(null, null, null);
|
||||
expect(view.nodes.length).toBe(1);
|
||||
expect(view.nodes[0].getAttribute('id')).toEqual('1');
|
||||
});
|
||||
|
||||
it('should consume element binding changes', () => {
|
||||
var pv = new ProtoView(createElement('<div class="ng-binding"></div>'),
|
||||
new ProtoWatchGroup());
|
||||
pv.bindElement(null);
|
||||
pv.bindElementProperty('id', parser.parseBinding('foo'));
|
||||
createView(pv);
|
||||
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 ProtoWatchGroup());
|
||||
pv.bindElement(null);
|
||||
pv.bindElementProperty('prop', parser.parseBinding('a'));
|
||||
|
||||
var view = pv.instantiate(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 ProtoWatchGroup());
|
||||
pv.bindElement(null);
|
||||
pv.bindElementProperty('a', parser.parseBinding('b'));
|
||||
|
||||
var view = pv.instantiate(null, null, null);
|
||||
expect(view.bindElements.length).toEqual(1);
|
||||
expect(view.bindElements[0]).toBe(view.nodes[0].childNodes[1]);
|
||||
});
|
||||
|
||||
ctx.foo = 'buz';
|
||||
cd.detectChanges();
|
||||
expect(view.bindElements[0].id).toEqual('buz');
|
||||
});
|
||||
|
||||
it('should consume directive watch expression change.', () => {
|
||||
var pv = new ProtoView(createElement('<div class="ng-binding"></div>'),
|
||||
new ProtoWatchGroup());
|
||||
pv.bindElement(new ProtoElementInjector(null, 0, [Directive]));
|
||||
pv.bindDirectiveProperty( 0, parser.parseBinding('foo'), 'prop', closureMap.setter('prop'));
|
||||
createView(pv);
|
||||
describe('collect text nodes with bindings', () => {
|
||||
|
||||
ctx.foo = 'buz';
|
||||
cd.detectChanges();
|
||||
expect(view.elementInjectors[0].get(Directive).prop).toEqual('buz');
|
||||
it('should collect text nodes under the root element', () => {
|
||||
var pv = new ProtoView(templateAwareCreateElement('<div class="ng-binding">{{}}<span></span>{{}}</div>'), new ProtoWatchGroup());
|
||||
pv.bindElement(null);
|
||||
pv.bindTextNode(0, parser.parseBinding('a'));
|
||||
pv.bindTextNode(2, parser.parseBinding('b'));
|
||||
|
||||
var view = pv.instantiate(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 ProtoWatchGroup());
|
||||
pv.bindElement(null);
|
||||
pv.bindTextNode(0, parser.parseBinding('b'));
|
||||
|
||||
var view = pv.instantiate(null, null, null);
|
||||
expect(view.textNodes.length).toEqual(1);
|
||||
expect(view.textNodes[0]).toBe(view.nodes[0].childNodes[1].childNodes[0]);
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
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(createElement('<div class="ng-binding"></div>'), new ProtoWatchGroup());
|
||||
pv.bindElement(new ProtoElementInjector(null, 1, [SomeDirective]));
|
||||
|
||||
var view = pv.instantiate(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(createElement('<div class="ng-binding"><span class="ng-binding"></span></div>'),
|
||||
new ProtoWatchGroup());
|
||||
var protoParent = new ProtoElementInjector(null, 0, [SomeDirective]);
|
||||
pv.bindElement(protoParent);
|
||||
pv.bindElement(new ProtoElementInjector(protoParent, 1, [AnotherDirective]));
|
||||
|
||||
var view = pv.instantiate(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]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('collect root element injectors', () => {
|
||||
|
||||
it('should collect a single root element injector', () => {
|
||||
var pv = new ProtoView(createElement('<div class="ng-binding"><span class="ng-binding"></span></div>'),
|
||||
new ProtoWatchGroup());
|
||||
var protoParent = new ProtoElementInjector(null, 0, [SomeDirective]);
|
||||
pv.bindElement(protoParent);
|
||||
pv.bindElement(new ProtoElementInjector(protoParent, 1, [AnotherDirective]));
|
||||
|
||||
var view = pv.instantiate(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(createElement('<div><span class="ng-binding"></span><span class="ng-binding"></span></div>'),
|
||||
new ProtoWatchGroup());
|
||||
pv.bindElement(new ProtoElementInjector(null, 1, [SomeDirective]));
|
||||
pv.bindElement(new ProtoElementInjector(null, 2, [AnotherDirective]));
|
||||
|
||||
var view = pv.instantiate(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('recurse over child component views', () => {
|
||||
var view, ctx;
|
||||
|
||||
function createComponentWithSubPV(subProtoView) {
|
||||
var pv = new ProtoView(createElement('<cmp class="ng-binding"></cmp>'), new ProtoWatchGroup());
|
||||
var binder = pv.bindElement(new ProtoElementInjector(null, 0, [SomeComponent], true));
|
||||
binder.componentDirective = new Reflector().annotatedType(SomeComponent);
|
||||
binder.nestedProtoView = subProtoView;
|
||||
return pv;
|
||||
}
|
||||
|
||||
function createNestedView(protoView) {
|
||||
ctx = new MyEvaluationContext();
|
||||
return protoView.instantiate(ctx, new Injector([]), null);
|
||||
}
|
||||
|
||||
it('should create shadow dom', () => {
|
||||
var subpv = new ProtoView(createElement('<span>hello shadow dom</span>'), new ProtoWatchGroup());
|
||||
var pv = createComponentWithSubPV(subpv);
|
||||
|
||||
var view = createNestedView(pv);
|
||||
|
||||
expect(view.nodes[0].shadowRoot.childNodes[0].childNodes[0].nodeValue).toEqual('hello shadow dom');
|
||||
});
|
||||
|
||||
it('should expose component services to the component', () => {
|
||||
var subpv = new ProtoView(createElement('<span></span>'), new ProtoWatchGroup());
|
||||
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(
|
||||
createElement('<div dec class="ng-binding">hello shadow dom</div>'), new ProtoWatchGroup());
|
||||
var subBinder = subpv.bindElement(
|
||||
new ProtoElementInjector(null, 0, [ServiceDependentDecorator]));
|
||||
var pv = createComponentWithSubPV(subpv);
|
||||
|
||||
var view = createNestedView(pv);
|
||||
|
||||
var subView = view.childViews[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);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -143,7 +213,7 @@ export function main() {
|
|||
|
||||
function createView(protoView) {
|
||||
ctx = new MyEvaluationContext();
|
||||
view = protoView.instantiate(ctx, null);
|
||||
view = protoView.instantiate(ctx, null, null);
|
||||
cd = new ChangeDetector(view.watchGroup);
|
||||
}
|
||||
|
||||
|
@ -174,26 +244,48 @@ export function main() {
|
|||
it('should consume directive watch expression change.', () => {
|
||||
var pv = new ProtoView(createElement('<div class="ng-binding"></div>'),
|
||||
new ProtoWatchGroup());
|
||||
pv.bindElement(new ProtoElementInjector(null, 0, [Directive]));
|
||||
pv.bindElement(new ProtoElementInjector(null, 0, [SomeDirective]));
|
||||
pv.bindDirectiveProperty( 0, parser.parseBinding('foo'), 'prop', closureMap.setter('prop'));
|
||||
createView(pv);
|
||||
|
||||
ctx.foo = 'buz';
|
||||
cd.detectChanges();
|
||||
expect(view.elementInjectors[0].get(Directive).prop).toEqual('buz');
|
||||
expect(view.elementInjectors[0].get(SomeDirective).prop).toEqual('buz');
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
class Directive {
|
||||
class SomeDirective {
|
||||
@FIELD('prop')
|
||||
constructor() {
|
||||
this.prop = 'foo';
|
||||
}
|
||||
}
|
||||
|
||||
class SomeService {}
|
||||
|
||||
@Component({
|
||||
componentServices: [SomeService]
|
||||
})
|
||||
class SomeComponent {
|
||||
constructor(service: SomeService) {
|
||||
this.service = service;
|
||||
}
|
||||
}
|
||||
|
||||
@Decorator({
|
||||
selector: '[dec]'
|
||||
})
|
||||
class ServiceDependentDecorator {
|
||||
constructor(component: SomeComponent, service: SomeService) {
|
||||
this.component = component;
|
||||
this.service = service;
|
||||
}
|
||||
}
|
||||
|
||||
class AnotherDirective {
|
||||
@FIELD('prop')
|
||||
constructor() {
|
||||
|
|
Loading…
Reference in New Issue