feat(viewPort): adds initial implementation of ViewPort.
ViewPort is the mechanism backing @Template directives. Those directives can use the viewport to dynamically create, attach and detach views.
This commit is contained in:
parent
9a28fa8590
commit
c6f14dd833
|
@ -4,6 +4,7 @@ import {List, ListWrapper} from 'facade/collection';
|
|||
import {Injector, Key, Dependency, bind, Binding, NoProviderError, ProviderError, CyclicDependencyError} from 'di/di';
|
||||
import {Parent, Ancestor} from 'core/annotations/visibility';
|
||||
import {View} from 'core/compiler/view';
|
||||
import {ViewPort} from 'core/compiler/viewport';
|
||||
import {NgElement} from 'core/dom/element';
|
||||
|
||||
var _MAX_DIRECTIVE_CONSTRUCTION_COUNTER = 10;
|
||||
|
@ -17,10 +18,12 @@ var _staticKeys;
|
|||
class StaticKeys {
|
||||
viewId:int;
|
||||
ngElementId:int;
|
||||
viewPortId:int;
|
||||
constructor() {
|
||||
//TODO: vsavkin Key.annotate(Key.get(View), 'static')
|
||||
this.viewId = Key.get(View).id;
|
||||
this.ngElementId = Key.get(NgElement).id;
|
||||
this.viewPortId = Key.get(ViewPort).id;
|
||||
}
|
||||
|
||||
static instance() {
|
||||
|
@ -61,6 +64,10 @@ class TreeNode {
|
|||
return this._parent;
|
||||
}
|
||||
|
||||
set parent(node:TreeNode) {
|
||||
this._parent = node;
|
||||
}
|
||||
|
||||
get children() {
|
||||
var res = [];
|
||||
var child = this._head;
|
||||
|
@ -92,12 +99,16 @@ class DirectiveDependency extends Dependency {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
// TODO(rado): benchmark and consider rolling in as ElementInjector fields.
|
||||
export class PreBuiltObjects {
|
||||
view:View;
|
||||
element:NgElement;
|
||||
constructor(view:View, element:NgElement) {
|
||||
viewPort:ViewPort;
|
||||
constructor(view, element:NgElement, viewPort: ViewPort) {
|
||||
this.view = view;
|
||||
this.element = element;
|
||||
this.viewPort = viewPort;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -410,6 +421,11 @@ export class ElementInjector extends TreeNode {
|
|||
var staticKeys = StaticKeys.instance();
|
||||
if (keyId === staticKeys.viewId) return this._preBuiltObjects.view;
|
||||
if (keyId === staticKeys.ngElementId) return this._preBuiltObjects.element;
|
||||
if (keyId === staticKeys.viewPortId) {
|
||||
if (isBlank(staticKeys.viewPortId)) throw new BaseException(
|
||||
'ViewPort is constructed only for @Template directives');
|
||||
return this._preBuiltObjects.viewPort;
|
||||
}
|
||||
//TODO add other objects as needed
|
||||
return _undefined;
|
||||
}
|
||||
|
|
|
@ -11,10 +11,11 @@ import {SetterFn} from 'reflection/types';
|
|||
import {FIELD, IMPLEMENTS, int, isPresent, isBlank} from 'facade/lang';
|
||||
import {Injector} from 'di/di';
|
||||
import {NgElement} from 'core/dom/element';
|
||||
import {ViewPort} from './viewport';
|
||||
|
||||
const NG_BINDING_CLASS = 'ng-binding';
|
||||
|
||||
/***
|
||||
/**
|
||||
* Const of making objects: http://jsperf.com/instantiate-size-of-object
|
||||
*/
|
||||
@IMPLEMENTS(WatchGroupDispatcher)
|
||||
|
@ -29,7 +30,8 @@ export class View {
|
|||
/// to keep track of the nodes.
|
||||
nodes:List<Node>;
|
||||
onChangeDispatcher:OnChangeDispatcher;
|
||||
childViews: List<View>;
|
||||
componentChildViews: List<View>;
|
||||
viewPorts: List<ViewPort>;
|
||||
constructor(nodes:List<Node>, elementInjectors:List,
|
||||
rootElementInjectors:List, textNodes:List, bindElements:List,
|
||||
protoRecordRange:ProtoRecordRange, context) {
|
||||
|
@ -41,9 +43,8 @@ export class View {
|
|||
this.bindElements = bindElements;
|
||||
this.recordRange = protoRecordRange.instantiate(this, MapWrapper.create());
|
||||
this.recordRange.setContext(context);
|
||||
// TODO(rado): Since this is only used in tests for now, investigate whether
|
||||
// we can remove it.
|
||||
this.childViews = [];
|
||||
this.componentChildViews = null;
|
||||
this.viewPorts = null;
|
||||
}
|
||||
|
||||
onRecordChange(record:Record, target) {
|
||||
|
@ -62,10 +63,24 @@ export class View {
|
|||
}
|
||||
}
|
||||
|
||||
addChild(childView: View) {
|
||||
ListWrapper.push(this.childViews, childView);
|
||||
addViewPort(viewPort: ViewPort) {
|
||||
if (isBlank(this.viewPorts)) this.viewPorts = [];
|
||||
ListWrapper.push(this.viewPorts, viewPort);
|
||||
}
|
||||
|
||||
addComponentChildView(childView: View) {
|
||||
if (isBlank(this.componentChildViews)) this.componentChildViews = [];
|
||||
ListWrapper.push(this.componentChildViews, childView);
|
||||
this.recordRange.addRange(childView.recordRange);
|
||||
}
|
||||
|
||||
addViewPortChildView(childView: View) {
|
||||
this.recordRange.addRange(childView.recordRange);
|
||||
}
|
||||
|
||||
removeViewPortChildView(childView: View) {
|
||||
childView.recordRange.remove();
|
||||
}
|
||||
}
|
||||
|
||||
export class ProtoView {
|
||||
|
@ -120,9 +135,10 @@ export class ProtoView {
|
|||
bindElements, this.protoRecordRange, context);
|
||||
|
||||
ProtoView._instantiateDirectives(
|
||||
view, elements, elementInjectors, lightDomAppInjector, shadowAppInjectors);
|
||||
ProtoView._instantiateChildComponentViews(
|
||||
elements, binders, elementInjectors, shadowAppInjectors, view);
|
||||
view, elements, binders, elementInjectors, lightDomAppInjector,
|
||||
shadowAppInjectors, hostElementInjector);
|
||||
ProtoView._instantiateChildComponentViews(view, elements, binders,
|
||||
elementInjectors, shadowAppInjectors);
|
||||
|
||||
return view;
|
||||
}
|
||||
|
@ -212,12 +228,25 @@ export class ProtoView {
|
|||
}
|
||||
|
||||
static _instantiateDirectives(
|
||||
view: View, elements:List, injectors:List<ElementInjectors>, lightDomAppInjector: Injector,
|
||||
shadowDomAppInjectors:List<Injectors>) {
|
||||
view, elements:List, binders: List<ElementBinder>, injectors:List<ElementInjectors>,
|
||||
lightDomAppInjector: Injector, shadowDomAppInjectors:List<Injectors>,
|
||||
hostElementInjector: ElementInjector) {
|
||||
for (var i = 0; i < injectors.length; ++i) {
|
||||
var preBuiltObjs = new PreBuiltObjects(view, new NgElement(elements[i]));
|
||||
if (injectors[i] != null) injectors[i].instantiateDirectives(
|
||||
var injector = injectors[i];
|
||||
if (injector != null) {
|
||||
var binder = binders[i];
|
||||
var element = elements[i];
|
||||
var ngElement = new NgElement(element);
|
||||
var viewPort = null;
|
||||
if (isPresent(binder.templateDirective)) {
|
||||
viewPort = new ViewPort(view, element, binder.nestedProtoView, injector);
|
||||
viewPort.attach(lightDomAppInjector, hostElementInjector);
|
||||
view.addViewPort(viewPort);
|
||||
}
|
||||
var preBuiltObjs = new PreBuiltObjects(view, ngElement, viewPort);
|
||||
injector.instantiateDirectives(
|
||||
lightDomAppInjector, shadowDomAppInjectors[i], preBuiltObjs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -252,20 +281,17 @@ export class ProtoView {
|
|||
}
|
||||
}
|
||||
|
||||
static _instantiateChildComponentViews(elements, binders, injectors,
|
||||
shadowDomAppInjectors: List<Injector>, view: View) {
|
||||
static _instantiateChildComponentViews(view: View, elements, binders,
|
||||
injectors, shadowDomAppInjectors: List<Injector>) {
|
||||
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);
|
||||
view.addComponentChildView(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]);
|
||||
}
|
||||
ViewPort.moveViewNodesIntoParent(shadowRoot, childView);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,119 @@
|
|||
import {View, ProtoView} from './view';
|
||||
import {DOM, Node, Element} from 'facade/dom';
|
||||
import {ListWrapper, MapWrapper, List} from 'facade/collection';
|
||||
import {BaseException} from 'facade/lang';
|
||||
import {Injector} from 'di/di';
|
||||
import {ElementInjector} from 'core/compiler/element_injector';
|
||||
import {isPresent, isBlank} from 'facade/lang';
|
||||
|
||||
export class ViewPort {
|
||||
parentView: View;
|
||||
templateElement: Element;
|
||||
defaultProtoView: ProtoView;
|
||||
_views: List<View>;
|
||||
_viewLastNode: List<Node>;
|
||||
elementInjector: ElementInjector;
|
||||
appInjector: Injector;
|
||||
hostElementInjector: ElementInjector;
|
||||
|
||||
constructor(parentView: View, templateElement: Element, defaultProtoView: ProtoView,
|
||||
elementInjector: ElementInjector) {
|
||||
this.parentView = parentView;
|
||||
this.templateElement = templateElement;
|
||||
this.defaultProtoView = defaultProtoView;
|
||||
this.elementInjector = elementInjector;
|
||||
|
||||
// The order in this list matches the DOM order.
|
||||
this._views = [];
|
||||
this.appInjector = null;
|
||||
this.hostElementInjector = null;
|
||||
}
|
||||
|
||||
attach(appInjector: Injector, hostElementInjector: ElementInjector) {
|
||||
this.appInjector = appInjector;
|
||||
this.hostElementInjector = hostElementInjector;
|
||||
}
|
||||
|
||||
detach() {
|
||||
this.appInjector = null;
|
||||
this.hostElementInjector = null;
|
||||
}
|
||||
|
||||
get(index: number): View {
|
||||
return this._views[index];
|
||||
}
|
||||
|
||||
get length() {
|
||||
return this._views.length;
|
||||
}
|
||||
|
||||
_siblingToInsertAfter(index: number) {
|
||||
if (index == 0) return this.templateElement;
|
||||
return ListWrapper.last(this._views[index - 1].nodes);
|
||||
}
|
||||
|
||||
get detached() {
|
||||
return isBlank(this.appInjector);
|
||||
}
|
||||
|
||||
// TODO(rado): profile and decide whether bounds checks should be added
|
||||
// to the methods below.
|
||||
create(atIndex=-1): View {
|
||||
if (this.detached) throw new BaseException(
|
||||
'Cannot create views on a detached view port');
|
||||
// TODO(rado): replace curried defaultProtoView.instantiate(appInjector,
|
||||
// hostElementInjector) with ViewFactory.
|
||||
var newView = this.defaultProtoView.instantiate(
|
||||
null, this.appInjector, this.hostElementInjector);
|
||||
return this.insert(newView, atIndex);
|
||||
}
|
||||
|
||||
insert(view, atIndex=-1): View {
|
||||
if (atIndex == -1) atIndex = this._views.length;
|
||||
ListWrapper.insert(this._views, atIndex, view);
|
||||
ViewPort.moveViewNodesAfterSibling(this._siblingToInsertAfter(atIndex), view);
|
||||
this.parentView.addViewPortChildView(view);
|
||||
this._linkElementInjectors(view);
|
||||
return view;
|
||||
}
|
||||
|
||||
remove(atIndex=-1): View {
|
||||
if (atIndex == -1) atIndex = this._views.length - 1;
|
||||
var removedView = this.get(atIndex);
|
||||
ListWrapper.removeAt(this._views, atIndex);
|
||||
ViewPort.removeViewNodesFromParent(this.templateElement.parentNode, removedView);
|
||||
this.parentView.removeViewPortChildView(removedView);
|
||||
this._unlinkElementInjectors(removedView);
|
||||
return removedView;
|
||||
}
|
||||
|
||||
_linkElementInjectors(view) {
|
||||
for (var i = 0; i < view.rootElementInjectors.length; ++i) {
|
||||
view.rootElementInjectors[i].parent = this.elementInjector;
|
||||
}
|
||||
}
|
||||
|
||||
_unlinkElementInjectors(view) {
|
||||
for (var i = 0; i < view.rootElementInjectors.length; ++i) {
|
||||
view.rootElementInjectors[i].parent = null;
|
||||
}
|
||||
}
|
||||
|
||||
static moveViewNodesIntoParent(parent, view) {
|
||||
for (var i = 0; i < view.nodes.length; ++i) {
|
||||
DOM.appendChild(parent, view.nodes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
static moveViewNodesAfterSibling(sibling, view) {
|
||||
for (var i = view.nodes.length - 1; i >= 0; --i) {
|
||||
DOM.insertAfter(sibling, view.nodes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
static removeViewNodesFromParent(parent, view) {
|
||||
for (var i = view.nodes.length - 1; i >= 0; --i) {
|
||||
DOM.removeChild(parent, view.nodes[i]);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,6 +6,7 @@ import {Parent, Ancestor} from 'core/annotations/visibility';
|
|||
import {Injector, Inject, bind} from 'di/di';
|
||||
import {View} from 'core/compiler/view';
|
||||
import {ProtoRecordRange} from 'change_detection/record_range';
|
||||
import {ViewPort} from 'core/compiler/viewport';
|
||||
import {NgElement} from 'core/dom/element';
|
||||
|
||||
//TODO: vsavkin: use a spy object
|
||||
|
@ -66,7 +67,7 @@ class NeedsView {
|
|||
}
|
||||
|
||||
export function main() {
|
||||
var defaultPreBuiltObjects = new PreBuiltObjects(null, null);
|
||||
var defaultPreBuiltObjects = new PreBuiltObjects(null, null, null);
|
||||
|
||||
function humanize(tree, names:List) {
|
||||
var lookupName = (item) =>
|
||||
|
@ -177,7 +178,7 @@ export function main() {
|
|||
|
||||
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));
|
||||
var inj = injector([NeedsView], null, null, new PreBuiltObjects(view, null, null));
|
||||
|
||||
expect(inj.get(NeedsView).view).toBe(view);
|
||||
});
|
||||
|
@ -282,17 +283,24 @@ export function main() {
|
|||
describe("pre built objects", function () {
|
||||
it("should return view", function () {
|
||||
var view = new DummyView();
|
||||
var inj = injector([], null, null, new PreBuiltObjects(view, null));
|
||||
var inj = injector([], null, null, new PreBuiltObjects(view, 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));
|
||||
var inj = injector([], null, null, new PreBuiltObjects(null, element, 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));
|
||||
|
||||
expect(inj.get(ViewPort)).toEqual(viewPort);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ import {describe, xit, it, expect, beforeEach, ddescribe, iit} from 'test_lib/te
|
|||
import {ProtoView, ElementPropertyMemento, DirectivePropertyMemento} from 'core/compiler/view';
|
||||
import {ProtoElementInjector, ElementInjector} from 'core/compiler/element_injector';
|
||||
import {DirectiveMetadataReader} from 'core/compiler/directive_metadata_reader';
|
||||
import {Component, Decorator} from 'core/annotations/annotations';
|
||||
import {Component, Decorator, Template} from 'core/annotations/annotations';
|
||||
import {ProtoRecordRange} from 'change_detection/record_range';
|
||||
import {ChangeDetector} from 'change_detection/change_detector';
|
||||
import {TemplateConfig} from 'core/annotations/template_config';
|
||||
|
@ -12,15 +12,17 @@ import {DOM, Element} from 'facade/dom';
|
|||
import {FIELD} from 'facade/lang';
|
||||
import {Injector} from 'di/di';
|
||||
import {View} from 'core/compiler/view';
|
||||
import {ViewPort} from 'core/compiler/viewport';
|
||||
import {reflector} from 'reflection/reflection';
|
||||
|
||||
export function main() {
|
||||
describe('view', function() {
|
||||
var parser, someComponentDirective;
|
||||
var parser, someComponentDirective, someTemplateDirective;
|
||||
|
||||
beforeEach(() => {
|
||||
parser = new Parser(new Lexer());
|
||||
someComponentDirective = new DirectiveMetadataReader().annotatedType(SomeComponent);
|
||||
someTemplateDirective = new DirectiveMetadataReader().annotatedType(SomeTemplate);
|
||||
});
|
||||
|
||||
|
||||
|
@ -213,7 +215,7 @@ export function main() {
|
|||
|
||||
var view = createNestedView(pv);
|
||||
|
||||
var subView = view.childViews[0];
|
||||
var subView = view.componentChildViews[0];
|
||||
var subInj = subView.rootElementInjectors[0];
|
||||
var subDecorator = subInj.get(ServiceDependentDecorator);
|
||||
var comp = view.rootElementInjectors[0].get(SomeComponent);
|
||||
|
@ -224,6 +226,28 @@ export function main() {
|
|||
});
|
||||
});
|
||||
|
||||
describe('recurse over child templateViews', () => {
|
||||
var ctx, view, cd;
|
||||
function createView(protoView) {
|
||||
ctx = new MyEvaluationContext();
|
||||
view = protoView.instantiate(ctx, null, null);
|
||||
}
|
||||
|
||||
it('should create a viewPort for the template directive', () => {
|
||||
var templateProtoView = new ProtoView(
|
||||
createElement('<div id="1"></div>'), new ProtoRecordRange());
|
||||
var pv = new ProtoView(createElement('<someTmpl class="ng-binding"></someTmpl>'), new ProtoRecordRange());
|
||||
var binder = pv.bindElement(new ProtoElementInjector(null, 0, [SomeTemplate]));
|
||||
binder.templateDirective = someTemplateDirective;
|
||||
binder.nestedProtoView = templateProtoView;
|
||||
|
||||
createView(pv);
|
||||
|
||||
var tmplComp = view.rootElementInjectors[0].get(SomeTemplate);
|
||||
expect(tmplComp.viewPort).not.toBe(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('react to record changes', () => {
|
||||
var view, cd, ctx;
|
||||
|
||||
|
@ -324,6 +348,17 @@ class ServiceDependentDecorator {
|
|||
}
|
||||
}
|
||||
|
||||
@Template({
|
||||
selector: 'someTmpl'
|
||||
})
|
||||
class SomeTemplate {
|
||||
viewPort: ViewPort;
|
||||
constructor(viewPort: ViewPort) {
|
||||
this.viewPort = viewPort;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class AnotherDirective {
|
||||
prop:string;
|
||||
constructor() {
|
||||
|
|
|
@ -0,0 +1,153 @@
|
|||
import {describe, xit, it, expect, beforeEach, ddescribe, iit} from 'test_lib/test_lib';
|
||||
import {View, ProtoView} from 'core/compiler/view';
|
||||
import {ViewPort} from 'core/compiler/viewport';
|
||||
import {DOM} from 'facade/dom';
|
||||
import {ListWrapper, MapWrapper} from 'facade/collection';
|
||||
import {Injector} from 'di/di';
|
||||
import {ProtoElementInjector, ElementInjector} from 'core/compiler/element_injector';
|
||||
import {ProtoRecordRange} from 'change_detection/record_range';
|
||||
import {Parser} from 'change_detection/parser/parser';
|
||||
import {Lexer} from 'change_detection/parser/lexer';
|
||||
|
||||
function createElement(html) {
|
||||
return DOM.createTemplate(html).content.firstChild;
|
||||
}
|
||||
|
||||
function createView(nodes) {
|
||||
return new View(nodes, [], [], [], [], new ProtoRecordRange(), null);
|
||||
}
|
||||
|
||||
export function main() {
|
||||
describe('viewport', () => {
|
||||
var viewPort, parentView, protoView, dom, customViewWithOneNode,
|
||||
customViewWithTwoNodes, elementInjector;
|
||||
|
||||
beforeEach(() => {
|
||||
dom = createElement(`<div><stuff></stuff><div insert-after-me></div><stuff></stuff></div>`);
|
||||
var insertionElement = dom.childNodes[1];
|
||||
parentView = createView([dom.childNodes[0]]);
|
||||
protoView = new ProtoView(createElement('<div>hi</div>'), new ProtoRecordRange());
|
||||
elementInjector = new ElementInjector(null, null, null);
|
||||
viewPort = new ViewPort(parentView, insertionElement, protoView, elementInjector);
|
||||
customViewWithOneNode = createView([createElement('<div>single</div>')]);
|
||||
customViewWithTwoNodes = createView([createElement('<div>one</div>'), createElement('<div>two</div>')]);
|
||||
});
|
||||
|
||||
describe('when detached', () => {
|
||||
it('should throw if create is called', () => {
|
||||
expect(() => viewPort.create()).toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when attached', () => {
|
||||
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.attach(new Injector([]), null);
|
||||
var fillerView = createView([createElement('<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);
|
||||
|
||||
var removedView = viewPort.remove();
|
||||
|
||||
expect(textInViewPort()).toEqual('filler');
|
||||
expect(removedView).toBe(customViewWithOneNode);
|
||||
expect(viewPort.length).toBe(1);
|
||||
});
|
||||
|
||||
it('should remove the view at a given index', () => {
|
||||
viewPort.insert(customViewWithOneNode);
|
||||
viewPort.insert(customViewWithTwoNodes);
|
||||
|
||||
var removedView = viewPort.remove(1);
|
||||
expect(removedView).toBe(customViewWithOneNode);
|
||||
expect(textInViewPort()).toEqual('filler one two');
|
||||
expect(viewPort.get(1)).toBe(customViewWithTwoNodes);
|
||||
expect(viewPort.length).toBe(2);
|
||||
});
|
||||
|
||||
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.attach(new Injector([]), null);
|
||||
|
||||
var pv = new ProtoView(createElement('<div class="ng-binding">{{}}</div>'),
|
||||
new ProtoRecordRange());
|
||||
pv.bindElement(new ProtoElementInjector(null, 1, [SomeDirective]));
|
||||
pv.bindTextNode(0, parser.parseBinding('foo').ast);
|
||||
fancyView = pv.instantiate(new Object(), null, null);
|
||||
});
|
||||
|
||||
it('attaching should update rootElementInjectors and parent RR', () => {
|
||||
viewPort.insert(fancyView);
|
||||
ListWrapper.forEach(fancyView.rootElementInjectors, (inj) =>
|
||||
expect(inj.parent).toBe(elementInjector));
|
||||
expect(parentView.recordRange.findFirstEnabledRecord()).not.toBe(null);
|
||||
});
|
||||
|
||||
it('detaching should update rootElementInjectors and parent RR', () => {
|
||||
viewPort.insert(fancyView);
|
||||
viewPort.remove();
|
||||
ListWrapper.forEach(fancyView.rootElementInjectors, (inj) =>
|
||||
expect(inj.parent).toBe(null));
|
||||
expect(parentView.recordRange.findFirstEnabledRecord()).toBe(null);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
class SomeDirective {
|
||||
prop;
|
||||
constructor() {
|
||||
this.prop = 'foo';
|
||||
}
|
||||
}
|
|
@ -47,6 +47,12 @@ class DOM {
|
|||
static appendChild(el, node) {
|
||||
el.append(node);
|
||||
}
|
||||
static removeChild(el, node) {
|
||||
node.remove();
|
||||
}
|
||||
static insertAfter(el, node) {
|
||||
el.parentNode.insertBefore(node, el.nextNode);
|
||||
}
|
||||
static setText(Text text, String value) {
|
||||
text.text = value;
|
||||
}
|
||||
|
|
|
@ -40,6 +40,12 @@ export class DOM {
|
|||
static appendChild(el, node) {
|
||||
el.appendChild(node);
|
||||
}
|
||||
static removeChild(el, node) {
|
||||
el.removeChild(node);
|
||||
}
|
||||
static insertAfter(el, node) {
|
||||
el.parentNode.insertBefore(node, el.nextSibling);
|
||||
}
|
||||
static setInnerHTML(el, value) {
|
||||
el.innerHTML = value;
|
||||
}
|
||||
|
|
|
@ -33,6 +33,10 @@ class NotExpect extends gns.NotExpect {
|
|||
void toEqual(expected) => toHaveSameProps(expected);
|
||||
}
|
||||
|
||||
beforeEach(fn) {
|
||||
gns.beforeEach(_enableReflection(fn));
|
||||
}
|
||||
|
||||
it(name, fn) {
|
||||
gns.it(name, _enableReflection(_handleAsync(fn)));
|
||||
}
|
||||
|
@ -62,4 +66,4 @@ _handleAsync(fn) {
|
|||
}
|
||||
|
||||
return fn;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue