diff --git a/packages/core/src/render3/STATUS.md b/packages/core/src/render3/STATUS.md index a8de96258b..20f59c399a 100644 --- a/packages/core/src/render3/STATUS.md +++ b/packages/core/src/render3/STATUS.md @@ -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()` | ✅ | diff --git a/packages/core/src/render3/di.ts b/packages/core/src/render3/di.ts index c6bac64a82..3200038789 100644 --- a/packages/core/src/render3/di.ts +++ b/packages/core/src/render3/di.ts @@ -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 { * @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 = >>( @@ -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).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); diff --git a/packages/core/src/render3/instructions.ts b/packages/core/src/render3/instructions.ts index 133decaf37..d6debea1f8 100644 --- a/packages/core/src/render3/instructions.ts +++ b/packages/core/src/render3/instructions.ts @@ -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( */ 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<extNode&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<extNode&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] !; diff --git a/packages/core/src/render3/interfaces/node.ts b/packages/core/src/render3/interfaces/node.ts index b295a49ef0..5b0034bbb0 100644 --- a/packages/core/src/render3/interfaces/node.ts +++ b/packages/core/src/render3/interfaces/node.ts @@ -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; } diff --git a/packages/core/src/render3/interfaces/renderer.ts b/packages/core/src/render3/interfaces/renderer.ts index 2bc0775a00..46ec25e317 100644 --- a/packages/core/src/render3/interfaces/renderer.ts +++ b/packages/core/src/render3/interfaces/renderer.ts @@ -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; diff --git a/packages/core/src/render3/node_manipulation.ts b/packages/core/src/render3/node_manipulation.ts index a04d6359ec..743a453f5c 100644 --- a/packages/core/src/render3/node_manipulation.ts +++ b/packages/core/src/render3/node_manipulation.ts @@ -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); } } diff --git a/packages/core/test/bundling/todo/bundle.golden_symbols.json b/packages/core/test/bundling/todo/bundle.golden_symbols.json index 08d05da9a9..7e32c98327 100644 --- a/packages/core/test/bundling/todo/bundle.golden_symbols.json +++ b/packages/core/test/bundling/todo/bundle.golden_symbols.json @@ -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" }, diff --git a/packages/core/test/render3/control_flow_spec.ts b/packages/core/test/render3/control_flow_spec.ts index 1a0ffabaec..a96c23a8b2 100644 --- a/packages/core/test/render3/control_flow_spec.ts +++ b/packages/core/test/render3/control_flow_spec.ts @@ -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('
Hello
'); }); + 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}; diff --git a/packages/core/test/render3/query_spec.ts b/packages/core/test/render3/query_spec.ts index 9c87c9edeb..1c127d4b92 100644 --- a/packages/core/test/render3/query_spec.ts +++ b/packages/core/test/render3/query_spec.ts @@ -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', () => { /** * @@ -383,7 +383,8 @@ describe('query', () => { const cmptInstance = renderComponent(Cmpt); const qList = (cmptInstance.query as QueryList); 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', () => { diff --git a/packages/core/test/render3/render_util.ts b/packages/core/test/render3/render_util.ts index 0a68b1ead8..5d08734f11 100644 --- a/packages/core/test/render3/render_util.ts +++ b/packages/core/test/render3/render_util.ts @@ -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(componentOrElement: T | RElement): string { } else { return stringifyElement(componentOrElement) .replace(/^
/, '') + .replace(/^
/, '') .replace(/<\/div>$/, '') .replace(' style=""', '') - .replace(//g, ''); + .replace(//g, ''); } } diff --git a/packages/core/test/render3/view_container_ref_spec.ts b/packages/core/test/render3/view_container_ref_spec.ts index 94ec687f7b..d8282f76e7 100644 --- a/packages/core/test/render3/view_container_ref_spec.ts +++ b/packages/core/test/render3/view_container_ref_spec.ts @@ -595,7 +595,7 @@ describe('ViewContainerRef', () => { expect(fixture.html).toEqual('

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('

**A**BC'); let viewRef = directiveInstance !.vcref.get(0);