From 70f1e2e04ab0fa0a1a13392693a52713eb496a4f Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Mon, 12 Oct 2020 21:38:06 -0700 Subject: [PATCH] refactor(core): Create `TNodeType.Text` to display full template in `TView` debug (#39233) When looking at `TView` debug template only Element nodes were displayed as `TNode.Element` was used for both `RElement` and `RText`. Additionally no text was stored in `TNode.value`. The result was that the whole template could not be reconstructed. This refactoring creates `TNodeType.Text` and store the text value in `TNode.value`. The refactoring also changes `TNodeType` into flag-like structure make it more efficient to check many different types at once. PR Close #39233 --- packages/core/src/debug/debug_node.ts | 6 +- packages/core/src/render3/di.ts | 10 +- packages/core/src/render3/i18n/i18n_apply.ts | 2 +- .../render3/i18n/i18n_insert_before_index.ts | 2 +- packages/core/src/render3/i18n/i18n_parse.ts | 15 +- packages/core/src/render3/i18n/i18n_util.ts | 4 +- .../core/src/render3/instructions/element.ts | 5 +- .../render3/instructions/element_container.ts | 4 +- .../core/src/render3/instructions/i18n.ts | 2 +- .../core/src/render3/instructions/listener.ts | 8 +- .../src/render3/instructions/lview_debug.ts | 8 +- .../core/src/render3/instructions/shared.ts | 108 ++++++------- .../core/src/render3/instructions/styling.ts | 2 +- .../core/src/render3/instructions/text.ts | 2 +- packages/core/src/render3/interfaces/node.ts | 145 +++++++++++------- packages/core/src/render3/interfaces/view.ts | 4 +- packages/core/src/render3/node_assert.ts | 59 +++---- .../core/src/render3/node_manipulation.ts | 48 +++--- packages/core/src/render3/query.ts | 16 +- packages/core/src/render3/state.ts | 1 + .../src/render3/view_engine_compatibility.ts | 14 +- packages/core/src/render3/view_ref.ts | 16 +- packages/core/test/acceptance/debug_spec.ts | 2 +- packages/core/test/acceptance/i18n_spec.ts | 2 +- packages/core/test/render3/i18n/i18n_spec.ts | 6 +- .../core/test/render3/interfaces/node_spec.ts | 21 ++- 26 files changed, 264 insertions(+), 248 deletions(-) diff --git a/packages/core/src/debug/debug_node.ts b/packages/core/src/debug/debug_node.ts index 8f0cc9a706..6497402c54 100644 --- a/packages/core/src/debug/debug_node.ts +++ b/packages/core/src/debug/debug_node.ts @@ -529,7 +529,7 @@ function _queryNodeChildrenR3( ngDevMode && assertTNodeForLView(tNode, lView); const nativeNode = getNativeByTNodeOrNull(tNode, lView); // For each type of TNode, specific logic is executed. - if (tNode.type === TNodeType.Element || tNode.type === TNodeType.ElementContainer) { + if (tNode.type & (TNodeType.AnyRNode | TNodeType.ElementContainer)) { // Case 1: the TNode is an element // The native node has to be checked. _addQueryMatchR3(nativeNode, predicate, matches, elementsOnly, rootNativeNode); @@ -565,14 +565,14 @@ function _queryNodeChildrenR3( _queryNodeChildrenInContainerR3( nodeOrContainer, predicate, matches, elementsOnly, rootNativeNode); } - } else if (tNode.type === TNodeType.Container) { + } else if (tNode.type & TNodeType.Container) { // Case 2: the TNode is a container // The native node has to be checked. const lContainer = lView[tNode.index]; _addQueryMatchR3(lContainer[NATIVE], predicate, matches, elementsOnly, rootNativeNode); // Each view inside the container has to be processed. _queryNodeChildrenInContainerR3(lContainer, predicate, matches, elementsOnly, rootNativeNode); - } else if (tNode.type === TNodeType.Projection) { + } else if (tNode.type & TNodeType.Projection) { // Case 3: the TNode is a projection insertion point (i.e. a ). // The nodes projected at this location all need to be processed. const componentView = lView![DECLARATION_COMPONENT_VIEW]; diff --git a/packages/core/src/render3/di.ts b/packages/core/src/render3/di.ts index 4b225ba508..e278b1112b 100644 --- a/packages/core/src/render3/di.ts +++ b/packages/core/src/render3/di.ts @@ -13,7 +13,7 @@ import {injectRootLimpMode, setInjectImplementation} from '../di/injector_compat import {getInjectorDef} from '../di/interface/defs'; import {InjectFlags} from '../di/interface/injector'; import {Type} from '../interface/type'; -import {assertDefined, assertEqual, assertIndexInRange, throwError} from '../util/assert'; +import {assertDefined, assertEqual, assertIndexInRange} from '../util/assert'; import {noSideEffects} from '../util/closure'; import {assertDirectiveDef, assertNodeInjector, assertTNodeForLView} from './assert'; @@ -25,7 +25,7 @@ import {isFactory, NO_PARENT_INJECTOR, NodeInjectorFactory, NodeInjectorOffset, import {AttributeMarker, TContainerNode, TDirectiveHostNode, TElementContainerNode, TElementNode, TNode, TNodeProviderIndexes, TNodeType} from './interfaces/node'; import {isComponentDef, isComponentHost} from './interfaces/type_checks'; import {DECLARATION_COMPONENT_VIEW, DECLARATION_VIEW, INJECTOR, LView, T_HOST, TData, TVIEW, TView, TViewType} from './interfaces/view'; -import {assertNodeOfPossibleTypes} from './node_assert'; +import {assertTNodeType} from './node_assert'; import {enterDI, leaveDI} from './state'; import {isNameOnlyAttributeMarker} from './util/attrs_utils'; import {getParentInjectorIndex, getParentInjectorView, hasParentInjector} from './util/injector_utils'; @@ -301,9 +301,7 @@ export function diPublicInInjector( * @publicApi */ export function injectAttributeImpl(tNode: TNode, attrNameToInject: string): string|null { - ngDevMode && - assertNodeOfPossibleTypes( - tNode, [TNodeType.Container, TNodeType.Element, TNodeType.ElementContainer]); + ngDevMode && assertTNodeType(tNode, TNodeType.AnyContainer | TNodeType.AnyRNode); ngDevMode && assertDefined(tNode, 'expecting tNode'); if (attrNameToInject === 'class') { return tNode.classes; @@ -506,7 +504,7 @@ function searchTokensOnInjector( // - AND the parent TNode is an Element. // This means that we just came from the Component's View and therefore are allowed to see // into the ViewProviders. - (previousTView != currentTView && (tNode.type === TNodeType.Element)); + (previousTView != currentTView && ((tNode.type & TNodeType.AnyRNode) !== 0)); // This special case happens when there is a @host on the inject and when we are searching // on the host element node. diff --git a/packages/core/src/render3/i18n/i18n_apply.ts b/packages/core/src/render3/i18n/i18n_apply.ts index 48c38fb4c9..e123460211 100644 --- a/packages/core/src/render3/i18n/i18n_apply.ts +++ b/packages/core/src/render3/i18n/i18n_apply.ts @@ -10,7 +10,7 @@ import {getPluralCase} from '../../i18n/localization'; import {assertDefined, assertDomNode, assertEqual, assertGreaterThan, assertIndexInRange, throwError} from '../../util/assert'; import {assertIndexInExpandoRange, assertTIcu} from '../assert'; import {attachPatchData} from '../context_discovery'; -import {elementPropertyInternal, setElementAttribute, textBindingInternal} from '../instructions/shared'; +import {elementPropertyInternal, setElementAttribute} from '../instructions/shared'; import {COMMENT_MARKER, ELEMENT_MARKER, getCurrentICUCaseIndex, getParentFromI18nMutateOpCode, getRefFromI18nMutateOpCode, I18nCreateOpCode, I18nCreateOpCodes, I18nMutateOpCode, I18nMutateOpCodes, I18nUpdateOpCode, I18nUpdateOpCodes, IcuType, TI18n, TIcu} from '../interfaces/i18n'; import {TNode} from '../interfaces/node'; import {RElement, RNode, RText} from '../interfaces/renderer'; diff --git a/packages/core/src/render3/i18n/i18n_insert_before_index.ts b/packages/core/src/render3/i18n/i18n_insert_before_index.ts index fc88f18041..f1b419945f 100644 --- a/packages/core/src/render3/i18n/i18n_insert_before_index.ts +++ b/packages/core/src/render3/i18n/i18n_insert_before_index.ts @@ -63,7 +63,7 @@ export function addTNodeAndUpdateInsertBeforeIndex(previousTNodes: TNode[], newT } function isI18nText(tNode: TNode): boolean { - return tNode.type !== TNodeType.Placeholder; + return !(tNode.type & TNodeType.Placeholder); } function isNewTNodeCreatedBefore(existingTNode: TNode, newTNode: TNode): boolean { diff --git a/packages/core/src/render3/i18n/i18n_parse.ts b/packages/core/src/render3/i18n/i18n_parse.ts index 44bf466091..5f20adde86 100644 --- a/packages/core/src/render3/i18n/i18n_parse.ts +++ b/packages/core/src/render3/i18n/i18n_parse.ts @@ -17,7 +17,7 @@ import {loadIcuContainerVisitor} from '../instructions/i18n_icu_container_visito import {allocExpando, createTNodeAtIndex, elementAttributeInternal, setInputsForProperty, setNgReflectProperties} from '../instructions/shared'; import {getDocument} from '../interfaces/document'; import {COMMENT_MARKER, ELEMENT_MARKER, ensureIcuContainerVisitorLoaded, I18nCreateOpCode, I18nCreateOpCodes, I18nMutateOpCode, i18nMutateOpCode, I18nMutateOpCodes, I18nUpdateOpCode, I18nUpdateOpCodes, IcuExpression, IcuType, TI18n, TIcu} from '../interfaces/i18n'; -import {TIcuContainerNode, TNode, TNodeType} from '../interfaces/node'; +import {TNode, TNodeType} from '../interfaces/node'; import {RComment, RElement} from '../interfaces/renderer'; import {SanitizerFn} from '../interfaces/sanitization'; import {HEADER_OFFSET, LView, TView} from '../interfaces/view'; @@ -152,7 +152,7 @@ export function i18nStartFirstCreatePass( */ function createTNodeAndAddOpCode( tView: TView, rootTNode: TNode|null, existingTNodes: TNode[], lView: LView, - createOpCodes: I18nCreateOpCodes, text: string, isICU: boolean): TNode { + createOpCodes: I18nCreateOpCodes, text: string|null, isICU: boolean): TNode { const i18nNodeIdx = allocExpando(tView, lView, 1); let opCode = i18nNodeIdx << I18nCreateOpCode.SHIFT; let parentTNode = getCurrentParentTNode(); @@ -174,9 +174,12 @@ function createTNodeAndAddOpCode( opCode |= I18nCreateOpCode.COMMENT; ensureIcuContainerVisitorLoaded(loadIcuContainerVisitor); } - createOpCodes.push(opCode, text); + createOpCodes.push(opCode, text === null ? '' : text); + // We store `{{?}}` so that when looking at debug `TNodeType.template` we can see where the + // bindings are. const tNode = createTNodeAtIndex( - tView, i18nNodeIdx, isICU ? TNodeType.IcuContainer : TNodeType.Element, null, null); + tView, i18nNodeIdx, isICU ? TNodeType.Icu : TNodeType.Text, + text === null ? (ngDevMode ? '{{?}}' : '') : text, null); addTNodeAndUpdateInsertBeforeIndex(existingTNodes, tNode); const tNodeIdx = tNode.index; setCurrentTNode(tNode, false /* Text nodes are self closing */); @@ -212,7 +215,7 @@ function i18nStartFirstCreatePassProcessTextNode( updateOpCodes: I18nUpdateOpCodes, lView: LView, text: string): void { const hasBinding = text.match(BINDING_REGEXP); const tNode = createTNodeAndAddOpCode( - tView, rootTNode, existingTNodes, lView, createOpCodes, hasBinding ? '' : text, false); + tView, rootTNode, existingTNodes, lView, createOpCodes, hasBinding ? null : text, false); if (hasBinding) { generateBindingUpdateOpCodes(updateOpCodes, text, tNode.index); } @@ -251,7 +254,7 @@ export function i18nAttributesFirstPass( const tNode = getTNode(tView, previousElementIndex - HEADER_OFFSET); // Set attributes for Elements only, for other types (like ElementContainer), // only set inputs below - if (tNode.type === TNodeType.Element) { + if (tNode.type & TNodeType.AnyRNode) { elementAttributeInternal(tNode, lView, attrName, value, null, null); } // Check if that attribute is a directive input diff --git a/packages/core/src/render3/i18n/i18n_util.ts b/packages/core/src/render3/i18n/i18n_util.ts index ce300e9539..cfdd9ea77b 100644 --- a/packages/core/src/render3/i18n/i18n_util.ts +++ b/packages/core/src/render3/i18n/i18n_util.ts @@ -12,7 +12,7 @@ import {createTNodeAtIndex} from '../instructions/shared'; import {TIcu} from '../interfaces/i18n'; import {TIcuContainerNode, TNode, TNodeType} from '../interfaces/node'; import {TView} from '../interfaces/view'; -import {assertNodeType} from '../node_assert'; +import {assertTNodeType} from '../node_assert'; import {addTNodeAndUpdateInsertBeforeIndex} from './i18n_insert_before_index'; @@ -69,7 +69,7 @@ export function setTIcu(tView: TView, index: number, tIcu: TIcu): void { if (tNode === null) { tView.data[index] = tIcu; } else { - ngDevMode && assertNodeType(tNode, TNodeType.IcuContainer); + ngDevMode && assertTNodeType(tNode, TNodeType.Icu); tNode.value = tIcu; } } diff --git a/packages/core/src/render3/instructions/element.ts b/packages/core/src/render3/instructions/element.ts index 01aaa4c4e5..4bfbe80d6c 100644 --- a/packages/core/src/render3/instructions/element.ts +++ b/packages/core/src/render3/instructions/element.ts @@ -14,7 +14,7 @@ import {hasClassInput, hasStyleInput, TAttributes, TElementNode, TNode, TNodeFla import {RElement} from '../interfaces/renderer'; import {isContentQueryHost, isDirectiveHost} from '../interfaces/type_checks'; import {HEADER_OFFSET, LView, RENDERER, TView} from '../interfaces/view'; -import {assertNodeType} from '../node_assert'; +import {assertTNodeType} from '../node_assert'; import {appendChild, createElementNode, writeDirectClass, writeDirectStyle} from '../node_manipulation'; import {decreaseElementDepthCount, getBindingIndex, getCurrentTNode, getElementDepthCount, getLView, getNamespace, getTView, increaseElementDepthCount, isCurrentTNodeParent, setCurrentTNode, setCurrentTNodeAsNotParent} from '../state'; import {computeStaticStyling} from '../styling/static_styling'; @@ -24,6 +24,7 @@ import {setDirectiveInputsWhichShadowsStyling} from './property'; import {createDirectivesInstances, executeContentQueries, getOrCreateTNode, matchingSchemas, resolveDirectives, saveResolvedLocalsInData} from './shared'; + function elementStartFirstCreatePass( index: number, tView: TView, lView: LView, native: RElement, name: string, attrsIndex?: number|null, localRefsIndex?: number): TElementNode { @@ -140,7 +141,7 @@ export function ɵɵelementEnd(): void { } const tNode = currentTNode; - ngDevMode && assertNodeType(tNode, TNodeType.Element); + ngDevMode && assertTNodeType(tNode, TNodeType.AnyRNode); decreaseElementDepthCount(); diff --git a/packages/core/src/render3/instructions/element_container.ts b/packages/core/src/render3/instructions/element_container.ts index 5a39cea605..f8288a0d0b 100644 --- a/packages/core/src/render3/instructions/element_container.ts +++ b/packages/core/src/render3/instructions/element_container.ts @@ -12,7 +12,7 @@ import {registerPostOrderHooks} from '../hooks'; import {TAttributes, TElementContainerNode, TNodeType} from '../interfaces/node'; import {isContentQueryHost, isDirectiveHost} from '../interfaces/type_checks'; import {HEADER_OFFSET, LView, RENDERER, T_HOST, TView} from '../interfaces/view'; -import {assertNodeType} from '../node_assert'; +import {assertTNodeType} from '../node_assert'; import {appendChild} from '../node_manipulation'; import {getBindingIndex, getCurrentTNode, getLView, getTView, isCurrentTNodeParent, setCurrentTNode, setCurrentTNodeAsNotParent} from '../state'; import {computeStaticStyling} from '../styling/static_styling'; @@ -108,7 +108,7 @@ export function ɵɵelementContainerEnd(): void { setCurrentTNode(currentTNode, false); } - ngDevMode && assertNodeType(currentTNode, TNodeType.ElementContainer); + ngDevMode && assertTNodeType(currentTNode, TNodeType.ElementContainer); if (tView.firstCreatePass) { registerPostOrderHooks(tView, currentTNode); diff --git a/packages/core/src/render3/instructions/i18n.ts b/packages/core/src/render3/instructions/i18n.ts index ddc9c1bc61..a8319b08de 100644 --- a/packages/core/src/render3/instructions/i18n.ts +++ b/packages/core/src/render3/instructions/i18n.ts @@ -62,7 +62,7 @@ export function ɵɵi18nStart( const parentRNode = getClosestRElement(tView, sameViewParentTNode, lView); // If `parentTNode` is an `ElementContainer` than it has ``. // When we do inserts we have to make sure to insert in front of ``. - const insertInFrontOf = parentTNode && parentTNode.type === TNodeType.ElementContainer ? + const insertInFrontOf = parentTNode && (parentTNode.type & TNodeType.ElementContainer) ? lView[parentTNode.index] : null; applyCreateOpCodes(lView, tI18n.create, parentRNode, insertInFrontOf); diff --git a/packages/core/src/render3/instructions/listener.ts b/packages/core/src/render3/instructions/listener.ts index fece61a0b9..9176a72c25 100644 --- a/packages/core/src/render3/instructions/listener.ts +++ b/packages/core/src/render3/instructions/listener.ts @@ -14,7 +14,7 @@ import {PropertyAliasValue, TNode, TNodeFlags, TNodeType} from '../interfaces/no import {GlobalTargetResolver, isProceduralRenderer, RElement, Renderer3} from '../interfaces/renderer'; import {isDirectiveHost} from '../interfaces/type_checks'; import {CLEANUP, FLAGS, LView, LViewFlags, RENDERER, TView} from '../interfaces/view'; -import {assertNodeOfPossibleTypes} from '../node_assert'; +import {assertTNodeType} from '../node_assert'; import {getCurrentDirectiveDef, getCurrentTNode, getLView, getTView} from '../state'; import {getComponentLViewByIndex, getNativeByTNode, unwrapRNode} from '../util/view_utils'; @@ -126,14 +126,12 @@ function listenerInternal( // register a listener and store its cleanup function on LView. const lCleanup = getLCleanup(lView); - ngDevMode && - assertNodeOfPossibleTypes( - tNode, [TNodeType.Element, TNodeType.Container, TNodeType.ElementContainer]); + ngDevMode && assertTNodeType(tNode, TNodeType.AnyRNode | TNodeType.AnyContainer); let processOutputs = true; // add native event listener - applicable to elements only - if (tNode.type === TNodeType.Element) { + if (tNode.type & TNodeType.AnyRNode) { const native = getNativeByTNode(tNode, lView) as RElement; const resolved = eventTargetResolver ? eventTargetResolver(native) : EMPTY_OBJ as any; const target = resolved.target || native; diff --git a/packages/core/src/render3/instructions/lview_debug.ts b/packages/core/src/render3/instructions/lview_debug.ts index 4d929b0642..421a6cd04d 100644 --- a/packages/core/src/render3/instructions/lview_debug.ts +++ b/packages/core/src/render3/instructions/lview_debug.ts @@ -17,7 +17,7 @@ import {getInjectorIndex} from '../di'; import {CONTAINER_HEADER_OFFSET, HAS_TRANSPLANTED_VIEWS, LContainer, MOVED_VIEWS, NATIVE} from '../interfaces/container'; import {ComponentTemplate, DirectiveDef, DirectiveDefList, PipeDefList, ViewQueriesFunction} from '../interfaces/definition'; import {NO_PARENT_INJECTOR, NodeInjectorOffset} from '../interfaces/injector'; -import {AttributeMarker, InsertBeforeIndex, PropertyAliases, TConstants, TContainerNode, TElementNode, TNode as ITNode, TNodeFlags, TNodeProviderIndexes, TNodeType, TNodeTypeAsString} from '../interfaces/node'; +import {AttributeMarker, InsertBeforeIndex, PropertyAliases, TConstants, TContainerNode, TElementNode, TNode as ITNode, TNodeFlags, TNodeProviderIndexes, TNodeType, toTNodeTypeAsString} from '../interfaces/node'; import {SelectorFlags} from '../interfaces/projection'; import {LQueries, TQueries} from '../interfaces/query'; import {RComment, RElement, Renderer3, RendererFactory3, RNode} from '../interfaces/renderer'; @@ -239,7 +239,7 @@ class TNode implements ITNode { } get type_(): string { - return TNodeTypeAsString[this.type] || `TNodeType.?${this.type}?`; + return toTNodeTypeAsString(this.type) || `TNodeType.?${this.type}?`; } get flags_(): string { @@ -256,7 +256,7 @@ class TNode implements ITNode { } get template_(): string { - if (this.value === null && this.type === TNodeType.Element) return '#text'; + if (this.type & TNodeType.Text) return this.value!; const buf: string[] = []; const tagName = typeof this.value === 'string' && this.value || this.type_; buf.push('<', tagName); @@ -594,7 +594,7 @@ export function buildDebugNode(tNode: ITNode, lView: LView): DebugNode { } return { html: toHtml(native), - type: TNodeTypeAsString[tNode.type], + type: toTNodeTypeAsString(tNode.type), native: native as any, children: toDebugNodes(tNode.child, lView), factories, diff --git a/packages/core/src/render3/instructions/shared.ts b/packages/core/src/render3/instructions/shared.ts index 3ed54440a8..2ea07375b8 100644 --- a/packages/core/src/render3/instructions/shared.ts +++ b/packages/core/src/render3/instructions/shared.ts @@ -5,43 +5,44 @@ * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -import { Injector } from '../../di'; -import { ErrorHandler } from '../../error_handler'; -import { DoCheck, OnChanges, OnInit } from '../../interface/lifecycle_hooks'; -import { CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA, SchemaMetadata } from '../../metadata/schema'; -import { ViewEncapsulation } from '../../metadata/view'; -import { validateAgainstEventAttributes, validateAgainstEventProperties } from '../../sanitization/sanitization'; -import { Sanitizer } from '../../sanitization/sanitizer'; -import { assertDefined, assertDomNode, assertEqual, assertGreaterThan, assertIndexInRange, assertLessThan, assertNotEqual, assertNotSame, assertSame, assertString } from '../../util/assert'; -import { createNamedArrayType } from '../../util/named_array_type'; -import { initNgDevMode } from '../../util/ng_dev_mode'; -import { normalizeDebugBindingName, normalizeDebugBindingValue } from '../../util/ng_reflect'; -import { stringify } from '../../util/stringify'; -import { assertFirstCreatePass, assertFirstUpdatePass, assertLContainer, assertLView, assertTNodeForLView, assertTNodeForTView } from '../assert'; -import { attachPatchData } from '../context_discovery'; -import { getFactoryDef } from '../definition'; -import { diPublicInInjector, getNodeInjectable, getOrCreateNodeInjectorForNode } from '../di'; -import { throwMultipleComponentError } from '../errors'; -import { executeCheckHooks, executeInitAndCheckHooks, incrementInitPhaseFlags } from '../hooks'; -import { CONTAINER_HEADER_OFFSET, HAS_TRANSPLANTED_VIEWS, LContainer, MOVED_VIEWS } from '../interfaces/container'; -import { ComponentDef, ComponentTemplate, DirectiveDef, DirectiveDefListOrFactory, PipeDefListOrFactory, RenderFlags, ViewQueriesFunction } from '../interfaces/definition'; -import { NodeInjectorFactory, NodeInjectorOffset } from '../interfaces/injector'; -import { AttributeMarker, InitialInputData, InitialInputs, LocalRefExtractor, PropertyAliases, PropertyAliasValue, TAttributes, TConstantsOrFactory, TContainerNode, TDirectiveHostNode, TElementContainerNode, TElementNode, TIcuContainerNode, TNode, TNodeFlags, TNodeProviderIndexes, TNodeType, TProjectionNode } from '../interfaces/node'; -import { isProceduralRenderer, RComment, RElement, Renderer3, RendererFactory3, RNode, RText } from '../interfaces/renderer'; -import { SanitizerFn } from '../interfaces/sanitization'; -import { isComponentDef, isComponentHost, isContentQueryHost, isRootView } from '../interfaces/type_checks'; -import { CHILD_HEAD, CHILD_TAIL, CLEANUP, CONTEXT, DECLARATION_COMPONENT_VIEW, DECLARATION_VIEW, FLAGS, HEADER_OFFSET, HOST, InitPhaseState, INJECTOR, LView, LViewFlags, NEXT, PARENT, RENDERER, RENDERER_FACTORY, RootContext, RootContextFlags, SANITIZER, TData, TRANSPLANTED_VIEWS_TO_REFRESH, TVIEW, TView, TViewType, T_HOST } from '../interfaces/view'; -import { assertNodeNotOfTypes, assertNodeOfPossibleTypes } from '../node_assert'; -import { updateTextNode } from '../node_manipulation'; -import { isInlineTemplate, isNodeMatchingSelectorList } from '../node_selector_matcher'; -import { enterView, getBindingsEnabled, getCurrentDirectiveIndex, getCurrentParentTNode, getCurrentTNode, getCurrentTNodePlaceholderOk, getSelectedIndex, isCurrentTNodeParent, isInCheckNoChangesMode, isInI18nBlock, leaveView, setBindingIndex, setBindingRootForHostBindings, setCurrentDirectiveIndex, setCurrentQueryIndex, setCurrentTNode, setIsInCheckNoChangesMode, setSelectedIndex } from '../state'; -import { NO_CHANGE } from '../tokens'; -import { isAnimationProp, mergeHostAttrs } from '../util/attrs_utils'; -import { INTERPOLATION_DELIMITER, renderStringify, stringifyForError } from '../util/misc_utils'; -import { getFirstLContainer, getLViewParent, getNextLContainer } from '../util/view_traversal_utils'; -import { getComponentLViewByIndex, getNativeByIndex, getNativeByTNode, isCreationMode, readPatchedLView, resetPreOrderHookFlags, unwrapLView, updateTransplantedViewCount, viewAttachedToChangeDetector } from '../util/view_utils'; -import { selectIndexInternal } from './advance'; -import { attachLContainerDebug, attachLViewDebug, cloneToLViewFromTViewBlueprint, cloneToTViewData, LCleanup, LViewBlueprint, MatchesArray, TCleanup, TNodeDebug, TNodeInitialInputs, TNodeLocalNames, TViewComponents, TViewConstructor } from './lview_debug'; +import {Injector} from '../../di'; +import {ErrorHandler} from '../../error_handler'; +import {DoCheck, OnChanges, OnInit} from '../../interface/lifecycle_hooks'; +import {CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA, SchemaMetadata} from '../../metadata/schema'; +import {ViewEncapsulation} from '../../metadata/view'; +import {validateAgainstEventAttributes, validateAgainstEventProperties} from '../../sanitization/sanitization'; +import {Sanitizer} from '../../sanitization/sanitizer'; +import {assertDefined, assertDomNode, assertEqual, assertGreaterThan, assertIndexInRange, assertLessThan, assertNotEqual, assertNotSame, assertSame, assertString} from '../../util/assert'; +import {createNamedArrayType} from '../../util/named_array_type'; +import {initNgDevMode} from '../../util/ng_dev_mode'; +import {normalizeDebugBindingName, normalizeDebugBindingValue} from '../../util/ng_reflect'; +import {stringify} from '../../util/stringify'; +import {assertFirstCreatePass, assertFirstUpdatePass, assertLContainer, assertLView, assertTNodeForLView, assertTNodeForTView} from '../assert'; +import {attachPatchData} from '../context_discovery'; +import {getFactoryDef} from '../definition'; +import {diPublicInInjector, getNodeInjectable, getOrCreateNodeInjectorForNode} from '../di'; +import {throwMultipleComponentError} from '../errors'; +import {executeCheckHooks, executeInitAndCheckHooks, incrementInitPhaseFlags} from '../hooks'; +import {CONTAINER_HEADER_OFFSET, HAS_TRANSPLANTED_VIEWS, LContainer, MOVED_VIEWS} from '../interfaces/container'; +import {ComponentDef, ComponentTemplate, DirectiveDef, DirectiveDefListOrFactory, PipeDefListOrFactory, RenderFlags, ViewQueriesFunction} from '../interfaces/definition'; +import {NodeInjectorFactory, NodeInjectorOffset} from '../interfaces/injector'; +import {AttributeMarker, InitialInputData, InitialInputs, LocalRefExtractor, PropertyAliases, PropertyAliasValue, TAttributes, TConstantsOrFactory, TContainerNode, TDirectiveHostNode, TElementContainerNode, TElementNode, TIcuContainerNode, TNode, TNodeFlags, TNodeProviderIndexes, TNodeType, TProjectionNode} from '../interfaces/node'; +import {isProceduralRenderer, RComment, RElement, Renderer3, RendererFactory3, RNode, RText} from '../interfaces/renderer'; +import {SanitizerFn} from '../interfaces/sanitization'; +import {isComponentDef, isComponentHost, isContentQueryHost, isRootView} from '../interfaces/type_checks'; +import {CHILD_HEAD, CHILD_TAIL, CLEANUP, CONTEXT, DECLARATION_COMPONENT_VIEW, DECLARATION_VIEW, FLAGS, HEADER_OFFSET, HOST, InitPhaseState, INJECTOR, LView, LViewFlags, NEXT, PARENT, RENDERER, RENDERER_FACTORY, RootContext, RootContextFlags, SANITIZER, T_HOST, TData, TRANSPLANTED_VIEWS_TO_REFRESH, TVIEW, TView, TViewType} from '../interfaces/view'; +import {assertPureTNodeType, assertTNodeType} from '../node_assert'; +import {updateTextNode} from '../node_manipulation'; +import {isInlineTemplate, isNodeMatchingSelectorList} from '../node_selector_matcher'; +import {enterView, getBindingsEnabled, getCurrentDirectiveIndex, getCurrentParentTNode, getCurrentTNode, getCurrentTNodePlaceholderOk, getSelectedIndex, isCurrentTNodeParent, isInCheckNoChangesMode, isInI18nBlock, leaveView, setBindingIndex, setBindingRootForHostBindings, setCurrentDirectiveIndex, setCurrentQueryIndex, setCurrentTNode, setIsInCheckNoChangesMode, setSelectedIndex} from '../state'; +import {NO_CHANGE} from '../tokens'; +import {isAnimationProp, mergeHostAttrs} from '../util/attrs_utils'; +import {INTERPOLATION_DELIMITER, renderStringify, stringifyForError} from '../util/misc_utils'; +import {getFirstLContainer, getLViewParent, getNextLContainer} from '../util/view_traversal_utils'; +import {getComponentLViewByIndex, getNativeByIndex, getNativeByTNode, isCreationMode, readPatchedLView, resetPreOrderHookFlags, unwrapLView, updateTransplantedViewCount, viewAttachedToChangeDetector} from '../util/view_utils'; + +import {selectIndexInternal} from './advance'; +import {attachLContainerDebug, attachLViewDebug, cloneToLViewFromTViewBlueprint, cloneToTViewData, LCleanup, LViewBlueprint, MatchesArray, TCleanup, TNodeDebug, TNodeInitialInputs, TNodeLocalNames, TViewComponents, TViewConstructor} from './lview_debug'; @@ -196,7 +197,7 @@ export function createLView( * @param attrs Any attrs for the native element, if applicable */ export function getOrCreateTNode( - tView: TView, index: number, type: TNodeType.Element, name: string|null, + tView: TView, index: number, type: TNodeType.Element|TNodeType.Text, name: string|null, attrs: TAttributes|null): TElementNode; export function getOrCreateTNode( tView: TView, index: number, type: TNodeType.Container, name: string|null, @@ -208,12 +209,13 @@ export function getOrCreateTNode( tView: TView, index: number, type: TNodeType.ElementContainer, name: string|null, attrs: TAttributes|null): TElementContainerNode; export function getOrCreateTNode( - tView: TView, index: number, type: TNodeType.IcuContainer, name: null, + tView: TView, index: number, type: TNodeType.Icu, name: null, attrs: TAttributes|null): TElementContainerNode; export function getOrCreateTNode( tView: TView, index: number, type: TNodeType, name: string|null, attrs: TAttributes|null): TElementNode&TContainerNode&TElementContainerNode&TProjectionNode&TIcuContainerNode { // Keep this function short, so that the VM will inline it. + ngDevMode && assertPureTNodeType(type); const adjustedIndex = index + HEADER_OFFSET; let tNode = tView.data[adjustedIndex] as TNode; if (tNode === null) { @@ -225,7 +227,7 @@ export function getOrCreateTNode( // removed, so we mark it as detached. tNode.flags |= TNodeFlags.isDetached; } - } else if (tNode.type == TNodeType.Placeholder) { + } else if (tNode.type & TNodeType.Placeholder) { tNode.type = type; tNode.value = name; tNode.attrs = attrs; @@ -818,13 +820,13 @@ export function createTNode( tView: TView, tParent: TElementNode|TContainerNode|null, type: TNodeType.Container, adjustedIndex: number, tagName: string|null, attrs: TAttributes|null): TContainerNode; export function createTNode( - tView: TView, tParent: TElementNode|TContainerNode|null, type: TNodeType.Element, + tView: TView, tParent: TElementNode|TContainerNode|null, type: TNodeType.Element|TNodeType.Text, adjustedIndex: number, tagName: string|null, attrs: TAttributes|null): TElementNode; export function createTNode( tView: TView, tParent: TElementNode|TContainerNode|null, type: TNodeType.ElementContainer, adjustedIndex: number, tagName: string|null, attrs: TAttributes|null): TElementContainerNode; export function createTNode( - tView: TView, tParent: TElementNode|TContainerNode|null, type: TNodeType.IcuContainer, + tView: TView, tParent: TElementNode|TContainerNode|null, type: TNodeType.Icu, adjustedIndex: number, tagName: string|null, attrs: TAttributes|null): TIcuContainerNode; export function createTNode( tView: TView, tParent: TElementNode|TContainerNode|null, type: TNodeType.Projection, @@ -1012,7 +1014,7 @@ export function elementPropertyInternal( if (ngDevMode) { setNgReflectProperties(lView, element, tNode.type, dataValue, value); } - } else if (tNode.type === TNodeType.Element) { + } else if (tNode.type & TNodeType.AnyRNode) { propName = mapPropName(propName); if (ngDevMode) { @@ -1034,7 +1036,7 @@ export function elementPropertyInternal( (element as RElement).setProperty ? (element as any).setProperty(propName, value) : (element as any)[propName] = value; } - } else if (tNode.type === TNodeType.Container || tNode.type === TNodeType.ElementContainer) { + } else if (tNode.type & TNodeType.AnyContainer) { // If the node is a container and the property didn't // match any of the inputs or schemas we should throw. if (ngDevMode && !matchingSchemas(tView, tNode.value)) { @@ -1057,7 +1059,7 @@ function setNgReflectProperty( const renderer = lView[RENDERER]; attrName = normalizeDebugBindingName(attrName); const debugValue = normalizeDebugBindingValue(value); - if (type === TNodeType.Element) { + if (type & TNodeType.AnyRNode) { if (value == null) { isProceduralRenderer(renderer) ? renderer.removeAttribute((element as RElement), attrName) : (element as RElement).removeAttribute(attrName); @@ -1079,7 +1081,7 @@ function setNgReflectProperty( export function setNgReflectProperties( lView: LView, element: RElement|RComment, type: TNodeType, dataValue: PropertyAliasValue, value: any) { - if (type === TNodeType.Element || type === TNodeType.Container) { + if (type & (TNodeType.AnyRNode | TNodeType.Container)) { /** * dataValue is an array containing runtime input or output names for the directives: * i+0: directive instance index @@ -1300,7 +1302,7 @@ function instantiateAllDirectives( const isComponent = isComponentDef(def); if (isComponent) { - ngDevMode && assertNodeOfPossibleTypes(tNode, [TNodeType.Element]); + ngDevMode && assertTNodeType(tNode, TNodeType.AnyRNode); addComponentLogic(lView, tNode as TElementNode, def as ComponentDef); } @@ -1386,9 +1388,7 @@ function findDirectiveDefMatches( tView: TView, viewData: LView, tNode: TElementNode|TContainerNode|TElementContainerNode): DirectiveDef[]|null { ngDevMode && assertFirstCreatePass(tView); - ngDevMode && - assertNodeOfPossibleTypes( - tNode, [TNodeType.Element, TNodeType.ElementContainer, TNodeType.Container]); + ngDevMode && assertTNodeType(tNode, TNodeType.AnyRNode | TNodeType.AnyContainer); const registry = tView.directiveRegistry; let matches: any[]|null = null; @@ -1401,8 +1401,8 @@ function findDirectiveDefMatches( if (isComponentDef(def)) { if (ngDevMode) { - assertNodeOfPossibleTypes( - tNode, [TNodeType.Element], + assertTNodeType( + tNode, TNodeType.Element, `"${tNode.value}" tags cannot be used as component hosts. ` + `Please use a different tag to activate the ${stringify(def.type)} component.`); @@ -1518,8 +1518,8 @@ export function elementAttributeInternal( if (ngDevMode) { assertNotSame(value, NO_CHANGE as any, 'Incoming value should never be NO_CHANGE.'); validateAgainstEventAttributes(name); - assertNodeNotOfTypes( - tNode, [TNodeType.Container, TNodeType.ElementContainer], + assertTNodeType( + tNode, TNodeType.Element, `Attempted to set attribute \`${name}\` on a container node. ` + `Host bindings are not valid on ng-container or ng-template.`); } diff --git a/packages/core/src/render3/instructions/styling.ts b/packages/core/src/render3/instructions/styling.ts index e5daab9dc6..e956e9a6e5 100644 --- a/packages/core/src/render3/instructions/styling.ts +++ b/packages/core/src/render3/instructions/styling.ts @@ -705,7 +705,7 @@ function updateStylingMap( function updateStyling( tView: TView, tNode: TNode, lView: LView, renderer: Renderer3, prop: string, value: string|undefined|null|boolean, isClassBased: boolean, bindingIndex: number) { - if (tNode.type !== TNodeType.Element) { + if (!(tNode.type & TNodeType.AnyRNode)) { // It is possible to have styling on non-elements (such as ng-container). // This is rare, but it does happen. In such a case, just ignore the binding. return; diff --git a/packages/core/src/render3/instructions/text.ts b/packages/core/src/render3/instructions/text.ts index 5dc5fdacbf..f834fc7dbe 100644 --- a/packages/core/src/render3/instructions/text.ts +++ b/packages/core/src/render3/instructions/text.ts @@ -35,7 +35,7 @@ export function ɵɵtext(index: number, value: string = ''): void { ngDevMode && assertIndexInRange(lView, adjustedIndex); const tNode = tView.firstCreatePass ? - getOrCreateTNode(tView, index, TNodeType.Element, null, null) : + getOrCreateTNode(tView, index, TNodeType.Text, value, null) : tView.data[adjustedIndex] as TElementNode; const textNative = lView[adjustedIndex] = createTextNode(lView[RENDERER], value); diff --git a/packages/core/src/render3/interfaces/node.ts b/packages/core/src/render3/interfaces/node.ts index d8a46638ea..f01f9d37a4 100644 --- a/packages/core/src/render3/interfaces/node.ts +++ b/packages/core/src/render3/interfaces/node.ts @@ -15,30 +15,47 @@ import {LView, TView} from './view'; /** * TNodeType corresponds to the {@link TNode} `type` property. + * + * NOTE: type IDs are such that we use each bit to denote a type. This is done so that we can easily + * check if the `TNode` is of more than one type. + * + * `if (tNode.type === TNodeType.Text || tNode.type === TNode.Element)` + * can be written as: + * `if (tNode.type & (TNodeType.Text | TNodeType.Element))` + * + * However any given `TNode` can only be of one type. */ export const enum TNodeType { - // FIXME(misko): Add `Text` type so that it would be much easier to reason/debug about `TNode`s. + /** + * The TNode contains information about a DOM element aka {@link RText}. + */ + Text = 0b1, + + /** + * The TNode contains information about a DOM element aka {@link RElement}. + */ + Element = 0b10, + /** * The TNode contains information about an {@link LContainer} for embedded views. */ - // FIXME(misko): Verify that we still need a `Container`, at the very least update the text. - Container = 0, - /** - * The TNode contains information about an `` projection - */ - Projection = 1, - /** - * The TNode contains information about a DOM element aka {@link RNode}. - */ - Element = 2, + Container = 0b100, + /** * The TNode contains information about an `` element {@link RNode}. */ - ElementContainer = 3, + ElementContainer = 0b1000, + + /** + * The TNode contains information about an `` projection + */ + Projection = 0b10000, + /** * The TNode contains information about an ICU comment used in `i18n`. */ - IcuContainer = 4, + Icu = 0b100000, + /** * Special node type representing a placeholder for future `TNode` at this location. * @@ -52,22 +69,31 @@ export const enum TNodeType { * location. Seeing a `Placeholder` `TNode` already there tells the system that it should reuse * existing `TNode` (rather than create a new one) and just update the missing information. */ - Placeholder = 5, + Placeholder = 0b1000000, + + // Combined Types These should never be used for `TNode.type` only as a useful way to check + // if `TNode.type` is one of several choices. + + // See: https://github.com/microsoft/TypeScript/issues/35875 why we can't refer to existing enum. + AnyRNode = 0b11, // Text | Element, + AnyContainer = 0b1100, // Container | ElementContainer, // See: } /** * Converts `TNodeType` into human readable text. * Make sure this matches with `TNodeType` */ -export const TNodeTypeAsString = [ - 'Container', // 0 - 'Projection', // 1 - 'Element', // 2 - 'ElementContainer', // 3 - 'IcuContainer', // 4 - 'Placeholder', // 5 -] as const; - +export function toTNodeTypeAsString(tNodeType: TNodeType): string { + let text = ''; + (tNodeType & TNodeType.Text) && (text += '|Text'); + (tNodeType & TNodeType.Element) && (text += '|Element'); + (tNodeType & TNodeType.Container) && (text += '|Container'); + (tNodeType & TNodeType.ElementContainer) && (text += '|ElementContainer'); + (tNodeType & TNodeType.Projection) && (text += '|Projection'); + (tNodeType & TNodeType.Icu) && (text += '|IcuContainer'); + (tNodeType & TNodeType.Placeholder) && (text += '|Placeholder'); + return text.length > 0 ? text.substring(1) : text; +} /** * Corresponds to the TNode.flags property. @@ -223,7 +249,8 @@ export const enum AttributeMarker { Template = 4, /** - * Signals that the following attribute is `ngProjectAs` and its value is a parsed `CssSelector`. + * Signals that the following attribute is `ngProjectAs` and its value is a parsed + * `CssSelector`. * * For example, given the following HTML: * @@ -273,10 +300,10 @@ export type TAttributes = (string|AttributeMarker|CssSelector)[]; export type TConstants = (TAttributes|string)[]; /** - * Factory function that returns an array of consts. Consts can be represented as a function in case - * any additional statements are required to define consts in the list. An example is i18n where - * additional i18n calls are generated, which should be executed when consts are requested for the - * first time. + * Factory function that returns an array of consts. Consts can be represented as a function in + * case any additional statements are required to define consts in the list. An example is i18n + * where additional i18n calls are generated, which should be executed when consts are requested + * for the first time. */ export type TConstantsFactory = () => TConstants; @@ -332,8 +359,8 @@ export interface TNode { * nodes. It can also insert `Hello ` and `!` text node as a child of `
`, but it can't * insert `World` because the `` node has not yet been created. In such a case the * `` `TNode` will have an array which will direct the `` to not only insert - * itself in front of `!` but also to insert the `World` (created by `ɵɵi18nStart`) into `` - * itself. + * itself in front of `!` but also to insert the `World` (created by `ɵɵi18nStart`) into + * `` itself. * * Pseudo code: * ``` @@ -369,10 +396,11 @@ export interface TNode { * * If the index === -1, there is no injector on this node or any ancestor node in this view. * - * If the index !== -1, it is the index of this node's injector OR the index of a parent injector - * in the same view. We pass the parent injector index down the node tree of a view so it's - * possible to find the parent injector without walking a potentially deep node tree. Injector - * indices are not set across view boundaries because there could be multiple component hosts. + * If the index !== -1, it is the index of this node's injector OR the index of a parent + * injector in the same view. We pass the parent injector index down the node tree of a view so + * it's possible to find the parent injector without walking a potentially deep node tree. + * Injector indices are not set across view boundaries because there could be multiple component + * hosts. * * If tNode.injectorIndex === tNode.parent.injectorIndex, then the index belongs to a parent * injector. @@ -398,8 +426,8 @@ export interface TNode { * * Valid values are: * - `-1` No `hostBindings` instruction has executed. - * - `directiveStart <= directiveStylingLast < directiveEnd`: Points to the `DirectiveDef` of the - * last styling instruction which executed in the `hostBindings`. + * - `directiveStart <= directiveStylingLast < directiveEnd`: Points to the `DirectiveDef` of + * the last styling instruction which executed in the `hostBindings`. * * This data is needed so that styling instructions know which static styling data needs to be * collected from the `DirectiveDef.hostAttrs`. A styling instruction needs to collect all data @@ -408,13 +436,14 @@ export interface TNode { directiveStylingLast: number; /** - * Stores indexes of property bindings. This field is only set in the ngDevMode and holds indexes - * of property bindings so TestBed can get bound property metadata for a given node. + * Stores indexes of property bindings. This field is only set in the ngDevMode and holds + * indexes of property bindings so TestBed can get bound property metadata for a given node. */ propertyBindings: number[]|null; /** - * Stores if Node isComponent, isProjected, hasContentQuery, hasClassInput and hasStyleInput etc. + * Stores if Node isComponent, isProjected, hasContentQuery, hasClassInput and hasStyleInput + * etc. */ flags: TNodeFlags; @@ -437,8 +466,8 @@ export interface TNode { value: any; /** - * Attributes associated with an element. We need to store attributes to support various use-cases - * (attribute injection, content projection with selectors, directives matching). + * Attributes associated with an element. We need to store attributes to support various + * use-cases (attribute injection, content projection with selectors, directives matching). * Attributes are stored statically because reading them from the DOM would be way too slow for * content projection and queries. * @@ -528,10 +557,10 @@ export interface TNode { next: TNode|null; /** - * The next projected sibling. Since in Angular content projection works on the node-by-node basis - * the act of projecting nodes might change nodes relationship at the insertion point (target - * view). At the same time we need to keep initial relationship between nodes as expressed in - * content view. + * The next projected sibling. Since in Angular content projection works on the node-by-node + * basis the act of projecting nodes might change nodes relationship at the insertion point + * (target view). At the same time we need to keep initial relationship between nodes as + * expressed in content view. */ projectionNext: TNode|null; @@ -584,8 +613,8 @@ export interface TNode { * - `projection` size is equal to the number of projections ``. The size of * `c1` will be `1` because `` has only one ``. * - we store `projection` with the host (`c1`, `c2`) rather than the `` (`cont1`) - * because the same component (``) can be used in multiple locations (`c1`, `c2`) and as - * a result have different set of nodes to project. + * because the same component (``) can be used in multiple locations (`c1`, `c2`) and + * as a result have different set of nodes to project. * - without `projection` it would be difficult to efficiently traverse nodes to be projected. * * If `typeof projection == 'number'` then `TNode` is a `` element: @@ -619,9 +648,9 @@ export interface TNode { * (e.g. `
`) * Must be stored separately from `tNode.styles` to facilitate setting directive * inputs that shadow the `style` property. If we used `tNode.styles` as is for shadowed inputs, - * we would feed host styles back into directives as "inputs". If we used `tNode.attrs`, we would - * have to concatenate the attributes on every template pass. Instead, we process once on first - * create pass and store here. + * we would feed host styles back into directives as "inputs". If we used `tNode.attrs`, we + * would have to concatenate the attributes on every template pass. Instead, we process once on + * first create pass and store here. */ stylesWithoutHost: string|null; @@ -629,8 +658,8 @@ export interface TNode { * A `KeyValueArray` version of residual `styles`. * * When there are styling instructions than each instruction stores the static styling - * which is of lower priority than itself. This means that there may be a higher priority styling - * than the instruction. + * which is of lower priority than itself. This means that there may be a higher priority + * styling than the instruction. * * Imagine: * ``` @@ -671,10 +700,10 @@ export interface TNode { * Populated when there are one or more initial classes on an element * (e.g. `
`) * Must be stored separately from `tNode.classes` to facilitate setting directive - * inputs that shadow the `class` property. If we used `tNode.classes` as is for shadowed inputs, - * we would feed host classes back into directives as "inputs". If we used `tNode.attrs`, we would - * have to concatenate the attributes on every template pass. Instead, we process once on first - * create pass and store here. + * inputs that shadow the `class` property. If we used `tNode.classes` as is for shadowed + * inputs, we would feed host classes back into directives as "inputs". If we used + * `tNode.attrs`, we would have to concatenate the attributes on every template pass. Instead, + * we process once on first create pass and store here. */ classesWithoutHost: string|null; @@ -740,8 +769,8 @@ export interface TElementNode extends TNode { /** * If this is a component TNode with projection, this will be an array of projected - * TNodes or native nodes (see TNode.projection for more info). If it's a regular element node or - * a component without projection, it will be null. + * TNodes or native nodes (see TNode.projection for more info). If it's a regular element node + * or a component without projection, it will be null. */ projection: (TNode|RNode[])[]|null; diff --git a/packages/core/src/render3/interfaces/view.ts b/packages/core/src/render3/interfaces/view.ts index ca2c415694..f026c21a7a 100644 --- a/packages/core/src/render3/interfaces/view.ts +++ b/packages/core/src/render3/interfaces/view.ts @@ -15,7 +15,7 @@ import {Sanitizer} from '../../sanitization/sanitizer'; import {LContainer} from './container'; import {ComponentDef, ComponentTemplate, DirectiveDef, DirectiveDefList, HostBindingsFunction, PipeDef, PipeDefList, ViewQueriesFunction} from './definition'; import {I18nUpdateOpCodes, TI18n, TIcu} from './i18n'; -import {TConstants, TNode, TNodeTypeAsString} from './node'; +import {TConstants, TNode} from './node'; import {PlayerHandler} from './player'; import {LQueries, TQueries} from './query'; import {RComment, RElement, Renderer3, RendererFactory3} from './renderer'; @@ -1025,7 +1025,7 @@ export interface DebugNode { /** * Human readable node type. */ - type: typeof TNodeTypeAsString[number]; + type: string; /** * DOM native node. diff --git a/packages/core/src/render3/node_assert.ts b/packages/core/src/render3/node_assert.ts index 9c64ee6a61..5305c90a26 100644 --- a/packages/core/src/render3/node_assert.ts +++ b/packages/core/src/render3/node_assert.ts @@ -6,44 +6,29 @@ * found in the LICENSE file at https://angular.io/license */ -import {assertDefined, assertEqual} from '../util/assert'; +import {assertDefined, throwError} from '../util/assert'; +import {TNode, TNodeType, toTNodeTypeAsString} from './interfaces/node'; -import {TContainerNode, TElementContainerNode, TElementNode, TIcuContainerNode, TNode, TNodeType, TNodeTypeAsString, TProjectionNode} from './interfaces/node'; - -export function assertNodeType( - tNode: TNode, type: TNodeType.Container): asserts tNode is TContainerNode; -export function assertNodeType( - tNode: TNode, type: TNodeType.Element): asserts tNode is TElementNode; -export function assertNodeType( - tNode: TNode, type: TNodeType.ElementContainer): asserts tNode is TElementContainerNode; -export function assertNodeType( - tNode: TNode, type: TNodeType.IcuContainer): asserts tNode is TIcuContainerNode; -export function assertNodeType( - tNode: TNode, type: TNodeType.Projection): asserts tNode is TProjectionNode; -export function assertNodeType(tNode: TNode, type: TNodeType): asserts tNode is TNode { +export function assertTNodeType( + tNode: TNode|null, expectedTypes: TNodeType, message?: string): void { assertDefined(tNode, 'should be called with a TNode'); - assertEqual(tNode.type, type, `should be a ${typeName(type)}`); + if ((tNode.type & expectedTypes) === 0) { + throwError( + message || + `Expected [${toTNodeTypeAsString(expectedTypes)}] but got ${ + toTNodeTypeAsString(tNode.type)}.`); + } } -export function assertNodeOfPossibleTypes( - tNode: TNode|null, types: TNodeType[], message?: string): void { - assertDefined(tNode, 'should be called with a TNode'); - const found = types.some(type => tNode.type === type); - assertEqual( - found, true, - message ?? - `Should be one of ${types.map(typeName).join(', ')} but got ${typeName(tNode.type)}`); -} - -export function assertNodeNotOfTypes(tNode: TNode, types: TNodeType[], message?: string): void { - assertDefined(tNode, 'should be called with a TNode'); - const found = types.some(type => tNode.type === type); - assertEqual( - found, false, - message ?? - `Should not be one of ${types.map(typeName).join(', ')} but got ${typeName(tNode.type)}`); -} - -function typeName(type: TNodeType): string { - return TNodeTypeAsString[type] || ''; -} +export function assertPureTNodeType(type: TNodeType) { + if (!(type === TNodeType.Element || // + type === TNodeType.Text || // + type === TNodeType.Container || // + type === TNodeType.ElementContainer || // + type === TNodeType.Icu || // + type === TNodeType.Projection || // + type === TNodeType.Placeholder)) { + throwError(`Expected TNodeType to have only a single type selected, but got ${ + toTNodeTypeAsString(type)}.`); + } +} \ No newline at end of file diff --git a/packages/core/src/render3/node_manipulation.ts b/packages/core/src/render3/node_manipulation.ts index fff333bff8..5d8157b528 100644 --- a/packages/core/src/render3/node_manipulation.ts +++ b/packages/core/src/render3/node_manipulation.ts @@ -22,10 +22,12 @@ import {unusedValueExportToPlacateAjd as unused3} from './interfaces/projection' import {isProceduralRenderer, ProceduralRenderer3, RComment, RElement, Renderer3, RNode, RText, unusedValueExportToPlacateAjd as unused4} from './interfaces/renderer'; import {isLContainer, isLView} from './interfaces/type_checks'; import {CHILD_HEAD, CLEANUP, DECLARATION_COMPONENT_VIEW, DECLARATION_LCONTAINER, DestroyHookData, FLAGS, HookData, HookFn, HOST, LView, LViewFlags, NEXT, PARENT, QUERIES, RENDERER, T_HOST, TVIEW, TView, TViewType, unusedValueExportToPlacateAjd as unused5} from './interfaces/view'; -import {assertNodeOfPossibleTypes, assertNodeType} from './node_assert'; +import {assertTNodeType} from './node_assert'; import {getLViewParent} from './util/view_traversal_utils'; import {getNativeByTNode, unwrapRNode, updateTransplantedViewCount} from './util/view_utils'; + + const unusedValueToPlacateAjd = unused1 + unused2 + unused3 + unused4 + unused5; const enum WalkTNodeTreeAction { @@ -539,9 +541,8 @@ export function getClosestRElement(tView: TView, tNode: TNode|null, lView: LView let parentTNode: TNode|null = tNode; // Skip over element and ICU containers as those are represented by a comment node and // can't be used as a render parent. - while (parentTNode != null && - (parentTNode.type === TNodeType.ElementContainer || - parentTNode.type === TNodeType.IcuContainer)) { + while (parentTNode !== null && + (parentTNode.type & (TNodeType.ElementContainer | TNodeType.Icu))) { tNode = parentTNode; parentTNode = tNode.parent; } @@ -553,7 +554,7 @@ export function getClosestRElement(tView: TView, tNode: TNode|null, lView: LView // it should always be eager. return lView[HOST]; } else { - // ngDevMode && assertTNodeType(parentTNode, TNodeType.AnyRNode | TNodeType.Container); + ngDevMode && assertTNodeType(parentTNode, TNodeType.AnyRNode | TNodeType.Container); if (parentTNode.flags & TNodeFlags.isComponentHost) { ngDevMode && assertTNodeForLView(parentTNode, lView); const tData = tView.data; @@ -650,8 +651,7 @@ function getInsertInFrontOfRNode(parentTNode: TNode, currentTNode: TNode, lView: const insertBeforeIndex = Array.isArray(tNodeInsertBeforeIndex) ? tNodeInsertBeforeIndex[0] : tNodeInsertBeforeIndex; if (insertBeforeIndex === null) { - if (parentTNode.type === TNodeType.ElementContainer || - parentTNode.type === TNodeType.IcuContainer) { + if (parentTNode.type & (TNodeType.ElementContainer | TNodeType.Icu)) { return getNativeByTNode(parentTNode, lView); } } else { @@ -716,7 +716,7 @@ function processI18nText( const isProcedural = isProceduralRenderer(renderer); let i18nParent: RElement|null = childRNode as RElement; let anchorRNode: RNode|null = null; - if (childTNode.type !== TNodeType.Element) { + if (!(childTNode.type & TNodeType.AnyRNode)) { anchorRNode = i18nParent; i18nParent = parentRElement; } @@ -738,17 +738,17 @@ function processI18nText( */ function getFirstNativeNode(lView: LView, tNode: TNode|null): RNode|null { if (tNode !== null) { - ngDevMode && assertNodeOfPossibleTypes(tNode, [ - TNodeType.Element, TNodeType.Container, TNodeType.ElementContainer, TNodeType.IcuContainer, - TNodeType.Projection - ]); + ngDevMode && + assertTNodeType( + tNode, + TNodeType.AnyRNode | TNodeType.AnyContainer | TNodeType.Icu | TNodeType.Projection); const tNodeType = tNode.type; - if (tNodeType === TNodeType.Element) { + if (tNodeType & TNodeType.AnyRNode) { return getNativeByTNode(tNode, lView); - } else if (tNodeType === TNodeType.Container) { + } else if (tNodeType & TNodeType.Container) { return getBeforeNodeForView(-1, lView[tNode.index]); - } else if (tNodeType === TNodeType.ElementContainer) { + } else if (tNodeType & TNodeType.ElementContainer) { const elIcuContainerChild = tNode.child; if (elIcuContainerChild !== null) { return getFirstNativeNode(lView, elIcuContainerChild); @@ -760,7 +760,7 @@ function getFirstNativeNode(lView: LView, tNode: TNode|null): RNode|null { return unwrapRNode(rNodeOrLContainer); } } - } else if (tNodeType === TNodeType.IcuContainer) { + } else if (tNodeType & TNodeType.Icu) { let nextRNode = icuContainerIterate(tNode as TIcuContainerNode, lView); let rNode: RNode|null = nextRNode(); // If the ICU container has no nodes, than we use the ICU anchor as the node. @@ -824,10 +824,10 @@ function applyNodes( parentRElement: RElement|null, beforeNode: RNode|null, isProjection: boolean) { while (tNode != null) { ngDevMode && assertTNodeForLView(tNode, lView); - ngDevMode && assertNodeOfPossibleTypes(tNode, [ - TNodeType.Container, TNodeType.Element, TNodeType.ElementContainer, TNodeType.Projection, - TNodeType.IcuContainer - ]); + ngDevMode && + assertTNodeType( + tNode, + TNodeType.AnyRNode | TNodeType.AnyContainer | TNodeType.Projection | TNodeType.Icu); const rawSlotValue = lView[tNode.index]; const tNodeType = tNode.type; if (isProjection) { @@ -837,21 +837,21 @@ function applyNodes( } } if ((tNode.flags & TNodeFlags.isDetached) !== TNodeFlags.isDetached) { - if (tNodeType === TNodeType.ElementContainer) { + if (tNodeType & TNodeType.ElementContainer) { applyNodes(renderer, action, tNode.child, lView, parentRElement, beforeNode, false); applyToElementOrContainer(action, renderer, parentRElement, rawSlotValue, beforeNode); - } else if (tNodeType === TNodeType.IcuContainer) { + } else if (tNodeType & TNodeType.Icu) { const nextRNode = icuContainerIterate(tNode as TIcuContainerNode, lView); let rNode: RNode|null; while (rNode = nextRNode()) { applyToElementOrContainer(action, renderer, parentRElement, rNode, beforeNode); } applyToElementOrContainer(action, renderer, parentRElement, rawSlotValue, beforeNode); - } else if (tNodeType === TNodeType.Projection) { + } else if (tNodeType & TNodeType.Projection) { applyProjectionRecursive( renderer, action, lView, tNode as TProjectionNode, parentRElement, beforeNode); } else { - ngDevMode && assertNodeOfPossibleTypes(tNode, [TNodeType.Element, TNodeType.Container]); + ngDevMode && assertTNodeType(tNode, TNodeType.AnyRNode | TNodeType.Container); applyToElementOrContainer(action, renderer, parentRElement, rawSlotValue, beforeNode); } } diff --git a/packages/core/src/render3/query.ts b/packages/core/src/render3/query.ts index 82d5b30e8f..0252352018 100644 --- a/packages/core/src/render3/query.ts +++ b/packages/core/src/render3/query.ts @@ -27,7 +27,7 @@ import {unusedValueExportToPlacateAjd as unused2} from './interfaces/injector'; import {TContainerNode, TElementContainerNode, TElementNode, TNode, TNodeType, unusedValueExportToPlacateAjd as unused3} from './interfaces/node'; import {LQueries, LQuery, TQueries, TQuery, TQueryMetadata, unusedValueExportToPlacateAjd as unused4} from './interfaces/query'; import {DECLARATION_LCONTAINER, LView, PARENT, QUERIES, TVIEW, TView} from './interfaces/view'; -import {assertNodeOfPossibleTypes} from './node_assert'; +import {assertTNodeType} from './node_assert'; import {getCurrentQueryIndex, getCurrentTNode, getLView, getTView, setCurrentQueryIndex} from './state'; import {isCreationMode} from './util/view_utils'; import {createContainerRef, createElementRef, createTemplateRef} from './view_engine_compatibility'; @@ -217,7 +217,7 @@ class TQuery_ implements TQuery { // - : here we need // to go past `` to determine parent node (but we shouldn't traverse // up past the query's host node!). - while (parent !== null && parent.type === TNodeType.ElementContainer && + while (parent !== null && (parent.type & TNodeType.ElementContainer) && parent.index !== declarationNodeIdx) { parent = parent.parent; } @@ -238,7 +238,7 @@ class TQuery_ implements TQuery { } } else { if ((predicate as any) === ViewEngine_TemplateRef) { - if (tNode.type === TNodeType.Container) { + if (tNode.type & TNodeType.Container) { this.matchTNodeWithReadOption(tView, tNode, -1); } } else { @@ -253,7 +253,7 @@ class TQuery_ implements TQuery { const read = this.metadata.read; if (read !== null) { if (read === ViewEngine_ElementRef || read === ViewContainerRef || - read === ViewEngine_TemplateRef && tNode.type === TNodeType.Container) { + read === ViewEngine_TemplateRef && (tNode.type & TNodeType.Container)) { this.addMatch(tNode.index, -2); } else { const directiveOrProviderIdx = @@ -299,9 +299,9 @@ function getIdxOfMatchingSelector(tNode: TNode, selector: string): number|null { function createResultByTNodeType(tNode: TNode, currentView: LView): any { - if (tNode.type === TNodeType.Element || tNode.type === TNodeType.ElementContainer) { + if (tNode.type & (TNodeType.AnyRNode | TNodeType.ElementContainer)) { return createElementRef(ViewEngine_ElementRef, tNode, currentView); - } else if (tNode.type === TNodeType.Container) { + } else if (tNode.type & TNodeType.Container) { return createTemplateRef(ViewEngine_TemplateRef, ViewEngine_ElementRef, tNode, currentView); } return null; @@ -327,9 +327,7 @@ function createSpecialToken(lView: LView, tNode: TNode, read: any): any { } else if (read === ViewEngine_TemplateRef) { return createTemplateRef(ViewEngine_TemplateRef, ViewEngine_ElementRef, tNode, lView); } else if (read === ViewContainerRef) { - ngDevMode && - assertNodeOfPossibleTypes( - tNode, [TNodeType.Element, TNodeType.Container, TNodeType.ElementContainer]); + ngDevMode && assertTNodeType(tNode, TNodeType.AnyRNode | TNodeType.AnyContainer); return createContainerRef( ViewContainerRef, ViewEngine_ElementRef, tNode as TElementNode | TContainerNode | TElementContainerNode, lView); diff --git a/packages/core/src/render3/state.ts b/packages/core/src/render3/state.ts index 6b0f15a13e..e625c8e2b7 100644 --- a/packages/core/src/render3/state.ts +++ b/packages/core/src/render3/state.ts @@ -12,6 +12,7 @@ import {DirectiveDef} from './interfaces/definition'; import {TNode, TNodeType} from './interfaces/node'; import {CONTEXT, DECLARATION_VIEW, LView, OpaqueViewState, TData, TVIEW, TView} from './interfaces/view'; import {MATH_ML_NAMESPACE, SVG_NAMESPACE} from './namespaces'; +import {assertTNodeType} from './node_assert'; import {getTNode} from './util/view_utils'; diff --git a/packages/core/src/render3/view_engine_compatibility.ts b/packages/core/src/render3/view_engine_compatibility.ts index 415b13998e..794f176f01 100644 --- a/packages/core/src/render3/view_engine_compatibility.ts +++ b/packages/core/src/render3/view_engine_compatibility.ts @@ -27,7 +27,7 @@ import {TContainerNode, TDirectiveHostNode, TElementContainerNode, TElementNode, import {isProceduralRenderer, RComment, RElement} from './interfaces/renderer'; import {isComponentHost, isLContainer, isLView, isRootView} from './interfaces/type_checks'; import {DECLARATION_COMPONENT_VIEW, DECLARATION_LCONTAINER, LView, LViewFlags, PARENT, QUERIES, RENDERER, T_HOST, TVIEW, TView} from './interfaces/view'; -import {assertNodeOfPossibleTypes} from './node_assert'; +import {assertTNodeType} from './node_assert'; import {addViewToContainer, appendChild, destroyLView, detachView, getBeforeNodeForView, insertView, nativeInsertBefore, nativeNextSibling, nativeParentNode} from './node_manipulation'; import {getCurrentTNode, getLView} from './state'; import {getParentInjectorIndex, getParentInjectorView, hasParentInjector} from './util/injector_utils'; @@ -123,7 +123,7 @@ export function createTemplateRef( }; } - if (hostTNode.type === TNodeType.Container) { + if (hostTNode.type & TNodeType.Container) { ngDevMode && assertDefined(hostTNode.tViews, 'TView must be allocated'); return new R3TemplateRef( hostView, hostTNode as TContainerNode, @@ -357,9 +357,7 @@ export function createContainerRef( }; } - ngDevMode && - assertNodeOfPossibleTypes( - hostTNode, [TNodeType.Container, TNodeType.Element, TNodeType.ElementContainer]); + ngDevMode && assertTNodeType(hostTNode, TNodeType.AnyContainer | TNodeType.AnyRNode); let lContainer: LContainer; const slotValue = hostView[hostTNode.index]; @@ -372,7 +370,7 @@ export function createContainerRef( // comment and we can reuse that comment as anchor element for the new LContainer. // The comment node in question is already part of the DOM structure so we don't need to append // it again. - if (hostTNode.type === TNodeType.ElementContainer) { + if (hostTNode.type & TNodeType.ElementContainer) { commentNode = unwrapRNode(slotValue) as RComment; } else { ngDevMode && ngDevMode.rendererCreateComment++; @@ -431,9 +429,7 @@ function createViewRef(tNode: TNode, lView: LView, isPipe: boolean): ViewEngine_ // Instead we want the LView for the component View and so we need to look it up. const componentView = getComponentLViewByIndex(tNode.index, lView); // look down return new ViewRef(componentView, componentView); - } else if ( - tNode.type === TNodeType.Element || tNode.type === TNodeType.Container || - tNode.type === TNodeType.ElementContainer || tNode.type === TNodeType.IcuContainer) { + } else if (tNode.type & (TNodeType.AnyRNode | TNodeType.AnyContainer | TNodeType.Icu)) { // The LView represents the location where the injection is requested from. // We need to locate the containing LView (in case where the `lView` is an embedded view) const hostComponentView = lView[DECLARATION_COMPONENT_VIEW]; // look up diff --git a/packages/core/src/render3/view_ref.ts b/packages/core/src/render3/view_ref.ts index 5db245b0f1..e31ffe5195 100644 --- a/packages/core/src/render3/view_ref.ts +++ b/packages/core/src/render3/view_ref.ts @@ -19,7 +19,7 @@ import {TElementNode, TIcuContainerNode, TNode, TNodeType} from './interfaces/no import {RNode} from './interfaces/renderer'; import {isLContainer} from './interfaces/type_checks'; import {CONTEXT, DECLARATION_COMPONENT_VIEW, FLAGS, LView, LViewFlags, T_HOST, TVIEW, TView} from './interfaces/view'; -import {assertNodeOfPossibleTypes} from './node_assert'; +import {assertTNodeType} from './node_assert'; import {destroyLView, renderDetachView} from './node_manipulation'; import {getLViewParent} from './util/view_traversal_utils'; import {unwrapRNode} from './util/view_utils'; @@ -324,10 +324,10 @@ function collectNativeNodes( tView: TView, lView: LView, tNode: TNode|null, result: any[], isProjection: boolean = false): any[] { while (tNode !== null) { - ngDevMode && assertNodeOfPossibleTypes(tNode, [ - TNodeType.Element, TNodeType.Container, TNodeType.Projection, TNodeType.ElementContainer, - TNodeType.IcuContainer - ]); + ngDevMode && + assertTNodeType( + tNode, + TNodeType.AnyRNode | TNodeType.AnyContainer | TNodeType.Projection | TNodeType.Icu); const lNode = lView[tNode.index]; if (lNode !== null) { @@ -349,15 +349,15 @@ function collectNativeNodes( } const tNodeType = tNode.type; - if (tNodeType === TNodeType.ElementContainer) { + if (tNodeType & TNodeType.ElementContainer) { collectNativeNodes(tView, lView, tNode.child, result); - } else if (tNodeType === TNodeType.IcuContainer) { + } else if (tNodeType & TNodeType.Icu) { const nextRNode = icuContainerIterate(tNode as TIcuContainerNode, lView); let rNode: RNode|null; while (rNode = nextRNode()) { result.push(rNode); } - } else if (tNodeType === TNodeType.Projection) { + } else if (tNodeType & TNodeType.Projection) { const componentView = lView[DECLARATION_COMPONENT_VIEW]; const componentHost = componentView[T_HOST] as TElementNode; const slotIdx = tNode.projection as number; diff --git a/packages/core/test/acceptance/debug_spec.ts b/packages/core/test/acceptance/debug_spec.ts index 8172c98f90..436d134d8b 100644 --- a/packages/core/test/acceptance/debug_spec.ts +++ b/packages/core/test/acceptance/debug_spec.ts @@ -70,7 +70,7 @@ onlyInIvy('Ivy specific').describe('Debug Representation', () => { length: 1, content: [{ index: HEADER_OFFSET + 3, - t: matchTNode({type: TNodeType.Element, value: null}), + t: matchTNode({type: TNodeType.Text, value: '{{?}}'}), l: matchDomText('Hello World') }] }); diff --git a/packages/core/test/acceptance/i18n_spec.ts b/packages/core/test/acceptance/i18n_spec.ts index ddabec0d61..6ce9eafcb8 100644 --- a/packages/core/test/acceptance/i18n_spec.ts +++ b/packages/core/test/acceptance/i18n_spec.ts @@ -618,7 +618,7 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => { const exclamation = b.nextSibling!; const lViewDebug = lView.debug!; expect(lViewDebug.nodes.map(toTypeContent)).toEqual([ - 'Element(Hello )', 'Element()', 'Element(!)' + 'Text(Hello )', 'Element()', 'Text(!)' ]); expect(lViewDebug.decls).toEqual({ start: HEADER_OFFSET, diff --git a/packages/core/test/render3/i18n/i18n_spec.ts b/packages/core/test/render3/i18n/i18n_spec.ts index c1447114ab..4eb5fd25a3 100644 --- a/packages/core/test/render3/i18n/i18n_spec.ts +++ b/packages/core/test/render3/i18n/i18n_spec.ts @@ -668,7 +668,7 @@ describe('Runtime i18n', () => { i18nRangeOffsetOpcode(0), 'Hello World!', // ]); const lViewDebug = fixture.lView.debug!; - expect(lViewDebug.template).toEqual('
#text
'); + expect(lViewDebug.template).toEqual('
Hello World!
'); }); it('should process text with a child node', () => { @@ -685,7 +685,7 @@ describe('Runtime i18n', () => { insertBeforeIndex: i18nRangeOffset(1), })); const lViewDebug = fixture.lView.debug!; - expect(lViewDebug.template).toEqual('
#text#text
'); + expect(lViewDebug.template).toEqual('
Hello !
'); }); it('should process text with a child node that has text', () => { @@ -729,7 +729,7 @@ describe('Runtime i18n', () => { 'if (mask & 0b10) { (lView[51] as Text).textContent = `${lView[i-2]}`; }' ])); const lViewDebug = fixture.lView.debug!; - expect(lViewDebug.template).toEqual('
#text#text#text
'); + expect(lViewDebug.template).toEqual('
{{?}}{{?}}!
'); }); it('should process text with a child template', () => { diff --git a/packages/core/test/render3/interfaces/node_spec.ts b/packages/core/test/render3/interfaces/node_spec.ts index e30cb5c5fe..8107af523c 100644 --- a/packages/core/test/render3/interfaces/node_spec.ts +++ b/packages/core/test/render3/interfaces/node_spec.ts @@ -6,16 +6,23 @@ * found in the LICENSE file at https://angular.io/license */ -import {TNodeType, TNodeTypeAsString} from '@angular/core/src/render3/interfaces/node'; +import {TNodeType, toTNodeTypeAsString} from '@angular/core/src/render3/interfaces/node'; describe('node interfaces', () => { describe('TNodeType', () => { - it('should agree with TNodeTypeAsString', () => { - expect(TNodeTypeAsString[TNodeType.Container]).toEqual('Container'); - expect(TNodeTypeAsString[TNodeType.Projection]).toEqual('Projection'); - expect(TNodeTypeAsString[TNodeType.Element]).toEqual('Element'); - expect(TNodeTypeAsString[TNodeType.ElementContainer]).toEqual('ElementContainer'); - expect(TNodeTypeAsString[TNodeType.IcuContainer]).toEqual('IcuContainer'); + it('should agree with toTNodeTypeAsString', () => { + expect(toTNodeTypeAsString(TNodeType.Element)).toEqual('Element'); + expect(toTNodeTypeAsString(TNodeType.Text)).toEqual('Text'); + expect(toTNodeTypeAsString(TNodeType.Container)).toEqual('Container'); + expect(toTNodeTypeAsString(TNodeType.Projection)).toEqual('Projection'); + expect(toTNodeTypeAsString(TNodeType.ElementContainer)).toEqual('ElementContainer'); + expect(toTNodeTypeAsString(TNodeType.Icu)).toEqual('IcuContainer'); + expect(toTNodeTypeAsString(TNodeType.Placeholder)).toEqual('Placeholder'); + + expect(toTNodeTypeAsString( + TNodeType.Container | TNodeType.Projection | TNodeType.Element | + TNodeType.ElementContainer | TNodeType.Icu)) + .toEqual('Element|Container|ElementContainer|Projection|IcuContainer'); }); }); }); \ No newline at end of file