refactor(ivy): use comment nodes to mark view containers (#24346)

PR Close #24346
This commit is contained in:
Marc Laval 2018-06-06 17:30:48 +02:00 committed by Miško Hevery
parent 153ba4dff3
commit e3c54e4465
11 changed files with 191 additions and 178 deletions

View File

@ -266,7 +266,7 @@ The goal is for the `@Component` (and friends) to be the compiler of template. S
| `data()` | n/a |
| `destroy()` | ✅ |
| `createElement()` | ✅ |
| `createComment()` | n/a |
| `createComment()` | |
| `createText()` | ✅ |
| `destroyNode()` | ✅ |
| `appendChild()` | ✅ |

View File

@ -26,9 +26,9 @@ import {LInjector} from './interfaces/injector';
import {AttributeMarker, LContainerNode, LElementNode, LNode, LViewNode, TNodeFlags, TNodeType} from './interfaces/node';
import {LQueries, QueryReadType} from './interfaces/query';
import {Renderer3} from './interfaces/renderer';
import {DIRECTIVES, HOST_NODE, INJECTOR, LViewData, QUERIES, TVIEW, TView} from './interfaces/view';
import {DIRECTIVES, HOST_NODE, INJECTOR, LViewData, QUERIES, RENDERER, TVIEW, TView} from './interfaces/view';
import {assertNodeOfPossibleTypes, assertNodeType} from './node_assert';
import {detachView, getParentLNode, insertView, removeView} from './node_manipulation';
import {appendChild, detachView, getParentLNode, insertView, removeView} from './node_manipulation';
import {notImplemented, stringify} from './util';
import {EmbeddedViewRef, ViewRef} from './view_ref';
@ -526,8 +526,7 @@ export class ReadFromInjectorFn<T> {
* @returns The ElementRef instance to use
*/
export function getOrCreateElementRef(di: LInjector): viewEngine_ElementRef {
return di.elementRef || (di.elementRef = new ElementRef(
di.node.tNode.type === TNodeType.Container ? null : di.node.native));
return di.elementRef || (di.elementRef = new ElementRef(di.node.native));
}
export const QUERY_READ_TEMPLATE_REF = <QueryReadType<viewEngine_TemplateRef<any>>>(
@ -574,8 +573,10 @@ export function getOrCreateContainerRef(di: LInjector): viewEngine_ViewContainer
ngDevMode && assertNodeOfPossibleTypes(vcRefHost, TNodeType.Container, TNodeType.Element);
const hostParent = getParentLNode(vcRefHost) !;
const lContainer = createLContainer(hostParent, vcRefHost.view, true);
const comment = vcRefHost.view[RENDERER].createComment(ngDevMode ? 'container' : '');
const lContainerNode: LContainerNode = createLNodeObject(
TNodeType.Container, vcRefHost.view, hostParent, undefined, lContainer, null);
TNodeType.Container, vcRefHost.view, hostParent, comment, lContainer, null);
appendChild(hostParent, comment, vcRefHost.view);
if (vcRefHost.queries) {
@ -648,9 +649,6 @@ class ViewContainerRef implements viewEngine_ViewContainerRef {
(viewRef as EmbeddedViewRef<any>).attachToViewContainerRef(this);
insertView(this._lContainerNode, lViewNode, adjustedIdx);
// invalidate cache of next sibling RNode (we do similar operation in the containerRefreshEnd
// instruction)
this._lContainerNode.native = undefined;
this._viewRefs.splice(adjustedIdx, 0, viewRef);

View File

@ -24,7 +24,7 @@ import {assertNodeType} from './node_assert';
import {appendChild, insertView, appendProjectedNode, removeView, canInsertNativeNode, createTextNode, getNextLNode, getChildLNode, getParentLNode, getLViewChild} from './node_manipulation';
import {isNodeMatchingSelectorList, matchingSelectorIndex} from './node_selector_matcher';
import {ComponentDef, ComponentTemplate, DirectiveDef, DirectiveDefListOrFactory, PipeDefListOrFactory, RenderFlags} from './interfaces/definition';
import {RElement, RText, Renderer3, RendererFactory3, ProceduralRenderer3, RendererStyleFlags3, isProceduralRenderer} from './interfaces/renderer';
import {RComment, RElement, RText, Renderer3, RendererFactory3, ProceduralRenderer3, RendererStyleFlags3, isProceduralRenderer} from './interfaces/renderer';
import {isDifferent, stringify} from './util';
import {ViewRef} from './view_ref';
@ -308,7 +308,7 @@ export function createLViewData<T>(
*/
export function createLNodeObject(
type: TNodeType, currentView: LViewData, parent: LNode | null,
native: RText | RElement | null | undefined, state: any,
native: RText | RElement | RComment | null, state: any,
queries: LQueries | null): LElementNode&LTextNode&LViewNode&LContainerNode&LProjectionNode {
return {
native: native as any,
@ -341,15 +341,15 @@ export function createLNode(
index: number, type: TNodeType.View, native: null, name: null, attrs: null,
lViewData: LViewData): LViewNode;
export function createLNode(
index: number, type: TNodeType.Container, native: undefined, name: string | null,
index: number, type: TNodeType.Container, native: RComment, name: string | null,
attrs: TAttributes | null, lContainer: LContainer): LContainerNode;
export function createLNode(
index: number, type: TNodeType.Projection, native: null, name: null, attrs: TAttributes | null,
lProjection: LProjection): LProjectionNode;
export function createLNode(
index: number, type: TNodeType, native: RText | RElement | null | undefined,
name: string | null, attrs: TAttributes | null, state?: null | LViewData | LContainer |
LProjection): LElementNode&LTextNode&LViewNode&LContainerNode&LProjectionNode {
index: number, type: TNodeType, native: RText | RElement | RComment | null, name: string | null,
attrs: TAttributes | null, state?: null | LViewData | LContainer | LProjection): LElementNode&
LTextNode&LViewNode&LContainerNode&LProjectionNode {
const parent = isParent ? previousOrParentNode :
previousOrParentNode && getParentLNode(previousOrParentNode) !as LNode;
// Parents cannot cross component boundaries because components will be used in multiple places,
@ -1571,8 +1571,10 @@ export function container(
const currentParent = isParent ? previousOrParentNode : getParentLNode(previousOrParentNode) !;
const lContainer = createLContainer(currentParent, viewData);
const node = createLNode(
index, TNodeType.Container, undefined, tagName || null, attrs || null, lContainer);
const comment = renderer.createComment(ngDevMode ? 'container' : '');
const node =
createLNode(index, TNodeType.Container, comment, tagName || null, attrs || null, lContainer);
appendChild(getParentLNode(node), comment, viewData);
if (firstTemplatePass) {
node.tNode.tViews =
@ -1609,9 +1611,6 @@ export function containerRefreshStart(index: number): void {
ngDevMode && assertNodeType(previousOrParentNode, TNodeType.Container);
isParent = true;
(previousOrParentNode as LContainerNode).data[ACTIVE_INDEX] = 0;
ngDevMode && assertSame(
(previousOrParentNode as LContainerNode).native, undefined,
`the container's native element should not have been set yet.`);
if (!checkNoChangesMode) {
// We need to execute init hooks here so ngOnInit hooks are called in top level views
@ -1635,7 +1634,6 @@ export function containerRefreshEnd(): void {
}
ngDevMode && assertNodeType(previousOrParentNode, TNodeType.Container);
const container = previousOrParentNode as LContainerNode;
container.native = undefined;
ngDevMode && assertNodeType(container, TNodeType.Container);
const nextIndex = container.data[ACTIVE_INDEX] !;

View File

@ -10,7 +10,7 @@ import {LContainer} from './container';
import {LInjector} from './injector';
import {LProjection} from './projection';
import {LQueries} from './query';
import {RElement, RText} from './renderer';
import {RComment, RElement, RText} from './renderer';
import {LViewData, TView} from './view';
@ -63,7 +63,7 @@ export interface LNode {
* - append children to their element parents in the DOM (e.g. `parent.native.appendChild(...)`)
* - retrieve the sibling elements of text nodes whose creation / insertion has been delayed
*/
readonly native: RElement|RText|null|undefined;
readonly native: RComment|RElement|RText|null;
/**
* If regular LElementNode, then `data` will be null.
@ -141,13 +141,13 @@ export interface LViewNode extends LNode {
/** Abstract node container which contains other views. */
export interface LContainerNode extends LNode {
/*
* Caches the reference of the first native node following this container in the same native
* parent.
* This is reset to undefined in containerRefreshEnd.
* When it is undefined, it means the value has not been computed yet.
* Otherwise, it contains the result of findBeforeNode(container, null).
* This comment node is appended to the container's parent element to mark where
* in the DOM the container's child views should be added.
*
* If the container is a root node of a view, this comment will not be appended
* until the parent view is processed.
*/
native: RElement|RText|null|undefined;
native: RComment;
readonly data: LContainer;
}

View File

@ -35,6 +35,7 @@ export type Renderer3 = ObjectOrientedRenderer3 | ProceduralRenderer3;
* (reducing payload size).
* */
export interface ObjectOrientedRenderer3 {
createComment(data: string): RComment;
createElement(tagName: string): RElement;
createElementNS(namespace: string, tagName: string): RElement;
createTextNode(data: string): RText;
@ -57,6 +58,7 @@ export function isProceduralRenderer(renderer: ProceduralRenderer3 | ObjectOrien
*/
export interface ProceduralRenderer3 {
destroy(): void;
createComment(value: string): RComment;
createElement(name: string, namespace?: string|null): RElement;
createText(value: string): RText;
/**
@ -144,6 +146,8 @@ export interface RDomTokenList {
export interface RText extends RNode { textContent: string|null; }
export interface RComment extends RNode {}
// Note: This hack is necessary so we don't erroneously get a circular dependency
// failure based on types.
export const unusedValueExportToPlacateAjd = 1;

View File

@ -10,60 +10,13 @@ import {callHooks} from './hooks';
import {LContainer, RENDER_PARENT, VIEWS, unusedValueExportToPlacateAjd as unused1} from './interfaces/container';
import {LContainerNode, LElementNode, LNode, LProjectionNode, LTextNode, LViewNode, TNodeType, unusedValueExportToPlacateAjd as unused2} from './interfaces/node';
import {unusedValueExportToPlacateAjd as unused3} from './interfaces/projection';
import {ProceduralRenderer3, RElement, RNode, RText, Renderer3, isProceduralRenderer, unusedValueExportToPlacateAjd as unused4} from './interfaces/renderer';
import {ProceduralRenderer3, RComment, RElement, RNode, RText, Renderer3, isProceduralRenderer, unusedValueExportToPlacateAjd as unused4} from './interfaces/renderer';
import {CLEANUP, DIRECTIVES, FLAGS, HEADER_OFFSET, HOST_NODE, HookData, LViewData, LViewFlags, NEXT, PARENT, QUERIES, RENDERER, TVIEW, unusedValueExportToPlacateAjd as unused5} from './interfaces/view';
import {assertNodeType} from './node_assert';
import {stringify} from './util';
const unusedValueToPlacateAjd = unused1 + unused2 + unused3 + unused4 + unused5;
/**
* Returns the first RNode following the given LNode in the same parent DOM element.
*
* This is needed in order to insert the given node with insertBefore.
*
* @param node The node whose following DOM node must be found.
* @param stopNode A parent node at which the lookup in the tree should be stopped, or null if the
* lookup should not be stopped until the result is found.
* @returns RNode before which the provided node should be inserted or null if the lookup was
* stopped
* or if there is no native node after the given logical node in the same native parent.
*/
function findNextRNodeSibling(node: LNode | null, stopNode: LNode | null): RElement|RText|null {
let currentNode = node;
while (currentNode && currentNode !== stopNode) {
let pNextOrParent = currentNode.pNextOrParent;
if (pNextOrParent) {
while (pNextOrParent.tNode.type !== TNodeType.Projection) {
const nativeNode = findFirstRNode(pNextOrParent);
if (nativeNode) {
return nativeNode;
}
pNextOrParent = pNextOrParent.pNextOrParent !;
}
currentNode = pNextOrParent;
} else {
let currentSibling = getNextLNode(currentNode);
while (currentSibling) {
const nativeNode = findFirstRNode(currentSibling);
if (nativeNode) {
return nativeNode;
}
currentSibling = getNextLNode(currentSibling);
}
const parentNode = getParentLNode(currentNode);
currentNode = null;
if (parentNode) {
const parentType = parentNode.tNode.type;
if (parentType === TNodeType.Container || parentType === TNodeType.View) {
currentNode = parentNode;
}
}
}
}
return null;
}
/** Retrieves the sibling node for the given node. */
export function getNextLNode(node: LNode): LNode|null {
// View nodes don't have TNodes, so their next must be retrieved through their LView.
@ -84,8 +37,8 @@ export function getChildLNode(node: LNode): LNode|null {
}
/** Retrieves the parent LNode of a given node. */
export function getParentLNode(node: LElementNode | LTextNode | LProjectionNode): LElementNode|
LViewNode;
export function getParentLNode(node: LContainerNode | LElementNode | LTextNode | LProjectionNode):
LElementNode|LViewNode;
export function getParentLNode(node: LViewNode): LContainerNode|null;
export function getParentLNode(node: LNode): LElementNode|LContainerNode|LViewNode|null;
export function getParentLNode(node: LNode): LElementNode|LContainerNode|LViewNode|null {
@ -115,98 +68,47 @@ function getNextLNodeWithProjection(node: LNode): LNode|null {
return getNextLNode(node);
}
/**
* Find the next node in the LNode tree, taking into account the place where a node is
* projected (in the shadow DOM) rather than where it comes from (in the light DOM).
*
* If there is no sibling node, this function goes to the next sibling of the parent node...
* until it reaches rootNode (at which point null is returned).
*
* @param initialNode The node whose following node in the LNode tree must be found.
* @param rootNode The root node at which the lookup should stop.
* @return LNode|null The following node in the LNode tree.
*/
function getNextOrParentSiblingNode(initialNode: LNode, rootNode: LNode): LNode|null {
let node: LNode|null = initialNode;
let nextNode = getNextLNodeWithProjection(node);
while (node && !nextNode) {
// if node.pNextOrParent is not null here, it is not the next node
// (because, at this point, nextNode is null, so it is the parent)
node = node.pNextOrParent || getParentLNode(node);
if (node === rootNode) {
return null;
}
nextNode = node && getNextLNodeWithProjection(node);
}
return nextNode;
}
/**
* Returns the first RNode inside the given LNode.
*
* @param node The node whose first DOM node must be found
* @returns RNode The first RNode of the given LNode or null if there is none.
*/
function findFirstRNode(rootNode: LNode): RElement|RText|null {
return walkLNodeTree(rootNode, rootNode, WalkLNodeTreeAction.Find) || null;
}
const enum WalkLNodeTreeAction {
/** returns the first available native node */
Find = 0,
/** node insert in the native environment */
Insert = 1,
Insert = 0,
/** node detach from the native environment */
Detach = 2,
Detach = 1,
/** node destruction using the renderer's API */
Destroy = 3,
Destroy = 2,
}
/**
* Walks a tree of LNodes, applying a transformation on the LElement nodes, either only on the first
* one found, or on all of them.
* NOTE: for performance reasons, the possible actions are inlined within the function instead of
* being passed as an argument.
*
* @param startingNode the node from which the walk is started.
* @param rootNode the root node considered.
* @param action Identifies the action to be performed on the LElement nodes.
* @param renderer Optional the current renderer, required for action modes 1, 2 and 3.
* @param renderParentNode Optionnal the render parent node to be set in all LContainerNodes found,
* required for action modes 1 and 2.
* @param beforeNode Optionnal the node before which elements should be added, required for action
* modes 1.
* @param action identifies the action to be performed on the LElement nodes.
* @param renderer the current renderer.
* @param renderParentNode Optional the render parent node to be set in all LContainerNodes found,
* required for action modes Insert and Destroy.
* @param beforeNode Optional the node before which elements should be added, required for action
* Insert.
*/
function walkLNodeTree(
startingNode: LNode | null, rootNode: LNode, action: WalkLNodeTreeAction, renderer?: Renderer3,
startingNode: LNode | null, rootNode: LNode, action: WalkLNodeTreeAction, renderer: Renderer3,
renderParentNode?: LElementNode | null, beforeNode?: RNode | null) {
let node: LNode|null = startingNode;
while (node) {
let nextNode: LNode|null = null;
const parent = renderParentNode ? renderParentNode.native : null;
if (node.tNode.type === TNodeType.Element) {
// Execute the action
if (action === WalkLNodeTreeAction.Find) {
return node.native;
} else if (action === WalkLNodeTreeAction.Insert) {
const parent = renderParentNode !.native;
isProceduralRenderer(renderer !) ?
(renderer as ProceduralRenderer3)
.insertBefore(parent !, node.native !, beforeNode as RNode | null) :
parent !.insertBefore(node.native !, beforeNode as RNode | null, true);
} else if (action === WalkLNodeTreeAction.Detach) {
const parent = renderParentNode !.native;
isProceduralRenderer(renderer !) ?
(renderer as ProceduralRenderer3).removeChild(parent as RElement, node.native !) :
parent !.removeChild(node.native !);
} else if (action === WalkLNodeTreeAction.Destroy) {
ngDevMode && ngDevMode.rendererDestroyNode++;
(renderer as ProceduralRenderer3).destroyNode !(node.native !);
executeNodeAction(action, renderer, parent, node.native !, beforeNode);
if (node.dynamicLContainerNode) {
executeNodeAction(
action, renderer, parent, node.dynamicLContainerNode.native !, beforeNode);
}
nextNode = getNextLNode(node);
} else if (node.tNode.type === TNodeType.Container) {
executeNodeAction(action, renderer, parent, node.native !, beforeNode);
const lContainerNode: LContainerNode = (node as LContainerNode);
const childContainerData: LContainer = lContainerNode.dynamicLContainerNode ?
lContainerNode.dynamicLContainerNode.data :
@ -216,6 +118,13 @@ function walkLNodeTree(
}
nextNode =
childContainerData[VIEWS].length ? getChildLNode(childContainerData[VIEWS][0]) : null;
if (nextNode) {
// When the walker enters a container, then the beforeNode has to become the local native
// comment node.
beforeNode = lContainerNode.dynamicLContainerNode ?
lContainerNode.dynamicLContainerNode.native :
lContainerNode.native;
}
} else if (node.tNode.type === TNodeType.Projection) {
// For Projection look at the first projected node
nextNode = (node as LProjectionNode).data.head;
@ -224,7 +133,55 @@ function walkLNodeTree(
nextNode = getChildLNode(node as LViewNode);
}
node = nextNode === null ? getNextOrParentSiblingNode(node, rootNode) : nextNode;
if (nextNode == null) {
/**
* Find the next node in the LNode tree, taking into account the place where a node is
* projected (in the shadow DOM) rather than where it comes from (in the light DOM).
*
* If there is no sibling node, then it goes to the next sibling of the parent node...
* until it reaches rootNode (at which point null is returned).
*/
let currentNode: LNode|null = node;
node = getNextLNodeWithProjection(currentNode);
while (currentNode && !node) {
// if node.pNextOrParent is not null here, it is not the next node
// (because, at this point, nextNode is null, so it is the parent)
currentNode = currentNode.pNextOrParent || getParentLNode(currentNode);
if (currentNode === rootNode) {
return null;
}
// When the walker exits a container, the beforeNode has to be restored to the previous
// value.
if (currentNode && !currentNode.pNextOrParent &&
currentNode.tNode.type === TNodeType.Container) {
beforeNode = currentNode.native;
}
node = currentNode && getNextLNodeWithProjection(currentNode);
}
} else {
node = nextNode;
}
}
}
/**
* NOTE: for performance reasons, the possible actions are inlined within the function instead of
* being passed as an argument.
*/
function executeNodeAction(
action: WalkLNodeTreeAction, renderer: Renderer3, parent: RElement | null,
node: RComment | RElement | RText, beforeNode?: RNode | null) {
if (action === WalkLNodeTreeAction.Insert) {
isProceduralRenderer(renderer !) ?
(renderer as ProceduralRenderer3).insertBefore(parent !, node, beforeNode as RNode | null) :
parent !.insertBefore(node, beforeNode as RNode | null, true);
} else if (action === WalkLNodeTreeAction.Detach) {
isProceduralRenderer(renderer !) ?
(renderer as ProceduralRenderer3).removeChild(parent !, node) :
parent !.removeChild(node);
} else if (action === WalkLNodeTreeAction.Destroy) {
ngDevMode && ngDevMode.rendererDestroyNode++;
(renderer as ProceduralRenderer3).destroyNode !(node);
}
}
@ -354,15 +311,9 @@ export function insertView(
// and we should wait until that parent processes its nodes (otherwise, we will insert this view's
// nodes twice - once now and once when its parent inserts its views).
if (container.data[RENDER_PARENT] !== null) {
let beforeNode = findNextRNodeSibling(viewNode, container);
if (!beforeNode) {
let containerNextNativeNode = container.native;
if (containerNextNativeNode === undefined) {
containerNextNativeNode = container.native = findNextRNodeSibling(container, null);
}
beforeNode = containerNextNativeNode;
}
// Find the node to insert in front of
const beforeNode =
index + 1 < views.length ? (getChildLNode(views[index + 1]) !).native : container.native;
addRemoveViewFromContainer(container, viewNode, true, beforeNode);
}
@ -581,9 +532,8 @@ export function appendChild(parent: LNode, child: RNode | null, currentView: LVi
export function appendProjectedNode(
node: LElementNode | LTextNode | LContainerNode, currentParent: LElementNode,
currentView: LViewData): void {
if (node.tNode.type !== TNodeType.Container) {
appendChild(currentParent, (node as LElementNode | LTextNode).native, currentView);
} else {
appendChild(currentParent, node.native, currentView);
if (node.tNode.type === TNodeType.Container) {
// The node we are adding is a Container and we are adding it to Element which
// is not a component (no more re-projection).
// Alternatively a container is projected at the root of a component's template
@ -598,5 +548,6 @@ export function appendProjectedNode(
}
if (node.dynamicLContainerNode) {
node.dynamicLContainerNode.data[RENDER_PARENT] = currentParent;
appendChild(currentParent, node.dynamicLContainerNode.native, currentView);
}
}

View File

@ -398,6 +398,9 @@
{
"name": "executeInitHooks"
},
{
"name": "executeNodeAction"
},
{
"name": "executeOnDestroys"
},
@ -419,12 +422,6 @@
{
"name": "findDirectiveMatches"
},
{
"name": "findFirstRNode"
},
{
"name": "findNextRNodeSibling"
},
{
"name": "firstTemplatePass"
},
@ -455,9 +452,6 @@
{
"name": "getNextLNodeWithProjection"
},
{
"name": "getNextOrParentSiblingNode"
},
{
"name": "getOrCreateContainerRef"
},

View File

@ -9,7 +9,7 @@
import {defineComponent} from '../../src/render3/definition';
import {bind, container, containerRefreshEnd, containerRefreshStart, elementEnd, elementStart, embeddedViewEnd, embeddedViewStart, text, textBinding} from '../../src/render3/instructions';
import {RenderFlags} from '../../src/render3/interfaces/definition';
import {ComponentFixture, createComponent, renderToHtml} from './render_util';
import {ComponentFixture, TemplateFixture, createComponent, renderToHtml} from './render_util';
describe('JS control flow', () => {
it('should work with if block', () => {
@ -134,6 +134,76 @@ describe('JS control flow', () => {
expect(renderToHtml(Template, ctx)).toEqual('<div><span>Hello</span></div>');
});
it('should work with nested adjacent if blocks', () => {
const ctx: {condition: boolean,
condition2: boolean,
condition3: boolean} = {condition: true, condition2: false, condition3: true};
/**
* % if(ctx.condition) {
* % if(ctx.condition2) {
* Hello
* % }
* % if(ctx.condition3) {
* World
* % }
* % }
*/
function createTemplate() { container(0); }
function updateTemplate() {
containerRefreshStart(0);
{
if (ctx.condition) {
let rf1 = embeddedViewStart(1);
{
if (rf1 & RenderFlags.Create) {
{ container(0); }
{ container(1); }
}
if (rf1 & RenderFlags.Update) {
containerRefreshStart(0);
{
if (ctx.condition2) {
let rf2 = embeddedViewStart(2);
{
if (rf2 & RenderFlags.Create) {
text(0, 'Hello');
}
}
embeddedViewEnd();
}
}
containerRefreshEnd();
containerRefreshStart(1);
{
if (ctx.condition3) {
let rf2 = embeddedViewStart(2);
{
if (rf2 & RenderFlags.Create) {
text(0, 'World');
}
}
embeddedViewEnd();
}
}
containerRefreshEnd();
}
}
embeddedViewEnd();
}
}
containerRefreshEnd();
}
const fixture = new TemplateFixture(createTemplate, updateTemplate);
expect(fixture.html).toEqual('World');
ctx.condition2 = true;
fixture.update();
expect(fixture.html).toEqual('HelloWorld');
});
it('should work with adjacent if blocks managing views in the same container', () => {
const ctx = {condition1: true, condition2: true, condition3: true};

View File

@ -361,7 +361,7 @@ describe('query', () => {
expect(isViewContainerRef(qList.first)).toBeTruthy();
});
it('should no longer read ElementRef with a native element pointing to comment DOM node from containers',
it('should read ElementRef with a native element pointing to comment DOM node from containers',
() => {
/**
* <ng-template #foo></ng-template>
@ -383,7 +383,8 @@ describe('query', () => {
const cmptInstance = renderComponent(Cmpt);
const qList = (cmptInstance.query as QueryList<any>);
expect(qList.length).toBe(1);
expect(qList.first.nativeElement).toBe(null);
expect(isElementRef(qList.first)).toBeTruthy();
expect(qList.first.nativeElement.nodeType).toBe(8); // Node.COMMENT_NODE = 8
});
it('should read TemplateRef from container nodes by default', () => {

View File

@ -32,11 +32,7 @@ export abstract class BaseFixture {
/**
* Current state of rendered HTML.
*/
get html(): string {
return (this.hostElement as any as Element)
.innerHTML.replace(/ style=""/g, '')
.replace(/ class=""/g, '');
}
get html(): string { return toHtml(this.hostElement as any as Element); }
}
function noop() {}
@ -223,9 +219,10 @@ export function toHtml<T>(componentOrElement: T | RElement): string {
} else {
return stringifyElement(componentOrElement)
.replace(/^<div host="">/, '')
.replace(/^<div fixture="mark">/, '')
.replace(/<\/div>$/, '')
.replace(' style=""', '')
.replace(/<!--[\w]*-->/g, '');
.replace(/<!--container-->/g, '');
}
}

View File

@ -595,7 +595,7 @@ describe('ViewContainerRef', () => {
expect(fixture.html).toEqual('<p vcref=""></p>ABC');
// The DOM is manually modified here to ensure that the text node is actually moved
fixture.hostElement.childNodes[1].nodeValue = '**A**';
fixture.hostElement.childNodes[2].nodeValue = '**A**';
expect(fixture.html).toEqual('<p vcref=""></p>**A**BC');
let viewRef = directiveInstance !.vcref.get(0);