feat(compiler): attach components and project light dom during compilation.
Closes #2529 BREAKING CHANGES: - shadow dom emulation no longer supports the `<content>` tag. Use the new `<ng-content>` instead (works with all shadow dom strategies). - removed `DomRenderer.setViewRootNodes` and `AppViewManager.getComponentView` -> use `DomRenderer.getNativeElementSync(elementRef)` and change shadow dom directly - the `Renderer` interface has changed: * `createView` now also has to support sub views * the notion of a container has been removed. Instead, the renderer has to implement methods to attach views next to elements or other views. * a RenderView now contains multiple RenderFragments. Fragments are used to move DOM nodes around. Internal changes / design changes: - Introduce notion of view fragments on render side - DomProtoViews and DomViews on render side are merged, AppProtoViews are not merged, AppViews are partially merged (they share arrays with the other merged AppViews but we keep individual AppView instances for now). - DomProtoViews always have a `<template>` element as root * needed for storing subviews * we have less chunks of DOM to clone now - remove fake ElementBinder / Bound element for root text bindings and model them explicitly. This removes a lot of special cases we had! - AppView shares data with nested component views - some methods in AppViewManager (create, hydrate, dehydrate) are iterative now * now possible as we have all child AppViews / ElementRefs already in an array!
This commit is contained in:
@@ -36,7 +36,7 @@ export function runCompilerCommonTests() {
|
||||
}
|
||||
var tplLoader = new FakeViewLoader(urlData);
|
||||
mockStepFactory = new MockStepFactory([new MockStep(processClosure)]);
|
||||
return new DomCompiler(mockStepFactory, tplLoader);
|
||||
return new DomCompiler(mockStepFactory, tplLoader, false);
|
||||
}
|
||||
|
||||
describe('compile', () => {
|
||||
@@ -64,7 +64,8 @@ export function runCompilerCommonTests() {
|
||||
{id: 'id', selector: 'CUSTOM', type: DirectiveMetadata.COMPONENT_TYPE});
|
||||
compiler.compileHost(dirMetadata)
|
||||
.then((protoView) => {
|
||||
expect(DOM.tagName(resolveInternalDomProtoView(protoView.render).element))
|
||||
expect(DOM.tagName(DOM.firstChild(DOM.content(
|
||||
resolveInternalDomProtoView(protoView.render).rootElement))))
|
||||
.toEqual('CUSTOM');
|
||||
expect(mockStepFactory.viewDef.directives).toEqual([dirMetadata]);
|
||||
expect(protoView.variableBindings)
|
||||
@@ -79,7 +80,7 @@ export function runCompilerCommonTests() {
|
||||
compiler.compile(
|
||||
new ViewDefinition({componentId: 'someId', template: 'inline component'}))
|
||||
.then((protoView) => {
|
||||
expect(DOM.getInnerHTML(resolveInternalDomProtoView(protoView.render).element))
|
||||
expect(DOM.getInnerHTML(resolveInternalDomProtoView(protoView.render).rootElement))
|
||||
.toEqual('inline component');
|
||||
async.done();
|
||||
});
|
||||
@@ -90,7 +91,7 @@ export function runCompilerCommonTests() {
|
||||
var compiler = createCompiler(EMPTY_STEP, urlData);
|
||||
compiler.compile(new ViewDefinition({componentId: 'someId', templateAbsUrl: 'someUrl'}))
|
||||
.then((protoView) => {
|
||||
expect(DOM.getInnerHTML(resolveInternalDomProtoView(protoView.render).element))
|
||||
expect(DOM.getInnerHTML(resolveInternalDomProtoView(protoView.render).rootElement))
|
||||
.toEqual('url component');
|
||||
async.done();
|
||||
});
|
||||
|
||||
@@ -2,9 +2,12 @@ import {describe, beforeEach, expect, it, iit, ddescribe, el} from 'angular2/tes
|
||||
import {TextInterpolationParser} from 'angular2/src/render/dom/compiler/text_interpolation_parser';
|
||||
import {CompilePipeline} from 'angular2/src/render/dom/compiler/compile_pipeline';
|
||||
import {MapWrapper, ListWrapper} from 'angular2/src/facade/collection';
|
||||
import {Lexer, Parser} from 'angular2/change_detection';
|
||||
import {Lexer, Parser, ASTWithSource} from 'angular2/change_detection';
|
||||
import {IgnoreChildrenStep} from './pipeline_spec';
|
||||
import {ElementBinderBuilder} from 'angular2/src/render/dom/view/proto_view_builder';
|
||||
import {
|
||||
ProtoViewBuilder,
|
||||
ElementBinderBuilder
|
||||
} from 'angular2/src/render/dom/view/proto_view_builder';
|
||||
import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||
|
||||
export function main() {
|
||||
@@ -14,48 +17,55 @@ export function main() {
|
||||
[new IgnoreChildrenStep(), new TextInterpolationParser(new Parser(new Lexer()))]);
|
||||
}
|
||||
|
||||
function process(element): List<ElementBinderBuilder> {
|
||||
return ListWrapper.map(createPipeline().process(element),
|
||||
(compileElement) => compileElement.inheritedElementBinder);
|
||||
function process(templateString: string): ProtoViewBuilder {
|
||||
var compileElements = createPipeline().process(DOM.createTemplate(templateString));
|
||||
return compileElements[0].inheritedProtoView;
|
||||
}
|
||||
|
||||
function assertTextBinding(elementBinder, bindingIndex, nodeIndex, expression) {
|
||||
expect(elementBinder.textBindings[bindingIndex].source).toEqual(expression);
|
||||
expect(elementBinder.textBindingNodes[bindingIndex])
|
||||
.toEqual(DOM.childNodes(DOM.templateAwareRoot(elementBinder.element))[nodeIndex]);
|
||||
function assertRootTextBinding(protoViewBuilder: ProtoViewBuilder, nodeIndex: number,
|
||||
expression: string) {
|
||||
var node = DOM.childNodes(DOM.templateAwareRoot(protoViewBuilder.rootElement))[nodeIndex];
|
||||
expect(protoViewBuilder.rootTextBindings.get(node).source).toEqual(expression);
|
||||
}
|
||||
|
||||
it('should find text interpolation in normal elements', () => {
|
||||
var result = process(el('<div>{{expr1}}<span></span>{{expr2}}</div>'))[0];
|
||||
assertTextBinding(result, 0, 0, "{{expr1}}");
|
||||
assertTextBinding(result, 1, 2, "{{expr2}}");
|
||||
function assertElementTextBinding(elementBinderBuilder: ElementBinderBuilder, nodeIndex: number,
|
||||
expression: string) {
|
||||
var node = DOM.childNodes(DOM.templateAwareRoot(elementBinderBuilder.element))[nodeIndex];
|
||||
expect(elementBinderBuilder.textBindings.get(node).source).toEqual(expression);
|
||||
}
|
||||
|
||||
it('should find root text interpolations', () => {
|
||||
var result = process('{{expr1}}{{expr2}}<div></div>{{expr3}}');
|
||||
assertRootTextBinding(result, 0, "{{expr1}}{{expr2}}");
|
||||
assertRootTextBinding(result, 2, "{{expr3}}");
|
||||
});
|
||||
|
||||
it('should find text interpolation in template elements', () => {
|
||||
var result = process(el('<template>{{expr1}}<span></span>{{expr2}}</template>'))[0];
|
||||
assertTextBinding(result, 0, 0, "{{expr1}}");
|
||||
assertTextBinding(result, 1, 2, "{{expr2}}");
|
||||
it('should find text interpolation in normal elements', () => {
|
||||
var result = process('<div>{{expr1}}<span></span>{{expr2}}</div>');
|
||||
assertElementTextBinding(result.elements[0], 0, "{{expr1}}");
|
||||
assertElementTextBinding(result.elements[0], 2, "{{expr2}}");
|
||||
});
|
||||
|
||||
it('should allow multiple expressions', () => {
|
||||
var result = process(el('<div>{{expr1}}{{expr2}}</div>'))[0];
|
||||
assertTextBinding(result, 0, 0, "{{expr1}}{{expr2}}");
|
||||
var result = process('<div>{{expr1}}{{expr2}}</div>');
|
||||
assertElementTextBinding(result.elements[0], 0, "{{expr1}}{{expr2}}");
|
||||
});
|
||||
|
||||
it('should not interpolate when compileChildren is false', () => {
|
||||
var results = process(el('<div>{{included}}<span ignore-children>{{excluded}}</span></div>'));
|
||||
assertTextBinding(results[0], 0, 0, "{{included}}");
|
||||
expect(results[1]).toBe(results[0]);
|
||||
var results = process('<div>{{included}}<span ignore-children>{{excluded}}</span></div>');
|
||||
assertElementTextBinding(results.elements[0], 0, "{{included}}");
|
||||
expect(results.elements.length).toBe(1);
|
||||
expect(results.elements[0].textBindings.size).toBe(1);
|
||||
});
|
||||
|
||||
it('should allow fixed text before, in between and after expressions', () => {
|
||||
var result = process(el('<div>a{{expr1}}b{{expr2}}c</div>'))[0];
|
||||
assertTextBinding(result, 0, 0, "a{{expr1}}b{{expr2}}c");
|
||||
var result = process('<div>a{{expr1}}b{{expr2}}c</div>');
|
||||
assertElementTextBinding(result.elements[0], 0, "a{{expr1}}b{{expr2}}c");
|
||||
});
|
||||
|
||||
it('should escape quotes in fixed parts', () => {
|
||||
var result = process(el("<div>'\"a{{expr1}}</div>"))[0];
|
||||
assertTextBinding(result, 0, 0, "'\"a{{expr1}}");
|
||||
var result = process("<div>'\"a{{expr1}}</div>");
|
||||
assertElementTextBinding(result.elements[0], 0, "'\"a{{expr1}}");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ export function main() {
|
||||
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 rootElement = DOM.createTemplate('<template if="true">a</template>');
|
||||
var results = createPipeline().process(rootElement);
|
||||
|
||||
expect(stringifyElement(results[1].element))
|
||||
@@ -38,32 +38,32 @@ export function main() {
|
||||
});
|
||||
|
||||
it('should mark the new <template> element as viewRoot', () => {
|
||||
var rootElement = el('<div><template if="true">a</template></div>');
|
||||
var rootElement = DOM.createTemplate('<template if="true">a</template>');
|
||||
var results = createPipeline().process(rootElement);
|
||||
expect(results[2].isViewRoot).toBe(true);
|
||||
});
|
||||
|
||||
it('should not wrap the root element', () => {
|
||||
var rootElement = el('<div></div>');
|
||||
var rootElement = DOM.createTemplate('');
|
||||
var results = createPipeline().process(rootElement);
|
||||
expect(results.length).toBe(1);
|
||||
expect(stringifyElement(rootElement)).toEqual('<div></div>');
|
||||
expect(stringifyElement(rootElement)).toEqual('<template></template>');
|
||||
});
|
||||
|
||||
it('should copy over the elementDescription', () => {
|
||||
var rootElement = el('<div><template if="true">a</template></div>');
|
||||
var rootElement = DOM.createTemplate('<template if="true">a</template>');
|
||||
var results = createPipeline().process(rootElement);
|
||||
expect(results[2].elementDescription).toBe(results[1].elementDescription);
|
||||
});
|
||||
|
||||
it('should clean out the inheritedElementBinder', () => {
|
||||
var rootElement = el('<div><template if="true">a</template></div>');
|
||||
var rootElement = DOM.createTemplate('<template if="true">a</template>');
|
||||
var results = createPipeline().process(rootElement);
|
||||
expect(results[2].inheritedElementBinder).toBe(null);
|
||||
});
|
||||
|
||||
it('should create a nestedProtoView', () => {
|
||||
var rootElement = el('<div><template if="true">a</template></div>');
|
||||
var rootElement = DOM.createTemplate('<template if="true">a</template>');
|
||||
var results = createPipeline().process(rootElement);
|
||||
expect(results[2].inheritedProtoView).not.toBe(null);
|
||||
expect(results[2].inheritedProtoView)
|
||||
@@ -78,18 +78,19 @@ export function main() {
|
||||
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 rootElement = DOM.createTemplate('<span template=""></span>');
|
||||
var originalChild = DOM.firstChild(DOM.content(rootElement));
|
||||
var results = createPipeline().process(rootElement);
|
||||
expect(results[0].element).toBe(rootElement);
|
||||
expect(stringifyElement(results[0].element))
|
||||
.toEqual('<div><template class="ng-binding"></template></div>');
|
||||
expect(stringifyElement(results[2].element)).toEqual('<span template=""></span>');
|
||||
expect(results[2].element).toBe(originalChild);
|
||||
.toEqual('<template><template class="ng-binding"></template></template>');
|
||||
expect(stringifyElement(results[2].element))
|
||||
.toEqual('<template><span template=""></span></template>');
|
||||
expect(DOM.firstChild(DOM.content(results[2].element))).toBe(originalChild);
|
||||
});
|
||||
|
||||
it('should work with top-level template node', () => {
|
||||
var rootElement = el('<template><div template>x</div></template>');
|
||||
var rootElement = DOM.createTemplate('<div template>x</div>');
|
||||
var originalChild = DOM.content(rootElement).childNodes[0];
|
||||
var results = createPipeline().process(rootElement);
|
||||
|
||||
@@ -98,17 +99,17 @@ export function main() {
|
||||
expect(results[2].isViewRoot).toBe(true);
|
||||
expect(stringifyElement(results[0].element))
|
||||
.toEqual('<template><template class="ng-binding"></template></template>');
|
||||
expect(results[2].element).toBe(originalChild);
|
||||
expect(DOM.firstChild(DOM.content(results[2].element))).toBe(originalChild);
|
||||
});
|
||||
|
||||
it('should mark the element as viewRoot', () => {
|
||||
var rootElement = el('<div><div template></div></div>');
|
||||
var rootElement = DOM.createTemplate('<div template></div>');
|
||||
var results = createPipeline().process(rootElement);
|
||||
expect(results[2].isViewRoot).toBe(true);
|
||||
});
|
||||
|
||||
it('should add property bindings from the template attribute', () => {
|
||||
var rootElement = el('<div><div template="some-prop:expr"></div></div>');
|
||||
var rootElement = DOM.createTemplate('<div template="some-prop:expr"></div>');
|
||||
var results = createPipeline().process(rootElement);
|
||||
expect(results[1].inheritedElementBinder.propertyBindings.get('someProp').source)
|
||||
.toEqual('expr');
|
||||
@@ -116,14 +117,14 @@ export function main() {
|
||||
});
|
||||
|
||||
it('should add variable mappings from the template attribute to the nestedProtoView', () => {
|
||||
var rootElement = el('<div><div template="var var-name=mapName"></div></div>');
|
||||
var rootElement = DOM.createTemplate('<div template="var var-name=mapName"></div>');
|
||||
var results = createPipeline().process(rootElement);
|
||||
expect(results[2].inheritedProtoView.variableBindings)
|
||||
.toEqual(MapWrapper.createFromStringMap({'mapName': 'varName'}));
|
||||
});
|
||||
|
||||
it('should add entries without value as attributes to the element', () => {
|
||||
var rootElement = el('<div><div template="varname"></div></div>');
|
||||
var rootElement = DOM.createTemplate('<div template="varname"></div>');
|
||||
var results = createPipeline().process(rootElement);
|
||||
expect(results[1].attrs().get('varname')).toEqual('');
|
||||
expect(results[1].inheritedElementBinder.propertyBindings).toEqual(new Map());
|
||||
@@ -131,32 +132,32 @@ export function main() {
|
||||
});
|
||||
|
||||
it('should iterate properly after a template dom modification', () => {
|
||||
var rootElement = el('<div><div template></div><after></after></div>');
|
||||
var rootElement = DOM.createTemplate('<div template></div><after></after>');
|
||||
var results = createPipeline().process(rootElement);
|
||||
// 1 root + 2 initial + 1 generated template elements
|
||||
expect(results.length).toEqual(4);
|
||||
// 1 root + 2 initial + 2 generated template elements
|
||||
expect(results.length).toEqual(5);
|
||||
});
|
||||
|
||||
it('should copy over the elementDescription', () => {
|
||||
var rootElement = el('<div><span template=""></span></div>');
|
||||
var rootElement = DOM.createTemplate('<span template=""></span>');
|
||||
var results = createPipeline().process(rootElement);
|
||||
expect(results[2].elementDescription).toBe(results[1].elementDescription);
|
||||
});
|
||||
|
||||
it('should clean out the inheritedElementBinder', () => {
|
||||
var rootElement = el('<div><span template=""></span></div>');
|
||||
var rootElement = DOM.createTemplate('<span template=""></span>');
|
||||
var results = createPipeline().process(rootElement);
|
||||
expect(results[2].inheritedElementBinder).toBe(null);
|
||||
});
|
||||
|
||||
it('should create a nestedProtoView', () => {
|
||||
var rootElement = el('<div><span template=""></span></div>');
|
||||
var rootElement = DOM.createTemplate('<span template=""></span>');
|
||||
var results = createPipeline().process(rootElement);
|
||||
expect(results[2].inheritedProtoView).not.toBe(null);
|
||||
expect(results[2].inheritedProtoView)
|
||||
.toBe(results[1].inheritedElementBinder.nestedProtoView);
|
||||
expect(stringifyElement(results[2].inheritedProtoView.rootElement))
|
||||
.toEqual('<span template=""></span>');
|
||||
.toEqual('<template><span template=""></span></template>');
|
||||
});
|
||||
|
||||
});
|
||||
@@ -164,24 +165,25 @@ export function main() {
|
||||
describe('elements with *directive_name attribute', () => {
|
||||
|
||||
it('should replace the element with an empty <template> element', () => {
|
||||
var rootElement = el('<div><span *ng-if></span></div>');
|
||||
var originalChild = rootElement.childNodes[0];
|
||||
var rootElement = DOM.createTemplate('<span *ng-if></span>');
|
||||
var originalChild = DOM.firstChild(DOM.content(rootElement));
|
||||
var results = createPipeline().process(rootElement);
|
||||
expect(results[0].element).toBe(rootElement);
|
||||
expect(stringifyElement(results[0].element))
|
||||
.toEqual('<div><template class="ng-binding" ng-if=""></template></div>');
|
||||
expect(stringifyElement(results[2].element)).toEqual('<span *ng-if=""></span>');
|
||||
expect(results[2].element).toBe(originalChild);
|
||||
.toEqual('<template><template class="ng-binding" ng-if=""></template></template>');
|
||||
expect(stringifyElement(results[2].element))
|
||||
.toEqual('<template><span *ng-if=""></span></template>');
|
||||
expect(DOM.firstChild(DOM.content(results[2].element))).toBe(originalChild);
|
||||
});
|
||||
|
||||
it('should mark the element as viewRoot', () => {
|
||||
var rootElement = el('<div><div *foo="bar"></div></div>');
|
||||
var rootElement = DOM.createTemplate('<div *foo="bar"></div>');
|
||||
var results = createPipeline().process(rootElement);
|
||||
expect(results[2].isViewRoot).toBe(true);
|
||||
});
|
||||
|
||||
it('should work with top-level template node', () => {
|
||||
var rootElement = el('<template><div *foo>x</div></template>');
|
||||
var rootElement = DOM.createTemplate('<div *foo>x</div>');
|
||||
var originalChild = DOM.content(rootElement).childNodes[0];
|
||||
var results = createPipeline().process(rootElement);
|
||||
|
||||
@@ -190,11 +192,11 @@ export function main() {
|
||||
expect(results[2].isViewRoot).toBe(true);
|
||||
expect(stringifyElement(results[0].element))
|
||||
.toEqual('<template><template class="ng-binding" foo=""></template></template>');
|
||||
expect(results[2].element).toBe(originalChild);
|
||||
expect(DOM.firstChild(DOM.content(results[2].element))).toBe(originalChild);
|
||||
});
|
||||
|
||||
it('should add property bindings from the template attribute', () => {
|
||||
var rootElement = el('<div><div *prop="expr"></div></div>');
|
||||
var rootElement = DOM.createTemplate('<div *prop="expr"></div>');
|
||||
var results = createPipeline().process(rootElement);
|
||||
expect(results[1].inheritedElementBinder.propertyBindings.get('prop').source)
|
||||
.toEqual('expr');
|
||||
@@ -202,14 +204,14 @@ export function main() {
|
||||
});
|
||||
|
||||
it('should add variable mappings from the template attribute to the nestedProtoView', () => {
|
||||
var rootElement = el('<div><div *foreach="var varName=mapName"></div></div>');
|
||||
var rootElement = DOM.createTemplate('<div *foreach="var varName=mapName"></div>');
|
||||
var results = createPipeline().process(rootElement);
|
||||
expect(results[2].inheritedProtoView.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 rootElement = DOM.createTemplate('<div *varname></div>');
|
||||
var results = createPipeline().process(rootElement);
|
||||
expect(results[1].attrs().get('varname')).toEqual('');
|
||||
expect(results[1].inheritedElementBinder.propertyBindings).toEqual(new Map());
|
||||
@@ -217,32 +219,32 @@ export function main() {
|
||||
});
|
||||
|
||||
it('should iterate properly after a template dom modification', () => {
|
||||
var rootElement = el('<div><div *foo></div><after></after></div>');
|
||||
var rootElement = DOM.createTemplate('<div *foo></div><after></after>');
|
||||
var results = createPipeline().process(rootElement);
|
||||
// 1 root + 2 initial + 1 generated template elements
|
||||
expect(results.length).toEqual(4);
|
||||
// 1 root + 2 initial + 2 generated template elements
|
||||
expect(results.length).toEqual(5);
|
||||
});
|
||||
|
||||
it('should copy over the elementDescription', () => {
|
||||
var rootElement = el('<div><span *foo></span></div>');
|
||||
var rootElement = DOM.createTemplate('<span *foo></span>');
|
||||
var results = createPipeline().process(rootElement);
|
||||
expect(results[2].elementDescription).toBe(results[1].elementDescription);
|
||||
});
|
||||
|
||||
it('should clean out the inheritedElementBinder', () => {
|
||||
var rootElement = el('<div><span *foo></span></div>');
|
||||
var rootElement = DOM.createTemplate('<span *foo></span>');
|
||||
var results = createPipeline().process(rootElement);
|
||||
expect(results[2].inheritedElementBinder).toBe(null);
|
||||
});
|
||||
|
||||
it('should create a nestedProtoView', () => {
|
||||
var rootElement = el('<div><span *foo></span></div>');
|
||||
var rootElement = DOM.createTemplate('<span *foo></span>');
|
||||
var results = createPipeline().process(rootElement);
|
||||
expect(results[2].inheritedProtoView).not.toBe(null);
|
||||
expect(results[2].inheritedProtoView)
|
||||
.toBe(results[1].inheritedElementBinder.nestedProtoView);
|
||||
expect(stringifyElement(results[2].inheritedProtoView.rootElement))
|
||||
.toEqual('<span *foo=""></span>');
|
||||
.toEqual('<template><span *foo=""></span></template>');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -16,10 +16,11 @@ import {
|
||||
import {MapWrapper} from 'angular2/src/facade/collection';
|
||||
import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||
|
||||
import {DomTestbed, TestView, elRef} from './dom_testbed';
|
||||
import {DomTestbed, TestRootView, elRef} from './dom_testbed';
|
||||
|
||||
import {ViewDefinition, DirectiveMetadata, RenderViewRef} from 'angular2/src/render/api';
|
||||
import {DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES} from 'angular2/src/render/dom/dom_renderer';
|
||||
import {ShadowDomStrategy, NativeShadowDomStrategy} from 'angular2/render';
|
||||
import {bind} from 'angular2/di';
|
||||
|
||||
export function main() {
|
||||
@@ -27,101 +28,69 @@ export function main() {
|
||||
beforeEachBindings(() => [DomTestbed]);
|
||||
|
||||
it('should create and destroy root host views while using the given elements in place',
|
||||
inject([AsyncTestCompleter, DomTestbed], (async, tb) => {
|
||||
inject([AsyncTestCompleter, DomTestbed], (async, tb: DomTestbed) => {
|
||||
tb.compiler.compileHost(someComponent)
|
||||
.then((hostProtoViewDto) => {
|
||||
var view =
|
||||
new TestView(tb.renderer.createRootHostView(hostProtoViewDto.render, '#root'));
|
||||
expect(view.rawView.rootNodes[0]).toEqual(tb.rootEl);
|
||||
|
||||
tb.renderer.destroyView(view.viewRef);
|
||||
// destroying a root view should not disconnect it!
|
||||
var view = new TestRootView(
|
||||
tb.renderer.createRootHostView(hostProtoViewDto.render, 0, '#root'));
|
||||
expect(tb.rootEl.parentNode).toBeTruthy();
|
||||
expect(view.hostElement).toEqual(tb.rootEl);
|
||||
|
||||
tb.renderer.detachFragment(view.fragments[0]);
|
||||
tb.renderer.destroyView(view.viewRef);
|
||||
expect(tb.rootEl.parentNode).toBeFalsy();
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should attach and detach component views',
|
||||
inject([AsyncTestCompleter, DomTestbed], (async, tb) => {
|
||||
tb.compileAll([
|
||||
someComponent,
|
||||
new ViewDefinition({componentId: 'someComponent', template: 'hello', directives: []})
|
||||
])
|
||||
.then((protoViewDtos) => {
|
||||
var rootView = tb.createRootView(protoViewDtos[0]);
|
||||
var cmpView = tb.createComponentView(rootView.viewRef, 0, protoViewDtos[1]);
|
||||
expect(tb.rootEl).toHaveText('hello');
|
||||
tb.destroyComponentView(rootView.viewRef, 0, cmpView.viewRef);
|
||||
expect(tb.rootEl).toHaveText('');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
it('should update text nodes',
|
||||
inject([AsyncTestCompleter, DomTestbed], (async, tb: DomTestbed) => {
|
||||
tb.compileAndMerge(
|
||||
someComponent,
|
||||
[
|
||||
new ViewDefinition(
|
||||
{componentId: 'someComponent', template: '{{a}}', directives: []})
|
||||
])
|
||||
.then((protoViewMergeMappings) => {
|
||||
var rootView = tb.createView(protoViewMergeMappings[0]);
|
||||
|
||||
it('should not create LightDom instances if the host element is empty',
|
||||
inject([AsyncTestCompleter, DomTestbed], (async, tb) => {
|
||||
tb.compileAll([
|
||||
someComponent,
|
||||
new ViewDefinition({
|
||||
componentId: 'someComponent',
|
||||
template: '<some-comp> <!-- comment -->\n </some-comp>',
|
||||
directives: [someComponent]
|
||||
})
|
||||
])
|
||||
.then((protoViewDtos) => {
|
||||
var rootView = tb.createRootView(protoViewDtos[0]);
|
||||
var cmpView = tb.createComponentView(rootView.viewRef, 0, protoViewDtos[1]);
|
||||
expect(cmpView.rawView.proto.elementBinders[0].componentId).toBe('someComponent');
|
||||
expect(cmpView.rawView.boundElements[0].lightDom).toBe(null);
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should update text nodes', inject([AsyncTestCompleter, DomTestbed], (async, tb) => {
|
||||
tb.compileAll([
|
||||
someComponent,
|
||||
new ViewDefinition({componentId: 'someComponent', template: '{{a}}', directives: []})
|
||||
])
|
||||
.then((protoViewDtos) => {
|
||||
var rootView = tb.createRootView(protoViewDtos[0]);
|
||||
var cmpView = tb.createComponentView(rootView.viewRef, 0, protoViewDtos[1]);
|
||||
|
||||
tb.renderer.setText(cmpView.viewRef, 0, 'hello');
|
||||
expect(tb.rootEl).toHaveText('hello');
|
||||
tb.renderer.setText(rootView.viewRef, 0, 'hello');
|
||||
expect(rootView.hostElement).toHaveText('hello');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should update any element property/attributes/class/style independent of the compilation',
|
||||
inject([AsyncTestCompleter, DomTestbed], (async, tb) => {
|
||||
tb.compileAll([
|
||||
someComponent,
|
||||
new ViewDefinition({
|
||||
componentId: 'someComponent',
|
||||
template: '<input [title]="y" style="position:absolute">',
|
||||
directives: []
|
||||
})
|
||||
])
|
||||
.then((protoViewDtos) => {
|
||||
var rootView = tb.createRootView(protoViewDtos[0]);
|
||||
var cmpView = tb.createComponentView(rootView.viewRef, 0, protoViewDtos[1]);
|
||||
inject([AsyncTestCompleter, DomTestbed], (async, tb: DomTestbed) => {
|
||||
tb.compileAndMerge(someComponent,
|
||||
[
|
||||
new ViewDefinition({
|
||||
componentId: 'someComponent',
|
||||
template: '<input [title]="y" style="position:absolute">',
|
||||
directives: []
|
||||
})
|
||||
])
|
||||
.then((protoViewMergeMappings) => {
|
||||
var rootView = tb.createView(protoViewMergeMappings[0]);
|
||||
|
||||
var el = DOM.childNodes(tb.rootEl)[0];
|
||||
tb.renderer.setElementProperty(elRef(cmpView.viewRef, 0), 'value', 'hello');
|
||||
var elr = elRef(rootView.viewRef, 1);
|
||||
var el = DOM.childNodes(rootView.hostElement)[0];
|
||||
tb.renderer.setElementProperty(elr, 'value', 'hello');
|
||||
expect((<HTMLInputElement>el).value).toEqual('hello');
|
||||
|
||||
tb.renderer.setElementClass(elRef(cmpView.viewRef, 0), 'a', true);
|
||||
expect((<HTMLInputElement>DOM.childNodes(tb.rootEl)[0]).value).toEqual('hello');
|
||||
tb.renderer.setElementClass(elRef(cmpView.viewRef, 0), 'a', false);
|
||||
tb.renderer.setElementClass(elr, 'a', true);
|
||||
expect((<HTMLInputElement>DOM.childNodes(rootView.hostElement)[0]).value)
|
||||
.toEqual('hello');
|
||||
tb.renderer.setElementClass(elr, 'a', false);
|
||||
expect(DOM.hasClass(el, 'a')).toBe(false);
|
||||
|
||||
tb.renderer.setElementStyle(elRef(cmpView.viewRef, 0), 'width', '10px');
|
||||
tb.renderer.setElementStyle(elr, 'width', '10px');
|
||||
expect(DOM.getStyle(el, 'width')).toEqual('10px');
|
||||
tb.renderer.setElementStyle(elRef(cmpView.viewRef, 0), 'width', null);
|
||||
tb.renderer.setElementStyle(elr, 'width', null);
|
||||
expect(DOM.getStyle(el, 'width')).toEqual('');
|
||||
|
||||
tb.renderer.setElementAttribute(elRef(cmpView.viewRef, 0), 'someAttr', 'someValue');
|
||||
tb.renderer.setElementAttribute(elr, 'someAttr', 'someValue');
|
||||
expect(DOM.getAttribute(el, 'some-attr')).toEqual('someValue');
|
||||
|
||||
async.done();
|
||||
@@ -131,16 +100,18 @@ export function main() {
|
||||
|
||||
it('should NOT reflect property values as attributes if flag is NOT set',
|
||||
inject([AsyncTestCompleter, DomTestbed], (async, tb) => {
|
||||
tb.compileAll([
|
||||
someComponent,
|
||||
new ViewDefinition(
|
||||
{componentId: 'someComponent', template: '<input [title]="y">', directives: []})
|
||||
])
|
||||
.then((protoViewDtos) => {
|
||||
var rootView = tb.createRootView(protoViewDtos[0]);
|
||||
var cmpView = tb.createComponentView(rootView.viewRef, 0, protoViewDtos[1]);
|
||||
var el = DOM.childNodes(tb.rootEl)[0];
|
||||
tb.renderer.setElementProperty(elRef(cmpView.viewRef, 0), 'maxLength', '20');
|
||||
tb.compileAndMerge(someComponent,
|
||||
[
|
||||
new ViewDefinition({
|
||||
componentId: 'someComponent',
|
||||
template: '<input [title]="y">',
|
||||
directives: []
|
||||
})
|
||||
])
|
||||
.then((protoViewMergeMappings) => {
|
||||
var rootView = tb.createView(protoViewMergeMappings[0]);
|
||||
var el = DOM.childNodes(rootView.hostElement)[0];
|
||||
tb.renderer.setElementProperty(elRef(rootView.viewRef, 1), 'maxLength', '20');
|
||||
expect(DOM.getAttribute(<HTMLInputElement>el, 'ng-reflect-max-length'))
|
||||
.toEqual(null);
|
||||
|
||||
@@ -150,21 +121,21 @@ export function main() {
|
||||
|
||||
describe('reflection', () => {
|
||||
beforeEachBindings(() => [bind(DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES).toValue(true)]);
|
||||
|
||||
it('should reflect property values as attributes if flag is set',
|
||||
inject([AsyncTestCompleter, DomTestbed], (async, tb) => {
|
||||
tb.compileAll([
|
||||
someComponent,
|
||||
new ViewDefinition({
|
||||
componentId: 'someComponent',
|
||||
template: '<input [title]="y">',
|
||||
directives: []
|
||||
})
|
||||
])
|
||||
.then((protoViewDtos) => {
|
||||
var rootView = tb.createRootView(protoViewDtos[0]);
|
||||
var cmpView = tb.createComponentView(rootView.viewRef, 0, protoViewDtos[1]);
|
||||
var el = DOM.childNodes(tb.rootEl)[0];
|
||||
tb.renderer.setElementProperty(elRef(cmpView.viewRef, 0), 'maxLength', '20');
|
||||
tb.compileAndMerge(someComponent,
|
||||
[
|
||||
new ViewDefinition({
|
||||
componentId: 'someComponent',
|
||||
template: '<input [title]="y">',
|
||||
directives: []
|
||||
})
|
||||
])
|
||||
.then((protoViewMergeMappings) => {
|
||||
var rootView = tb.createView(protoViewMergeMappings[0]);
|
||||
var el = DOM.childNodes(rootView.hostElement)[0];
|
||||
tb.renderer.setElementProperty(elRef(rootView.viewRef, 1), 'maxLength', '20');
|
||||
expect(DOM.getAttribute(<HTMLInputElement>el, 'ng-reflect-max-length'))
|
||||
.toEqual('20');
|
||||
async.done();
|
||||
@@ -174,70 +145,69 @@ export function main() {
|
||||
|
||||
if (DOM.supportsDOMEvents()) {
|
||||
it('should call actions on the element independent of the compilation',
|
||||
inject([AsyncTestCompleter, DomTestbed], (async, tb) => {
|
||||
tb.compileAll([
|
||||
someComponent,
|
||||
new ViewDefinition({
|
||||
componentId: 'someComponent',
|
||||
template: '<input [title]="y"></input>',
|
||||
directives: []
|
||||
})
|
||||
])
|
||||
.then((protoViewDtos) => {
|
||||
var views = tb.createRootViews(protoViewDtos);
|
||||
var componentView = views[1];
|
||||
inject([AsyncTestCompleter, DomTestbed], (async, tb: DomTestbed) => {
|
||||
tb.compileAndMerge(someComponent,
|
||||
[
|
||||
new ViewDefinition({
|
||||
componentId: 'someComponent',
|
||||
template: '<input [title]="y"></input>',
|
||||
directives: []
|
||||
})
|
||||
])
|
||||
.then((protoViewMergeMappings) => {
|
||||
var rootView = tb.createView(protoViewMergeMappings[0]);
|
||||
|
||||
tb.renderer.invokeElementMethod(elRef(componentView.viewRef, 0), 'setAttribute',
|
||||
tb.renderer.invokeElementMethod(elRef(rootView.viewRef, 1), 'setAttribute',
|
||||
['a', 'b']);
|
||||
|
||||
expect(DOM.getAttribute(DOM.childNodes(tb.rootEl)[0], 'a')).toEqual('b');
|
||||
expect(DOM.getAttribute(DOM.childNodes(rootView.hostElement)[0], 'a'))
|
||||
.toEqual('b');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
it('should add and remove views to and from containers',
|
||||
inject([AsyncTestCompleter, DomTestbed], (async, tb) => {
|
||||
tb.compileAll([
|
||||
someComponent,
|
||||
new ViewDefinition({
|
||||
componentId: 'someComponent',
|
||||
template: '<template>hello</template>',
|
||||
directives: []
|
||||
})
|
||||
])
|
||||
.then((protoViewDtos) => {
|
||||
var rootView = tb.createRootView(protoViewDtos[0]);
|
||||
var cmpView = tb.createComponentView(rootView.viewRef, 0, protoViewDtos[1]);
|
||||
it('should add and remove fragments',
|
||||
inject([AsyncTestCompleter, DomTestbed], (async, tb: DomTestbed) => {
|
||||
tb.compileAndMerge(someComponent,
|
||||
[
|
||||
new ViewDefinition({
|
||||
componentId: 'someComponent',
|
||||
template: '<template>hello</template>',
|
||||
directives: []
|
||||
})
|
||||
])
|
||||
.then((protoViewMergeMappings) => {
|
||||
var rootView = tb.createView(protoViewMergeMappings[0]);
|
||||
|
||||
var childProto = protoViewDtos[1].elementBinders[0].nestedProtoView;
|
||||
expect(tb.rootEl).toHaveText('');
|
||||
var childView = tb.createViewInContainer(cmpView.viewRef, 0, 0, childProto);
|
||||
expect(tb.rootEl).toHaveText('hello');
|
||||
tb.destroyViewInContainer(cmpView.viewRef, 0, 0, childView.viewRef);
|
||||
expect(tb.rootEl).toHaveText('');
|
||||
var elr = elRef(rootView.viewRef, 1);
|
||||
expect(rootView.hostElement).toHaveText('');
|
||||
var fragment = rootView.fragments[1];
|
||||
tb.renderer.attachFragmentAfterElement(elr, fragment);
|
||||
expect(rootView.hostElement).toHaveText('hello');
|
||||
tb.renderer.detachFragment(fragment);
|
||||
expect(rootView.hostElement).toHaveText('');
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should handle events', inject([AsyncTestCompleter, DomTestbed], (async, tb: DomTestbed) => {
|
||||
tb.compileAll([
|
||||
someComponent,
|
||||
new ViewDefinition({
|
||||
componentId: 'someComponent',
|
||||
template: '<input (change)="doSomething()">',
|
||||
directives: []
|
||||
})
|
||||
])
|
||||
tb.compileAndMerge(someComponent,
|
||||
[
|
||||
new ViewDefinition({
|
||||
componentId: 'someComponent',
|
||||
template: '<input (change)="doSomething()">',
|
||||
directives: []
|
||||
})
|
||||
])
|
||||
.then((protoViewDtos) => {
|
||||
var rootView = tb.createRootView(protoViewDtos[0]);
|
||||
var cmpView = tb.createComponentView(rootView.viewRef, 0, protoViewDtos[1]);
|
||||
var rootView = tb.createView(protoViewDtos[0]);
|
||||
|
||||
tb.triggerEvent(cmpView.viewRef, 0, 'change');
|
||||
var eventEntry = cmpView.events[0];
|
||||
tb.triggerEvent(elRef(rootView.viewRef, 1), 'change');
|
||||
var eventEntry = rootView.events[0];
|
||||
// bound element index
|
||||
expect(eventEntry[0]).toEqual(0);
|
||||
expect(eventEntry[0]).toEqual(1);
|
||||
// event type
|
||||
expect(eventEntry[1]).toEqual('change');
|
||||
// actual event
|
||||
@@ -247,6 +217,30 @@ export function main() {
|
||||
|
||||
}));
|
||||
|
||||
if (DOM.supportsNativeShadowDOM()) {
|
||||
describe('native shadow dom support', () => {
|
||||
beforeEachBindings(
|
||||
() => { return [bind(ShadowDomStrategy).toValue(new NativeShadowDomStrategy())]; });
|
||||
|
||||
it('should support shadow dom components',
|
||||
inject([AsyncTestCompleter, DomTestbed], (async, tb: DomTestbed) => {
|
||||
tb.compileAndMerge(
|
||||
someComponent,
|
||||
[
|
||||
new ViewDefinition(
|
||||
{componentId: 'someComponent', template: 'hello', directives: []})
|
||||
])
|
||||
.then((protoViewMergeMappings) => {
|
||||
var rootView = tb.createView(protoViewMergeMappings[0]);
|
||||
expect(DOM.getShadowRoot(rootView.hostElement)).toHaveText('hello');
|
||||
async.done();
|
||||
});
|
||||
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,48 +1,57 @@
|
||||
import {Inject, Injectable} from 'angular2/di';
|
||||
import {isPresent} from 'angular2/src/facade/lang';
|
||||
import {MapWrapper, ListWrapper, List, Map} from 'angular2/src/facade/collection';
|
||||
import {PromiseWrapper, Promise} from 'angular2/src/facade/async';
|
||||
import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||
|
||||
import {DomRenderer, DOCUMENT_TOKEN} from 'angular2/src/render/dom/dom_renderer';
|
||||
import {DefaultDomCompiler} from 'angular2/src/render/dom/compiler/compiler';
|
||||
import {DomView} from 'angular2/src/render/dom/view/view';
|
||||
import {
|
||||
RenderViewWithFragments,
|
||||
RenderFragmentRef,
|
||||
RenderViewRef,
|
||||
ProtoViewDto,
|
||||
ViewDefinition,
|
||||
EventDispatcher,
|
||||
RenderEventDispatcher,
|
||||
DirectiveMetadata,
|
||||
RenderElementRef
|
||||
RenderElementRef,
|
||||
RenderProtoViewMergeMapping,
|
||||
RenderProtoViewRef
|
||||
} from 'angular2/src/render/api';
|
||||
import {resolveInternalDomView} from 'angular2/src/render/dom/view/view';
|
||||
import {resolveInternalDomFragment} from 'angular2/src/render/dom/view/fragment';
|
||||
import {el, dispatchEvent} from 'angular2/test_lib';
|
||||
|
||||
export class TestView {
|
||||
rawView: DomView;
|
||||
export class TestRootView {
|
||||
viewRef: RenderViewRef;
|
||||
fragments: RenderFragmentRef[];
|
||||
hostElement: Element;
|
||||
events: List<List<any>>;
|
||||
|
||||
constructor(viewRef: RenderViewRef) {
|
||||
this.viewRef = viewRef;
|
||||
this.rawView = resolveInternalDomView(viewRef);
|
||||
constructor(viewWithFragments: RenderViewWithFragments) {
|
||||
this.viewRef = viewWithFragments.viewRef;
|
||||
this.fragments = viewWithFragments.fragmentRefs;
|
||||
this.hostElement = <Element>resolveInternalDomFragment(this.fragments[0])[0];
|
||||
this.events = [];
|
||||
}
|
||||
}
|
||||
|
||||
export class TestRenderElementRef implements RenderElementRef {
|
||||
constructor(public renderView: RenderViewRef, public renderBoundElementIndex: number) {}
|
||||
}
|
||||
|
||||
export function elRef(renderView: RenderViewRef, boundElementIndex: number) {
|
||||
return new TestRenderElementRef(renderView, boundElementIndex);
|
||||
}
|
||||
|
||||
class TestRenderElementRef implements RenderElementRef {
|
||||
constructor(public renderView: RenderViewRef, public boundElementIndex: number) {}
|
||||
}
|
||||
export function rootNodes(view: RenderViewRef) {}
|
||||
|
||||
class LoggingEventDispatcher implements EventDispatcher {
|
||||
class LoggingEventDispatcher implements RenderEventDispatcher {
|
||||
log: List<List<any>>;
|
||||
|
||||
constructor(log: List<List<any>>) { this.log = log; }
|
||||
|
||||
dispatchEvent(elementIndex: number, eventName: string, locals: Map<string, any>) {
|
||||
dispatchRenderEvent(elementIndex: number, eventName: string, locals: Map<string, any>) {
|
||||
this.log.push([elementIndex, eventName, locals]);
|
||||
return true;
|
||||
}
|
||||
@@ -67,71 +76,62 @@ export class DomTestbed {
|
||||
DOM.appendChild(DOM.querySelector(document, 'body'), this.rootEl);
|
||||
}
|
||||
|
||||
compileAll(directivesOrViewDefinitions:
|
||||
List<DirectiveMetadata | ViewDefinition>): Promise<List<ProtoViewDto>> {
|
||||
return PromiseWrapper.all(ListWrapper.map(directivesOrViewDefinitions, (entry) => {
|
||||
if (entry instanceof DirectiveMetadata) {
|
||||
return this.compiler.compileHost(entry);
|
||||
} else {
|
||||
return this.compiler.compile(entry);
|
||||
}
|
||||
}));
|
||||
compile(host: DirectiveMetadata, componentViews: ViewDefinition[]): Promise<ProtoViewDto[]> {
|
||||
var promises = [this.compiler.compileHost(host)];
|
||||
componentViews.forEach(view => promises.push(this.compiler.compile(view)));
|
||||
return PromiseWrapper.all(promises);
|
||||
}
|
||||
|
||||
_createTestView(viewRef: RenderViewRef) {
|
||||
var testView = new TestView(viewRef);
|
||||
this.renderer.setEventDispatcher(viewRef, new LoggingEventDispatcher(testView.events));
|
||||
merge(protoViews:
|
||||
List<ProtoViewDto | RenderProtoViewRef>): Promise<RenderProtoViewMergeMapping[]> {
|
||||
return this.compiler.mergeProtoViewsRecursively(collectMergeRenderProtoViewsRecurse(
|
||||
<ProtoViewDto>protoViews[0], ListWrapper.slice(protoViews, 1)));
|
||||
}
|
||||
|
||||
compileAndMerge(host: DirectiveMetadata,
|
||||
componentViews: ViewDefinition[]): Promise<RenderProtoViewMergeMapping[]> {
|
||||
return this.compile(host, componentViews).then(protoViewDtos => this.merge(protoViewDtos));
|
||||
}
|
||||
|
||||
_createTestView(viewWithFragments: RenderViewWithFragments) {
|
||||
var testView = new TestRootView(viewWithFragments);
|
||||
this.renderer.setEventDispatcher(viewWithFragments.viewRef,
|
||||
new LoggingEventDispatcher(testView.events));
|
||||
return testView;
|
||||
}
|
||||
|
||||
createRootView(rootProtoView: ProtoViewDto): TestView {
|
||||
var viewRef = this.renderer.createRootHostView(rootProtoView.render, '#root');
|
||||
this.renderer.hydrateView(viewRef);
|
||||
return this._createTestView(viewRef);
|
||||
createView(protoView: RenderProtoViewMergeMapping): TestRootView {
|
||||
var viewWithFragments = this.renderer.createView(protoView.mergedProtoViewRef, 0);
|
||||
this.renderer.hydrateView(viewWithFragments.viewRef);
|
||||
return this._createTestView(viewWithFragments);
|
||||
}
|
||||
|
||||
createComponentView(parentViewRef: RenderViewRef, boundElementIndex: number,
|
||||
componentProtoView: ProtoViewDto): TestView {
|
||||
var componentViewRef = this.renderer.createView(componentProtoView.render);
|
||||
this.renderer.attachComponentView(elRef(parentViewRef, boundElementIndex), componentViewRef);
|
||||
this.renderer.hydrateView(componentViewRef);
|
||||
return this._createTestView(componentViewRef);
|
||||
}
|
||||
|
||||
createRootViews(protoViews: List<ProtoViewDto>): List<TestView> {
|
||||
var views = [];
|
||||
var lastView = this.createRootView(protoViews[0]);
|
||||
views.push(lastView);
|
||||
for (var i = 1; i < protoViews.length; i++) {
|
||||
lastView = this.createComponentView(lastView.viewRef, 0, protoViews[i]);
|
||||
views.push(lastView);
|
||||
}
|
||||
return views;
|
||||
}
|
||||
|
||||
destroyComponentView(parentViewRef: RenderViewRef, boundElementIndex: number,
|
||||
componentView: RenderViewRef) {
|
||||
this.renderer.dehydrateView(componentView);
|
||||
this.renderer.detachComponentView(elRef(parentViewRef, boundElementIndex), componentView);
|
||||
}
|
||||
|
||||
createViewInContainer(parentViewRef: RenderViewRef, boundElementIndex: number, atIndex: number,
|
||||
protoView: ProtoViewDto): TestView {
|
||||
var viewRef = this.renderer.createView(protoView.render);
|
||||
this.renderer.attachViewInContainer(elRef(parentViewRef, boundElementIndex), atIndex, viewRef);
|
||||
this.renderer.hydrateView(viewRef);
|
||||
return this._createTestView(viewRef);
|
||||
}
|
||||
|
||||
destroyViewInContainer(parentViewRef: RenderViewRef, boundElementIndex: number, atIndex: number,
|
||||
viewRef: RenderViewRef) {
|
||||
this.renderer.dehydrateView(viewRef);
|
||||
this.renderer.detachViewInContainer(elRef(parentViewRef, boundElementIndex), atIndex, viewRef);
|
||||
this.renderer.destroyView(viewRef);
|
||||
}
|
||||
|
||||
triggerEvent(viewRef: RenderViewRef, boundElementIndex: number, eventName: string) {
|
||||
var element = resolveInternalDomView(viewRef).boundElements[boundElementIndex].element;
|
||||
triggerEvent(elementRef: RenderElementRef, eventName: string) {
|
||||
var element = resolveInternalDomView(elementRef.renderView)
|
||||
.boundElements[elementRef.renderBoundElementIndex];
|
||||
dispatchEvent(element, eventName);
|
||||
}
|
||||
}
|
||||
|
||||
function collectMergeRenderProtoViewsRecurse(current: ProtoViewDto,
|
||||
components: List<ProtoViewDto | RenderProtoViewRef>):
|
||||
List<RenderProtoViewRef | List<any>> {
|
||||
var result = [current.render];
|
||||
current.elementBinders.forEach((elementBinder) => {
|
||||
if (isPresent(elementBinder.nestedProtoView)) {
|
||||
result.push(collectMergeRenderProtoViewsRecurse(elementBinder.nestedProtoView, components));
|
||||
} else if (elementBinder.directives.length > 0) {
|
||||
if (components.length > 0) {
|
||||
var comp = components.shift();
|
||||
if (comp instanceof ProtoViewDto) {
|
||||
result.push(collectMergeRenderProtoViewsRecurse(comp, components));
|
||||
} else {
|
||||
result.push(comp);
|
||||
}
|
||||
} else {
|
||||
result.push(null);
|
||||
}
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
import {
|
||||
describe,
|
||||
beforeEach,
|
||||
it,
|
||||
expect,
|
||||
ddescribe,
|
||||
iit,
|
||||
SpyObject,
|
||||
el,
|
||||
proxy
|
||||
} from 'angular2/test_lib';
|
||||
import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||
import {Content} from 'angular2/src/render/dom/shadow_dom/content_tag';
|
||||
|
||||
var _scriptStart = `<script start=""></script>`;
|
||||
var _scriptEnd = `<script end=""></script>`;
|
||||
|
||||
export function main() {
|
||||
describe('Content', function() {
|
||||
var parent;
|
||||
var content;
|
||||
|
||||
beforeEach(() => {
|
||||
parent = el(`<div>${_scriptStart}${_scriptEnd}`);
|
||||
content = DOM.firstChild(parent);
|
||||
});
|
||||
|
||||
it("should insert the nodes", () => {
|
||||
var c = new Content(content, '');
|
||||
c.init(null);
|
||||
c.insert([el("<a></a>"), el("<b></b>")])
|
||||
|
||||
expect(DOM.getInnerHTML(parent))
|
||||
.toEqual(`${_scriptStart}<a></a><b></b>${_scriptEnd}`);
|
||||
});
|
||||
|
||||
it("should remove the nodes from the previous insertion", () => {
|
||||
var c = new Content(content, '');
|
||||
c.init(null);
|
||||
c.insert([el("<a></a>")]);
|
||||
c.insert([el("<b></b>")]);
|
||||
|
||||
expect(DOM.getInnerHTML(parent)).toEqual(`${_scriptStart}<b></b>${_scriptEnd}`);
|
||||
});
|
||||
|
||||
it("should insert empty list", () => {
|
||||
var c = new Content(content, '');
|
||||
c.init(null);
|
||||
c.insert([el("<a></a>")]);
|
||||
c.insert([]);
|
||||
|
||||
expect(DOM.getInnerHTML(parent)).toEqual(`${_scriptStart}${_scriptEnd}`);
|
||||
});
|
||||
});
|
||||
}
|
||||
+2
-4
@@ -32,10 +32,8 @@ export function main() {
|
||||
resetShadowDomCache();
|
||||
});
|
||||
|
||||
it('should use the host element as shadow root', () => {
|
||||
var host = el('<div><span>original content</span></div>');
|
||||
expect(strategy.prepareShadowRoot(host)).toBe(host);
|
||||
});
|
||||
it('should report that this is not the native strategy',
|
||||
() => { expect(strategy.hasNativeContentElement()).toBe(false); });
|
||||
|
||||
it('should scope styles', () => {
|
||||
var styleElement = el('<style>.foo {} :host {}</style>');
|
||||
|
||||
+2
-4
@@ -34,10 +34,8 @@ export function main() {
|
||||
resetShadowDomCache();
|
||||
});
|
||||
|
||||
it('should use the host element as shadow root', () => {
|
||||
var host = el('<div><span>original content</span></div>');
|
||||
expect(strategy.prepareShadowRoot(host)).toBe(host);
|
||||
});
|
||||
it('should report that this is not the native strategy',
|
||||
() => { expect(strategy.hasNativeContentElement()).toBe(false); });
|
||||
|
||||
it('should move the style element to the style host', () => {
|
||||
var compileElement = el('<div><style>.one {}</style></div>');
|
||||
|
||||
@@ -1,253 +0,0 @@
|
||||
import {
|
||||
describe,
|
||||
beforeEach,
|
||||
it,
|
||||
expect,
|
||||
ddescribe,
|
||||
iit,
|
||||
SpyObject,
|
||||
el,
|
||||
proxy,
|
||||
stringifyElement
|
||||
} from 'angular2/test_lib';
|
||||
import {IMPLEMENTS, isBlank, isPresent} from 'angular2/src/facade/lang';
|
||||
import {ListWrapper, MapWrapper} from 'angular2/src/facade/collection';
|
||||
import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||
import {Content} from 'angular2/src/render/dom/shadow_dom/content_tag';
|
||||
import {LightDom} from 'angular2/src/render/dom/shadow_dom/light_dom';
|
||||
import {DomView} from 'angular2/src/render/dom/view/view';
|
||||
import {DomProtoView} from 'angular2/src/render/dom/view/proto_view';
|
||||
import {DomViewContainer} from 'angular2/src/render/dom/view/view_container';
|
||||
import {DomElement} from 'angular2/src/render/dom/view/element';
|
||||
|
||||
@proxy
|
||||
@IMPLEMENTS(DomProtoView)
|
||||
class FakeProtoView extends SpyObject {
|
||||
constructor(public transitiveContentTagCount: number) { super(DomProtoView); }
|
||||
|
||||
noSuchMethod(i) { super.noSuchMethod(i); }
|
||||
}
|
||||
|
||||
@proxy
|
||||
@IMPLEMENTS(DomView)
|
||||
class FakeView extends SpyObject {
|
||||
boundElements: any[];
|
||||
proto;
|
||||
|
||||
constructor(containers = null, transitiveContentTagCount: number = 1) {
|
||||
super(DomView);
|
||||
this.proto = new FakeProtoView(transitiveContentTagCount);
|
||||
this.boundElements = [];
|
||||
if (isPresent(containers)) {
|
||||
ListWrapper.forEach(containers, (c) => {
|
||||
var element = null;
|
||||
var contentTag = null;
|
||||
var vc = null;
|
||||
if (c instanceof FakeContentTag) {
|
||||
contentTag = c;
|
||||
element = c.contentStartElement;
|
||||
}
|
||||
if (c instanceof FakeViewContainer) {
|
||||
vc = c;
|
||||
element = c.templateElement;
|
||||
}
|
||||
var boundElement = new DomElement(null, element, contentTag);
|
||||
boundElement.viewContainer = vc;
|
||||
this.boundElements.push(boundElement);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
noSuchMethod(i) { super.noSuchMethod(i); }
|
||||
}
|
||||
|
||||
@proxy
|
||||
@IMPLEMENTS(DomViewContainer)
|
||||
class FakeViewContainer extends SpyObject {
|
||||
_nodes;
|
||||
_contentTagContainers;
|
||||
templateElement;
|
||||
|
||||
constructor(templateEl, nodes = null, views = null) {
|
||||
super(DomViewContainer);
|
||||
this.templateElement = templateEl;
|
||||
this._nodes = nodes;
|
||||
this._contentTagContainers = views;
|
||||
}
|
||||
|
||||
nodes() { return this._nodes; }
|
||||
|
||||
contentTagContainers() { return this._contentTagContainers; }
|
||||
|
||||
noSuchMethod(i) { super.noSuchMethod(i); }
|
||||
}
|
||||
|
||||
|
||||
@proxy
|
||||
@IMPLEMENTS(Content)
|
||||
class FakeContentTag extends SpyObject {
|
||||
select;
|
||||
_nodes;
|
||||
contentStartElement;
|
||||
|
||||
constructor(contentEl, select = '', nodes = null) {
|
||||
super(Content);
|
||||
this.contentStartElement = contentEl;
|
||||
this.select = select;
|
||||
this._nodes = nodes;
|
||||
}
|
||||
|
||||
insert(nodes) { this._nodes = nodes; }
|
||||
|
||||
nodes() { return this._nodes; }
|
||||
|
||||
noSuchMethod(i) { super.noSuchMethod(i); }
|
||||
}
|
||||
|
||||
function createLightDom(hostView, shadowView, el) {
|
||||
var lightDom = new LightDom(hostView, el);
|
||||
lightDom.attachShadowDomView(shadowView);
|
||||
return lightDom;
|
||||
}
|
||||
|
||||
export function main() {
|
||||
describe('LightDom', function() {
|
||||
var lightDomView;
|
||||
|
||||
beforeEach(() => { lightDomView = new FakeView(); });
|
||||
|
||||
describe("contentTags", () => {
|
||||
it("should collect unconditional content tags", () => {
|
||||
var tag = new FakeContentTag(el('<script></script>'));
|
||||
var shadowDomView = new FakeView([tag]);
|
||||
|
||||
var lightDom = createLightDom(lightDomView, shadowDomView, el("<div></div>"));
|
||||
|
||||
expect(lightDom.contentTags()).toEqual([tag]);
|
||||
});
|
||||
|
||||
it("should collect content tags from ViewContainers", () => {
|
||||
var tag = new FakeContentTag(el('<script></script>'));
|
||||
var vc = new FakeViewContainer(null, null, [new FakeView([tag])]);
|
||||
var shadowDomView = new FakeView([vc]);
|
||||
var lightDom = createLightDom(lightDomView, shadowDomView, el("<div></div>"));
|
||||
|
||||
expect(lightDom.contentTags()).toEqual([tag]);
|
||||
});
|
||||
|
||||
it("should not walk views that can't have content tags", () => {
|
||||
var tag = new FakeContentTag(el('<script></script>'));
|
||||
var shadowDomView = new FakeView([tag], 0);
|
||||
|
||||
var lightDom = createLightDom(lightDomView, shadowDomView, el("<div></div>"));
|
||||
|
||||
expect(lightDom.contentTags()).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("expandedDomNodes", () => {
|
||||
it("should contain root nodes", () => {
|
||||
var lightDomEl = el("<div><a></a></div>");
|
||||
var lightDom = createLightDom(lightDomView, new FakeView(), lightDomEl);
|
||||
expect(toHtml(lightDom.expandedDomNodes())).toEqual(["<a></a>"]);
|
||||
});
|
||||
|
||||
it("should include view container nodes", () => {
|
||||
var lightDomEl = el("<div><template></template></div>");
|
||||
var lightDom = createLightDom(
|
||||
new FakeView([
|
||||
new FakeViewContainer(DOM.firstChild(lightDomEl), // template element
|
||||
[el('<a></a>')] // light DOM nodes of view container
|
||||
)
|
||||
]),
|
||||
null, lightDomEl);
|
||||
|
||||
expect(toHtml(lightDom.expandedDomNodes())).toEqual(["<a></a>"]);
|
||||
});
|
||||
|
||||
it("should include content nodes", () => {
|
||||
var lightDomEl = el("<div><content></content></div>");
|
||||
var lightDom =
|
||||
createLightDom(new FakeView([
|
||||
new FakeContentTag(DOM.firstChild(lightDomEl), // content element
|
||||
'', // selector
|
||||
[el('<a></a>')] // light DOM nodes of content tag
|
||||
)
|
||||
]),
|
||||
null, lightDomEl);
|
||||
|
||||
expect(toHtml(lightDom.expandedDomNodes())).toEqual(["<a></a>"]);
|
||||
});
|
||||
|
||||
it("should work when the element injector array contains nulls", () => {
|
||||
var lightDomEl = el("<div><a></a></div>")
|
||||
|
||||
var lightDomView = new FakeView();
|
||||
|
||||
var lightDom = createLightDom(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(null, "a");
|
||||
var contentB = new FakeContentTag(null, "b");
|
||||
|
||||
var lightDomEl = el("<div><a>1</a><b>2</b><a>3</a></div>")
|
||||
|
||||
var lightDom =
|
||||
createLightDom(lightDomView, new FakeView([contentA, contentB]), 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(null, "b");
|
||||
|
||||
var lightDomEl = el("<div><a>1</a><b>2</b><a>3</a></div>")
|
||||
|
||||
var lightDom =
|
||||
createLightDom(lightDomView, new FakeView([wildcard, contentB]), lightDomEl);
|
||||
|
||||
lightDom.redistribute();
|
||||
|
||||
expect(toHtml(wildcard.nodes())).toEqual(["<a>1</a>", "<b>2</b>", "<a>3</a>"]);
|
||||
expect(toHtml(contentB.nodes())).toEqual([]);
|
||||
});
|
||||
|
||||
it("should remove all nodes if there are no content tags", () => {
|
||||
var lightDomEl = el("<div><a>1</a><b>2</b><a>3</a></div>")
|
||||
|
||||
var lightDom = createLightDom(lightDomView, new FakeView([]), lightDomEl);
|
||||
|
||||
lightDom.redistribute();
|
||||
|
||||
expect(DOM.childNodes(lightDomEl).length).toBe(0);
|
||||
});
|
||||
|
||||
it("should remove all not projected nodes", () => {
|
||||
var lightDomEl = el("<div><a>1</a><b>2</b><a>3</a></div>");
|
||||
var bNode = DOM.childNodes(lightDomEl)[1];
|
||||
|
||||
var lightDom =
|
||||
createLightDom(lightDomView, new FakeView([new FakeContentTag(null, "a")]), lightDomEl);
|
||||
|
||||
lightDom.redistribute();
|
||||
|
||||
expect(bNode.parentNode).toBe(null);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function toHtml(nodes) {
|
||||
if (isBlank(nodes)) return [];
|
||||
return ListWrapper.map(nodes, stringifyElement);
|
||||
}
|
||||
@@ -16,19 +16,13 @@ import {
|
||||
NativeShadowDomStrategy
|
||||
} from 'angular2/src/render/dom/shadow_dom/native_shadow_dom_strategy';
|
||||
|
||||
import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||
|
||||
export function main() {
|
||||
var strategy;
|
||||
var strategy: NativeShadowDomStrategy;
|
||||
|
||||
describe('NativeShadowDomStrategy', () => {
|
||||
beforeEach(() => { strategy = new NativeShadowDomStrategy(); });
|
||||
|
||||
if (DOM.supportsNativeShadowDOM()) {
|
||||
it('should use the native shadow root', () => {
|
||||
var host = el('<div><span>original content</span></div>');
|
||||
expect(strategy.prepareShadowRoot(host)).toBe(DOM.getShadowRoot(host));
|
||||
});
|
||||
}
|
||||
it('should report that this is the native strategy',
|
||||
() => { expect(strategy.hasNativeContentElement()).toBe(true); });
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,555 +0,0 @@
|
||||
import {
|
||||
AsyncTestCompleter,
|
||||
beforeEach,
|
||||
ddescribe,
|
||||
describe,
|
||||
el,
|
||||
expect,
|
||||
iit,
|
||||
inject,
|
||||
it,
|
||||
xit,
|
||||
beforeEachBindings,
|
||||
SpyObject,
|
||||
} from 'angular2/test_lib';
|
||||
|
||||
import {bind} from 'angular2/di';
|
||||
import {MapWrapper, ListWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
|
||||
import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||
|
||||
import {ViewDefinition, DirectiveMetadata} from 'angular2/src/render/api';
|
||||
|
||||
import {ShadowDomStrategy} from 'angular2/src/render/dom/shadow_dom/shadow_dom_strategy';
|
||||
import {
|
||||
EmulatedScopedShadowDomStrategy
|
||||
} from 'angular2/src/render/dom/shadow_dom/emulated_scoped_shadow_dom_strategy';
|
||||
import {
|
||||
EmulatedUnscopedShadowDomStrategy
|
||||
} from 'angular2/src/render/dom/shadow_dom/emulated_unscoped_shadow_dom_strategy';
|
||||
import {
|
||||
NativeShadowDomStrategy
|
||||
} from 'angular2/src/render/dom/shadow_dom/native_shadow_dom_strategy';
|
||||
|
||||
import {DomTestbed, elRef} from './dom_testbed';
|
||||
|
||||
export function main() {
|
||||
describe('ShadowDom integration tests', function() {
|
||||
var styleHost = DOM.createElement('div');
|
||||
|
||||
var strategies = {
|
||||
"scoped": bind(ShadowDomStrategy).toValue(new EmulatedScopedShadowDomStrategy(styleHost)),
|
||||
"unscoped": bind(ShadowDomStrategy).toValue(new EmulatedUnscopedShadowDomStrategy(styleHost))
|
||||
};
|
||||
if (DOM.supportsNativeShadowDOM()) {
|
||||
StringMapWrapper.set(strategies, "native",
|
||||
bind(ShadowDomStrategy).toValue(new NativeShadowDomStrategy()));
|
||||
}
|
||||
|
||||
StringMapWrapper.forEach(strategies, (strategyBinding, name) => {
|
||||
describe(`${name} shadow dom strategy`, () => {
|
||||
beforeEachBindings(() => { return [strategyBinding, DomTestbed]; });
|
||||
|
||||
// GH-2095 - https://github.com/angular/angular/issues/2095
|
||||
// important as we are adding a content end element during compilation,
|
||||
// which could skrew up text node indices.
|
||||
it('should support text nodes after content tags',
|
||||
inject([DomTestbed, AsyncTestCompleter], (tb, async) => {
|
||||
tb.compileAll([
|
||||
simple,
|
||||
new ViewDefinition({
|
||||
componentId: 'simple',
|
||||
template: '<content></content><p>P,</p>{{a}}',
|
||||
directives: []
|
||||
})
|
||||
])
|
||||
.then((protoViewDtos) => {
|
||||
var rootView = tb.createRootView(protoViewDtos[0]);
|
||||
var cmpView = tb.createComponentView(rootView.viewRef, 0, protoViewDtos[1]);
|
||||
|
||||
tb.renderer.setText(cmpView.viewRef, 0, 'text');
|
||||
expect(tb.rootEl).toHaveText('P,text');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
// important as we are moving style tags around during compilation,
|
||||
// which could skrew up text node indices.
|
||||
it('should support text nodes after style tags',
|
||||
inject([DomTestbed, AsyncTestCompleter], (tb, async) => {
|
||||
tb.compileAll([
|
||||
simple,
|
||||
new ViewDefinition({
|
||||
componentId: 'simple',
|
||||
template: '<style></style><p>P,</p>{{a}}',
|
||||
directives: []
|
||||
})
|
||||
])
|
||||
.then((protoViewDtos) => {
|
||||
var rootView = tb.createRootView(protoViewDtos[0]);
|
||||
var cmpView = tb.createComponentView(rootView.viewRef, 0, protoViewDtos[1]);
|
||||
|
||||
tb.renderer.setText(cmpView.viewRef, 0, 'text');
|
||||
expect(tb.rootEl).toHaveText('P,text');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should support simple components',
|
||||
inject([AsyncTestCompleter, DomTestbed], (async, tb) => {
|
||||
tb.compileAll([
|
||||
mainDir,
|
||||
new ViewDefinition({
|
||||
componentId: 'main',
|
||||
template: '<simple>' +
|
||||
'<div>A</div>' +
|
||||
'</simple>',
|
||||
directives: [simple]
|
||||
}),
|
||||
simpleTemplate
|
||||
])
|
||||
.then((protoViews) => {
|
||||
tb.createRootViews(protoViews);
|
||||
|
||||
expect(tb.rootEl).toHaveText('SIMPLE(A)');
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should support simple components with text interpolation as direct children',
|
||||
inject([AsyncTestCompleter, DomTestbed], (async, tb) => {
|
||||
tb.compileAll([
|
||||
mainDir,
|
||||
new ViewDefinition({
|
||||
componentId: 'main',
|
||||
template: '<simple>' +
|
||||
'{{text}}' +
|
||||
'</simple>',
|
||||
directives: [simple]
|
||||
}),
|
||||
simpleTemplate
|
||||
])
|
||||
.then((protoViews) => {
|
||||
var cmpView = tb.createRootViews(protoViews)[1];
|
||||
tb.renderer.setText(cmpView.viewRef, 0, 'A');
|
||||
|
||||
expect(tb.rootEl).toHaveText('SIMPLE(A)');
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should not show the light dom even if there is not content tag',
|
||||
inject([AsyncTestCompleter, DomTestbed], (async, tb) => {
|
||||
tb.compileAll([
|
||||
mainDir,
|
||||
new ViewDefinition({
|
||||
componentId: 'main',
|
||||
template: '<empty>' +
|
||||
'<div>A</div>' +
|
||||
'</empty>',
|
||||
directives: [empty]
|
||||
}),
|
||||
emptyTemplate
|
||||
])
|
||||
.then((protoViews) => {
|
||||
tb.createRootViews(protoViews);
|
||||
|
||||
expect(tb.rootEl).toHaveText('');
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should support dynamic components',
|
||||
inject([AsyncTestCompleter, DomTestbed], (async, tb) => {
|
||||
tb.compileAll([
|
||||
mainDir,
|
||||
new ViewDefinition({
|
||||
componentId: 'main',
|
||||
template: '<dynamic>' +
|
||||
'<div>A</div>' +
|
||||
'</dynamic>',
|
||||
directives: [dynamicComponent]
|
||||
}),
|
||||
simpleTemplate
|
||||
])
|
||||
.then((protoViews) => {
|
||||
var views = tb.createRootViews(ListWrapper.slice(protoViews, 0, 2));
|
||||
tb.createComponentView(views[1].viewRef, 0, protoViews[2]);
|
||||
|
||||
expect(tb.rootEl).toHaveText('SIMPLE(A)');
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should support multiple content tags',
|
||||
inject([AsyncTestCompleter, DomTestbed], (async, tb) => {
|
||||
tb.compileAll([
|
||||
mainDir,
|
||||
new ViewDefinition({
|
||||
componentId: 'main',
|
||||
template: '<multiple-content-tags>' +
|
||||
'<div>B</div>' +
|
||||
'<div>C</div>' +
|
||||
'<div class="left">A</div>' +
|
||||
'</multiple-content-tags>',
|
||||
directives: [multipleContentTagsComponent]
|
||||
}),
|
||||
multipleContentTagsTemplate
|
||||
])
|
||||
.then((protoViews) => {
|
||||
tb.createRootViews(protoViews);
|
||||
|
||||
expect(tb.rootEl).toHaveText('(A, BC)');
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should redistribute only direct children',
|
||||
inject([AsyncTestCompleter, DomTestbed], (async, tb) => {
|
||||
tb.compileAll([
|
||||
mainDir,
|
||||
new ViewDefinition({
|
||||
componentId: 'main',
|
||||
template: '<multiple-content-tags>' +
|
||||
'<div>B<div class="left">A</div></div>' +
|
||||
'<div>C</div>' +
|
||||
'</multiple-content-tags>',
|
||||
directives: [multipleContentTagsComponent]
|
||||
}),
|
||||
multipleContentTagsTemplate
|
||||
])
|
||||
.then((protoViews) => {
|
||||
tb.createRootViews(protoViews);
|
||||
|
||||
expect(tb.rootEl).toHaveText('(, BAC)');
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it("should redistribute direct child viewcontainers when the light dom changes",
|
||||
inject([AsyncTestCompleter, DomTestbed], (async, tb) => {
|
||||
tb.compileAll([
|
||||
mainDir,
|
||||
new ViewDefinition({
|
||||
componentId: 'main',
|
||||
template: '<multiple-content-tags>' +
|
||||
'<div><div template="manual" class="left">A</div></div>' +
|
||||
'<div>B</div>' +
|
||||
'</multiple-content-tags>',
|
||||
directives: [multipleContentTagsComponent, manualViewportDirective]
|
||||
}),
|
||||
multipleContentTagsTemplate
|
||||
])
|
||||
.then((protoViews) => {
|
||||
var views = tb.createRootViews(protoViews);
|
||||
var childProtoView = protoViews[1].elementBinders[1].nestedProtoView;
|
||||
expect(tb.rootEl).toHaveText('(, B)');
|
||||
|
||||
var childView = tb.createViewInContainer(views[1].viewRef, 1, 0, childProtoView);
|
||||
|
||||
expect(tb.rootEl).toHaveText('(, AB)');
|
||||
|
||||
tb.destroyViewInContainer(views[1].viewRef, 1, 0, childView.viewRef);
|
||||
|
||||
expect(tb.rootEl).toHaveText('(, B)');
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it("should redistribute when the light dom changes",
|
||||
inject([AsyncTestCompleter, DomTestbed], (async, tb) => {
|
||||
tb.compileAll([
|
||||
mainDir,
|
||||
new ViewDefinition({
|
||||
componentId: 'main',
|
||||
template: '<multiple-content-tags>' +
|
||||
'<div template="manual" class="left">A</div>' +
|
||||
'<div>B</div>' +
|
||||
'</multiple-content-tags>',
|
||||
directives: [multipleContentTagsComponent, manualViewportDirective]
|
||||
}),
|
||||
multipleContentTagsTemplate
|
||||
])
|
||||
.then((protoViews) => {
|
||||
var views = tb.createRootViews(protoViews);
|
||||
var childProtoView = protoViews[1].elementBinders[1].nestedProtoView;
|
||||
|
||||
expect(tb.rootEl).toHaveText('(, B)');
|
||||
|
||||
var childView = tb.createViewInContainer(views[1].viewRef, 1, 0, childProtoView);
|
||||
|
||||
expect(tb.rootEl).toHaveText('(A, B)');
|
||||
|
||||
tb.destroyViewInContainer(views[1].viewRef, 1, 0, childView.viewRef);
|
||||
|
||||
expect(tb.rootEl).toHaveText('(, B)');
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it("should support nested components",
|
||||
inject([AsyncTestCompleter, DomTestbed], (async, tb) => {
|
||||
tb.compileAll([
|
||||
mainDir,
|
||||
new ViewDefinition({
|
||||
componentId: 'main',
|
||||
template: '<outer-with-indirect-nested>' +
|
||||
'<div>A</div>' +
|
||||
'<div>B</div>' +
|
||||
'</outer-with-indirect-nested>',
|
||||
directives: [outerWithIndirectNestedComponent]
|
||||
}),
|
||||
outerWithIndirectNestedTemplate,
|
||||
simpleTemplate
|
||||
])
|
||||
.then((protoViews) => {
|
||||
tb.createRootViews(protoViews);
|
||||
|
||||
expect(tb.rootEl).toHaveText('OUTER(SIMPLE(AB))');
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it("should support nesting with content being direct child of a nested component",
|
||||
inject([AsyncTestCompleter, DomTestbed], (async, tb) => {
|
||||
tb.compileAll([
|
||||
mainDir,
|
||||
new ViewDefinition({
|
||||
componentId: 'main',
|
||||
template: '<outer>' +
|
||||
'<div template="manual" class="left">A</div>' +
|
||||
'<div>B</div>' +
|
||||
'<div>C</div>' +
|
||||
'</outer>',
|
||||
directives: [outerComponent, manualViewportDirective]
|
||||
}),
|
||||
outerTemplate,
|
||||
innerTemplate,
|
||||
innerInnerTemplate
|
||||
])
|
||||
.then((protoViews) => {
|
||||
var views = tb.createRootViews(protoViews);
|
||||
var childProtoView = protoViews[1].elementBinders[1].nestedProtoView;
|
||||
|
||||
expect(tb.rootEl).toHaveText('OUTER(INNER(INNERINNER(,BC)))');
|
||||
|
||||
tb.createViewInContainer(views[1].viewRef, 1, 0, childProtoView);
|
||||
|
||||
expect(tb.rootEl).toHaveText('OUTER(INNER(INNERINNER(A,BC)))');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should redistribute when the shadow dom changes',
|
||||
inject([AsyncTestCompleter, DomTestbed], (async, tb) => {
|
||||
tb.compileAll([
|
||||
mainDir,
|
||||
new ViewDefinition({
|
||||
componentId: 'main',
|
||||
template: '<conditional-content>' +
|
||||
'<div class="left">A</div>' +
|
||||
'<div>B</div>' +
|
||||
'<div>C</div>' +
|
||||
'</conditional-content>',
|
||||
directives: [conditionalContentComponent]
|
||||
}),
|
||||
conditionalContentTemplate
|
||||
])
|
||||
.then((protoViews) => {
|
||||
var views = tb.createRootViews(protoViews);
|
||||
var childProtoView = protoViews[2].elementBinders[0].nestedProtoView;
|
||||
|
||||
expect(tb.rootEl).toHaveText('(, ABC)');
|
||||
|
||||
var childView = tb.createViewInContainer(views[2].viewRef, 0, 0, childProtoView);
|
||||
|
||||
expect(tb.rootEl).toHaveText('(A, BC)');
|
||||
|
||||
tb.destroyViewInContainer(views[2].viewRef, 0, 0, childView.viewRef);
|
||||
|
||||
expect(tb.rootEl).toHaveText('(, ABC)');
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it("should support tabs with view caching",
|
||||
inject([AsyncTestCompleter, DomTestbed], (async, tb) => {
|
||||
tb.compileAll([
|
||||
mainDir,
|
||||
new ViewDefinition({
|
||||
componentId: 'main',
|
||||
template: '(<tab><span>0</span></tab>' +
|
||||
'<tab><span>1</span></tab>' +
|
||||
'<tab><span>2</span></tab>)',
|
||||
directives: [tabComponent]
|
||||
}),
|
||||
tabTemplate
|
||||
])
|
||||
.then((protoViews) => {
|
||||
var views = tb.createRootViews(ListWrapper.slice(protoViews, 0, 2));
|
||||
var tabProtoView = protoViews[2];
|
||||
var tabChildProtoView = tabProtoView.elementBinders[0].nestedProtoView;
|
||||
|
||||
var tab1View = tb.createComponentView(views[1].viewRef, 0, tabProtoView);
|
||||
var tab2View = tb.createComponentView(views[1].viewRef, 1, tabProtoView);
|
||||
var tab3View = tb.createComponentView(views[1].viewRef, 2, tabProtoView);
|
||||
|
||||
expect(tb.rootEl).toHaveText('()');
|
||||
|
||||
var tabChildView =
|
||||
tb.createViewInContainer(tab1View.viewRef, 0, 0, tabChildProtoView);
|
||||
|
||||
expect(tb.rootEl).toHaveText('(TAB(0))');
|
||||
|
||||
tb.renderer.dehydrateView(tabChildView.viewRef);
|
||||
tb.renderer.detachViewInContainer(elRef(tab1View.viewRef, 0), 0,
|
||||
tabChildView.viewRef);
|
||||
|
||||
tb.renderer.attachViewInContainer(elRef(tab2View.viewRef, 0), 0,
|
||||
tabChildView.viewRef);
|
||||
tb.renderer.hydrateView(tabChildView.viewRef);
|
||||
|
||||
expect(tb.rootEl).toHaveText('(TAB(1))');
|
||||
|
||||
tb.renderer.dehydrateView(tabChildView.viewRef);
|
||||
tb.renderer.detachViewInContainer(elRef(tab2View.viewRef, 0), 0,
|
||||
tabChildView.viewRef);
|
||||
|
||||
tb.renderer.attachViewInContainer(elRef(tab3View.viewRef, 0), 0,
|
||||
tabChildView.viewRef);
|
||||
tb.renderer.hydrateView(tabChildView.viewRef);
|
||||
|
||||
expect(tb.rootEl).toHaveText('(TAB(2))');
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
// Implement once ElementRef 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.rootNodes[1];
|
||||
// input.focus();
|
||||
//
|
||||
// expect(document.activeElement.id).toEqual("focused-input");
|
||||
//
|
||||
// // update class of input
|
||||
//
|
||||
// expect(document.activeElement.id).toEqual("focused-input");
|
||||
// });
|
||||
//});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
var mainDir = DirectiveMetadata.create(
|
||||
{selector: 'main', id: 'main', type: DirectiveMetadata.COMPONENT_TYPE});
|
||||
|
||||
var simple = DirectiveMetadata.create(
|
||||
{selector: 'simple', id: 'simple', type: DirectiveMetadata.COMPONENT_TYPE});
|
||||
|
||||
var empty = DirectiveMetadata.create(
|
||||
{selector: 'empty', id: 'empty', type: DirectiveMetadata.COMPONENT_TYPE});
|
||||
|
||||
var dynamicComponent = DirectiveMetadata.create(
|
||||
{selector: 'dynamic', id: 'dynamic', type: DirectiveMetadata.COMPONENT_TYPE});
|
||||
|
||||
var multipleContentTagsComponent = DirectiveMetadata.create({
|
||||
selector: 'multiple-content-tags',
|
||||
id: 'multiple-content-tags',
|
||||
type: DirectiveMetadata.COMPONENT_TYPE
|
||||
});
|
||||
|
||||
var manualViewportDirective = DirectiveMetadata.create(
|
||||
{selector: '[manual]', id: 'manual', type: DirectiveMetadata.DIRECTIVE_TYPE});
|
||||
|
||||
var outerWithIndirectNestedComponent = DirectiveMetadata.create({
|
||||
selector: 'outer-with-indirect-nested',
|
||||
id: 'outer-with-indirect-nested',
|
||||
type: DirectiveMetadata.COMPONENT_TYPE
|
||||
});
|
||||
|
||||
var outerComponent = DirectiveMetadata.create(
|
||||
{selector: 'outer', id: 'outer', type: DirectiveMetadata.COMPONENT_TYPE});
|
||||
|
||||
var innerComponent = DirectiveMetadata.create(
|
||||
{selector: 'inner', id: 'inner', type: DirectiveMetadata.COMPONENT_TYPE});
|
||||
|
||||
var innerInnerComponent = DirectiveMetadata.create(
|
||||
{selector: 'innerinner', id: 'innerinner', type: DirectiveMetadata.COMPONENT_TYPE});
|
||||
|
||||
var conditionalContentComponent = DirectiveMetadata.create({
|
||||
selector: 'conditional-content',
|
||||
id: 'conditional-content',
|
||||
type: DirectiveMetadata.COMPONENT_TYPE
|
||||
});
|
||||
|
||||
var autoViewportDirective = DirectiveMetadata.create(
|
||||
{selector: '[auto]', id: 'auto', properties: ['auto'], type: DirectiveMetadata.DIRECTIVE_TYPE});
|
||||
|
||||
var tabComponent =
|
||||
DirectiveMetadata.create({selector: 'tab', id: 'tab', type: DirectiveMetadata.COMPONENT_TYPE});
|
||||
|
||||
var simpleTemplate = new ViewDefinition(
|
||||
{componentId: 'simple', template: 'SIMPLE(<content></content>)', directives: []});
|
||||
|
||||
var emptyTemplate = new ViewDefinition({componentId: 'empty', template: '', directives: []});
|
||||
|
||||
var multipleContentTagsTemplate = new ViewDefinition({
|
||||
componentId: 'multiple-content-tags',
|
||||
template: '(<content select=".left"></content>, <content></content>)',
|
||||
directives: []
|
||||
});
|
||||
|
||||
var outerWithIndirectNestedTemplate = new ViewDefinition({
|
||||
componentId: 'outer-with-indirect-nested',
|
||||
template: 'OUTER(<simple><div><content></content></div></simple>)',
|
||||
directives: [simple]
|
||||
});
|
||||
|
||||
var outerTemplate = new ViewDefinition({
|
||||
componentId: 'outer',
|
||||
template: 'OUTER(<inner><content></content></inner>)',
|
||||
directives: [innerComponent]
|
||||
});
|
||||
|
||||
var innerTemplate = new ViewDefinition({
|
||||
componentId: 'inner',
|
||||
template: 'INNER(<innerinner><content></content></innerinner>)',
|
||||
directives: [innerInnerComponent]
|
||||
});
|
||||
|
||||
var innerInnerTemplate = new ViewDefinition({
|
||||
componentId: 'innerinner',
|
||||
template: 'INNERINNER(<content select=".left"></content>,<content></content>)',
|
||||
directives: []
|
||||
});
|
||||
|
||||
var conditionalContentTemplate = new ViewDefinition({
|
||||
componentId: 'conditional-content',
|
||||
template:
|
||||
'<div>(<div *auto="cond"><content select=".left"></content></div>, <content></content>)</div>',
|
||||
directives: [autoViewportDirective]
|
||||
});
|
||||
|
||||
var tabTemplate = new ViewDefinition({
|
||||
componentId: 'tab',
|
||||
template: '<div><div *auto="cond">TAB(<content></content>)</div></div>',
|
||||
directives: [autoViewportDirective]
|
||||
});
|
||||
@@ -14,13 +14,15 @@ import {
|
||||
import {ProtoViewBuilder} from 'angular2/src/render/dom/view/proto_view_builder';
|
||||
import {ASTWithSource, AST} from 'angular2/change_detection';
|
||||
import {PropertyBindingType, ViewType} from 'angular2/src/render/api';
|
||||
import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||
|
||||
export function main() {
|
||||
function emptyExpr() { return new ASTWithSource(new AST(), 'empty', 'empty'); }
|
||||
|
||||
describe('ProtoViewBuilder', () => {
|
||||
var builder;
|
||||
beforeEach(() => { builder = new ProtoViewBuilder(el('<div/>'), ViewType.EMBEDDED); });
|
||||
beforeEach(
|
||||
() => { builder = new ProtoViewBuilder(DOM.createTemplate(''), ViewType.EMBEDDED); });
|
||||
|
||||
if (!IS_DARTIUM) {
|
||||
describe('verification of properties', () => {
|
||||
|
||||
@@ -0,0 +1,291 @@
|
||||
import {
|
||||
AsyncTestCompleter,
|
||||
beforeEach,
|
||||
ddescribe,
|
||||
describe,
|
||||
el,
|
||||
expect,
|
||||
iit,
|
||||
inject,
|
||||
it,
|
||||
xit,
|
||||
beforeEachBindings,
|
||||
SpyObject,
|
||||
stringifyElement
|
||||
} from 'angular2/test_lib';
|
||||
|
||||
import {isPresent} from 'angular2/src/facade/lang';
|
||||
|
||||
import {DomTestbed} from '../dom_testbed';
|
||||
|
||||
import {
|
||||
ViewDefinition,
|
||||
DirectiveMetadata,
|
||||
RenderProtoViewMergeMapping
|
||||
} from 'angular2/src/render/api';
|
||||
import {bind} from 'angular2/di';
|
||||
|
||||
import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||
import {cloneAndQueryProtoView} from 'angular2/src/render/dom/util';
|
||||
import {resolveInternalDomProtoView} from 'angular2/src/render/dom/view/proto_view';
|
||||
|
||||
import {ShadowDomStrategy, NativeShadowDomStrategy} from 'angular2/render';
|
||||
|
||||
export function main() {
|
||||
describe('ProtoViewMerger integration test', () => {
|
||||
beforeEachBindings(() => [DomTestbed]);
|
||||
|
||||
describe('component views', () => {
|
||||
it('should merge a component view',
|
||||
runAndAssert('root', ['a'], ['<root class="ng-binding" idx="0">a</root>']));
|
||||
|
||||
it('should merge component views with interpolation at root level',
|
||||
runAndAssert('root', ['{{a}}'], ['<root class="ng-binding" idx="0">{0}</root>']));
|
||||
|
||||
it('should merge component views with interpolation not at root level',
|
||||
runAndAssert('root', ['<div>{{a}}</div>'], [
|
||||
'<root class="ng-binding" idx="0"><div class="ng-binding" idx="1">{0}</div></root>'
|
||||
]));
|
||||
|
||||
it('should merge component views with bound elements',
|
||||
runAndAssert('root', ['<div #a></div>'], [
|
||||
'<root class="ng-binding" idx="0"><div #a="" class="ng-binding" idx="1"></div></root>'
|
||||
]));
|
||||
});
|
||||
|
||||
describe('embedded views', () => {
|
||||
|
||||
it('should merge embedded views as fragments',
|
||||
runAndAssert('root', ['<template>a</template>'], [
|
||||
'<root class="ng-binding" idx="0"><template class="ng-binding" idx="1"></template></root>',
|
||||
'a'
|
||||
]));
|
||||
|
||||
it('should merge embedded views with interpolation at root level',
|
||||
runAndAssert('root', ['<template>{{a}}</template>'], [
|
||||
'<root class="ng-binding" idx="0"><template class="ng-binding" idx="1"></template></root>',
|
||||
'{0}'
|
||||
]));
|
||||
|
||||
it('should merge embedded views with interpolation not at root level',
|
||||
runAndAssert('root', ['<div *ng-if>{{a}}</div>'], [
|
||||
'<root class="ng-binding" idx="0"><template class="ng-binding" idx="1" ng-if=""></template></root>',
|
||||
'<div *ng-if="" class="ng-binding" idx="2">{0}</div>'
|
||||
]));
|
||||
|
||||
it('should merge embedded views with bound elements',
|
||||
runAndAssert('root', ['<div *ng-if #a></div>'], [
|
||||
'<root class="ng-binding" idx="0"><template class="ng-binding" idx="1" ng-if=""></template></root>',
|
||||
'<div #a="" *ng-if="" class="ng-binding" idx="2"></div>'
|
||||
]));
|
||||
|
||||
});
|
||||
|
||||
describe('projection', () => {
|
||||
|
||||
it('should remove text nodes if there is no ng-content',
|
||||
runAndAssert(
|
||||
'root', ['<a>b</a>', ''],
|
||||
['<root class="ng-binding" idx="0"><a class="ng-binding" idx="1"></a></root>']));
|
||||
|
||||
it('should project static text',
|
||||
runAndAssert(
|
||||
'root', ['<a>b</a>', 'A(<ng-content></ng-content>)'],
|
||||
['<root class="ng-binding" idx="0"><a class="ng-binding" idx="1">A(b)</a></root>']));
|
||||
|
||||
it('should project text interpolation',
|
||||
runAndAssert(
|
||||
'root', ['<a>{{b}}</a>', 'A(<ng-content></ng-content>)'],
|
||||
['<root class="ng-binding" idx="0"><a class="ng-binding" idx="1">A({0})</a></root>']));
|
||||
|
||||
it('should project elements',
|
||||
runAndAssert('root', ['<a><div></div></a>', 'A(<ng-content></ng-content>)'], [
|
||||
'<root class="ng-binding" idx="0"><a class="ng-binding" idx="1">A(<div></div>)</a></root>'
|
||||
]));
|
||||
|
||||
it('should project elements using the selector',
|
||||
runAndAssert(
|
||||
'root',
|
||||
[
|
||||
'<a><div class="x">a</div><span></span><div class="x">b</div></a>',
|
||||
'A(<ng-content select=".x"></ng-content>)'
|
||||
],
|
||||
[
|
||||
'<root class="ng-binding" idx="0"><a class="ng-binding" idx="1">A(<div class="x">a</div><div class="x">b</div>)</a></root>'
|
||||
]));
|
||||
|
||||
it('should reproject',
|
||||
runAndAssert(
|
||||
'root',
|
||||
['<a>x</a>', 'A(<b><ng-content></ng-content></b>)', 'B(<ng-content></ng-content>)'], [
|
||||
'<root class="ng-binding" idx="0"><a class="ng-binding" idx="1">A(<b class="ng-binding" idx="2">B(x)</b>)</a></root>'
|
||||
]));
|
||||
|
||||
it('should reproject by combining selectors',
|
||||
runAndAssert(
|
||||
'root',
|
||||
[
|
||||
'<a><div class="x"></div><div class="x y"></div><div class="y"></div></a>',
|
||||
'A(<b><ng-content select=".x"></ng-content></b>)',
|
||||
'B(<ng-content select=".y"></ng-content>)'
|
||||
],
|
||||
[
|
||||
'<root class="ng-binding" idx="0"><a class="ng-binding" idx="1">A(<b class="ng-binding" idx="2">B(<div class="x y"></div>)</b>)</a></root>'
|
||||
]));
|
||||
|
||||
it('should keep non projected embedded views (so that they can be moved manually)',
|
||||
runAndAssert(
|
||||
'root', ['<a><template class="x">b</template></a>', ''],
|
||||
['<root class="ng-binding" idx="0"><a class="ng-binding" idx="1"></a></root>', 'b']));
|
||||
|
||||
it('should project embedded views and match the template element',
|
||||
runAndAssert(
|
||||
'root', ['<a><template class="x">b</template></a>', 'A(<ng-content></ng-content>)'], [
|
||||
'<root class="ng-binding" idx="0"><a class="ng-binding" idx="1">A(<template class="x ng-binding" idx="2"></template>)</a></root>',
|
||||
'b'
|
||||
]));
|
||||
|
||||
it('should project embedded views and match the single root element',
|
||||
runAndAssert(
|
||||
'root', ['<a><div class="x" *ng-if></div></a>', 'A(<ng-content></ng-content>)'], [
|
||||
'<root class="ng-binding" idx="0"><a class="ng-binding" idx="1">A(<template class="ng-binding" idx="2" ng-if=""></template>)</a></root>',
|
||||
'<div *ng-if="" class="x"></div>'
|
||||
]));
|
||||
|
||||
it('should project nodes using the ng-content in embedded views',
|
||||
runAndAssert('root', ['<a>b</a>', 'A(<ng-content *ng-if></ng-content>)'], [
|
||||
'<root class="ng-binding" idx="0"><a class="ng-binding" idx="1">A(<template class="ng-binding" idx="2" ng-if=""></template>)</a></root>',
|
||||
'b'
|
||||
]));
|
||||
|
||||
it('should allow to use wildcard selector after embedded view with non wildcard selector',
|
||||
runAndAssert(
|
||||
'root',
|
||||
[
|
||||
'<a><div class="x">a</div>b</a>',
|
||||
'A(<ng-content select=".x" *ng-if></ng-content>, <ng-content></ng-content>)'
|
||||
],
|
||||
[
|
||||
'<root class="ng-binding" idx="0"><a class="ng-binding" idx="1">A(<template class="ng-binding" idx="2" ng-if=""></template>, b)</a></root>',
|
||||
'<div class="x">a</div>'
|
||||
]));
|
||||
|
||||
});
|
||||
|
||||
describe('composition', () => {
|
||||
it('should merge multiple component views',
|
||||
runAndAssert('root', ['<a></a><b></b>', 'c', 'd'], [
|
||||
'<root class="ng-binding" idx="0"><a class="ng-binding" idx="1">c</a><b class="ng-binding" idx="2">d</b></root>'
|
||||
]));
|
||||
|
||||
it('should merge multiple embedded views as fragments',
|
||||
runAndAssert('root', ['<div *ng-if></div><span *ng-for></span>'], [
|
||||
'<root class="ng-binding" idx="0"><template class="ng-binding" idx="1" ng-if=""></template><template class="ng-binding" idx="2" ng-for=""></template></root>',
|
||||
'<div *ng-if=""></div>',
|
||||
'<span *ng-for=""></span>'
|
||||
]));
|
||||
|
||||
it('should merge nested embedded views as fragments',
|
||||
runAndAssert('root', ['<div *ng-if><span *ng-for></span></div>'], [
|
||||
'<root class="ng-binding" idx="0"><template class="ng-binding" idx="1" ng-if=""></template></root>',
|
||||
'<div *ng-if=""><template class="ng-binding" idx="2" ng-for=""></template></div>',
|
||||
'<span *ng-for=""></span>'
|
||||
]));
|
||||
|
||||
});
|
||||
|
||||
describe('element index mapping should be grouped by view and view depth first', () => {
|
||||
|
||||
it('should map component views correctly',
|
||||
runAndAssert('root', ['<a></a><b></b>', '<c></c>'], [
|
||||
'<root class="ng-binding" idx="0"><a class="ng-binding" idx="1"><c class="ng-binding" idx="3"></c></a><b class="ng-binding" idx="2"></b></root>'
|
||||
]));
|
||||
|
||||
it('should map moved projected elements correctly',
|
||||
runAndAssert(
|
||||
'root',
|
||||
[
|
||||
'<a><b></b><c></c></a>',
|
||||
'<ng-content select="c"></ng-content><ng-content select="b"></ng-content>'
|
||||
],
|
||||
[
|
||||
'<root class="ng-binding" idx="0"><a class="ng-binding" idx="1"><c class="ng-binding" idx="3"></c><b class="ng-binding" idx="2"></b></a></root>'
|
||||
]));
|
||||
|
||||
});
|
||||
|
||||
describe('text index mapping should be grouped by view and view depth first', () => {
|
||||
|
||||
it('should map component views correctly', runAndAssert('root', ['<a></a>{{b}}', '{{c}}'], [
|
||||
'<root class="ng-binding" idx="0"><a class="ng-binding" idx="1">{1}</a>{0}</root>'
|
||||
]));
|
||||
|
||||
it('should map moved projected elements correctly',
|
||||
runAndAssert(
|
||||
'root',
|
||||
[
|
||||
'<a><div x>{{x}}</div><div y>{{y}}</div></a>',
|
||||
'<ng-content select="[y]"></ng-content><ng-content select="[x]"></ng-content>'
|
||||
],
|
||||
[
|
||||
'<root class="ng-binding" idx="0"><a class="ng-binding" idx="1"><div class="ng-binding" idx="3" y="">{1}</div><div class="ng-binding" idx="2" x="">{0}</div></a></root>'
|
||||
]));
|
||||
|
||||
});
|
||||
|
||||
describe('native shadow dom support', () => {
|
||||
beforeEachBindings(
|
||||
() => { return [bind(ShadowDomStrategy).toValue(new NativeShadowDomStrategy())]; });
|
||||
|
||||
it('should keep the non projected light dom and wrap the component view into a shadow-root element',
|
||||
runAndAssert('root', ['<a>b</a>', 'c'], [
|
||||
'<root class="ng-binding" idx="0"><shadow-root><a class="ng-binding" idx="1"><shadow-root>c</shadow-root>b</a></shadow-root></root>'
|
||||
]));
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
function runAndAssert(hostElementName: string, componentTemplates: string[],
|
||||
expectedFragments: string[]) {
|
||||
var rootComp = DirectiveMetadata.create(
|
||||
{id: 'rootComp', type: DirectiveMetadata.COMPONENT_TYPE, selector: hostElementName});
|
||||
return inject([AsyncTestCompleter, DomTestbed], (async, tb: DomTestbed) => {
|
||||
tb.compileAndMerge(rootComp, componentTemplates.map(template => new ViewDefinition({
|
||||
componentId: 'someComp',
|
||||
template: template,
|
||||
directives: [aComp, bComp, cComp]
|
||||
})))
|
||||
.then((mergeMappings) => {
|
||||
expect(stringify(mergeMappings[0])).toEqual(expectedFragments);
|
||||
async.done();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function stringify(protoViewMergeMapping: RenderProtoViewMergeMapping): string[] {
|
||||
var testView = cloneAndQueryProtoView(
|
||||
resolveInternalDomProtoView(protoViewMergeMapping.mergedProtoViewRef), false);
|
||||
for (var i = 0; i < protoViewMergeMapping.mappedElementIndices.length; i++) {
|
||||
var renderElIdx = protoViewMergeMapping.mappedElementIndices[i];
|
||||
if (isPresent(renderElIdx)) {
|
||||
DOM.setAttribute(testView.boundElements[renderElIdx], 'idx', `${i}`);
|
||||
}
|
||||
}
|
||||
for (var i = 0; i < protoViewMergeMapping.mappedTextIndices.length; i++) {
|
||||
var renderTextIdx = protoViewMergeMapping.mappedTextIndices[i];
|
||||
if (isPresent(renderTextIdx)) {
|
||||
DOM.setText(testView.boundTextNodes[renderTextIdx], `{${i}}`);
|
||||
}
|
||||
}
|
||||
expect(protoViewMergeMapping.fragmentCount).toEqual(testView.fragments.length);
|
||||
return testView.fragments.map(nodes => nodes.map(node => stringifyElement(node)).join(''));
|
||||
}
|
||||
|
||||
var aComp =
|
||||
DirectiveMetadata.create({id: 'aComp', type: DirectiveMetadata.COMPONENT_TYPE, selector: 'a'});
|
||||
var bComp =
|
||||
DirectiveMetadata.create({id: 'bComp', type: DirectiveMetadata.COMPONENT_TYPE, selector: 'b'});
|
||||
var cComp =
|
||||
DirectiveMetadata.create({id: 'cComp', type: DirectiveMetadata.COMPONENT_TYPE, selector: 'c'});
|
||||
@@ -0,0 +1,56 @@
|
||||
import {
|
||||
AsyncTestCompleter,
|
||||
beforeEach,
|
||||
ddescribe,
|
||||
describe,
|
||||
el,
|
||||
expect,
|
||||
iit,
|
||||
inject,
|
||||
it,
|
||||
xit,
|
||||
beforeEachBindings,
|
||||
SpyObject,
|
||||
stringifyElement
|
||||
} from 'angular2/test_lib';
|
||||
|
||||
import {mergeSelectors} from 'angular2/src/render/dom/view/proto_view_merger';
|
||||
|
||||
export function main() {
|
||||
describe('ProtoViewMerger test', () => {
|
||||
|
||||
describe('mergeSelectors', () => {
|
||||
it('should merge empty selectors', () => {
|
||||
expect(mergeSelectors('', 'a')).toEqual('a');
|
||||
expect(mergeSelectors('a', '')).toEqual('a');
|
||||
expect(mergeSelectors('', '')).toEqual('');
|
||||
});
|
||||
|
||||
it('should merge wildcard selectors', () => {
|
||||
expect(mergeSelectors('*', 'a')).toEqual('a');
|
||||
expect(mergeSelectors('a', '*')).toEqual('a');
|
||||
expect(mergeSelectors('*', '*')).toEqual('*');
|
||||
});
|
||||
|
||||
it('should merge 2 element selectors',
|
||||
() => { expect(mergeSelectors('a', 'b')).toEqual('_not-matchable_'); });
|
||||
|
||||
it('should merge elements and non element selector', () => {
|
||||
expect(mergeSelectors('a', '.b')).toEqual('a.b');
|
||||
expect(mergeSelectors('.b', 'a')).toEqual('a.b');
|
||||
});
|
||||
|
||||
it('should merge attributes', () => {
|
||||
expect(mergeSelectors('[a]', '[b]')).toEqual('[a][b]');
|
||||
expect(mergeSelectors('[a][b]', '[c][d]')).toEqual('[a][b][c][d]');
|
||||
expect(mergeSelectors('[a=1]', '[b=2]')).toEqual('[a=1][b=2]');
|
||||
});
|
||||
|
||||
it('should merge classes', () => {
|
||||
expect(mergeSelectors('.a', '.b')).toEqual('.a.b');
|
||||
expect(mergeSelectors('.a.b', '.c.d')).toEqual('.a.b.c.d');
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
@@ -19,9 +19,8 @@ import {isBlank} from 'angular2/src/facade/lang';
|
||||
import {ListWrapper} from 'angular2/src/facade/collection';
|
||||
|
||||
import {DomProtoView} from 'angular2/src/render/dom/view/proto_view';
|
||||
import {ElementBinder} from 'angular2/src/render/dom/view/element_binder';
|
||||
import {DomElementBinder} from 'angular2/src/render/dom/view/element_binder';
|
||||
import {DomView} from 'angular2/src/render/dom/view/view';
|
||||
import {DomElement} from 'angular2/src/render/dom/view/element';
|
||||
import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||
|
||||
export function main() {
|
||||
@@ -30,54 +29,33 @@ export function main() {
|
||||
if (isBlank(binders)) {
|
||||
binders = [];
|
||||
}
|
||||
var rootEl = el('<div></div>');
|
||||
return new DomProtoView({
|
||||
element: rootEl,
|
||||
elementBinders: binders,
|
||||
transitiveContentTagCount: 0,
|
||||
boundTextNodeCount: 0
|
||||
});
|
||||
var rootEl = DOM.createTemplate('<div></div>');
|
||||
return DomProtoView.create(null, <Element>rootEl, [1], [], binders, null, null, null);
|
||||
}
|
||||
|
||||
function createElementBinder() { return new DomElementBinder({textNodeIndices: []}); }
|
||||
|
||||
function createView(pv = null, boundElementCount = 0) {
|
||||
if (isBlank(pv)) {
|
||||
pv = createProtoView(ListWrapper.createFixedSize(boundElementCount));
|
||||
var elementBinders = ListWrapper.createFixedSize(boundElementCount);
|
||||
for (var i = 0; i < boundElementCount; i++) {
|
||||
elementBinders[i] = createElementBinder();
|
||||
}
|
||||
pv = createProtoView(elementBinders);
|
||||
}
|
||||
var root = el('<div><div></div></div>');
|
||||
var boundElements = [];
|
||||
for (var i = 0; i < boundElementCount; i++) {
|
||||
boundElements.push(new DomElement(pv.elementBinders[i], el('<span></span'), null));
|
||||
boundElements.push(el('<span></span'));
|
||||
}
|
||||
return new DomView(pv, [DOM.childNodes(root)[0]], [], boundElements);
|
||||
return new DomView(pv, [DOM.childNodes(root)[0]], boundElements);
|
||||
}
|
||||
|
||||
function createElementBinder(parentIndex: number = 0, distanceToParent: number = 1) {
|
||||
return new ElementBinder(
|
||||
{parentIndex: parentIndex, distanceToParent: distanceToParent, textNodeIndices: []});
|
||||
}
|
||||
|
||||
describe('getDirectParentElement', () => {
|
||||
|
||||
it('should return the DomElement of the direct parent', () => {
|
||||
var pv = createProtoView([createElementBinder(), createElementBinder(0, 1)]);
|
||||
var view = createView(pv, 2);
|
||||
expect(view.getDirectParentElement(1)).toBe(view.boundElements[0]);
|
||||
});
|
||||
|
||||
it('should return null if the direct parent is not bound', () => {
|
||||
var pv = createProtoView(
|
||||
[createElementBinder(), createElementBinder(), createElementBinder(0, 2)]);
|
||||
var view = createView(pv, 3);
|
||||
expect(view.getDirectParentElement(2)).toBe(null);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('setElementProperty', () => {
|
||||
var el, view;
|
||||
beforeEach(() => {
|
||||
view = createView(null, 1);
|
||||
el = view.boundElements[0].element;
|
||||
el = view.boundElements[0];
|
||||
});
|
||||
|
||||
it('should update the property value', () => {
|
||||
@@ -91,7 +69,7 @@ export function main() {
|
||||
var el, view;
|
||||
beforeEach(() => {
|
||||
view = createView(null, 1);
|
||||
el = view.boundElements[0].element;
|
||||
el = view.boundElements[0];
|
||||
});
|
||||
|
||||
it('should update and remove an attribute', () => {
|
||||
@@ -111,7 +89,7 @@ export function main() {
|
||||
var el, view;
|
||||
beforeEach(() => {
|
||||
view = createView(null, 1);
|
||||
el = view.boundElements[0].element;
|
||||
el = view.boundElements[0];
|
||||
});
|
||||
|
||||
it('should set and remove a class', () => {
|
||||
@@ -135,7 +113,7 @@ export function main() {
|
||||
var el, view;
|
||||
beforeEach(() => {
|
||||
view = createView(null, 1);
|
||||
el = view.boundElements[0].element;
|
||||
el = view.boundElements[0];
|
||||
});
|
||||
|
||||
it('should set and remove styles', () => {
|
||||
|
||||
Reference in New Issue
Block a user