diff --git a/goldens/size-tracking/aio-payloads.json b/goldens/size-tracking/aio-payloads.json index 9c4ee1c9ef..504f3a5b36 100755 --- a/goldens/size-tracking/aio-payloads.json +++ b/goldens/size-tracking/aio-payloads.json @@ -12,7 +12,7 @@ "master": { "uncompressed": { "runtime-es2015": 3033, - "main-es2015": 453111, + "main-es2015": 453725, "polyfills-es2015": 55230 } } diff --git a/goldens/size-tracking/integration-payloads.json b/goldens/size-tracking/integration-payloads.json index e190459892..6a6ab6f093 100644 --- a/goldens/size-tracking/integration-payloads.json +++ b/goldens/size-tracking/integration-payloads.json @@ -3,7 +3,7 @@ "master": { "uncompressed": { "runtime-es2015": 1170, - "main-es2015": 138189, + "main-es2015": 138718, "polyfills-es2015": 36964 } } @@ -30,7 +30,7 @@ "master": { "uncompressed": { "runtime-es2015": 1190, - "main-es2015": 136546, + "main-es2015": 137087, "polyfills-es2015": 37641 } } @@ -39,7 +39,7 @@ "master": { "uncompressed": { "runtime-es2015": 3354, - "main-es2015": 295076, + "main-es2015": 295755, "polyfills-es2015": 36975, "src_app_lazy_lazy_module_ts-es2015": 825 } diff --git a/packages/core/src/debug/debug_node.ts b/packages/core/src/debug/debug_node.ts index f5db218fac..c630f82a3c 100644 --- a/packages/core/src/debug/debug_node.ts +++ b/packages/core/src/debug/debug_node.ts @@ -262,9 +262,10 @@ class DebugElement__POST_R3__ extends DebugNode__POST_R3__ implements DebugEleme } get name(): string { - const context = getLContext(this.nativeNode); - if (context !== null) { - const lView = context.lView; + const context = getLContext(this.nativeNode)!; + const lView = context ? context.lView : null; + + if (lView !== null) { const tData = lView[TVIEW].data; const tNode = tData[context.nodeIndex] as TNode; return tNode.value!; @@ -286,12 +287,13 @@ class DebugElement__POST_R3__ extends DebugNode__POST_R3__ implements DebugEleme * - attribute bindings (e.g. `[attr.role]="menu"`) */ get properties(): {[key: string]: any;} { - const context = getLContext(this.nativeNode); - if (context === null) { + const context = getLContext(this.nativeNode)!; + const lView = context ? context.lView : null; + + if (lView === null) { return {}; } - const lView = context.lView; const tData = lView[TVIEW].data; const tNode = tData[context.nodeIndex] as TNode; @@ -312,12 +314,13 @@ class DebugElement__POST_R3__ extends DebugNode__POST_R3__ implements DebugEleme return attributes; } - const context = getLContext(element); - if (context === null) { + const context = getLContext(element)!; + const lView = context ? context.lView : null; + + if (lView === null) { return {}; } - const lView = context.lView; const tNodeAttrs = (lView[TVIEW].data[context.nodeIndex] as TNode).attrs; const lowercaseTNodeAttrs: string[] = []; @@ -502,11 +505,12 @@ function _queryAllR3( function _queryAllR3( parentElement: DebugElement, predicate: Predicate|Predicate, matches: DebugElement[]|DebugNode[], elementsOnly: boolean) { - const context = getLContext(parentElement.nativeNode); - if (context !== null) { - const parentTNode = context.lView[TVIEW].data[context.nodeIndex] as TNode; + const context = getLContext(parentElement.nativeNode)!; + const lView = context ? context.lView : null; + if (lView !== null) { + const parentTNode = lView[TVIEW].data[context.nodeIndex] as TNode; _queryNodeChildrenR3( - parentTNode, context.lView, predicate, matches, elementsOnly, parentElement.nativeNode); + parentTNode, lView, predicate, matches, elementsOnly, parentElement.nativeNode); } else { // If the context is null, then `parentElement` was either created with Renderer2 or native DOM // APIs. diff --git a/packages/core/src/render3/context_discovery.ts b/packages/core/src/render3/context_discovery.ts index 43f4207eb7..e57b980f0c 100644 --- a/packages/core/src/render3/context_discovery.ts +++ b/packages/core/src/render3/context_discovery.ts @@ -8,12 +8,15 @@ import '../util/ng_dev_mode'; import {assertDefined, assertDomNode} from '../util/assert'; - import {EMPTY_ARRAY} from '../util/empty'; + +import {assertLView} from './assert'; import {LContext} from './interfaces/context'; +import {getLViewById} from './interfaces/lview_tracking'; import {TNode, TNodeFlags} from './interfaces/node'; import {RElement, RNode} from './interfaces/renderer_dom'; -import {CONTEXT, HEADER_OFFSET, HOST, LView, TVIEW} from './interfaces/view'; +import {isLView} from './interfaces/type_checks'; +import {CONTEXT, HEADER_OFFSET, HOST, ID, LView, TVIEW} from './interfaces/view'; import {getComponentLViewByIndex, unwrapRNode} from './util/view_utils'; @@ -43,7 +46,7 @@ export function getLContext(target: any): LContext|null { if (mpValue) { // only when it's an array is it considered an LView instance // ... otherwise it's an already constructed LContext instance - if (Array.isArray(mpValue)) { + if (isLView(mpValue)) { const lView: LView = mpValue!; let nodeIndex: number; let component: any = undefined; @@ -105,12 +108,7 @@ export function getLContext(target: any): LContext|null { while (parent = parent.parentNode) { const parentContext = readPatchedData(parent); if (parentContext) { - let lView: LView|null; - if (Array.isArray(parentContext)) { - lView = parentContext as LView; - } else { - lView = parentContext.lView; - } + const lView = Array.isArray(parentContext) ? parentContext as LView : parentContext.lView; // the edge of the app was also reached here through another means // (maybe because the DOM was changed manually). @@ -136,14 +134,7 @@ export function getLContext(target: any): LContext|null { * Creates an empty instance of a `LContext` context */ function createLContext(lView: LView, nodeIndex: number, native: RNode): LContext { - return { - lView, - nodeIndex, - native, - component: undefined, - directives: undefined, - localRefs: undefined, - }; + return new LContext(lView[ID], nodeIndex, native); } /** @@ -153,21 +144,24 @@ function createLContext(lView: LView, nodeIndex: number, native: RNode): LContex * @returns The component's view */ export function getComponentViewByInstance(componentInstance: {}): LView { - let lView = readPatchedData(componentInstance); - let view: LView; + let patchedData = readPatchedData(componentInstance); + let lView: LView; - if (Array.isArray(lView)) { - const nodeIndex = findViaComponent(lView, componentInstance); - view = getComponentLViewByIndex(nodeIndex, lView); - const context = createLContext(lView, nodeIndex, view[HOST] as RElement); + if (isLView(patchedData)) { + const contextLView: LView = patchedData; + const nodeIndex = findViaComponent(contextLView, componentInstance); + lView = getComponentLViewByIndex(nodeIndex, contextLView); + const context = createLContext(contextLView, nodeIndex, lView[HOST] as RElement); context.component = componentInstance; attachPatchData(componentInstance, context); attachPatchData(context.native, context); } else { - const context = lView as any as LContext; - view = getComponentLViewByIndex(context.nodeIndex, context.lView); + const context = patchedData as unknown as LContext; + const contextLView = context.lView!; + ngDevMode && assertLView(contextLView); + lView = getComponentLViewByIndex(context.nodeIndex, contextLView); } - return view; + return lView; } /** @@ -181,7 +175,10 @@ const MONKEY_PATCH_KEY_NAME = '__ngContext__'; */ export function attachPatchData(target: any, data: LView|LContext) { ngDevMode && assertDefined(target, 'Target expected'); - target[MONKEY_PATCH_KEY_NAME] = data; + // Only attach the ID of the view in order to avoid memory leaks (see #41047). We only do this + // for `LView`, because we have control over when an `LView` is created and destroyed, whereas + // we can't know when to remove an `LContext`. + target[MONKEY_PATCH_KEY_NAME] = isLView(data) ? data[ID] : data; } /** @@ -190,13 +187,14 @@ export function attachPatchData(target: any, data: LView|LContext) { */ export function readPatchedData(target: any): LView|LContext|null { ngDevMode && assertDefined(target, 'Target expected'); - return target[MONKEY_PATCH_KEY_NAME] || null; + const data = target[MONKEY_PATCH_KEY_NAME]; + return (typeof data === 'number') ? getLViewById(data) : data || null; } export function readPatchedLView(target: any): LView|null { const value = readPatchedData(target); if (value) { - return Array.isArray(value) ? value : (value as LContext).lView; + return isLView(value) ? value : value.lView; } return null; } diff --git a/packages/core/src/render3/instructions/lview_debug.ts b/packages/core/src/render3/instructions/lview_debug.ts index 1fd21dfbfd..7a7551f27d 100644 --- a/packages/core/src/render3/instructions/lview_debug.ts +++ b/packages/core/src/render3/instructions/lview_debug.ts @@ -25,7 +25,7 @@ import {LQueries, TQueries} from '../interfaces/query'; import {Renderer3, RendererFactory3} from '../interfaces/renderer'; import {RComment, RElement, RNode} from '../interfaces/renderer_dom'; import {getTStylingRangeNext, getTStylingRangeNextDuplicate, getTStylingRangePrev, getTStylingRangePrevDuplicate, TStylingKey, TStylingRange} from '../interfaces/styling'; -import {CHILD_HEAD, CHILD_TAIL, CLEANUP, CONTEXT, DebugNode, DECLARATION_VIEW, DestroyHookData, FLAGS, HEADER_OFFSET, HookData, HOST, HostBindingOpCodes, INJECTOR, LContainerDebug as ILContainerDebug, LView, LViewDebug as ILViewDebug, LViewDebugRange, LViewDebugRangeContent, LViewFlags, NEXT, NodeInjectorDebug, PARENT, QUERIES, RENDERER, RENDERER_FACTORY, SANITIZER, T_HOST, TData, TView as ITView, TVIEW, TView, TViewType, TViewTypeAsString} from '../interfaces/view'; +import {CHILD_HEAD, CHILD_TAIL, CLEANUP, CONTEXT, DebugNode, DECLARATION_VIEW, DestroyHookData, FLAGS, HEADER_OFFSET, HookData, HOST, HostBindingOpCodes, ID, INJECTOR, LContainerDebug as ILContainerDebug, LView, LViewDebug as ILViewDebug, LViewDebugRange, LViewDebugRangeContent, LViewFlags, NEXT, NodeInjectorDebug, PARENT, QUERIES, RENDERER, RENDERER_FACTORY, SANITIZER, T_HOST, TData, TView as ITView, TVIEW, TView, TViewType, TViewTypeAsString} from '../interfaces/view'; import {attachDebugObject} from '../util/debug_utils'; import {getParentInjectorIndex, getParentInjectorView} from '../util/injector_utils'; import {unwrapRNode} from '../util/view_utils'; @@ -513,6 +513,9 @@ export class LViewDebug implements ILViewDebug { get tHost(): ITNode|null { return this._raw_lView[T_HOST]; } + get id(): number { + return this._raw_lView[ID]; + } get decls(): LViewDebugRange { return toLViewRange(this.tView, this._raw_lView, HEADER_OFFSET, this.tView.bindingStartIndex); diff --git a/packages/core/src/render3/instructions/shared.ts b/packages/core/src/render3/instructions/shared.ts index 34d6ef6daf..47590176da 100644 --- a/packages/core/src/render3/instructions/shared.ts +++ b/packages/core/src/render3/instructions/shared.ts @@ -28,12 +28,13 @@ import {executeCheckHooks, executeInitAndCheckHooks, incrementInitPhaseFlags} fr import {CONTAINER_HEADER_OFFSET, HAS_TRANSPLANTED_VIEWS, LContainer, MOVED_VIEWS} from '../interfaces/container'; import {ComponentDef, ComponentTemplate, DirectiveDef, DirectiveDefListOrFactory, HostBindingsFunction, PipeDefListOrFactory, RenderFlags, ViewQueriesFunction} from '../interfaces/definition'; import {NodeInjectorFactory} from '../interfaces/injector'; +import {registerLView} from '../interfaces/lview_tracking'; import {AttributeMarker, InitialInputData, InitialInputs, LocalRefExtractor, PropertyAliases, PropertyAliasValue, TAttributes, TConstantsOrFactory, TContainerNode, TDirectiveHostNode, TElementContainerNode, TElementNode, TIcuContainerNode, TNode, TNodeFlags, TNodeType, TProjectionNode} from '../interfaces/node'; import {isProceduralRenderer, Renderer3, RendererFactory3} from '../interfaces/renderer'; import {RComment, RElement, RNode, RText} from '../interfaces/renderer_dom'; 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, HostBindingOpCodes, 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 {CHILD_HEAD, CHILD_TAIL, CLEANUP, CONTEXT, DECLARATION_COMPONENT_VIEW, DECLARATION_VIEW, FLAGS, HEADER_OFFSET, HOST, HostBindingOpCodes, ID, 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'; @@ -143,6 +144,7 @@ export function createLView( lView[SANITIZER] = sanitizer || parentLView && parentLView[SANITIZER] || null!; lView[INJECTOR as any] = injector || parentLView && parentLView[INJECTOR] || null; lView[T_HOST] = tHostNode; + lView[ID] = registerLView(lView); ngDevMode && assertEqual( tView.type == TViewType.Embedded ? parentLView !== null : true, true, @@ -1906,9 +1908,12 @@ export function scheduleTick(rootContext: RootContext, flags: RootContextFlags) export function tickRootContext(rootContext: RootContext) { for (let i = 0; i < rootContext.components.length; i++) { const rootComponent = rootContext.components[i]; - const lView = readPatchedLView(rootComponent)!; - const tView = lView[TVIEW]; - renderComponentOrTemplate(tView, lView, tView.template, rootComponent); + const lView = readPatchedLView(rootComponent); + // We might not have an `LView` if the component was destroyed. + if (lView !== null) { + const tView = lView[TVIEW]; + renderComponentOrTemplate(tView, lView, tView.template, rootComponent); + } } } diff --git a/packages/core/src/render3/interfaces/context.ts b/packages/core/src/render3/interfaces/context.ts index 6f78e26891..e097719190 100644 --- a/packages/core/src/render3/interfaces/context.ts +++ b/packages/core/src/render3/interfaces/context.ts @@ -7,6 +7,7 @@ */ +import {getLViewById} from './lview_tracking'; import {RNode} from './renderer_dom'; import {LView} from './view'; @@ -21,35 +22,41 @@ import {LView} from './view'; * function. The component, element and each directive instance will share the same instance * of the context. */ -export interface LContext { - /** - * The component's parent view data. - */ - lView: LView; - - /** - * The index instance of the node. - */ - nodeIndex: number; - - /** - * The instance of the DOM node that is attached to the lNode. - */ - native: RNode; - +export class LContext { /** * The instance of the Component node. */ - component: {}|null|undefined; + public component: {}|null|undefined; /** * The list of active directives that exist on this element. */ - directives: any[]|null|undefined; + public directives: any[]|null|undefined; /** - * The map of local references (local reference name => element or directive instance) that exist - * on this element. + * The map of local references (local reference name => element or directive instance) that + * exist on this element. */ - localRefs: {[key: string]: any}|null|undefined; + public localRefs: {[key: string]: any}|null|undefined; + + /** Component's parent view data. */ + get lView(): LView|null { + return getLViewById(this.lViewId); + } + + constructor( + /** + * ID of the component's parent view data. + */ + private lViewId: number, + + /** + * The index instance of the node. + */ + public nodeIndex: number, + + /** + * The instance of the DOM node that is attached to the lNode. + */ + public native: RNode) {} } diff --git a/packages/core/src/render3/interfaces/lview_tracking.ts b/packages/core/src/render3/interfaces/lview_tracking.ts new file mode 100644 index 0000000000..7c965ff98a --- /dev/null +++ b/packages/core/src/render3/interfaces/lview_tracking.ts @@ -0,0 +1,35 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * 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 {assertNumber} from '../../util/assert'; +import {ID, LView} from './view'; + +// Keeps track of the currently-active LViews. +const TRACKED_LVIEWS = new Map(); + +// Used for generating unique IDs for LViews. +let uniqueIdCounter = 0; + +/** Starts tracking an LView and returns a unique ID that can be used for future lookups. */ +export function registerLView(lView: LView): number { + const id = uniqueIdCounter++; + TRACKED_LVIEWS.set(id, lView); + return id; +} + +/** Gets an LView by its unique ID. */ +export function getLViewById(id: number): LView|null { + ngDevMode && assertNumber(id, 'ID used for LView lookup must be a number'); + return TRACKED_LVIEWS.get(id) || null; +} + +/** Stops tracking an LView. */ +export function unregisterLView(lView: LView): void { + ngDevMode && assertNumber(lView[ID], 'Cannot stop tracking an LView that does not have an ID'); + TRACKED_LVIEWS.delete(lView[ID]); +} diff --git a/packages/core/src/render3/interfaces/view.ts b/packages/core/src/render3/interfaces/view.ts index af0202bcf6..34d12482f4 100644 --- a/packages/core/src/render3/interfaces/view.ts +++ b/packages/core/src/render3/interfaces/view.ts @@ -47,6 +47,7 @@ export const DECLARATION_COMPONENT_VIEW = 16; export const DECLARATION_LCONTAINER = 17; export const PREORDER_HOOK_FLAGS = 18; export const QUERIES = 19; +export const ID = 20; /** * Size of LView's header. Necessary to adjust for it when setting slots. * @@ -54,7 +55,7 @@ export const QUERIES = 19; * instruction index into `LView` index. All other indexes should be in the `LView` index space and * there should be no need to refer to `HEADER_OFFSET` anywhere else. */ -export const HEADER_OFFSET = 20; +export const HEADER_OFFSET = 21; // This interface replaces the real LView interface if it is an arg or a @@ -326,6 +327,9 @@ export interface LView extends Array { * are not `Dirty`/`CheckAlways`. */ [TRANSPLANTED_VIEWS_TO_REFRESH]: number; + + /** Unique ID of the view. Used for `__ngContext__` lookups in the `LView` registry. */ + [ID]: number; } /** Flags associated with an LView (saved in LView[FLAGS]) */ diff --git a/packages/core/src/render3/node_manipulation.ts b/packages/core/src/render3/node_manipulation.ts index 801957fe21..35ce766098 100644 --- a/packages/core/src/render3/node_manipulation.ts +++ b/packages/core/src/render3/node_manipulation.ts @@ -19,6 +19,7 @@ import {icuContainerIterate} from './i18n/i18n_tree_shaking'; import {CONTAINER_HEADER_OFFSET, HAS_TRANSPLANTED_VIEWS, LContainer, MOVED_VIEWS, NATIVE, unusedValueExportToPlacateAjd as unused1} from './interfaces/container'; import {ComponentDef} from './interfaces/definition'; import {NodeInjectorFactory} from './interfaces/injector'; +import {unregisterLView} from './interfaces/lview_tracking'; import {TElementNode, TIcuContainerNode, TNode, TNodeFlags, TNodeType, TProjectionNode, unusedValueExportToPlacateAjd as unused2} from './interfaces/node'; import {unusedValueExportToPlacateAjd as unused3} from './interfaces/projection'; import {isProceduralRenderer, ProceduralRenderer3, Renderer3, unusedValueExportToPlacateAjd as unused4} from './interfaces/renderer'; @@ -395,6 +396,7 @@ export function destroyLView(tView: TView, lView: LView) { } destroyViewTree(lView); + unregisterLView(lView); } } diff --git a/packages/core/src/render3/util/discovery_utils.ts b/packages/core/src/render3/util/discovery_utils.ts index 430df1bf2d..37d60efe8f 100644 --- a/packages/core/src/render3/util/discovery_utils.ts +++ b/packages/core/src/render3/util/discovery_utils.ts @@ -11,7 +11,7 @@ import {Injector} from '../../di/injector'; import {ViewEncapsulation} from '../../metadata/view'; import {assertEqual} from '../../util/assert'; import {assertLView} from '../assert'; -import {discoverLocalRefs, getComponentAtNodeIndex, getDirectivesAtNodeIndex, getLContext} from '../context_discovery'; +import {discoverLocalRefs, getComponentAtNodeIndex, getDirectivesAtNodeIndex, getLContext, readPatchedLView} from '../context_discovery'; import {getComponentDef, getDirectiveDef} from '../definition'; import {NodeInjector} from '../di'; import {buildDebugNode} from '../instructions/lview_debug'; @@ -20,6 +20,7 @@ import {DirectiveDef} from '../interfaces/definition'; import {TElementNode, TNode, TNodeProviderIndexes} from '../interfaces/node'; import {isLView} from '../interfaces/type_checks'; import {CLEANUP, CONTEXT, DebugNode, FLAGS, LView, LViewFlags, T_HOST, TVIEW, TViewType} from '../interfaces/view'; + import {stringifyForError} from './stringify_utils'; import {getLViewParent, getRootContext} from './view_traversal_utils'; import {getTNode, unwrapRNode} from './view_utils'; @@ -52,12 +53,16 @@ import {getTNode, unwrapRNode} from './view_utils'; * @globalApi ng */ export function getComponent(element: Element): T|null { - assertDomElement(element); + ngDevMode && assertDomElement(element); const context = getLContext(element); if (context === null) return null; if (context.component === undefined) { - context.component = getComponentAtNodeIndex(context.nodeIndex, context.lView); + const lView = context.lView; + if (lView === null) { + return null; + } + context.component = getComponentAtNodeIndex(context.nodeIndex, lView); } return context.component as T; @@ -78,8 +83,9 @@ export function getComponent(element: Element): T|null { */ export function getContext(element: Element): T|null { assertDomElement(element); - const context = getLContext(element); - return context === null ? null : context.lView[CONTEXT] as T; + const context = getLContext(element)!; + const lView = context ? context.lView : null; + return lView === null ? null : lView[CONTEXT] as T; } /** @@ -98,12 +104,11 @@ export function getContext(element: Element): T|null { * @globalApi ng */ export function getOwningComponent(elementOrDir: Element|{}): T|null { - const context = getLContext(elementOrDir); - if (context === null) return null; + const context = getLContext(elementOrDir)!; + let lView = context ? context.lView : null; + if (lView === null) return null; - let lView = context.lView; let parent: LView|null; - ngDevMode && assertLView(lView); while (lView[TVIEW].type === TViewType.Embedded && (parent = getLViewParent(lView)!)) { lView = parent; } @@ -122,7 +127,8 @@ export function getOwningComponent(elementOrDir: Element|{}): T|null { * @globalApi ng */ export function getRootComponents(elementOrDir: Element|{}): {}[] { - return [...getRootContext(elementOrDir).components]; + const lView = readPatchedLView(elementOrDir); + return lView !== null ? [...getRootContext(lView).components] : []; } /** @@ -136,11 +142,12 @@ export function getRootComponents(elementOrDir: Element|{}): {}[] { * @globalApi ng */ export function getInjector(elementOrDir: Element|{}): Injector { - const context = getLContext(elementOrDir); - if (context === null) return Injector.NULL; + const context = getLContext(elementOrDir)!; + const lView = context ? context.lView : null; + if (lView === null) return Injector.NULL; - const tNode = context.lView[TVIEW].data[context.nodeIndex] as TElementNode; - return new NodeInjector(tNode, context.lView); + const tNode = lView[TVIEW].data[context.nodeIndex] as TElementNode; + return new NodeInjector(tNode, lView); } /** @@ -149,9 +156,9 @@ export function getInjector(elementOrDir: Element|{}): Injector { * @param element Element for which the injection tokens should be retrieved. */ export function getInjectionTokens(element: Element): any[] { - const context = getLContext(element); - if (context === null) return []; - const lView = context.lView; + const context = getLContext(element)!; + const lView = context ? context.lView : null; + if (lView === null) return []; const tView = lView[TVIEW]; const tNode = tView.data[context.nodeIndex] as TNode; const providerTokens: any[] = []; @@ -200,12 +207,12 @@ export function getDirectives(node: Node): {}[] { return []; } - const context = getLContext(node); - if (context === null) { + const context = getLContext(node)!; + const lView = context ? context.lView : null; + if (lView === null) { return []; } - const lView = context.lView; const tView = lView[TVIEW]; const nodeIndex = context.nodeIndex; if (!tView?.data[nodeIndex]) { @@ -297,7 +304,11 @@ export function getLocalRefs(target: {}): {[key: string]: any} { if (context === null) return {}; if (context.localRefs === undefined) { - context.localRefs = discoverLocalRefs(context.lView, context.nodeIndex); + const lView = context.lView; + if (lView === null) { + return {}; + } + context.localRefs = discoverLocalRefs(lView, context.nodeIndex); } return context.localRefs || {}; @@ -383,11 +394,11 @@ export interface Listener { * @globalApi ng */ export function getListeners(element: Element): Listener[] { - assertDomElement(element); + ngDevMode && assertDomElement(element); const lContext = getLContext(element); - if (lContext === null) return []; + const lView = lContext === null ? null : lContext.lView; + if (lView === null) return []; - const lView = lContext.lView; const tView = lView[TVIEW]; const lCleanup = lView[CLEANUP]; const tCleanup = tView.cleanup; @@ -441,12 +452,13 @@ export function getDebugNode(element: Element): DebugNode|null { throw new Error('Expecting instance of DOM Element'); } - const lContext = getLContext(element); - if (lContext === null) { + const lContext = getLContext(element)!; + const lView = lContext ? lContext.lView : null; + + if (lView === null) { return null; } - const lView = lContext.lView; const nodeIndex = lContext.nodeIndex; if (nodeIndex !== -1) { const valueInLView = lView[nodeIndex]; @@ -473,7 +485,8 @@ export function getDebugNode(element: Element): DebugNode|null { export function getComponentLView(target: any): LView { const lContext = getLContext(target)!; const nodeIndx = lContext.nodeIndex; - const lView = lContext.lView; + const lView = lContext.lView!; + ngDevMode && assertLView(lView); const componentLView = lView[nodeIndx]; ngDevMode && assertLView(componentLView); return componentLView; diff --git a/packages/core/test/acceptance/component_spec.ts b/packages/core/test/acceptance/component_spec.ts index 31994aadac..5b4c2b23ae 100644 --- a/packages/core/test/acceptance/component_spec.ts +++ b/packages/core/test/acceptance/component_spec.ts @@ -141,6 +141,47 @@ describe('component', () => { expect(fixture.nativeElement.textContent.trim()).toBe('hello'); }); + onlyInIvy('ViewEngine has a specific error for this while Ivy does not') + .it('should not throw when calling `detectChanges` on the ChangeDetectorRef of a destroyed view', + () => { + @Component({template: 'hello'}) + class HelloComponent { + } + + // TODO: This module is only used to declare the `entryComponets` since + // `configureTestingModule` doesn't support it. The module can be removed + // once ViewEngine is removed. + @NgModule({ + declarations: [HelloComponent], + exports: [HelloComponent], + entryComponents: [HelloComponent] + }) + class HelloModule { + } + + @Component({template: `
`}) + class App { + @ViewChild('insertionPoint', {read: ViewContainerRef}) + viewContainerRef!: ViewContainerRef; + constructor(public componentFactoryResolver: ComponentFactoryResolver) {} + } + + TestBed.configureTestingModule({declarations: [App], imports: [HelloModule]}); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + const instance = fixture.componentInstance; + const factory = + instance.componentFactoryResolver.resolveComponentFactory(HelloComponent); + const componentRef = instance.viewContainerRef.createComponent(factory); + fixture.detectChanges(); + + expect(() => { + componentRef.destroy(); + componentRef.changeDetectorRef.detectChanges(); + }).not.toThrow(); + }); + // TODO: add tests with Native once tests run in real browser (domino doesn't support shadow root) describe('encapsulation', () => { @Component({ diff --git a/packages/core/test/acceptance/debug_spec.ts b/packages/core/test/acceptance/debug_spec.ts index 436d134d8b..c12985b8ab 100644 --- a/packages/core/test/acceptance/debug_spec.ts +++ b/packages/core/test/acceptance/debug_spec.ts @@ -27,7 +27,7 @@ onlyInIvy('Ivy specific').describe('Debug Representation', () => { const fixture = TestBed.createComponent(MyComponent); fixture.detectChanges(); - const hostView = getLContext(fixture.componentInstance)!.lView.debug!; + const hostView = getLContext(fixture.componentInstance)!.lView!.debug!; expect(hostView.hostHTML).toEqual(null); const myCompView = hostView.childViews[0] as LViewDebug; expect(myCompView.hostHTML).toContain('
Hello World
'); @@ -47,7 +47,7 @@ onlyInIvy('Ivy specific').describe('Debug Representation', () => { const fixture = TestBed.createComponent(MyComponent); fixture.detectChanges(); - const hostView = getLContext(fixture.componentInstance)!.lView.debug!; + const hostView = getLContext(fixture.componentInstance)!.lView!.debug!; const myComponentView = hostView.childViews[0] as LViewDebug; expect(myComponentView.decls).toEqual({ start: HEADER_OFFSET, diff --git a/packages/core/test/acceptance/discover_utils_spec.ts b/packages/core/test/acceptance/discover_utils_spec.ts index a1d589eabe..05a583b3a4 100644 --- a/packages/core/test/acceptance/discover_utils_spec.ts +++ b/packages/core/test/acceptance/discover_utils_spec.ts @@ -69,16 +69,19 @@ onlyInIvy('Ivy-specific utilities').describe('discovery utils', () => { @Component({ selector: 'my-app', template: ` - {{text}} + {{text}}
- +

+ Bold ` }) class MyApp { text: string = 'INIT'; + spanVisible = true; + conditionalChildVisible = true; @Input('a') b = 2; @Output('c') d = new EventEmitter(); constructor() { @@ -105,6 +108,15 @@ onlyInIvy('Ivy-specific utilities').describe('discovery utils', () => { expect(getComponent(child[0])).toEqual(childComponent[0]); expect(getComponent(child[1])).toEqual(childComponent[1]); }); + it('should not throw when called on a destroyed node', () => { + expect(getComponent(span[0])).toEqual(null); + expect(getComponent(child[2])).toEqual(childComponent[2]); + fixture.componentInstance.spanVisible = false; + fixture.componentInstance.conditionalChildVisible = false; + fixture.detectChanges(); + expect(getComponent(span[0])).toEqual(null); + expect(getComponent(child[2])).toEqual(childComponent[2]); + }); }); describe('getComponentLView', () => { @@ -131,6 +143,12 @@ onlyInIvy('Ivy-specific utilities').describe('discovery utils', () => { expect(getContext<{$implicit: boolean}>(child[2])!.$implicit).toEqual(true); expect(getContext(p[0])).toEqual(childComponent[0]); }); + it('should return null for destroyed node', () => { + expect(getContext(span[0])).toBeTruthy(); + fixture.componentInstance.spanVisible = false; + fixture.detectChanges(); + expect(getContext(span[0])).toBeNull(); + }); }); describe('getHostElement', () => { @@ -146,6 +164,12 @@ onlyInIvy('Ivy-specific utilities').describe('discovery utils', () => { it('should throw on unknown target', () => { expect(() => getHostElement({})).toThrowError(); // }); + it('should return element for destroyed node', () => { + expect(getHostElement(span[0])).toEqual(span[0]); + fixture.componentInstance.spanVisible = false; + fixture.detectChanges(); + expect(getHostElement(span[0])).toEqual(span[0]); + }); }); describe('getInjector', () => { @@ -163,6 +187,12 @@ onlyInIvy('Ivy-specific utilities').describe('discovery utils', () => { expect(getInjector(dirA[0]).get(String)).toEqual('Module'); expect(getInjector(dirA[1]).get(String)).toEqual('Child'); }); + it('should retrieve injector from destroyed node', () => { + expect(getInjector(span[0])).toBeTruthy(); + fixture.componentInstance.spanVisible = false; + fixture.detectChanges(); + expect(getInjector(span[0])).toBeTruthy(); + }); }); describe('getDirectives', () => { @@ -175,6 +205,12 @@ onlyInIvy('Ivy-specific utilities').describe('discovery utils', () => { expect(getDirectives(div[0])).toEqual([dirA[0]]); expect(getDirectives(child[1])).toEqual([dirA[1]]); }); + it('should return empty array for destroyed node', () => { + expect(getDirectives(span[0])).toEqual([]); + fixture.componentInstance.spanVisible = false; + fixture.detectChanges(); + expect(getDirectives(span[0])).toEqual([]); + }); }); describe('getOwningComponent', () => { @@ -202,6 +238,12 @@ onlyInIvy('Ivy-specific utilities').describe('discovery utils', () => { expect(getOwningComponent(dirA[0])).toEqual(myApp); expect(getOwningComponent(dirA[1])).toEqual(myApp); }); + it('should return null for destroyed node', () => { + expect(getOwningComponent(span[0])).toEqual(myApp); + fixture.componentInstance.spanVisible = false; + fixture.detectChanges(); + expect(getOwningComponent(span[0])).toEqual(null); + }); }); describe('getLocalRefs', () => { @@ -219,6 +261,13 @@ onlyInIvy('Ivy-specific utilities').describe('discovery utils', () => { expect(getLocalRefs(child[1])).toEqual({child: childComponent[1]}); expect(getLocalRefs(dirA[1])).toEqual({child: childComponent[1]}); }); + + it('should retrieve from a destroyed node', () => { + expect(getLocalRefs(span[0])).toEqual({}); + fixture.componentInstance.spanVisible = false; + fixture.detectChanges(); + expect(getLocalRefs(span[0])).toEqual({}); + }); }); describe('getRootComponents', () => { @@ -234,6 +283,12 @@ onlyInIvy('Ivy-specific utilities').describe('discovery utils', () => { expect(getRootComponents(div[0])).toEqual(rootComponents); expect(getRootComponents(p[0])).toEqual(rootComponents); }); + it('should return an empty array for a destroyed node', () => { + expect(getRootComponents(span[0])).toEqual([myApp]); + fixture.componentInstance.spanVisible = false; + fixture.detectChanges(); + expect(getRootComponents(span[0])).toEqual([]); + }); }); describe('getListeners', () => { @@ -251,6 +306,12 @@ onlyInIvy('Ivy-specific utilities').describe('discovery utils', () => { listeners[0].callback('CLICKED'); expect(log).toEqual(['CLICKED']); }); + it('should return no listeners for destroyed node', () => { + expect(getListeners(span[0]).length).toEqual(1); + fixture.componentInstance.spanVisible = false; + fixture.detectChanges(); + expect(getListeners(span[0]).length).toEqual(0); + }); }); describe('getInjectionTokens', () => { @@ -259,6 +320,12 @@ onlyInIvy('Ivy-specific utilities').describe('discovery utils', () => { expect(getInjectionTokens(child[0])).toEqual([String, Child]); expect(getInjectionTokens(child[1])).toEqual([String, Child, DirectiveA]); }); + it('should retrieve tokens from destroyed node', () => { + expect(getInjectionTokens(span[0])).toEqual([]); + fixture.componentInstance.spanVisible = false; + fixture.detectChanges(); + expect(getInjectionTokens(span[0])).toEqual([]); + }); }); describe('markDirty', () => { diff --git a/packages/core/test/acceptance/i18n_spec.ts b/packages/core/test/acceptance/i18n_spec.ts index cc7fb156ce..d430787feb 100644 --- a/packages/core/test/acceptance/i18n_spec.ts +++ b/packages/core/test/acceptance/i18n_spec.ts @@ -652,12 +652,12 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => { const lViewDebug = lView.debug!; fixture.detectChanges(); expect((fixture.nativeElement as Element).textContent).toEqual('just now'); - expect(lViewDebug.nodes.map(toTypeContent)).toEqual(['IcuContainer()']); + expect(lViewDebug.nodes.map(toTypeContent)).toEqual(['IcuContainer()']); // We want to ensure that the ICU container does not have any content! // This is because the content is instance dependent and therefore can't be shared // across `TNode`s. expect(lViewDebug.nodes[0].children.map(toTypeContent)).toEqual([]); - expect(fixture.nativeElement.innerHTML).toEqual('just now'); + expect(fixture.nativeElement.innerHTML).toEqual('just now'); }); it('should support multiple ICUs', () => { @@ -674,15 +674,15 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => { `); const lView = getComponentLView(fixture.componentInstance); expect(lView.debug!.nodes.map(toTypeContent)).toEqual([ - 'IcuContainer()', 'IcuContainer()', + 'IcuContainer()', ]); // We want to ensure that the ICU container does not have any content! // This is because the content is instance dependent and therefore can't be shared // across `TNode`s. expect(lView.debug!.nodes[0].children.map(toTypeContent)).toEqual([]); expect(fixture.nativeElement.innerHTML) - .toEqual('just nowMr. Angular'); + .toEqual('just nowMr. Angular'); }); }); }); @@ -778,19 +778,19 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => { other {({{name}})} }`); expect(fixture.nativeElement.innerHTML) - .toEqual(`
aucun email! - (Angular)
`); + .toEqual(`
aucun email! - (Angular)
`); fixture.componentRef.instance.count = 4; fixture.detectChanges(); expect(fixture.nativeElement.innerHTML) .toEqual( - `
4 emails - (Angular)
`); + `
4 emails - (Angular)
`); fixture.componentRef.instance.count = 0; fixture.componentRef.instance.name = 'John'; fixture.detectChanges(); expect(fixture.nativeElement.innerHTML) - .toEqual(`
aucun email! - (John)
`); + .toEqual(`
aucun email! - (John)
`); }); it('with custom interpolation config', () => { @@ -829,9 +829,9 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => { expect(fixture.nativeElement.innerHTML) .toEqual( `
` + - `aucun email!` + + `aucun email!` + ` - ` + - `(Angular)` + + `(Angular)` + `
`); fixture.componentRef.instance.count = 4; @@ -839,9 +839,9 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => { expect(fixture.nativeElement.innerHTML) .toEqual( `
` + - `4 emails` + + `4 emails` + ` - ` + - `(Angular)` + + `(Angular)` + `
`); fixture.componentRef.instance.count = 0; @@ -850,9 +850,9 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => { expect(fixture.nativeElement.innerHTML) .toEqual( `
` + - `aucun email!` + + `aucun email!` + ` - ` + - `(John)` + + `(John)` + `
`); }); @@ -867,7 +867,7 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => { other {({{name}})} }`); expect(fixture.nativeElement.innerHTML) - .toEqual(`
(Angular)
`); @@ -887,7 +887,7 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => { other {({{name}})} }`); expect(fixture.nativeElement.innerHTML) - .toEqual(`(Angular)`); + .toEqual(`(Angular)`); }); it('inside ', () => { @@ -922,12 +922,12 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => { other {animals} }!} }`); - expect(fixture.nativeElement.innerHTML).toEqual(`
zero
`); + expect(fixture.nativeElement.innerHTML).toEqual(`
zero
`); fixture.componentRef.instance.count = 4; fixture.detectChanges(); expect(fixture.nativeElement.innerHTML) - .toEqual(`
4 animaux!
`); + .toEqual(`
4 animaux!
`); }); it('nested with interpolations in "other" blocks', () => { @@ -947,16 +947,16 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => { }!} other {other - {{count}}} }`); - expect(fixture.nativeElement.innerHTML).toEqual(`
zero
`); + expect(fixture.nativeElement.innerHTML).toEqual(`
zero
`); fixture.componentRef.instance.count = 2; fixture.detectChanges(); expect(fixture.nativeElement.innerHTML) - .toEqual(`
2 animaux!
`); + .toEqual(`
2 animaux!
`); fixture.componentRef.instance.count = 4; fixture.detectChanges(); - expect(fixture.nativeElement.innerHTML).toEqual(`
autre - 4
`); + expect(fixture.nativeElement.innerHTML).toEqual(`
autre - 4
`); }); it('should return the correct plural form for ICU expressions when using "ro" locale', () => { @@ -989,31 +989,31 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => { =other {lots of emails} }`); - expect(fixture.nativeElement.innerHTML).toEqual('no email'); + expect(fixture.nativeElement.innerHTML).toEqual('no email'); // Change detection cycle, no model changes fixture.detectChanges(); - expect(fixture.nativeElement.innerHTML).toEqual('no email'); + expect(fixture.nativeElement.innerHTML).toEqual('no email'); fixture.componentInstance.count = 3; fixture.detectChanges(); - expect(fixture.nativeElement.innerHTML).toEqual('a few emails'); + expect(fixture.nativeElement.innerHTML).toEqual('a few emails'); fixture.componentInstance.count = 1; fixture.detectChanges(); - expect(fixture.nativeElement.innerHTML).toEqual('one email'); + expect(fixture.nativeElement.innerHTML).toEqual('one email'); fixture.componentInstance.count = 10; fixture.detectChanges(); - expect(fixture.nativeElement.innerHTML).toEqual('a few emails'); + expect(fixture.nativeElement.innerHTML).toEqual('a few emails'); fixture.componentInstance.count = 20; fixture.detectChanges(); - expect(fixture.nativeElement.innerHTML).toEqual('lots of emails'); + expect(fixture.nativeElement.innerHTML).toEqual('lots of emails'); fixture.componentInstance.count = 0; fixture.detectChanges(); - expect(fixture.nativeElement.innerHTML).toEqual('no email'); + expect(fixture.nativeElement.innerHTML).toEqual('no email'); }); it(`should return the correct plural form for ICU expressions when using "es" locale`, () => { @@ -1040,31 +1040,31 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => { =other {lots of emails} }`); - expect(fixture.nativeElement.innerHTML).toEqual('no email'); + expect(fixture.nativeElement.innerHTML).toEqual('no email'); // Change detection cycle, no model changes fixture.detectChanges(); - expect(fixture.nativeElement.innerHTML).toEqual('no email'); + expect(fixture.nativeElement.innerHTML).toEqual('no email'); fixture.componentInstance.count = 3; fixture.detectChanges(); - expect(fixture.nativeElement.innerHTML).toEqual('lots of emails'); + expect(fixture.nativeElement.innerHTML).toEqual('lots of emails'); fixture.componentInstance.count = 1; fixture.detectChanges(); - expect(fixture.nativeElement.innerHTML).toEqual('one email'); + expect(fixture.nativeElement.innerHTML).toEqual('one email'); fixture.componentInstance.count = 10; fixture.detectChanges(); - expect(fixture.nativeElement.innerHTML).toEqual('lots of emails'); + expect(fixture.nativeElement.innerHTML).toEqual('lots of emails'); fixture.componentInstance.count = 20; fixture.detectChanges(); - expect(fixture.nativeElement.innerHTML).toEqual('lots of emails'); + expect(fixture.nativeElement.innerHTML).toEqual('lots of emails'); fixture.componentInstance.count = 0; fixture.detectChanges(); - expect(fixture.nativeElement.innerHTML).toEqual('no email'); + expect(fixture.nativeElement.innerHTML).toEqual('no email'); }); it('projection', () => { @@ -1159,12 +1159,12 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => { const fixture = TestBed.createComponent(App); fixture.detectChanges(); expect(fixture.debugElement.nativeElement.innerHTML) - .toContain('
ONE
'); + .toContain('
ONE
'); fixture.componentRef.instance.count = 2; fixture.detectChanges(); expect(fixture.debugElement.nativeElement.innerHTML) - .toContain('
OTHER
'); + .toContain('
OTHER
'); // destroy component fixture.componentInstance.condition = false; @@ -1176,7 +1176,7 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => { fixture.componentInstance.count = 1; fixture.detectChanges(); expect(fixture.debugElement.nativeElement.innerHTML) - .toContain('
ONE
'); + .toContain('
ONE
'); }); it('with nested ICU expression and inside a container when creating a view via vcr.createEmbeddedView', @@ -1248,12 +1248,12 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => { fixture.detectChanges(); expect(fixture.debugElement.nativeElement.innerHTML) .toBe( - '
2 animals!
'); + '
2 animals!
'); fixture.componentRef.instance.count = 1; fixture.detectChanges(); expect(fixture.debugElement.nativeElement.innerHTML) - .toBe('
ONE
'); + .toBe('
ONE
'); }); it('with nested containers', () => { @@ -2357,13 +2357,13 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => { fixture.detectChanges(); expect(fixture.nativeElement.innerHTML) .toEqual( - `
Contenu enfant et projection depuis Parent
`); + `
Contenu enfant et projection depuis Parent
`); fixture.componentRef.instance.name = 'angular'; fixture.detectChanges(); expect(fixture.nativeElement.innerHTML) .toEqual( - `
Contenu enfant et projection depuis Angular
`); + `
Contenu enfant et projection depuis Angular
`); }); it(`shouldn't project deleted projections in i18n blocks`, () => { diff --git a/packages/core/test/acceptance/ngdevmode_debug_spec.ts b/packages/core/test/acceptance/ngdevmode_debug_spec.ts index 64763f7650..f3db593b68 100644 --- a/packages/core/test/acceptance/ngdevmode_debug_spec.ts +++ b/packages/core/test/acceptance/ngdevmode_debug_spec.ts @@ -32,7 +32,7 @@ onlyInIvy('Debug information exist in ivy only').describe('ngDevMode debug', () TestBed.configureTestingModule({declarations: [MyApp], imports: [CommonModule]}); const fixture = TestBed.createComponent(MyApp); - const rootLView = getLContext(fixture.nativeElement)!.lView; + const rootLView = getLContext(fixture.nativeElement)!.lView!; expect(rootLView.constructor.name).toEqual('LRootView'); const componentLView = getComponentLView(fixture.componentInstance); @@ -41,7 +41,7 @@ onlyInIvy('Debug information exist in ivy only').describe('ngDevMode debug', () const element: HTMLElement = fixture.nativeElement; fixture.detectChanges(); const li = element.querySelector('li')!; - const embeddedLView = getLContext(li)!.lView; + const embeddedLView = getLContext(li)!.lView!; expect(embeddedLView.constructor.name).toEqual('LEmbeddedView_MyApp_li_1'); }); }); diff --git a/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json b/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json index dd29afd07a..5b040b0822 100644 --- a/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json +++ b/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json @@ -47,6 +47,9 @@ { "name": "SimpleChange" }, + { + "name": "TRACKED_LVIEWS" + }, { "name": "TriggerComponent" }, @@ -254,6 +257,9 @@ { "name": "isInlineTemplate" }, + { + "name": "isLView" + }, { "name": "isNodeMatchingSelector" }, @@ -353,6 +359,9 @@ { "name": "setUpAttributes" }, + { + "name": "uniqueIdCounter" + }, { "name": "updateTransplantedViewCount" }, diff --git a/packages/core/test/bundling/forms_reactive/bundle.golden_symbols.json b/packages/core/test/bundling/forms_reactive/bundle.golden_symbols.json index b89832be26..3bd0484344 100644 --- a/packages/core/test/bundling/forms_reactive/bundle.golden_symbols.json +++ b/packages/core/test/bundling/forms_reactive/bundle.golden_symbols.json @@ -548,6 +548,9 @@ { "name": "THROW_IF_NOT_FOUND" }, + { + "name": "TRACKED_LVIEWS" + }, { "name": "TRANSITION_ID" }, @@ -1568,6 +1571,9 @@ { "name": "u" }, + { + "name": "uniqueIdCounter" + }, { "name": "unwrapRNode" }, diff --git a/packages/core/test/bundling/forms_reactive/forms_e2e_spec.ts b/packages/core/test/bundling/forms_reactive/forms_e2e_spec.ts index 65ee1ee746..5efe851e89 100644 --- a/packages/core/test/bundling/forms_reactive/forms_e2e_spec.ts +++ b/packages/core/test/bundling/forms_reactive/forms_e2e_spec.ts @@ -7,7 +7,6 @@ */ import '@angular/compiler'; -import {ɵwhenRendered as whenRendered} from '@angular/core'; import {withBody} from '@angular/private/testing'; import * as path from 'path'; @@ -18,8 +17,8 @@ describe('functional test for reactive forms', () => { BUNDLES.forEach((bundle) => { describe(`using ${bundle} bundle`, () => { it('should render template form', withBody('', async () => { - require(path.join(PACKAGE, bundle)); - await (window as any).waitForApp; + const {whenRendered, bootstrapApp} = require(path.join(PACKAGE, bundle)); + await bootstrapApp(); // Reactive forms const reactiveFormsComponent = (window as any).reactiveFormsComponent; diff --git a/packages/core/test/bundling/forms_reactive/index.ts b/packages/core/test/bundling/forms_reactive/index.ts index b44c4f410b..441b97afb8 100644 --- a/packages/core/test/bundling/forms_reactive/index.ts +++ b/packages/core/test/bundling/forms_reactive/index.ts @@ -5,7 +5,7 @@ * 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 {Component, NgModule, ɵNgModuleFactory as NgModuleFactory} from '@angular/core'; +import {Component, NgModule, ɵNgModuleFactory as NgModuleFactory, ɵwhenRendered as whenRendered} from '@angular/core'; import {FormArray, FormBuilder, FormControl, FormGroup, ReactiveFormsModule, Validators} from '@angular/forms'; import {BrowserModule, platformBrowser} from '@angular/platform-browser'; @@ -93,5 +93,15 @@ class FormsExampleModule { } } -(window as any).waitForApp = platformBrowser().bootstrapModuleFactory( - new NgModuleFactory(FormsExampleModule), {ngZone: 'noop'}); +function bootstrapApp() { + return platformBrowser().bootstrapModuleFactory( + new NgModuleFactory(FormsExampleModule), {ngZone: 'noop'}); +} + +// This bundle includes `@angular/core` within it which means that the test asserting +// against it will load a different core bundle. These symbols are exposed so that they +// can interact with the correct `@angular/core` instance. +module.exports = { + whenRendered, + bootstrapApp +}; diff --git a/packages/core/test/bundling/forms_template_driven/bundle.golden_symbols.json b/packages/core/test/bundling/forms_template_driven/bundle.golden_symbols.json index df9a8e4ae4..76743abf90 100644 --- a/packages/core/test/bundling/forms_template_driven/bundle.golden_symbols.json +++ b/packages/core/test/bundling/forms_template_driven/bundle.golden_symbols.json @@ -536,6 +536,9 @@ { "name": "THROW_IF_NOT_FOUND" }, + { + "name": "TRACKED_LVIEWS" + }, { "name": "TRANSITION_ID" }, @@ -1541,6 +1544,9 @@ { "name": "u" }, + { + "name": "uniqueIdCounter" + }, { "name": "unwrapRNode" }, diff --git a/packages/core/test/bundling/forms_template_driven/forms_e2e_spec.ts b/packages/core/test/bundling/forms_template_driven/forms_e2e_spec.ts index fb67ed4908..3cbb8b2a97 100644 --- a/packages/core/test/bundling/forms_template_driven/forms_e2e_spec.ts +++ b/packages/core/test/bundling/forms_template_driven/forms_e2e_spec.ts @@ -7,7 +7,6 @@ */ import '@angular/compiler'; -import {ɵwhenRendered as whenRendered} from '@angular/core'; import {withBody} from '@angular/private/testing'; import * as path from 'path'; @@ -18,8 +17,8 @@ describe('functional test for forms', () => { BUNDLES.forEach((bundle) => { describe(`using ${bundle} bundle`, () => { it('should render template form', withBody('', async () => { - require(path.join(PACKAGE, bundle)); - await (window as any).waitForApp; + const {bootstrapApp, whenRendered} = require(path.join(PACKAGE, bundle)); + await bootstrapApp(); // Template forms const templateFormsComponent = (window as any).templateFormsComponent; diff --git a/packages/core/test/bundling/forms_template_driven/index.ts b/packages/core/test/bundling/forms_template_driven/index.ts index fb03c5c0c5..ac81bc9e23 100644 --- a/packages/core/test/bundling/forms_template_driven/index.ts +++ b/packages/core/test/bundling/forms_template_driven/index.ts @@ -5,7 +5,7 @@ * 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 {Component, NgModule, ɵNgModuleFactory as NgModuleFactory} from '@angular/core'; +import {Component, NgModule, ɵNgModuleFactory as NgModuleFactory, ɵwhenRendered as whenRendered} from '@angular/core'; import {FormsModule} from '@angular/forms'; import {BrowserModule, platformBrowser} from '@angular/platform-browser'; @@ -70,5 +70,15 @@ class FormsExampleModule { } } -(window as any).waitForApp = platformBrowser().bootstrapModuleFactory( - new NgModuleFactory(FormsExampleModule), {ngZone: 'noop'}); +function bootstrapApp() { + return platformBrowser().bootstrapModuleFactory( + new NgModuleFactory(FormsExampleModule), {ngZone: 'noop'}); +} + +// This bundle includes `@angular/core` within it which means that the test asserting +// against it will load a different core bundle. These symbols are exposed so that they +// can interact with the correct `@angular/core` instance. +module.exports = { + whenRendered, + bootstrapApp +}; diff --git a/packages/core/test/bundling/hello_world/bundle.golden_symbols.json b/packages/core/test/bundling/hello_world/bundle.golden_symbols.json index 9f68ceb496..99bd5d32ec 100644 --- a/packages/core/test/bundling/hello_world/bundle.golden_symbols.json +++ b/packages/core/test/bundling/hello_world/bundle.golden_symbols.json @@ -41,6 +41,9 @@ { "name": "SimpleChange" }, + { + "name": "TRACKED_LVIEWS" + }, { "name": "ViewEncapsulation" }, @@ -173,6 +176,9 @@ { "name": "isInCheckNoChangesMode" }, + { + "name": "isLView" + }, { "name": "isProceduralRenderer" }, @@ -236,6 +242,9 @@ { "name": "setSelectedIndex" }, + { + "name": "uniqueIdCounter" + }, { "name": "updateTransplantedViewCount" }, diff --git a/packages/core/test/bundling/router/bundle.golden_symbols.json b/packages/core/test/bundling/router/bundle.golden_symbols.json index 2f236a71e8..d84e3b0b76 100644 --- a/packages/core/test/bundling/router/bundle.golden_symbols.json +++ b/packages/core/test/bundling/router/bundle.golden_symbols.json @@ -785,6 +785,9 @@ { "name": "TQuery_" }, + { + "name": "TRACKED_LVIEWS" + }, { "name": "TRANSITION_ID" }, @@ -2015,6 +2018,9 @@ { "name": "u" }, + { + "name": "uniqueIdCounter" + }, { "name": "unwrapElementRef" }, diff --git a/packages/core/test/bundling/todo/bundle.golden_symbols.json b/packages/core/test/bundling/todo/bundle.golden_symbols.json index 057cd33ab4..80bf8cb991 100644 --- a/packages/core/test/bundling/todo/bundle.golden_symbols.json +++ b/packages/core/test/bundling/todo/bundle.golden_symbols.json @@ -32,6 +32,9 @@ { "name": "IterableDiffers" }, + { + "name": "LContext" + }, { "name": "NG_COMP_DEF" }, @@ -110,6 +113,9 @@ { "name": "SkipSelf" }, + { + "name": "TRACKED_LVIEWS" + }, { "name": "TemplateRef" }, @@ -380,6 +386,9 @@ { "name": "getLView" }, + { + "name": "getLViewById" + }, { "name": "getLViewParent" }, @@ -740,6 +749,9 @@ { "name": "trackByIdentity" }, + { + "name": "uniqueIdCounter" + }, { "name": "unwrapRNode" }, diff --git a/packages/core/test/bundling/todo/index.ts b/packages/core/test/bundling/todo/index.ts index c9efb5b83b..de6dc37dbe 100644 --- a/packages/core/test/bundling/todo/index.ts +++ b/packages/core/test/bundling/todo/index.ts @@ -9,7 +9,7 @@ import '@angular/core/test/bundling/util/src/reflect_metadata'; import {CommonModule} from '@angular/common'; -import {Component, Injectable, NgModule, ViewEncapsulation, ɵmarkDirty as markDirty, ɵrenderComponent as renderComponent} from '@angular/core'; +import {Component, Injectable, NgModule, ViewEncapsulation, ɵmarkDirty as markDirty, ɵrenderComponent as renderComponent, ɵwhenRendered as whenRendered} from '@angular/core'; class Todo { editing: boolean; @@ -133,7 +133,9 @@ class TodoStore { class ToDoAppComponent { newTodoText = ''; - constructor(public todoStore: TodoStore) {} + constructor(public todoStore: TodoStore) { + (window as any).todoAppComponent = this; + } cancelEditingTodo(todo: Todo) { todo.editing = false; @@ -200,3 +202,8 @@ class ToDoAppModule { } renderComponent(ToDoAppComponent); + +// This bundle includes `@angular/core` within it which means that the test asserting +// against it will load a different core bundle. These symbols are exposed so that they +// can interact with the correct `@angular/core` instance. +module.exports = {whenRendered}; diff --git a/packages/core/test/bundling/todo/todo_e2e_spec.ts b/packages/core/test/bundling/todo/todo_e2e_spec.ts index b3506d457b..be682aa0f2 100644 --- a/packages/core/test/bundling/todo/todo_e2e_spec.ts +++ b/packages/core/test/bundling/todo/todo_e2e_spec.ts @@ -7,14 +7,9 @@ */ import '@angular/compiler'; -import {ɵwhenRendered as whenRendered} from '@angular/core'; -import {getComponent} from '@angular/core/src/render3'; import {withBody} from '@angular/private/testing'; import * as path from 'path'; -const UTF8 = { - encoding: 'utf-8' -}; const PACKAGE = 'angular/packages/core/test/bundling/todo'; const BUNDLES = ['bundle.js', 'bundle.min_debug.js', 'bundle.min.js']; @@ -22,13 +17,12 @@ describe('functional test for todo', () => { BUNDLES.forEach(bundle => { describe(bundle, () => { it('should render todo', withBody('', async () => { - require(path.join(PACKAGE, bundle)); - const toDoAppComponent = getComponent(document.querySelector('todo-app')!); + const {whenRendered} = require(path.join(PACKAGE, bundle)); expect(document.body.textContent).toContain('todos'); expect(document.body.textContent).toContain('Demonstrate Components'); expect(document.body.textContent).toContain('4 items left'); document.querySelector('button')!.click(); - await whenRendered(toDoAppComponent); + await whenRendered((window as any).todoAppComponent); expect(document.body.textContent).toContain('3 items left'); })); }); diff --git a/packages/core/test/bundling/todo_i18n/index.ts b/packages/core/test/bundling/todo_i18n/index.ts index 0025af70e5..b20a3532b5 100644 --- a/packages/core/test/bundling/todo_i18n/index.ts +++ b/packages/core/test/bundling/todo_i18n/index.ts @@ -8,7 +8,7 @@ import '@angular/core/test/bundling/util/src/reflect_metadata'; import './translations'; import {CommonModule} from '@angular/common'; -import {Component, Injectable, NgModule, ViewEncapsulation, ɵmarkDirty as markDirty, ɵrenderComponent as renderComponent} from '@angular/core'; +import {Component, Injectable, NgModule, ViewEncapsulation, ɵmarkDirty as markDirty, ɵrenderComponent as renderComponent, ɵwhenRendered as whenRendered} from '@angular/core'; class Todo { editing: boolean; @@ -127,7 +127,9 @@ class TodoStore { class ToDoAppComponent { newTodoText = ''; - constructor(public todoStore: TodoStore) {} + constructor(public todoStore: TodoStore) { + (window as any).todoAppComponent = this; + } cancelEditingTodo(todo: Todo) { todo.editing = false; @@ -194,3 +196,8 @@ class ToDoAppModule { } renderComponent(ToDoAppComponent); + +// This bundle includes `@angular/core` within it which means that the test asserting +// against it will load a different core bundle. These symbols are exposed so that they +// can interact with the correct `@angular/core` instance. +module.exports = {whenRendered}; diff --git a/packages/core/test/bundling/todo_i18n/todo_e2e_spec.ts b/packages/core/test/bundling/todo_i18n/todo_e2e_spec.ts index 9e55a6df77..5979f08c69 100644 --- a/packages/core/test/bundling/todo_i18n/todo_e2e_spec.ts +++ b/packages/core/test/bundling/todo_i18n/todo_e2e_spec.ts @@ -8,8 +8,6 @@ import '@angular/localize/init'; import '@angular/compiler'; -import {ɵwhenRendered as whenRendered} from '@angular/core'; -import {getComponent} from '@angular/core/src/render3'; import {clearTranslations} from '@angular/localize'; import {withBody} from '@angular/private/testing'; import * as path from 'path'; @@ -22,8 +20,7 @@ describe('functional test for todo i18n', () => { describe(bundle, () => { it('should render todo i18n', withBody('', async () => { clearTranslations(); - require(path.join(PACKAGE, bundle)); - const toDoAppComponent = getComponent(document.querySelector('todo-app')!); + const {whenRendered} = require(path.join(PACKAGE, bundle)); expect(document.body.textContent).toContain('liste de tâches'); expect(document.body.textContent).toContain('Démontrer les components'); expect(document.body.textContent).toContain('Démontrer NgModules'); @@ -31,7 +28,7 @@ describe('functional test for todo i18n', () => { expect(document.querySelector('.new-todo')!.getAttribute('placeholder')) .toEqual(`Qu'y a-t-il à faire ?`); document.querySelector('button')!.click(); - await whenRendered(toDoAppComponent); + await whenRendered((window as any).todoAppComponent); expect(document.body.textContent).toContain('3 tâches restantes'); })); }); diff --git a/packages/core/test/bundling/todo_r2/index.ts b/packages/core/test/bundling/todo_r2/index.ts index bd8b360f86..2c1860b4a7 100644 --- a/packages/core/test/bundling/todo_r2/index.ts +++ b/packages/core/test/bundling/todo_r2/index.ts @@ -9,7 +9,7 @@ import '@angular/core/test/bundling/util/src/reflect_metadata'; import {CommonModule} from '@angular/common'; -import {Component, Injectable, NgModule, ɵNgModuleFactory as NgModuleFactory} from '@angular/core'; +import {Component, Injectable, NgModule, ɵNgModuleFactory as NgModuleFactory, ɵwhenRendered as whenRendered} from '@angular/core'; import {BrowserModule, platformBrowser} from '@angular/platform-browser'; class Todo { @@ -195,5 +195,15 @@ class ToDoAppModule { } } -(window as any).waitForApp = - platformBrowser().bootstrapModuleFactory(new NgModuleFactory(ToDoAppModule), {ngZone: 'noop'}); +function bootstrapApp() { + return platformBrowser().bootstrapModuleFactory( + new NgModuleFactory(ToDoAppModule), {ngZone: 'noop'}); +} + +// This bundle includes `@angular/core` within it which means that the test asserting +// against it will load a different core bundle. These symbols are exposed so that they +// can interact with the correct `@angular/core` instance. +module.exports = { + whenRendered, + bootstrapApp +}; diff --git a/packages/core/test/bundling/todo_r2/todo_e2e_spec.ts b/packages/core/test/bundling/todo_r2/todo_e2e_spec.ts index 7fb449cd96..98781a6407 100644 --- a/packages/core/test/bundling/todo_r2/todo_e2e_spec.ts +++ b/packages/core/test/bundling/todo_r2/todo_e2e_spec.ts @@ -7,13 +7,9 @@ */ import '@angular/compiler'; -import {ɵwhenRendered as whenRendered} from '@angular/core'; import {withBody} from '@angular/private/testing'; import * as path from 'path'; -const UTF8 = { - encoding: 'utf-8' -}; const PACKAGE = 'angular/packages/core/test/bundling/todo_r2'; const BUNDLES = ['bundle.js', 'bundle.min_debug.js', 'bundle.min.js']; @@ -22,8 +18,8 @@ describe('functional test for todo', () => { describe(bundle, () => { it('should place styles on the elements within the component', withBody('', async () => { - require(path.join(PACKAGE, bundle)); - await (window as any).waitForApp; + const {bootstrapApp, whenRendered} = require(path.join(PACKAGE, bundle)); + await bootstrapApp(); const toDoAppComponent = (window as any).toDoAppComponent; await whenRendered(toDoAppComponent); diff --git a/packages/core/test/render3/i18n/i18n_insert_before_index_spec.ts b/packages/core/test/render3/i18n/i18n_insert_before_index_spec.ts index 2921d9a2e7..e0b4b4f582 100644 --- a/packages/core/test/render3/i18n/i18n_insert_before_index_spec.ts +++ b/packages/core/test/render3/i18n/i18n_insert_before_index_spec.ts @@ -29,9 +29,9 @@ describe('addTNodeAndUpdateInsertBeforeIndex', () => { it('should add first node', () => { const previousTNodes: TNode[] = []; - addTNodeAndUpdateInsertBeforeIndex(previousTNodes, tPlaceholderElementNode(20)); + addTNodeAndUpdateInsertBeforeIndex(previousTNodes, tPlaceholderElementNode(21)); expect(previousTNodes).toEqual([ - matchTNode({index: 20, insertBeforeIndex: null}), + matchTNode({index: 21, insertBeforeIndex: null}), ]); }); @@ -39,14 +39,14 @@ describe('addTNodeAndUpdateInsertBeforeIndex', () => { describe('whose index is greater than those already there', () => { it('should not update the `insertBeforeIndex` values', () => { const previousTNodes: TNode[] = [ - tPlaceholderElementNode(20), tPlaceholderElementNode(21), + tPlaceholderElementNode(22), ]; - addTNodeAndUpdateInsertBeforeIndex(previousTNodes, tPlaceholderElementNode(22)); + addTNodeAndUpdateInsertBeforeIndex(previousTNodes, tPlaceholderElementNode(23)); expect(previousTNodes).toEqual([ - matchTNode({index: 20, insertBeforeIndex: null}), matchTNode({index: 21, insertBeforeIndex: null}), matchTNode({index: 22, insertBeforeIndex: null}), + matchTNode({index: 23, insertBeforeIndex: null}), ]); }); }); @@ -54,44 +54,44 @@ describe('addTNodeAndUpdateInsertBeforeIndex', () => { describe('whose index is smaller than current nodes', () => { it('should update the previous insertBeforeIndex', () => { const previousTNodes: TNode[] = [ - tPlaceholderElementNode(21), tPlaceholderElementNode(22), + tPlaceholderElementNode(23), ]; - addTNodeAndUpdateInsertBeforeIndex(previousTNodes, tPlaceholderElementNode(20)); + addTNodeAndUpdateInsertBeforeIndex(previousTNodes, tPlaceholderElementNode(21)); expect(previousTNodes).toEqual([ - matchTNode({index: 21, insertBeforeIndex: 20}), - matchTNode({index: 22, insertBeforeIndex: 20}), - matchTNode({index: 20, insertBeforeIndex: null}), + matchTNode({index: 22, insertBeforeIndex: 21}), + matchTNode({index: 23, insertBeforeIndex: 21}), + matchTNode({index: 21, insertBeforeIndex: null}), ]); }); it('should not update the previous insertBeforeIndex if it is already set', () => { const previousTNodes: TNode[] = [ - tPlaceholderElementNode(22, 21), - tPlaceholderElementNode(23, 21), - tPlaceholderElementNode(21), + tPlaceholderElementNode(23, 22), + tPlaceholderElementNode(24, 22), + tPlaceholderElementNode(22), ]; - addTNodeAndUpdateInsertBeforeIndex(previousTNodes, tPlaceholderElementNode(20)); + addTNodeAndUpdateInsertBeforeIndex(previousTNodes, tPlaceholderElementNode(21)); expect(previousTNodes).toEqual([ + matchTNode({index: 23, insertBeforeIndex: 22}), + matchTNode({index: 24, insertBeforeIndex: 22}), matchTNode({index: 22, insertBeforeIndex: 21}), - matchTNode({index: 23, insertBeforeIndex: 21}), - matchTNode({index: 21, insertBeforeIndex: 20}), - matchTNode({index: 20, insertBeforeIndex: null}), + matchTNode({index: 21, insertBeforeIndex: null}), ]); }); it('should not update the previous insertBeforeIndex if it is created after', () => { const previousTNodes: TNode[] = [ - tPlaceholderElementNode(25, 20), - tPlaceholderElementNode(26, 20), - tPlaceholderElementNode(20), + tPlaceholderElementNode(26, 21), + tPlaceholderElementNode(27, 21), + tPlaceholderElementNode(21), ]; - addTNodeAndUpdateInsertBeforeIndex(previousTNodes, tPlaceholderElementNode(23)); + addTNodeAndUpdateInsertBeforeIndex(previousTNodes, tPlaceholderElementNode(24)); expect(previousTNodes).toEqual([ - matchTNode({index: 25, insertBeforeIndex: 20}), - matchTNode({index: 26, insertBeforeIndex: 20}), - matchTNode({index: 20, insertBeforeIndex: null}), - matchTNode({index: 23, insertBeforeIndex: null}), + matchTNode({index: 26, insertBeforeIndex: 21}), + matchTNode({index: 27, insertBeforeIndex: 21}), + matchTNode({index: 21, insertBeforeIndex: null}), + matchTNode({index: 24, insertBeforeIndex: null}), ]); }); }); @@ -101,14 +101,14 @@ describe('addTNodeAndUpdateInsertBeforeIndex', () => { describe('whose index is greater than those already there', () => { it('should not update the `insertBeforeIndex` values', () => { const previousTNodes: TNode[] = [ - tPlaceholderElementNode(20), tPlaceholderElementNode(21), + tPlaceholderElementNode(22), ]; - addTNodeAndUpdateInsertBeforeIndex(previousTNodes, tI18NTextNode(22)); + addTNodeAndUpdateInsertBeforeIndex(previousTNodes, tI18NTextNode(23)); expect(previousTNodes).toEqual([ - matchTNode({index: 20, insertBeforeIndex: 22}), - matchTNode({index: 21, insertBeforeIndex: 22}), - matchTNode({index: 22, insertBeforeIndex: null}), + matchTNode({index: 21, insertBeforeIndex: 23}), + matchTNode({index: 22, insertBeforeIndex: 23}), + matchTNode({index: 23, insertBeforeIndex: null}), ]); }); }); @@ -116,44 +116,44 @@ describe('addTNodeAndUpdateInsertBeforeIndex', () => { describe('whose index is smaller than current nodes', () => { it('should update the previous insertBeforeIndex', () => { const previousTNodes: TNode[] = [ - tPlaceholderElementNode(21), tPlaceholderElementNode(22), + tPlaceholderElementNode(23), ]; - addTNodeAndUpdateInsertBeforeIndex(previousTNodes, tI18NTextNode(20)); + addTNodeAndUpdateInsertBeforeIndex(previousTNodes, tI18NTextNode(21)); expect(previousTNodes).toEqual([ - matchTNode({index: 21, insertBeforeIndex: 20}), - matchTNode({index: 22, insertBeforeIndex: 20}), - matchTNode({index: 20, insertBeforeIndex: null}), + matchTNode({index: 22, insertBeforeIndex: 21}), + matchTNode({index: 23, insertBeforeIndex: 21}), + matchTNode({index: 21, insertBeforeIndex: null}), ]); }); it('should not update the previous insertBeforeIndex if it is already set', () => { const previousTNodes: TNode[] = [ - tPlaceholderElementNode(22, 21), - tPlaceholderElementNode(23, 21), - tPlaceholderElementNode(21), + tPlaceholderElementNode(23, 22), + tPlaceholderElementNode(24, 22), + tPlaceholderElementNode(22), ]; - addTNodeAndUpdateInsertBeforeIndex(previousTNodes, tI18NTextNode(20)); + addTNodeAndUpdateInsertBeforeIndex(previousTNodes, tI18NTextNode(21)); expect(previousTNodes).toEqual([ + matchTNode({index: 23, insertBeforeIndex: 22}), + matchTNode({index: 24, insertBeforeIndex: 22}), matchTNode({index: 22, insertBeforeIndex: 21}), - matchTNode({index: 23, insertBeforeIndex: 21}), - matchTNode({index: 21, insertBeforeIndex: 20}), - matchTNode({index: 20, insertBeforeIndex: null}), + matchTNode({index: 21, insertBeforeIndex: null}), ]); }); it('should not update the previous insertBeforeIndex if it is created after', () => { const previousTNodes: TNode[] = [ - tPlaceholderElementNode(25, 20), - tPlaceholderElementNode(26, 20), - tPlaceholderElementNode(20), + tPlaceholderElementNode(26, 21), + tPlaceholderElementNode(27, 21), + tPlaceholderElementNode(21), ]; - addTNodeAndUpdateInsertBeforeIndex(previousTNodes, tI18NTextNode(23)); + addTNodeAndUpdateInsertBeforeIndex(previousTNodes, tI18NTextNode(24)); expect(previousTNodes).toEqual([ - matchTNode({index: 25, insertBeforeIndex: 20}), - matchTNode({index: 26, insertBeforeIndex: 20}), - matchTNode({index: 20, insertBeforeIndex: 23}), - matchTNode({index: 23, insertBeforeIndex: null}), + matchTNode({index: 26, insertBeforeIndex: 21}), + matchTNode({index: 27, insertBeforeIndex: 21}), + matchTNode({index: 21, insertBeforeIndex: 24}), + matchTNode({index: 24, insertBeforeIndex: null}), ]); }); }); @@ -162,22 +162,22 @@ describe('addTNodeAndUpdateInsertBeforeIndex', () => { describe('scenario', () => { it('should rearrange the nodes', () => { const previousTNodes: TNode[] = []; - addTNodeAndUpdateInsertBeforeIndex(previousTNodes, tPlaceholderElementNode(22)); - addTNodeAndUpdateInsertBeforeIndex(previousTNodes, tPlaceholderElementNode(28)); - addTNodeAndUpdateInsertBeforeIndex(previousTNodes, tPlaceholderElementNode(24)); - addTNodeAndUpdateInsertBeforeIndex(previousTNodes, tPlaceholderElementNode(25)); - addTNodeAndUpdateInsertBeforeIndex(previousTNodes, tI18NTextNode(29)); addTNodeAndUpdateInsertBeforeIndex(previousTNodes, tPlaceholderElementNode(23)); - addTNodeAndUpdateInsertBeforeIndex(previousTNodes, tPlaceholderElementNode(27)); + addTNodeAndUpdateInsertBeforeIndex(previousTNodes, tPlaceholderElementNode(29)); + addTNodeAndUpdateInsertBeforeIndex(previousTNodes, tPlaceholderElementNode(25)); + addTNodeAndUpdateInsertBeforeIndex(previousTNodes, tPlaceholderElementNode(26)); + addTNodeAndUpdateInsertBeforeIndex(previousTNodes, tI18NTextNode(30)); + addTNodeAndUpdateInsertBeforeIndex(previousTNodes, tPlaceholderElementNode(24)); + addTNodeAndUpdateInsertBeforeIndex(previousTNodes, tPlaceholderElementNode(28)); expect(previousTNodes).toEqual([ - matchTNode({index: 22, insertBeforeIndex: 29}), - matchTNode({index: 28, insertBeforeIndex: 24}), - matchTNode({index: 24, insertBeforeIndex: 29}), - matchTNode({index: 25, insertBeforeIndex: 29}), - matchTNode({index: 29, insertBeforeIndex: null}), - matchTNode({index: 23, insertBeforeIndex: null}), - matchTNode({index: 27, insertBeforeIndex: null}), + matchTNode({index: 23, insertBeforeIndex: 30}), + matchTNode({index: 29, insertBeforeIndex: 25}), + matchTNode({index: 25, insertBeforeIndex: 30}), + matchTNode({index: 26, insertBeforeIndex: 30}), + matchTNode({index: 30, insertBeforeIndex: null}), + matchTNode({index: 24, insertBeforeIndex: null}), + matchTNode({index: 28, insertBeforeIndex: null}), ]); }); }); -}); \ No newline at end of file +}); diff --git a/packages/core/test/render3/i18n/i18n_parse_spec.ts b/packages/core/test/render3/i18n/i18n_parse_spec.ts index 339453a1b0..41826fbc5e 100644 --- a/packages/core/test/render3/i18n/i18n_parse_spec.ts +++ b/packages/core/test/render3/i18n/i18n_parse_spec.ts @@ -26,8 +26,8 @@ describe('i18n_parse', () => { const tI18n = toT18n('some text'); expect(tI18n).toEqual(matchTI18n({ create: matchDebug([ - 'lView[22] = document.createText("some text");', - 'parent.appendChild(lView[22]);', + 'lView[23] = document.createText("some text");', + 'parent.appendChild(lView[23]);', ]), update: [] as unknown as I18nUpdateOpCodes, })); @@ -40,16 +40,16 @@ describe('i18n_parse', () => { // TData | LView // ---------------------------+------------------------------- // ----- DECL ----- - // 20: TI18n | + // 21: TI18n | // ----- VARS ----- - // 21: Binding for ICU | + // 22: Binding for ICU | // ----- EXPANDO ----- - // 22: null | #text(before|) - // 23: TIcu | - // 24: null | currently selected ICU case - // 25: null | #text(caseA) - // 26: null | #text(otherCase) - // 27: null | #text(|after) + // 23: null | #text(before|) + // 24: TIcu | + // 25: null | currently selected ICU case + // 26: null | #text(caseA) + // 27: null | #text(otherCase) + // 28: null | #text(|after) const tI18n = toT18n(`before|{ �0�, select, A {caseA} @@ -57,30 +57,30 @@ describe('i18n_parse', () => { }|after`); expect(tI18n).toEqual(matchTI18n({ create: matchDebug([ - 'lView[22] = document.createText("before|");', - 'parent.appendChild(lView[22]);', - 'lView[23] = document.createComment("ICU 20:0");', + 'lView[23] = document.createText("before|");', 'parent.appendChild(lView[23]);', - 'lView[27] = document.createText("|after");', - 'parent.appendChild(lView[27]);', + 'lView[24] = document.createComment("ICU 21:0");', + 'parent.appendChild(lView[24]);', + 'lView[28] = document.createText("|after");', + 'parent.appendChild(lView[28]);', ]), update: matchDebug([ - 'if (mask & 0b1) { icuSwitchCase(23, `${lView[i-1]}`); }', + 'if (mask & 0b1) { icuSwitchCase(24, `${lView[i-1]}`); }', ]) })); - expect(getTIcu(fixture.tView, 23)).toEqual(matchTIcu({ + expect(getTIcu(fixture.tView, 24)).toEqual(matchTIcu({ type: IcuType.select, - anchorIdx: 23, - currentCaseLViewIndex: 24, + anchorIdx: 24, + currentCaseLViewIndex: 25, cases: ['A', 'other'], create: [ matchDebug([ - 'lView[25] = document.createTextNode("caseA")', - '(lView[0] as Element).appendChild(lView[25])' + 'lView[26] = document.createTextNode("caseA")', + '(lView[0] as Element).appendChild(lView[26])' ]), matchDebug([ - 'lView[26] = document.createTextNode("otherCase")', - '(lView[0] as Element).appendChild(lView[26])', + 'lView[27] = document.createTextNode("otherCase")', + '(lView[0] as Element).appendChild(lView[27])', ]) ], update: [ @@ -88,29 +88,29 @@ describe('i18n_parse', () => { matchDebug([]), ], remove: [ - matchDebug(['remove(lView[25])']), matchDebug(['remove(lView[26])']), + matchDebug(['remove(lView[27])']), ], })); fixture.apply(() => { applyCreateOpCodes(fixture.lView, tI18n.create, fixture.host, null); - expect(fixture.host.innerHTML).toEqual('before||after'); + expect(fixture.host.innerHTML).toEqual('before||after'); }); fixture.apply(() => { ɵɵi18nExp('A'); ɵɵi18nApply(0); // index 0 + HEADER_OFFSET = 20; - expect(fixture.host.innerHTML).toEqual('before|caseA|after'); + expect(fixture.host.innerHTML).toEqual('before|caseA|after'); }); fixture.apply(() => { ɵɵi18nExp('x'); ɵɵi18nApply(0); // index 0 + HEADER_OFFSET = 20; - expect(fixture.host.innerHTML).toEqual('before|otherCase|after'); + expect(fixture.host.innerHTML).toEqual('before|otherCase|after'); }); fixture.apply(() => { ɵɵi18nExp('A'); ɵɵi18nApply(0); // index 0 + HEADER_OFFSET = 20; - expect(fixture.host.innerHTML).toEqual('before|caseA|after'); + expect(fixture.host.innerHTML).toEqual('before|caseA|after'); }); }); @@ -122,23 +122,23 @@ describe('i18n_parse', () => { }`); fixture.apply(() => { applyCreateOpCodes(fixture.lView, tI18n.create, fixture.host, null); - expect(fixture.host.innerHTML).toEqual(''); + expect(fixture.host.innerHTML).toEqual(''); }); fixture.apply(() => { ɵɵi18nExp('A'); ɵɵi18nApply(0); // index 0 + HEADER_OFFSET = 20; - expect(fixture.host.innerHTML).toEqual('Hello world!'); + expect(fixture.host.innerHTML).toEqual('Hello world!'); }); fixture.apply(() => { ɵɵi18nExp('x'); ɵɵi18nApply(0); // index 0 + HEADER_OFFSET = 20; expect(fixture.host.innerHTML) - .toEqual('
nestedOther
'); + .toEqual('
nestedOther
'); }); fixture.apply(() => { ɵɵi18nExp('A'); ɵɵi18nApply(0); // index 0 + HEADER_OFFSET = 20; - expect(fixture.host.innerHTML).toEqual('Hello world!'); + expect(fixture.host.innerHTML).toEqual('Hello world!'); }); }); @@ -148,21 +148,21 @@ describe('i18n_parse', () => { // TData | LView // ---------------------------+------------------------------- // ----- DECL ----- - // 20: TI18n | + // 21: TI18n | // ----- VARS ----- - // 21: Binding for parent ICU | - // 22: Binding for child ICU | + // 22: Binding for parent ICU | // 23: Binding for child ICU | + // 24: Binding for child ICU | // ----- EXPANDO ----- - // 24: TIcu (parent) | - // 25: null | currently selected ICU case - // 26: null | #text( parentA ) - // 27: TIcu (child) | - // 28: null | currently selected ICU case - // 29: null | #text(nested0) - // 30: null | #text({{�2�}}) - // 31: null | #text( ) - // 32: null | #text( parentOther ) + // 25: TIcu (parent) | + // 26: null | currently selected ICU case + // 27: null | #text( parentA ) + // 28: TIcu (child) | + // 29: null | currently selected ICU case + // 30: null | #text(nested0) + // 31: null | #text({{�2�}}) + // 32: null | #text( ) + // 33: null | #text( parentOther ) const tI18n = toT18n(`{ �0�, select, A {parentA {�1�, select, 0 {nested0} other {�2�}}!} @@ -170,32 +170,32 @@ describe('i18n_parse', () => { }`); expect(tI18n).toEqual(matchTI18n({ create: matchDebug([ - 'lView[24] = document.createComment("ICU 20:0");', - 'parent.appendChild(lView[24]);', + 'lView[25] = document.createComment("ICU 21:0");', + 'parent.appendChild(lView[25]);', ]), update: matchDebug([ - 'if (mask & 0b1) { icuSwitchCase(24, `${lView[i-1]}`); }', - 'if (mask & 0b10) { icuSwitchCase(27, `${lView[i-2]}`); }', - 'if (mask & 0b100) { icuUpdateCase(27); }', + 'if (mask & 0b1) { icuSwitchCase(25, `${lView[i-1]}`); }', + 'if (mask & 0b10) { icuSwitchCase(28, `${lView[i-2]}`); }', + 'if (mask & 0b100) { icuUpdateCase(28); }', ]), })); - expect(getTIcu(fixture.tView, 24)).toEqual(matchTIcu({ + expect(getTIcu(fixture.tView, 25)).toEqual(matchTIcu({ type: IcuType.select, - anchorIdx: 24, - currentCaseLViewIndex: 25, + anchorIdx: 25, + currentCaseLViewIndex: 26, cases: ['A', 'other'], create: [ matchDebug([ - 'lView[26] = document.createTextNode("parentA ")', - '(lView[0] as Element).appendChild(lView[26])', - 'lView[27] = document.createComment("nested ICU 0")', + 'lView[27] = document.createTextNode("parentA ")', '(lView[0] as Element).appendChild(lView[27])', - 'lView[31] = document.createTextNode("!")', - '(lView[0] as Element).appendChild(lView[31])', + 'lView[28] = document.createComment("nested ICU 0")', + '(lView[0] as Element).appendChild(lView[28])', + 'lView[32] = document.createTextNode("!")', + '(lView[0] as Element).appendChild(lView[32])', ]), matchDebug([ - 'lView[32] = document.createTextNode("parentOther")', - '(lView[0] as Element).appendChild(lView[32])', + 'lView[33] = document.createTextNode("parentOther")', + '(lView[0] as Element).appendChild(lView[33])', ]) ], update: [ @@ -204,47 +204,47 @@ describe('i18n_parse', () => { ], remove: [ matchDebug([ - 'remove(lView[26])', - 'removeNestedICU(27)', 'remove(lView[27])', - 'remove(lView[31])', + 'removeNestedICU(28)', + 'remove(lView[28])', + 'remove(lView[32])', ]), matchDebug([ - 'remove(lView[32])', + 'remove(lView[33])', ]) ], })); - expect(getTIcu(fixture.tView, 27)).toEqual(matchTIcu({ + expect(getTIcu(fixture.tView, 28)).toEqual(matchTIcu({ type: IcuType.select, - anchorIdx: 27, - currentCaseLViewIndex: 28, + anchorIdx: 28, + currentCaseLViewIndex: 29, cases: ['0', 'other'], create: [ matchDebug([ - 'lView[29] = document.createTextNode("nested0")', - '(lView[0] as Element).appendChild(lView[29])' + 'lView[30] = document.createTextNode("nested0")', + '(lView[0] as Element).appendChild(lView[30])' ]), matchDebug([ - 'lView[30] = document.createTextNode("")', - '(lView[0] as Element).appendChild(lView[30])', + 'lView[31] = document.createTextNode("")', + '(lView[0] as Element).appendChild(lView[31])', ]) ], update: [ matchDebug([]), matchDebug([ - 'if (mask & 0b100) { (lView[30] as Text).textContent = `${lView[i-3]}`; }', + 'if (mask & 0b100) { (lView[31] as Text).textContent = `${lView[i-3]}`; }', ]), ], remove: [ - matchDebug(['remove(lView[29])']), matchDebug(['remove(lView[30])']), + matchDebug(['remove(lView[31])']), ], })); fixture.apply(() => { applyCreateOpCodes(fixture.lView, tI18n.create, fixture.host, null); - expect(fixture.host.innerHTML).toEqual(''); + expect(fixture.host.innerHTML).toEqual(''); }); fixture.apply(() => { ɵɵi18nExp('A'); @@ -252,28 +252,28 @@ describe('i18n_parse', () => { ɵɵi18nExp('value1'); ɵɵi18nApply(0); // index 0 + HEADER_OFFSET = 20; expect(fixture.host.innerHTML) - .toEqual('parentA nested0!'); + .toEqual('parentA nested0!'); }); fixture.apply(() => { ɵɵi18nExp('A'); ɵɵi18nExp('x'); ɵɵi18nExp('value1'); ɵɵi18nApply(0); // index 0 + HEADER_OFFSET = 20; - expect(fixture.host.innerHTML).toEqual('parentA value1!'); + expect(fixture.host.innerHTML).toEqual('parentA value1!'); }); fixture.apply(() => { ɵɵi18nExp('x'); ɵɵi18nExp('x'); ɵɵi18nExp('value2'); ɵɵi18nApply(0); // index 0 + HEADER_OFFSET = 20; - expect(fixture.host.innerHTML).toEqual('parentOther'); + expect(fixture.host.innerHTML).toEqual('parentOther'); }); fixture.apply(() => { ɵɵi18nExp('A'); ɵɵi18nExp('A'); ɵɵi18nExp('value2'); ɵɵi18nApply(0); // index 0 + HEADER_OFFSET = 20; - expect(fixture.host.innerHTML).toEqual('parentA value2!'); + expect(fixture.host.innerHTML).toEqual('parentA value2!'); }); }); }); diff --git a/packages/core/test/render3/i18n/i18n_spec.ts b/packages/core/test/render3/i18n/i18n_spec.ts index 26c2be6c2a..70666f4257 100644 --- a/packages/core/test/render3/i18n/i18n_spec.ts +++ b/packages/core/test/render3/i18n/i18n_spec.ts @@ -128,7 +128,7 @@ describe('Runtime i18n', () => { }, undefined, nbConsts, HEADER_OFFSET + index); expect((opCodes as any).update.debug).toEqual([ - 'if (mask & 0b1) { (lView[22] as Text).textContent = `Hello ${lView[i-1]}!`; }' + 'if (mask & 0b1) { (lView[23] as Text).textContent = `Hello ${lView[i-1]}!`; }' ]); expect(opCodes).toEqual({ @@ -137,7 +137,7 @@ describe('Runtime i18n', () => { `parent.appendChild(lView[${HEADER_OFFSET + 2}]);`, ]), update: matchDebug([ - 'if (mask & 0b1) { (lView[22] as Text).textContent = `Hello ${lView[i-1]}!`; }', + 'if (mask & 0b1) { (lView[23] as Text).textContent = `Hello ${lView[i-1]}!`; }', ]), }); }); @@ -158,7 +158,7 @@ describe('Runtime i18n', () => { `parent.appendChild(lView[${HEADER_OFFSET + 2}]);`, ]), update: matchDebug([ - 'if (mask & 0b11) { (lView[22] as Text).textContent = `Hello ${lView[i-1]} and ${lView[i-2]}, again ${lView[i-1]}!`; }', + 'if (mask & 0b11) { (lView[23] as Text).textContent = `Hello ${lView[i-1]} and ${lView[i-2]}, again ${lView[i-1]}!`; }', ]), }); }); @@ -193,7 +193,7 @@ describe('Runtime i18n', () => { `parent.appendChild(lView[${HEADER_OFFSET + 4}]);`, ]), update: matchDebug([ - 'if (mask & 0b1) { (lView[23] as Text).textContent = `${lView[i-1]} is rendered as: `; }', + 'if (mask & 0b1) { (lView[24] as Text).textContent = `${lView[i-1]} is rendered as: `; }', ]), }); @@ -249,26 +249,26 @@ describe('Runtime i18n', () => { expect(opCodes).toEqual({ create: matchDebug([ - `lView[${HEADER_OFFSET + 2}] = document.createComment("ICU 21:0");`, + `lView[${HEADER_OFFSET + 2}] = document.createComment("ICU 22:0");`, `parent.appendChild(lView[${HEADER_OFFSET + 2}]);`, ]), update: matchDebug([ - 'if (mask & 0b1) { icuSwitchCase(22, `${lView[i-1]}`); }', - 'if (mask & 0b1) { icuUpdateCase(22); }', + 'if (mask & 0b1) { icuSwitchCase(23, `${lView[i-1]}`); }', + 'if (mask & 0b1) { icuUpdateCase(23); }', ]), }); - expect(getTIcu(tView, 22)).toEqual({ + expect(getTIcu(tView, 23)).toEqual({ type: 1, - currentCaseLViewIndex: 23, - anchorIdx: 22, + currentCaseLViewIndex: 24, + anchorIdx: 23, cases: ['0', '1', 'other'], create: [ matchDebug([ `lView[${HEADER_OFFSET + 4}] = document.createTextNode("no ")`, `(lView[${HEADER_OFFSET + 0}] as Element).appendChild(lView[${HEADER_OFFSET + 4}])`, - 'lView[25] = document.createElement("b")', + 'lView[26] = document.createElement("b")', `(lView[${HEADER_OFFSET + 0}] as Element).appendChild(lView[${HEADER_OFFSET + 5}])`, - '(lView[25] as Element).setAttribute("title", "none")', + '(lView[26] as Element).setAttribute("title", "none")', `lView[${HEADER_OFFSET + 6}] = document.createTextNode("emails")`, `(lView[${HEADER_OFFSET + 5}] as Element).appendChild(lView[${HEADER_OFFSET + 6}])`, `lView[${HEADER_OFFSET + 7}] = document.createTextNode("!")`, @@ -277,41 +277,41 @@ describe('Runtime i18n', () => { matchDebug([ `lView[${HEADER_OFFSET + 8}] = document.createTextNode("one ")`, `(lView[${HEADER_OFFSET + 0}] as Element).appendChild(lView[${HEADER_OFFSET + 8}])`, - 'lView[29] = document.createElement("i")', + 'lView[30] = document.createElement("i")', `(lView[${HEADER_OFFSET + 0}] as Element).appendChild(lView[${HEADER_OFFSET + 9}])`, - 'lView[30] = document.createTextNode("email")', - '(lView[29] as Element).appendChild(lView[30])', + 'lView[31] = document.createTextNode("email")', + '(lView[30] as Element).appendChild(lView[31])', ]), matchDebug([ - 'lView[31] = document.createTextNode("")', - '(lView[20] as Element).appendChild(lView[31])', - 'lView[32] = document.createElement("span")', - '(lView[20] as Element).appendChild(lView[32])', - 'lView[33] = document.createTextNode("emails")', - '(lView[32] as Element).appendChild(lView[33])', + 'lView[32] = document.createTextNode("")', + '(lView[21] as Element).appendChild(lView[32])', + 'lView[33] = document.createElement("span")', + '(lView[21] as Element).appendChild(lView[33])', + 'lView[34] = document.createTextNode("emails")', + '(lView[33] as Element).appendChild(lView[34])', ]), ], remove: [ matchDebug([ - 'remove(lView[24])', 'remove(lView[25])', - 'remove(lView[27])', - ]), - matchDebug([ + 'remove(lView[26])', 'remove(lView[28])', - 'remove(lView[29])', ]), matchDebug([ - 'remove(lView[31])', + 'remove(lView[29])', + 'remove(lView[30])', + ]), + matchDebug([ 'remove(lView[32])', + 'remove(lView[33])', ]), ], update: [ matchDebug([]), matchDebug([]), matchDebug([ - 'if (mask & 0b1) { (lView[31] as Text).textContent = `${lView[i-1]} `; }', - 'if (mask & 0b10) { (lView[32] as Element).setAttribute(\'title\', `${lView[i-2]}`); }', + 'if (mask & 0b1) { (lView[32] as Text).textContent = `${lView[i-1]} `; }', + 'if (mask & 0b10) { (lView[33] as Element).setAttribute(\'title\', `${lView[i-2]}`); }', ]), ] }); @@ -336,19 +336,19 @@ describe('Runtime i18n', () => { expect(opCodes).toEqual({ create: matchDebug([ - `lView[${HEADER_OFFSET + 2}] = document.createComment("ICU 21:0");`, + `lView[${HEADER_OFFSET + 2}] = document.createComment("ICU 22:0");`, `parent.appendChild(lView[${HEADER_OFFSET + 2}]);`, ]), update: matchDebug([ - 'if (mask & 0b1) { icuSwitchCase(22, `${lView[i-1]}`); }', - 'if (mask & 0b10) { icuSwitchCase(26, `${lView[i-2]}`); }', - 'if (mask & 0b1) { icuUpdateCase(22); }', + 'if (mask & 0b1) { icuSwitchCase(23, `${lView[i-1]}`); }', + 'if (mask & 0b10) { icuSwitchCase(27, `${lView[i-2]}`); }', + 'if (mask & 0b1) { icuUpdateCase(23); }', ]), }); - expect(getTIcu(tView, 22)).toEqual({ + expect(getTIcu(tView, 23)).toEqual({ type: 1, - anchorIdx: 22, - currentCaseLViewIndex: 23, + anchorIdx: 23, + currentCaseLViewIndex: 24, cases: ['0', 'other'], create: [ matchDebug([ @@ -358,34 +358,34 @@ describe('Runtime i18n', () => { matchDebug([ `lView[${HEADER_OFFSET + 5}] = document.createTextNode("")`, `(lView[${HEADER_OFFSET + 0}] as Element).appendChild(lView[${HEADER_OFFSET + 5}])`, - 'lView[26] = document.createComment("nested ICU 0")', + 'lView[27] = document.createComment("nested ICU 0")', `(lView[${HEADER_OFFSET + 0}] as Element).appendChild(lView[${HEADER_OFFSET + 6}])`, - 'lView[31] = document.createTextNode("!")', - '(lView[20] as Element).appendChild(lView[31])', + 'lView[32] = document.createTextNode("!")', + '(lView[21] as Element).appendChild(lView[32])', ]), ], update: [ matchDebug([]), matchDebug([ - 'if (mask & 0b1) { (lView[25] as Text).textContent = `${lView[i-1]} `; }', + 'if (mask & 0b1) { (lView[26] as Text).textContent = `${lView[i-1]} `; }', ]), ], remove: [ matchDebug([ - 'remove(lView[24])', + 'remove(lView[25])', ]), matchDebug([ - 'remove(lView[25])', - 'removeNestedICU(26)', 'remove(lView[26])', - 'remove(lView[31])', + 'removeNestedICU(27)', + 'remove(lView[27])', + 'remove(lView[32])', ]), ], }); - expect(tView.data[26]).toEqual({ + expect(tView.data[27]).toEqual({ type: 0, - anchorIdx: 26, - currentCaseLViewIndex: 27, + anchorIdx: 27, + currentCaseLViewIndex: 28, cases: ['cat', 'dog', 'other'], create: [ matchDebug([ @@ -397,8 +397,8 @@ describe('Runtime i18n', () => { `(lView[${HEADER_OFFSET + 0}] as Element).appendChild(lView[${HEADER_OFFSET + 9}])`, ]), matchDebug([ - 'lView[30] = document.createTextNode("animals")', - '(lView[20] as Element).appendChild(lView[30])', + 'lView[31] = document.createTextNode("animals")', + '(lView[21] as Element).appendChild(lView[31])', ]), ], update: [ @@ -407,9 +407,9 @@ describe('Runtime i18n', () => { matchDebug([]), ], remove: [ - matchDebug(['remove(lView[28])']), matchDebug(['remove(lView[29])']), matchDebug(['remove(lView[30])']), + matchDebug(['remove(lView[31])']), ], }); }); @@ -428,7 +428,7 @@ describe('Runtime i18n', () => { }, undefined, nbConsts, HEADER_OFFSET + index); expect(opCodes).toEqual(matchDebug([ - 'if (mask & 0b1) { (lView[20] as Element).setAttribute(\'title\', `Hello ${lView[i-1]}!`); }', + 'if (mask & 0b1) { (lView[21] as Element).setAttribute(\'title\', `Hello ${lView[i-1]}!`); }', ])); }); @@ -444,7 +444,7 @@ describe('Runtime i18n', () => { }, undefined, nbConsts, HEADER_OFFSET + index); expect(opCodes).toEqual(matchDebug([ - 'if (mask & 0b11) { (lView[20] as Element).setAttribute(\'title\', `Hello ${lView[i-1]} and ${lView[i-2]}, again ${lView[i-1]}!`); }', + 'if (mask & 0b11) { (lView[21] as Element).setAttribute(\'title\', `Hello ${lView[i-1]} and ${lView[i-2]}, again ${lView[i-1]}!`); }', ])); }); @@ -460,8 +460,8 @@ describe('Runtime i18n', () => { }, undefined, nbConsts, HEADER_OFFSET + index); expect(opCodes).toEqual(matchDebug([ - 'if (mask & 0b1) { (lView[20] as Element).setAttribute(\'title\', `Hello ${lView[i-1]}!`); }', - 'if (mask & 0b1) { (lView[20] as Element).setAttribute(\'aria-label\', `Hello ${lView[i-1]}!`); }', + 'if (mask & 0b1) { (lView[21] as Element).setAttribute(\'title\', `Hello ${lView[i-1]}!`); }', + 'if (mask & 0b1) { (lView[21] as Element).setAttribute(\'aria-label\', `Hello ${lView[i-1]}!`); }', ])); }); }); @@ -702,8 +702,8 @@ describe('Runtime i18n', () => { ], })); expect(ti18n.update).toEqual(matchDebug([ - 'if (mask & 0b1) { (lView[50] as Text).textContent = `${lView[i-1]} `; }', - 'if (mask & 0b10) { (lView[51] as Text).textContent = `${lView[i-2]}`; }' + 'if (mask & 0b1) { (lView[51] as Text).textContent = `${lView[i-1]} `; }', + 'if (mask & 0b10) { (lView[52] as Text).textContent = `${lView[i-2]}`; }' ])); const lViewDebug = fixture.lView.debug!; expect(lViewDebug.template).toEqual('
{{?}}{{?}}!
'); @@ -714,16 +714,16 @@ describe('Runtime i18n', () => { fixture.tView, 0, fixture.lView, HEADER_OFFSET + 1, 'Hello �*2:1�World�/*2:1�!', -1); const ti18n = fixture.tView.data[HEADER_OFFSET + 1] as TI18n; expect(ti18n.create.debug).toEqual([ - 'lView[50] = document.createText("Hello ");', - 'parent.appendChild(lView[50]);', - 'lView[51] = document.createText("!");', + 'lView[51] = document.createText("Hello ");', 'parent.appendChild(lView[51]);', + 'lView[52] = document.createText("!");', + 'parent.appendChild(lView[52]);', ]); // Leave behind `Placeholder` to be picked up by `TNode` creation. // It should insert itself in front of "!" expect(fixture.tView.data[HEADER_OFFSET + 2]).toEqual(matchTNode({ type: TNodeType.Placeholder, - insertBeforeIndex: 51, + insertBeforeIndex: 52, })); }); }); diff --git a/packages/core/test/render3/instructions/lview_debug_spec.ts b/packages/core/test/render3/instructions/lview_debug_spec.ts index 56219550e3..99b7a06c79 100644 --- a/packages/core/test/render3/instructions/lview_debug_spec.ts +++ b/packages/core/test/render3/instructions/lview_debug_spec.ts @@ -242,7 +242,7 @@ describe('lView_debug', () => { cumulativeBloom: jasmine.anything(), providers: [MyChild.ɵdir], viewProviders: [], - parentInjectorIndex: 22, + parentInjectorIndex: 23, }); }); }); diff --git a/packages/core/test/render3/integration_spec.ts b/packages/core/test/render3/integration_spec.ts index fdd830d44b..56784af808 100644 --- a/packages/core/test/render3/integration_spec.ts +++ b/packages/core/test/render3/integration_spec.ts @@ -13,7 +13,7 @@ import {AttributeMarker, ɵɵadvance, ɵɵattribute, ɵɵdefineComponent, ɵɵde import {ɵɵelement, ɵɵelementEnd, ɵɵelementStart, ɵɵprojection, ɵɵprojectionDef, ɵɵtemplate, ɵɵtext} from '../../src/render3/instructions/all'; import {RenderFlags} from '../../src/render3/interfaces/definition'; import {domRendererFactory3, Renderer3, RendererFactory3} from '../../src/render3/interfaces/renderer'; -import {CONTEXT, HEADER_OFFSET} from '../../src/render3/interfaces/view'; +import {CONTEXT, HEADER_OFFSET, ID, LView} from '../../src/render3/interfaces/view'; import {ɵɵsanitizeUrl} from '../../src/sanitization/sanitization'; import {Sanitizer} from '../../src/sanitization/sanitizer'; import {SecurityContext} from '../../src/sanitization/security'; @@ -399,19 +399,17 @@ describe('element discovery', () => { const section = fixture.hostElement.querySelector('section')!; const sectionContext = getLContext(section)!; - const sectionLView = sectionContext.lView!; expect(sectionContext.nodeIndex).toEqual(HEADER_OFFSET); - expect(sectionLView.length).toBeGreaterThan(HEADER_OFFSET); + expect(sectionContext.lView!.length).toBeGreaterThan(HEADER_OFFSET); expect(sectionContext.native).toBe(section); const div = fixture.hostElement.querySelector('div')!; const divContext = getLContext(div)!; - const divLView = divContext.lView!; expect(divContext.nodeIndex).toEqual(HEADER_OFFSET + 1); - expect(divLView.length).toBeGreaterThan(HEADER_OFFSET); + expect(divContext.lView!.length).toBeGreaterThan(HEADER_OFFSET); expect(divContext.native).toBe(div); - expect(divLView).toBe(sectionLView); + expect(divContext.lView).toBe(sectionContext.lView); }); it('should cache the element context on a element was pre-emptively monkey-patched', () => { @@ -738,7 +736,7 @@ describe('element discovery', () => { const div1 = hostElm.querySelector('div:first-child')! as any; const div2 = hostElm.querySelector('div:last-child')! as any; const context = getLContext(hostElm)!; - const componentView = context.lView[context.nodeIndex]; + const componentView = context.lView![context.nodeIndex]; expect(componentView).toContain(myDir1Instance); expect(componentView).toContain(myDir2Instance); @@ -917,7 +915,7 @@ describe('element discovery', () => { const context = getLContext(child)!; expect(readPatchedData(child)).toBeTruthy(); - const componentData = context.lView[context.nodeIndex]; + const componentData = context.lView![context.nodeIndex]; const component = componentData[CONTEXT]; expect(component instanceof ChildComp).toBeTruthy(); expect(readPatchedData(component)).toBe(context.lView);