refactor(render): don’t store DOM nodes but store strings for big ProtoViews.
Also inserts comment nodes before/after projected nodes so that text nodes don’t get merged when we serialize/deserialize them. Closes #3356 First part of #3364
This commit is contained in:
parent
c08403935f
commit
0dbdd5cd3c
|
@ -238,6 +238,9 @@ class BrowserDomAdapter extends GenericBrowserDomAdapter {
|
||||||
void setChecked(InputElement el, bool isChecked) {
|
void setChecked(InputElement el, bool isChecked) {
|
||||||
el.checked = isChecked;
|
el.checked = isChecked;
|
||||||
}
|
}
|
||||||
|
Comment createComment(String text) {
|
||||||
|
return new Comment(text);
|
||||||
|
}
|
||||||
TemplateElement createTemplate(String html) {
|
TemplateElement createTemplate(String html) {
|
||||||
var t = new TemplateElement();
|
var t = new TemplateElement();
|
||||||
// We do not sanitize because templates are part of the application code
|
// We do not sanitize because templates are part of the application code
|
||||||
|
@ -341,6 +344,9 @@ class BrowserDomAdapter extends GenericBrowserDomAdapter {
|
||||||
Node importIntoDoc(Node node) {
|
Node importIntoDoc(Node node) {
|
||||||
return document.importNode(node, true);
|
return document.importNode(node, true);
|
||||||
}
|
}
|
||||||
|
Node adoptNode(Node node) {
|
||||||
|
return document.adoptNode(node);
|
||||||
|
}
|
||||||
bool isPageRule(CssRule rule) => rule is CssPageRule;
|
bool isPageRule(CssRule rule) => rule is CssPageRule;
|
||||||
bool isStyleRule(CssRule rule) => rule is CssStyleRule;
|
bool isStyleRule(CssRule rule) => rule is CssStyleRule;
|
||||||
bool isMediaRule(CssRule rule) => rule is CssMediaRule;
|
bool isMediaRule(CssRule rule) => rule is CssMediaRule;
|
||||||
|
|
|
@ -153,6 +153,7 @@ export class BrowserDomAdapter extends GenericBrowserDomAdapter {
|
||||||
setValue(el, value: string) { el.value = value; }
|
setValue(el, value: string) { el.value = value; }
|
||||||
getChecked(el): boolean { return el.checked; }
|
getChecked(el): boolean { return el.checked; }
|
||||||
setChecked(el, value: boolean) { el.checked = value; }
|
setChecked(el, value: boolean) { el.checked = value; }
|
||||||
|
createComment(text: string): Comment { return document.createComment(text); }
|
||||||
createTemplate(html): HTMLElement {
|
createTemplate(html): HTMLElement {
|
||||||
var t = document.createElement('template');
|
var t = document.createElement('template');
|
||||||
t.innerHTML = html;
|
t.innerHTML = html;
|
||||||
|
@ -238,6 +239,7 @@ export class BrowserDomAdapter extends GenericBrowserDomAdapter {
|
||||||
}
|
}
|
||||||
return document.importNode(toImport, true);
|
return document.importNode(toImport, true);
|
||||||
}
|
}
|
||||||
|
adoptNode(node: Node): any { return document.adoptNode(node); }
|
||||||
isPageRule(rule): boolean { return rule.type === CSSRule.PAGE_RULE; }
|
isPageRule(rule): boolean { return rule.type === CSSRule.PAGE_RULE; }
|
||||||
isStyleRule(rule): boolean { return rule.type === CSSRule.STYLE_RULE; }
|
isStyleRule(rule): boolean { return rule.type === CSSRule.STYLE_RULE; }
|
||||||
isMediaRule(rule): boolean { return rule.type === CSSRule.MEDIA_RULE; }
|
isMediaRule(rule): boolean { return rule.type === CSSRule.MEDIA_RULE; }
|
||||||
|
|
|
@ -69,6 +69,7 @@ export class DomAdapter {
|
||||||
setValue(el, value: string) { throw _abstract(); }
|
setValue(el, value: string) { throw _abstract(); }
|
||||||
getChecked(el): boolean { throw _abstract(); }
|
getChecked(el): boolean { throw _abstract(); }
|
||||||
setChecked(el, value: boolean) { throw _abstract(); }
|
setChecked(el, value: boolean) { throw _abstract(); }
|
||||||
|
createComment(text: string): any { throw _abstract(); }
|
||||||
createTemplate(html): HTMLElement { throw _abstract(); }
|
createTemplate(html): HTMLElement { throw _abstract(); }
|
||||||
createElement(tagName, doc = null): HTMLElement { throw _abstract(); }
|
createElement(tagName, doc = null): HTMLElement { throw _abstract(); }
|
||||||
createTextNode(text: string, doc = null): Text { throw _abstract(); }
|
createTextNode(text: string, doc = null): Text { throw _abstract(); }
|
||||||
|
@ -110,6 +111,7 @@ export class DomAdapter {
|
||||||
hasShadowRoot(node): boolean { throw _abstract(); }
|
hasShadowRoot(node): boolean { throw _abstract(); }
|
||||||
isShadowRoot(node): boolean { throw _abstract(); }
|
isShadowRoot(node): boolean { throw _abstract(); }
|
||||||
importIntoDoc /*<T extends Node>*/ (node: Node /*T*/): Node /*T*/ { throw _abstract(); }
|
importIntoDoc /*<T extends Node>*/ (node: Node /*T*/): Node /*T*/ { throw _abstract(); }
|
||||||
|
adoptNode /*<T extends Node>*/ (node: Node /*T*/): Node /*T*/ { throw _abstract(); }
|
||||||
isPageRule(rule): boolean { throw _abstract(); }
|
isPageRule(rule): boolean { throw _abstract(); }
|
||||||
isStyleRule(rule): boolean { throw _abstract(); }
|
isStyleRule(rule): boolean { throw _abstract(); }
|
||||||
isMediaRule(rule): boolean { throw _abstract(); }
|
isMediaRule(rule): boolean { throw _abstract(); }
|
||||||
|
|
|
@ -184,6 +184,7 @@ class Html5LibDomAdapter implements DomAdapter {
|
||||||
setChecked(el, bool value) {
|
setChecked(el, bool value) {
|
||||||
throw 'not implemented';
|
throw 'not implemented';
|
||||||
}
|
}
|
||||||
|
createComment(String text) => new Comment(text);
|
||||||
createTemplate(String html) => createElement('template')..innerHtml = html;
|
createTemplate(String html) => createElement('template')..innerHtml = html;
|
||||||
createElement(tagName, [doc]) {
|
createElement(tagName, [doc]) {
|
||||||
return new Element.tag(tagName);
|
return new Element.tag(tagName);
|
||||||
|
@ -292,6 +293,9 @@ class Html5LibDomAdapter implements DomAdapter {
|
||||||
importIntoDoc(node) {
|
importIntoDoc(node) {
|
||||||
throw 'not implemented';
|
throw 'not implemented';
|
||||||
}
|
}
|
||||||
|
adoptNode(node) {
|
||||||
|
throw 'not implemented';
|
||||||
|
}
|
||||||
bool isPageRule(rule) {
|
bool isPageRule(rule) {
|
||||||
throw 'not implemented';
|
throw 'not implemented';
|
||||||
}
|
}
|
||||||
|
|
|
@ -77,6 +77,9 @@ export class Parse5DomAdapter extends DomAdapter {
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
elementMatches(node, selector: string, matcher = null): boolean {
|
elementMatches(node, selector: string, matcher = null): boolean {
|
||||||
|
if (!selector || selector === '*') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
var result = false;
|
var result = false;
|
||||||
if (selector && selector.charAt(0) == "#") {
|
if (selector && selector.charAt(0) == "#") {
|
||||||
result = this.getAttribute(node, 'id') == selector.substring(1);
|
result = this.getAttribute(node, 'id') == selector.substring(1);
|
||||||
|
@ -252,6 +255,7 @@ export class Parse5DomAdapter extends DomAdapter {
|
||||||
setValue(el, value: string) { el.value = value; }
|
setValue(el, value: string) { el.value = value; }
|
||||||
getChecked(el): boolean { return el.checked; }
|
getChecked(el): boolean { return el.checked; }
|
||||||
setChecked(el, value: boolean) { el.checked = value; }
|
setChecked(el, value: boolean) { el.checked = value; }
|
||||||
|
createComment(text: string): Comment { return treeAdapter.createCommentNode(text); }
|
||||||
createTemplate(html): HTMLElement {
|
createTemplate(html): HTMLElement {
|
||||||
var template = treeAdapter.createElement("template", 'http://www.w3.org/1999/xhtml', []);
|
var template = treeAdapter.createElement("template", 'http://www.w3.org/1999/xhtml', []);
|
||||||
var content = parser.parseFragment(html);
|
var content = parser.parseFragment(html);
|
||||||
|
@ -447,6 +451,7 @@ export class Parse5DomAdapter extends DomAdapter {
|
||||||
hasShadowRoot(node): boolean { return isPresent(node.shadowRoot); }
|
hasShadowRoot(node): boolean { return isPresent(node.shadowRoot); }
|
||||||
isShadowRoot(node): boolean { return this.getShadowRoot(node) == node; }
|
isShadowRoot(node): boolean { return this.getShadowRoot(node) == node; }
|
||||||
importIntoDoc(node): any { return this.clone(node); }
|
importIntoDoc(node): any { return this.clone(node); }
|
||||||
|
adoptNode(node): any { return node; }
|
||||||
isPageRule(rule): boolean {
|
isPageRule(rule): boolean {
|
||||||
return rule.type === 6; // CSSRule.PAGE_RULE
|
return rule.type === 6; // CSSRule.PAGE_RULE
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,8 @@ export const EVENT_TARGET_SEPARATOR = ':';
|
||||||
export const NG_CONTENT_ELEMENT_NAME = 'ng-content';
|
export const NG_CONTENT_ELEMENT_NAME = 'ng-content';
|
||||||
export const NG_SHADOW_ROOT_ELEMENT_NAME = 'shadow-root';
|
export const NG_SHADOW_ROOT_ELEMENT_NAME = 'shadow-root';
|
||||||
|
|
||||||
|
const MAX_IN_MEMORY_ELEMENTS_PER_TEMPLATE = 20;
|
||||||
|
|
||||||
var CAMEL_CASE_REGEXP = /([A-Z])/g;
|
var CAMEL_CASE_REGEXP = /([A-Z])/g;
|
||||||
var DASH_CASE_REGEXP = /-([a-z])/g;
|
var DASH_CASE_REGEXP = /-([a-z])/g;
|
||||||
|
|
||||||
|
@ -57,8 +59,7 @@ export class ClonedProtoView {
|
||||||
|
|
||||||
export function cloneAndQueryProtoView(pv: DomProtoView, importIntoDocument: boolean):
|
export function cloneAndQueryProtoView(pv: DomProtoView, importIntoDocument: boolean):
|
||||||
ClonedProtoView {
|
ClonedProtoView {
|
||||||
var templateContent = importIntoDocument ? DOM.importIntoDoc(DOM.content(pv.rootElement)) :
|
var templateContent = pv.cloneableTemplate.clone(importIntoDocument);
|
||||||
DOM.clone(DOM.content(pv.rootElement));
|
|
||||||
|
|
||||||
var boundElements = queryBoundElements(templateContent, pv.isSingleElementFragment);
|
var boundElements = queryBoundElements(templateContent, pv.isSingleElementFragment);
|
||||||
var boundTextNodes = queryBoundTextNodes(templateContent, pv.rootTextNodeIndices, boundElements,
|
var boundTextNodes = queryBoundTextNodes(templateContent, pv.rootTextNodeIndices, boundElements,
|
||||||
|
@ -140,3 +141,45 @@ export function prependAll(parentNode: Node, nodes: Node[]) {
|
||||||
lastInsertedNode = node;
|
lastInsertedNode = node;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface CloneableTemplate { clone(importIntoDoc: boolean): Node; }
|
||||||
|
|
||||||
|
export class SerializedCloneableTemplate implements CloneableTemplate {
|
||||||
|
templateString: string;
|
||||||
|
constructor(templateRoot: Element) { this.templateString = DOM.getInnerHTML(templateRoot); }
|
||||||
|
clone(importIntoDoc: boolean): Node {
|
||||||
|
var result = DOM.content(DOM.createTemplate(this.templateString));
|
||||||
|
if (importIntoDoc) {
|
||||||
|
result = DOM.adoptNode(result);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ReferenceCloneableTemplate implements CloneableTemplate {
|
||||||
|
constructor(public templateRoot: Element) {}
|
||||||
|
clone(importIntoDoc: boolean): Node {
|
||||||
|
if (importIntoDoc) {
|
||||||
|
return DOM.importIntoDoc(DOM.content(this.templateRoot));
|
||||||
|
} else {
|
||||||
|
return DOM.clone(DOM.content(this.templateRoot));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function prepareTemplateForClone(templateRoot: Element): CloneableTemplate {
|
||||||
|
var root = DOM.content(templateRoot);
|
||||||
|
var elementCount = DOM.querySelectorAll(root, '*').length;
|
||||||
|
var firstChild = DOM.firstChild(root);
|
||||||
|
var forceSerialize =
|
||||||
|
isPresent(firstChild) && DOM.isCommentNode(firstChild) ? DOM.nodeValue(firstChild) : null;
|
||||||
|
if (forceSerialize == 'nocache') {
|
||||||
|
return new SerializedCloneableTemplate(templateRoot);
|
||||||
|
} else if (forceSerialize == 'cache') {
|
||||||
|
return new ReferenceCloneableTemplate(templateRoot);
|
||||||
|
} else if (elementCount > MAX_IN_MEMORY_ELEMENTS_PER_TEMPLATE) {
|
||||||
|
return new SerializedCloneableTemplate(templateRoot);
|
||||||
|
} else {
|
||||||
|
return new ReferenceCloneableTemplate(templateRoot);
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,6 +5,8 @@ import {RenderProtoViewRef, ViewType, ViewEncapsulation} from '../../api';
|
||||||
|
|
||||||
import {DOM} from 'angular2/src/dom/dom_adapter';
|
import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||||
|
|
||||||
|
import {prepareTemplateForClone, CloneableTemplate} from '../util';
|
||||||
|
|
||||||
export function resolveInternalDomProtoView(protoViewRef: RenderProtoViewRef): DomProtoView {
|
export function resolveInternalDomProtoView(protoViewRef: RenderProtoViewRef): DomProtoView {
|
||||||
return (<DomProtoViewRef>protoViewRef)._protoView;
|
return (<DomProtoViewRef>protoViewRef)._protoView;
|
||||||
}
|
}
|
||||||
|
@ -25,12 +27,12 @@ export class DomProtoView {
|
||||||
var isSingleElementFragment = fragmentsRootNodeCount.length === 1 &&
|
var isSingleElementFragment = fragmentsRootNodeCount.length === 1 &&
|
||||||
fragmentsRootNodeCount[0] === 1 &&
|
fragmentsRootNodeCount[0] === 1 &&
|
||||||
DOM.isElementNode(DOM.firstChild(DOM.content(rootElement)));
|
DOM.isElementNode(DOM.firstChild(DOM.content(rootElement)));
|
||||||
return new DomProtoView(type, rootElement, viewEncapsulation, elementBinders, hostAttributes,
|
return new DomProtoView(type, prepareTemplateForClone(rootElement), viewEncapsulation,
|
||||||
rootTextNodeIndices, boundTextNodeCount, fragmentsRootNodeCount,
|
elementBinders, hostAttributes, rootTextNodeIndices, boundTextNodeCount,
|
||||||
isSingleElementFragment);
|
fragmentsRootNodeCount, isSingleElementFragment);
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(public type: ViewType, public rootElement: Element,
|
constructor(public type: ViewType, public cloneableTemplate: CloneableTemplate,
|
||||||
public encapsulation: ViewEncapsulation,
|
public encapsulation: ViewEncapsulation,
|
||||||
public elementBinders: List<DomElementBinder>,
|
public elementBinders: List<DomElementBinder>,
|
||||||
public hostAttributes: Map<string, string>, public rootTextNodeIndices: number[],
|
public hostAttributes: Map<string, string>, public rootTextNodeIndices: number[],
|
||||||
|
|
|
@ -251,6 +251,7 @@ function appendComponentNodesToHost(hostProtoView: ClonedProtoView, binderIdx: n
|
||||||
|
|
||||||
function projectMatchingNodes(selector: string, contentElement: Element, nodes: Node[]): Node[] {
|
function projectMatchingNodes(selector: string, contentElement: Element, nodes: Node[]): Node[] {
|
||||||
var remaining = [];
|
var remaining = [];
|
||||||
|
DOM.insertBefore(contentElement, DOM.createComment('['));
|
||||||
for (var i = 0; i < nodes.length; i++) {
|
for (var i = 0; i < nodes.length; i++) {
|
||||||
var node = nodes[i];
|
var node = nodes[i];
|
||||||
var matches = false;
|
var matches = false;
|
||||||
|
@ -265,6 +266,7 @@ function projectMatchingNodes(selector: string, contentElement: Element, nodes:
|
||||||
remaining.push(node);
|
remaining.push(node);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
DOM.insertBefore(contentElement, DOM.createComment(']'));
|
||||||
DOM.remove(contentElement);
|
DOM.remove(contentElement);
|
||||||
return remaining;
|
return remaining;
|
||||||
}
|
}
|
||||||
|
|
|
@ -77,6 +77,8 @@ export function stringifyElement(el): string {
|
||||||
if (!ListWrapper.contains(_singleTagWhitelist, tagName)) {
|
if (!ListWrapper.contains(_singleTagWhitelist, tagName)) {
|
||||||
result += `</${tagName}>`;
|
result += `</${tagName}>`;
|
||||||
}
|
}
|
||||||
|
} else if (DOM.isCommentNode(el)) {
|
||||||
|
result += `<!--${DOM.nodeValue(el)}-->`;
|
||||||
} else {
|
} else {
|
||||||
result += DOM.getText(el);
|
result += DOM.getText(el);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1390,6 +1390,28 @@ export function main() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
describe('different proto view storages', () => {
|
||||||
|
function runWithMode(mode: string) {
|
||||||
|
return inject(
|
||||||
|
[TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||||
|
tcb.overrideView(MyComp,
|
||||||
|
new viewAnn.View({template: `<!--${mode}--><div>{{ctxProp}}</div>`}))
|
||||||
|
.createAsync(MyComp)
|
||||||
|
.then((rootTC) => {
|
||||||
|
rootTC.componentInstance.ctxProp = 'Hello World!';
|
||||||
|
|
||||||
|
rootTC.detectChanges();
|
||||||
|
expect(rootTC.nativeElement).toHaveText('Hello World!');
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
it('should work with storing DOM nodes', runWithMode('cache'));
|
||||||
|
|
||||||
|
it('should work with serializing the DOM nodes', runWithMode('nocache'));
|
||||||
|
});
|
||||||
|
|
||||||
// Disabled until a solution is found, refs:
|
// Disabled until a solution is found, refs:
|
||||||
// - https://github.com/angular/angular/issues/776
|
// - https://github.com/angular/angular/issues/776
|
||||||
// - https://github.com/angular/angular/commit/81f3f32
|
// - https://github.com/angular/angular/commit/81f3f32
|
||||||
|
|
|
@ -34,6 +34,8 @@ import {SharedStylesHost} from 'angular2/src/render/dom/view/shared_styles_host'
|
||||||
|
|
||||||
import {MockStep} from './pipeline_spec';
|
import {MockStep} from './pipeline_spec';
|
||||||
|
|
||||||
|
import {ReferenceCloneableTemplate} from 'angular2/src/render/dom/util';
|
||||||
|
|
||||||
export function runCompilerCommonTests() {
|
export function runCompilerCommonTests() {
|
||||||
describe('DomCompiler', function() {
|
describe('DomCompiler', function() {
|
||||||
var mockStepFactory: MockStepFactory;
|
var mockStepFactory: MockStepFactory;
|
||||||
|
@ -78,8 +80,7 @@ export function runCompilerCommonTests() {
|
||||||
{id: 'id', selector: 'custom', type: DirectiveMetadata.COMPONENT_TYPE});
|
{id: 'id', selector: 'custom', type: DirectiveMetadata.COMPONENT_TYPE});
|
||||||
compiler.compileHost(dirMetadata)
|
compiler.compileHost(dirMetadata)
|
||||||
.then((protoView) => {
|
.then((protoView) => {
|
||||||
expect(DOM.tagName(DOM.firstChild(DOM.content(
|
expect(DOM.tagName(DOM.firstChild(DOM.content(templateRoot(protoView))))
|
||||||
resolveInternalDomProtoView(protoView.render).rootElement)))
|
|
||||||
.toLowerCase())
|
.toLowerCase())
|
||||||
.toEqual('custom');
|
.toEqual('custom');
|
||||||
expect(mockStepFactory.viewDef.directives).toEqual([dirMetadata]);
|
expect(mockStepFactory.viewDef.directives).toEqual([dirMetadata]);
|
||||||
|
@ -95,8 +96,7 @@ export function runCompilerCommonTests() {
|
||||||
compiler.compile(
|
compiler.compile(
|
||||||
new ViewDefinition({componentId: 'someId', template: 'inline component'}))
|
new ViewDefinition({componentId: 'someId', template: 'inline component'}))
|
||||||
.then((protoView) => {
|
.then((protoView) => {
|
||||||
expect(DOM.getInnerHTML(resolveInternalDomProtoView(protoView.render).rootElement))
|
expect(DOM.getInnerHTML(templateRoot(protoView))).toEqual('inline component');
|
||||||
.toEqual('inline component');
|
|
||||||
async.done();
|
async.done();
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
@ -106,8 +106,7 @@ export function runCompilerCommonTests() {
|
||||||
var compiler = createCompiler(EMPTY_STEP, null, urlData);
|
var compiler = createCompiler(EMPTY_STEP, null, urlData);
|
||||||
compiler.compile(new ViewDefinition({componentId: 'someId', templateAbsUrl: 'someUrl'}))
|
compiler.compile(new ViewDefinition({componentId: 'someId', templateAbsUrl: 'someUrl'}))
|
||||||
.then((protoView) => {
|
.then((protoView) => {
|
||||||
expect(DOM.getInnerHTML(resolveInternalDomProtoView(protoView.render).rootElement))
|
expect(DOM.getInnerHTML(templateRoot(protoView))).toEqual('url component');
|
||||||
.toEqual('url component');
|
|
||||||
async.done();
|
async.done();
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
@ -173,8 +172,7 @@ export function runCompilerCommonTests() {
|
||||||
encapsulation: ViewEncapsulation.NONE
|
encapsulation: ViewEncapsulation.NONE
|
||||||
}))
|
}))
|
||||||
.then((protoViewDto) => {
|
.then((protoViewDto) => {
|
||||||
var domProtoView = resolveInternalDomProtoView(protoViewDto.render);
|
expect(DOM.getInnerHTML(templateRoot(protoViewDto))).toEqual('');
|
||||||
expect(DOM.getInnerHTML(domProtoView.rootElement)).toEqual('');
|
|
||||||
expect(sharedStylesHost.getAllStyles()).toEqual(['a {};']);
|
expect(sharedStylesHost.getAllStyles()).toEqual(['a {};']);
|
||||||
async.done();
|
async.done();
|
||||||
});
|
});
|
||||||
|
@ -190,8 +188,7 @@ export function runCompilerCommonTests() {
|
||||||
encapsulation: ViewEncapsulation.EMULATED
|
encapsulation: ViewEncapsulation.EMULATED
|
||||||
}))
|
}))
|
||||||
.then((protoViewDto) => {
|
.then((protoViewDto) => {
|
||||||
var domProtoView = resolveInternalDomProtoView(protoViewDto.render);
|
expect(DOM.getInnerHTML(templateRoot(protoViewDto))).toEqual('');
|
||||||
expect(DOM.getInnerHTML(domProtoView.rootElement)).toEqual('');
|
|
||||||
expect(sharedStylesHost.getAllStyles()).toEqual(['a {};']);
|
expect(sharedStylesHost.getAllStyles()).toEqual(['a {};']);
|
||||||
async.done();
|
async.done();
|
||||||
});
|
});
|
||||||
|
@ -208,8 +205,7 @@ export function runCompilerCommonTests() {
|
||||||
encapsulation: ViewEncapsulation.NATIVE
|
encapsulation: ViewEncapsulation.NATIVE
|
||||||
}))
|
}))
|
||||||
.then((protoViewDto) => {
|
.then((protoViewDto) => {
|
||||||
var domProtoView = resolveInternalDomProtoView(protoViewDto.render);
|
expect(DOM.getInnerHTML(templateRoot(protoViewDto)))
|
||||||
expect(DOM.getInnerHTML(domProtoView.rootElement))
|
|
||||||
.toEqual('<style>a {};</style>');
|
.toEqual('<style>a {};</style>');
|
||||||
expect(sharedStylesHost.getAllStyles()).toEqual([]);
|
expect(sharedStylesHost.getAllStyles()).toEqual([]);
|
||||||
async.done();
|
async.done();
|
||||||
|
@ -259,6 +255,11 @@ export function runCompilerCommonTests() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function templateRoot(protoViewDto: ProtoViewDto) {
|
||||||
|
var pv = resolveInternalDomProtoView(protoViewDto.render);
|
||||||
|
return (<ReferenceCloneableTemplate>pv.cloneableTemplate).templateRoot;
|
||||||
|
}
|
||||||
|
|
||||||
class MockStepFactory extends CompileStepFactory {
|
class MockStepFactory extends CompileStepFactory {
|
||||||
steps: List<CompileStep>;
|
steps: List<CompileStep>;
|
||||||
subTaskPromises: List<Promise<any>>;
|
subTaskPromises: List<Promise<any>>;
|
||||||
|
|
|
@ -0,0 +1,97 @@
|
||||||
|
import {
|
||||||
|
AsyncTestCompleter,
|
||||||
|
beforeEach,
|
||||||
|
ddescribe,
|
||||||
|
describe,
|
||||||
|
el,
|
||||||
|
expect,
|
||||||
|
iit,
|
||||||
|
inject,
|
||||||
|
it,
|
||||||
|
xit,
|
||||||
|
beforeEachBindings,
|
||||||
|
SpyObject,
|
||||||
|
} from 'angular2/test_lib';
|
||||||
|
|
||||||
|
import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||||
|
import {
|
||||||
|
prepareTemplateForClone,
|
||||||
|
ReferenceCloneableTemplate,
|
||||||
|
SerializedCloneableTemplate
|
||||||
|
} from 'angular2/src/render/dom/util';
|
||||||
|
|
||||||
|
export function main() {
|
||||||
|
describe('Dom util', () => {
|
||||||
|
|
||||||
|
describe('prepareTemplateForClone', () => {
|
||||||
|
it('should use a reference for small templates', () => {
|
||||||
|
var t = DOM.createTemplate('');
|
||||||
|
var ct = prepareTemplateForClone(t);
|
||||||
|
expect((<ReferenceCloneableTemplate>ct).templateRoot).toBe(t);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should use a reference for big templates with a force comment', () => {
|
||||||
|
var templateString = '<!--cache-->';
|
||||||
|
for (var i = 0; i < 100; i++) {
|
||||||
|
templateString += '<div></div>';
|
||||||
|
}
|
||||||
|
var t = DOM.createTemplate(templateString);
|
||||||
|
var ct = prepareTemplateForClone(t);
|
||||||
|
expect((<ReferenceCloneableTemplate>ct).templateRoot).toBe(t);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should serialize for big templates', () => {
|
||||||
|
var templateString = '';
|
||||||
|
for (var i = 0; i < 100; i++) {
|
||||||
|
templateString += '<div></div>';
|
||||||
|
}
|
||||||
|
var t = DOM.createTemplate(templateString);
|
||||||
|
var ct = prepareTemplateForClone(t);
|
||||||
|
expect((<SerializedCloneableTemplate>ct).templateString).toEqual(templateString);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should serialize for templates with the force comment', () => {
|
||||||
|
var templateString = '<!--nocache-->';
|
||||||
|
var t = DOM.createTemplate(templateString);
|
||||||
|
var ct = prepareTemplateForClone(t);
|
||||||
|
expect((<SerializedCloneableTemplate>ct).templateString).toEqual(templateString);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('ReferenceCloneableTemplate', () => {
|
||||||
|
it('should return template.content nodes (no import)', () => {
|
||||||
|
var t = DOM.createTemplate('a');
|
||||||
|
var ct = new ReferenceCloneableTemplate(t);
|
||||||
|
var clone = ct.clone(false);
|
||||||
|
expect(clone).not.toBe(DOM.content(t));
|
||||||
|
expect(DOM.getText(DOM.firstChild(clone))).toEqual('a');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return template.content nodes (import into doc)', () => {
|
||||||
|
var t = DOM.createTemplate('a');
|
||||||
|
var ct = new ReferenceCloneableTemplate(t);
|
||||||
|
var clone = ct.clone(true);
|
||||||
|
expect(clone).not.toBe(DOM.content(t));
|
||||||
|
expect(DOM.getText(DOM.firstChild(clone))).toEqual('a');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('SerializedCloneableTemplate', () => {
|
||||||
|
it('should return template.content nodes (no import)', () => {
|
||||||
|
var t = DOM.createTemplate('a');
|
||||||
|
var ct = new SerializedCloneableTemplate(t);
|
||||||
|
var clone = ct.clone(false);
|
||||||
|
expect(clone).not.toBe(DOM.content(t));
|
||||||
|
expect(DOM.getText(DOM.firstChild(clone))).toEqual('a');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return template.content nodes (import into doc)', () => {
|
||||||
|
var t = DOM.createTemplate('a');
|
||||||
|
var ct = new SerializedCloneableTemplate(t);
|
||||||
|
var clone = ct.clone(true);
|
||||||
|
expect(clone).not.toBe(DOM.content(t));
|
||||||
|
expect(DOM.getText(DOM.firstChild(clone))).toEqual('a');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
|
@ -3,6 +3,7 @@ import {
|
||||||
beforeEach,
|
beforeEach,
|
||||||
ddescribe,
|
ddescribe,
|
||||||
describe,
|
describe,
|
||||||
|
xdescribe,
|
||||||
el,
|
el,
|
||||||
expect,
|
expect,
|
||||||
iit,
|
iit,
|
||||||
|
@ -27,8 +28,8 @@ import {
|
||||||
} from 'angular2/src/render/api';
|
} from 'angular2/src/render/api';
|
||||||
|
|
||||||
import {DOM} from 'angular2/src/dom/dom_adapter';
|
import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||||
import {cloneAndQueryProtoView} from 'angular2/src/render/dom/util';
|
import {cloneAndQueryProtoView, ReferenceCloneableTemplate} from 'angular2/src/render/dom/util';
|
||||||
import {resolveInternalDomProtoView} from 'angular2/src/render/dom/view/proto_view';
|
import {resolveInternalDomProtoView, DomProtoView} from 'angular2/src/render/dom/view/proto_view';
|
||||||
import {ProtoViewBuilder} from 'angular2/src/render/dom/view/proto_view_builder';
|
import {ProtoViewBuilder} from 'angular2/src/render/dom/view/proto_view_builder';
|
||||||
import {ElementSchemaRegistry} from 'angular2/src/render/dom/schema/element_schema_registry';
|
import {ElementSchemaRegistry} from 'angular2/src/render/dom/schema/element_schema_registry';
|
||||||
|
|
||||||
|
@ -90,23 +91,23 @@ export function main() {
|
||||||
['<root class="ng-binding" idx="0"><a class="ng-binding" idx="1"></a></root>']));
|
['<root class="ng-binding" idx="0"><a class="ng-binding" idx="1"></a></root>']));
|
||||||
|
|
||||||
it('should project static text',
|
it('should project static text',
|
||||||
runAndAssert(
|
runAndAssert('root', ['<a>b</a>', 'A(<ng-content></ng-content>)'], [
|
||||||
'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>'
|
||||||
['<root class="ng-binding" idx="0"><a class="ng-binding" idx="1">A(b)</a></root>']));
|
]));
|
||||||
|
|
||||||
it('should project text interpolation',
|
it('should project text interpolation',
|
||||||
runAndAssert(
|
runAndAssert('root', ['<a>{{b}}</a>', 'A(<ng-content></ng-content>)'], [
|
||||||
'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>'
|
||||||
['<root class="ng-binding" idx="0"><a class="ng-binding" idx="1">A({0})</a></root>']));
|
]));
|
||||||
|
|
||||||
it('should project text interpolation to elements without bindings',
|
it('should project text interpolation to elements without bindings',
|
||||||
runAndAssert('root', ['<a>{{b}}</a>', '<div><ng-content></ng-content></div>'], [
|
runAndAssert('root', ['<a>{{b}}</a>', '<div><ng-content></ng-content></div>'], [
|
||||||
'<root class="ng-binding" idx="0"><a class="ng-binding" idx="1"><div class="ng-binding">{0}</div></a></root>'
|
'<root class="ng-binding" idx="0"><a class="ng-binding" idx="1"><div class="ng-binding"><!--[-->{0}<!--]--></div></a></root>'
|
||||||
]));
|
]));
|
||||||
|
|
||||||
it('should project elements',
|
it('should project elements',
|
||||||
runAndAssert('root', ['<a><div></div></a>', 'A(<ng-content></ng-content>)'], [
|
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>'
|
'<root class="ng-binding" idx="0"><a class="ng-binding" idx="1">A(<!--[--><div></div><!--]-->)</a></root>'
|
||||||
]));
|
]));
|
||||||
|
|
||||||
it('should project elements using the selector',
|
it('should project elements using the selector',
|
||||||
|
@ -117,14 +118,26 @@ export function main() {
|
||||||
'A(<ng-content select=".x"></ng-content>)'
|
'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>'
|
'<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',
|
it('should reproject',
|
||||||
runAndAssert(
|
runAndAssert(
|
||||||
'root',
|
'root',
|
||||||
['<a>x</a>', 'A(<b><ng-content></ng-content></b>)', 'B(<ng-content></ng-content>)'], [
|
['<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>'
|
'<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 text interpolation to sibling text nodes',
|
||||||
|
runAndAssert(
|
||||||
|
'root',
|
||||||
|
[
|
||||||
|
'<a>{{x}}</a>',
|
||||||
|
'<b>A(<ng-content></ng-content>)</b>)',
|
||||||
|
'B(<ng-content></ng-content>)'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'<root class="ng-binding" idx="0"><a class="ng-binding" idx="1"><b class="ng-binding" idx="2">B(<!--[-->A(<!--[-->{0}<!--]-->)<!--]-->)</b>)</a></root>'
|
||||||
]));
|
]));
|
||||||
|
|
||||||
it('should reproject by combining selectors',
|
it('should reproject by combining selectors',
|
||||||
|
@ -136,7 +149,7 @@ export function main() {
|
||||||
'B(<ng-content select=".y"></ng-content>)'
|
'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>'
|
'<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 as fragments (so that they can be moved manually)',
|
it('should keep non projected embedded views as fragments (so that they can be moved manually)',
|
||||||
|
@ -147,14 +160,14 @@ export function main() {
|
||||||
it('should project embedded views and match the template element',
|
it('should project embedded views and match the template element',
|
||||||
runAndAssert(
|
runAndAssert(
|
||||||
'root', ['<a><template class="x">b</template></a>', 'A(<ng-content></ng-content>)'], [
|
'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>',
|
'<root class="ng-binding" idx="0"><a class="ng-binding" idx="1">A(<!--[--><template class="x ng-binding" idx="2"></template><!--]-->)</a></root>',
|
||||||
'b'
|
'b'
|
||||||
]));
|
]));
|
||||||
|
|
||||||
it('should project nodes using the ng-content in embedded views',
|
it('should project nodes using the ng-content in embedded views',
|
||||||
runAndAssert('root', ['<a>b</a>', 'A(<ng-content *ng-if></ng-content>)'], [
|
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>',
|
'<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'
|
'<!--[-->b<!--]-->'
|
||||||
]));
|
]));
|
||||||
|
|
||||||
it('should allow to use wildcard selector after embedded view with non wildcard selector',
|
it('should allow to use wildcard selector after embedded view with non wildcard selector',
|
||||||
|
@ -165,8 +178,8 @@ export function main() {
|
||||||
'A(<ng-content select=".x" *ng-if></ng-content>, <ng-content></ng-content>)'
|
'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>',
|
'<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>'
|
'<!--[--><div class="x">a</div><!--]-->'
|
||||||
]));
|
]));
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@ -208,7 +221,7 @@ export function main() {
|
||||||
'<ng-content select="c"></ng-content><ng-content select="b"></ng-content>'
|
'<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>'
|
'<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>'
|
||||||
]));
|
]));
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@ -227,7 +240,7 @@ export function main() {
|
||||||
'<ng-content select="[y]"></ng-content><ng-content select="[x]"></ng-content>'
|
'<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>'
|
'<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>'
|
||||||
]));
|
]));
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@ -252,7 +265,7 @@ export function main() {
|
||||||
tb.merge([rootProtoViewDto, componentProtoViewDto])
|
tb.merge([rootProtoViewDto, componentProtoViewDto])
|
||||||
.then(mergeMappings => {
|
.then(mergeMappings => {
|
||||||
var domPv = resolveInternalDomProtoView(mergeMappings.mergedProtoViewRef);
|
var domPv = resolveInternalDomProtoView(mergeMappings.mergedProtoViewRef);
|
||||||
expect(DOM.getInnerHTML(domPv.rootElement))
|
expect(DOM.getInnerHTML(templateRoot(domPv)))
|
||||||
.toEqual('<root class="ng-binding" a="b"></root>');
|
.toEqual('<root class="ng-binding" a="b"></root>');
|
||||||
async.done();
|
async.done();
|
||||||
});
|
});
|
||||||
|
@ -264,6 +277,10 @@ export function main() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function templateRoot(pv: DomProtoView) {
|
||||||
|
return (<ReferenceCloneableTemplate>pv.cloneableTemplate).templateRoot;
|
||||||
|
}
|
||||||
|
|
||||||
function runAndAssert(hostElementName: string, componentTemplates: string[],
|
function runAndAssert(hostElementName: string, componentTemplates: string[],
|
||||||
expectedFragments: string[]) {
|
expectedFragments: string[]) {
|
||||||
var useNativeEncapsulation = hostElementName.startsWith('native-');
|
var useNativeEncapsulation = hostElementName.startsWith('native-');
|
||||||
|
|
|
@ -34,11 +34,12 @@ import {
|
||||||
RenderViewWithFragmentsStore,
|
RenderViewWithFragmentsStore,
|
||||||
WorkerRenderViewRef
|
WorkerRenderViewRef
|
||||||
} from 'angular2/src/web-workers/shared/render_view_with_fragments_store';
|
} from 'angular2/src/web-workers/shared/render_view_with_fragments_store';
|
||||||
import {resolveInternalDomProtoView} from 'angular2/src/render/dom/view/proto_view';
|
import {resolveInternalDomProtoView, DomProtoView} from 'angular2/src/render/dom/view/proto_view';
|
||||||
import {someComponent} from '../../render/dom/dom_renderer_integration_spec';
|
import {someComponent} from '../../render/dom/dom_renderer_integration_spec';
|
||||||
import {WebWorkerMain} from 'angular2/src/web-workers/ui/impl';
|
import {WebWorkerMain} from 'angular2/src/web-workers/ui/impl';
|
||||||
import {AnchorBasedAppRootUrl} from 'angular2/src/services/anchor_based_app_root_url';
|
import {AnchorBasedAppRootUrl} from 'angular2/src/services/anchor_based_app_root_url';
|
||||||
import {MockMessageBus, MockMessageBusSink, MockMessageBusSource} from './worker_test_util';
|
import {MockMessageBus, MockMessageBusSink, MockMessageBusSource} from './worker_test_util';
|
||||||
|
import {ReferenceCloneableTemplate} from 'angular2/src/render/dom/util';
|
||||||
|
|
||||||
export function main() {
|
export function main() {
|
||||||
function createBroker(workerSerializer: Serializer, uiSerializer: Serializer, tb: DomTestbed,
|
function createBroker(workerSerializer: Serializer, uiSerializer: Serializer, tb: DomTestbed,
|
||||||
|
@ -103,7 +104,7 @@ export function main() {
|
||||||
compiler.compileHost(dirMetadata)
|
compiler.compileHost(dirMetadata)
|
||||||
.then((protoView) => {
|
.then((protoView) => {
|
||||||
expect(DOM.tagName(DOM.firstChild(DOM.content(
|
expect(DOM.tagName(DOM.firstChild(DOM.content(
|
||||||
resolveWebWorkerRef(protoView.render).rootElement)))
|
templateRoot(resolveWebWorkerRef(protoView.render)))))
|
||||||
.toLowerCase())
|
.toLowerCase())
|
||||||
.toEqual('custom');
|
.toEqual('custom');
|
||||||
expect(protoView).not.toBeNull();
|
expect(protoView).not.toBeNull();
|
||||||
|
@ -297,6 +298,10 @@ class WorkerTestRootView extends TestRootView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function templateRoot(pv: DomProtoView) {
|
||||||
|
return (<ReferenceCloneableTemplate>pv.cloneableTemplate).templateRoot;
|
||||||
|
}
|
||||||
|
|
||||||
function createSerializer(protoViewRefStore: RenderProtoViewRefStore,
|
function createSerializer(protoViewRefStore: RenderProtoViewRefStore,
|
||||||
renderViewStore: RenderViewWithFragmentsStore): Serializer {
|
renderViewStore: RenderViewWithFragmentsStore): Serializer {
|
||||||
var injector = createTestInjector([
|
var injector = createTestInjector([
|
||||||
|
|
Loading…
Reference in New Issue