diff --git a/packages/core/src/debug/debug_node.ts b/packages/core/src/debug/debug_node.ts index 1fb53711a0bd6..6993c92af56fe 100644 --- a/packages/core/src/debug/debug_node.ts +++ b/packages/core/src/debug/debug_node.ts @@ -7,7 +7,8 @@ */ import {Injector} from '../di/injector'; -import {assertTNodeForLView} from '../render3/assert'; +import {assertLView, assertTNodeForLView} from '../render3/assert'; +import {getLViewById} from '../render3/instructions/lview_tracking'; import {CONTAINER_HEADER_OFFSET, LContainer, NATIVE} from '../render3/interfaces/container'; import {TElementNode, TNode, TNodeFlags, TNodeType} from '../render3/interfaces/node'; import {isComponentHost, isLContainer} from '../render3/interfaces/type_checks'; @@ -263,8 +264,9 @@ class DebugElement__POST_R3__ extends DebugNode__POST_R3__ implements DebugEleme get name(): string { try { const context = loadLContext(this.nativeNode)!; - const lView = context.lView; - const tData = lView[TVIEW].data; + const lView = getLViewById(context.lViewId); + ngDevMode && assertLView(lView); + const tData = lView![TVIEW].data; const tNode = tData[context.nodeIndex] as TNode; return tNode.value!; } catch (e) { @@ -290,8 +292,9 @@ class DebugElement__POST_R3__ extends DebugNode__POST_R3__ implements DebugEleme return {}; } - const lView = context.lView; - const tData = lView[TVIEW].data; + const lView = getLViewById(context.lViewId); + ngDevMode && assertLView(lView); + const tData = lView![TVIEW].data; const tNode = tData[context.nodeIndex] as TNode; const properties: {[key: string]: string} = {}; @@ -299,7 +302,7 @@ class DebugElement__POST_R3__ extends DebugNode__POST_R3__ implements DebugEleme copyDomProperties(this.nativeElement, properties); // Collect properties from the bindings. This is needed for animation renderer which has // synthetic properties which don't get reflected into the DOM. - collectPropertyBindings(properties, tNode, lView, tData); + collectPropertyBindings(properties, tNode, lView!, tData); return properties; } @@ -316,8 +319,9 @@ class DebugElement__POST_R3__ extends DebugNode__POST_R3__ implements DebugEleme return {}; } - const lView = context.lView; - const tNodeAttrs = (lView[TVIEW].data[context.nodeIndex] as TNode).attrs; + const lView = getLViewById(context.lViewId); + ngDevMode && assertLView(lView); + const tNodeAttrs = (lView![TVIEW].data[context.nodeIndex] as TNode).attrs; const lowercaseTNodeAttrs: string[] = []; // For debug nodes we take the element's attribute directly from the DOM since it allows us @@ -503,9 +507,11 @@ function _queryAllR3( matches: DebugElement[]|DebugNode[], elementsOnly: boolean) { const context = loadLContext(parentElement.nativeNode, false); if (context !== null) { - const parentTNode = context.lView[TVIEW].data[context.nodeIndex] as TNode; + const lView = getLViewById(context.lViewId); + ngDevMode && assertLView(lView); + 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 43f4207eb7ad2..54712dc34e676 100644 --- a/packages/core/src/render3/context_discovery.ts +++ b/packages/core/src/render3/context_discovery.ts @@ -8,12 +8,14 @@ import '../util/ng_dev_mode'; import {assertDefined, assertDomNode} from '../util/assert'; - import {EMPTY_ARRAY} from '../util/empty'; + +import {assertLView} from './assert'; +import {getLViewById} from './instructions/lview_tracking'; import {LContext} from './interfaces/context'; import {TNode, TNodeFlags} from './interfaces/node'; import {RElement, RNode} from './interfaces/renderer_dom'; -import {CONTEXT, HEADER_OFFSET, HOST, LView, TVIEW} from './interfaces/view'; +import {CONTEXT, HEADER_OFFSET, HOST, ID, LView, TVIEW} from './interfaces/view'; import {getComponentLViewByIndex, unwrapRNode} from './util/view_utils'; @@ -109,7 +111,8 @@ export function getLContext(target: any): LContext|null { if (Array.isArray(parentContext)) { lView = parentContext as LView; } else { - lView = parentContext.lView; + lView = getLViewById(parentContext.lViewId); + ngDevMode && assertLView(lView); } // the edge of the app was also reached here through another means @@ -137,7 +140,7 @@ export function getLContext(target: any): LContext|null { */ function createLContext(lView: LView, nodeIndex: number, native: RNode): LContext { return { - lView, + lViewId: lView[ID], nodeIndex, native, component: undefined, @@ -153,19 +156,21 @@ function createLContext(lView: LView, nodeIndex: number, native: RNode): LContex * @returns The component's view */ export function getComponentViewByInstance(componentInstance: {}): LView { - let lView = readPatchedData(componentInstance); + let patchedData = readPatchedData(componentInstance); let view: LView; - if (Array.isArray(lView)) { - const nodeIndex = findViaComponent(lView, componentInstance); - view = getComponentLViewByIndex(nodeIndex, lView); - const context = createLContext(lView, nodeIndex, view[HOST] as RElement); + if (Array.isArray(patchedData)) { + const nodeIndex = findViaComponent(patchedData, componentInstance); + view = getComponentLViewByIndex(nodeIndex, patchedData); + const context = createLContext(patchedData, nodeIndex, view[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 any as LContext; + const lView = getLViewById(context.lViewId)!; + ngDevMode && assertLView(lView); + view = getComponentLViewByIndex(context.nodeIndex, lView); } return view; } @@ -181,7 +186,8 @@ 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). + target[MONKEY_PATCH_KEY_NAME] = Array.isArray(data) ? data[ID] : data; } /** @@ -190,13 +196,17 @@ 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]; + if (typeof data === 'number') { + return getLViewById(data); + } + return 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 Array.isArray(value) ? value : getLViewById(value.lViewId); } return null; } diff --git a/packages/core/src/render3/instructions/lview_debug.ts b/packages/core/src/render3/instructions/lview_debug.ts index 1fd21dfbfdbef..7a7551f27df69 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/lview_tracking.ts b/packages/core/src/render3/instructions/lview_tracking.ts new file mode 100644 index 0000000000000..290b7ebddc817 --- /dev/null +++ b/packages/core/src/render3/instructions/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 '../interfaces/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 trackLView(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 stopTrackingLView(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/instructions/shared.ts b/packages/core/src/render3/instructions/shared.ts index 6bf02d590ac38..d980ce647cc57 100644 --- a/packages/core/src/render3/instructions/shared.ts +++ b/packages/core/src/render3/instructions/shared.ts @@ -33,7 +33,7 @@ import {isProceduralRenderer, Renderer3, RendererFactory3} from '../interfaces/r 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'; @@ -47,6 +47,7 @@ import {getComponentLViewByIndex, getNativeByIndex, getNativeByTNode, isCreation import {selectIndexInternal} from './advance'; import {attachLContainerDebug, attachLViewDebug, cloneToLViewFromTViewBlueprint, cloneToTViewData, LCleanup, LViewBlueprint, MatchesArray, TCleanup, TNodeDebug, TNodeInitialInputs, TNodeLocalNames, TViewComponents, TViewConstructor} from './lview_debug'; +import {trackLView} from './lview_tracking'; @@ -142,6 +143,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] = trackLView(lView); ngDevMode && assertEqual( tView.type == TViewType.Embedded ? parentLView !== null : true, true, diff --git a/packages/core/src/render3/interfaces/context.ts b/packages/core/src/render3/interfaces/context.ts index 6f78e268918d5..8bb31bfecbf63 100644 --- a/packages/core/src/render3/interfaces/context.ts +++ b/packages/core/src/render3/interfaces/context.ts @@ -8,7 +8,6 @@ import {RNode} from './renderer_dom'; -import {LView} from './view'; /** @@ -23,9 +22,9 @@ import {LView} from './view'; */ export interface LContext { /** - * The component's parent view data. + * ID of the component's parent view data. */ - lView: LView; + lViewId: number; /** * The index instance of the node. diff --git a/packages/core/src/render3/interfaces/view.ts b/packages/core/src/render3/interfaces/view.ts index 64e3b020529c4..279ca3267c0b5 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 801957fe21dc5..f10ae8042c1fd 100644 --- a/packages/core/src/render3/node_manipulation.ts +++ b/packages/core/src/render3/node_manipulation.ts @@ -16,6 +16,7 @@ import {escapeCommentText} from '../util/dom'; import {assertLContainer, assertLView, assertParentView, assertProjectionSlots, assertTNodeForLView} from './assert'; import {attachPatchData} from './context_discovery'; import {icuContainerIterate} from './i18n/i18n_tree_shaking'; +import {stopTrackingLView} from './instructions/lview_tracking'; 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'; @@ -395,6 +396,7 @@ export function destroyLView(tView: TView, lView: LView) { } destroyViewTree(lView); + stopTrackingLView(lView); } } diff --git a/packages/core/src/render3/util/discovery_utils.ts b/packages/core/src/render3/util/discovery_utils.ts index 1966517cd487b..1e4fa5af4e98e 100644 --- a/packages/core/src/render3/util/discovery_utils.ts +++ b/packages/core/src/render3/util/discovery_utils.ts @@ -12,6 +12,7 @@ import {assertLView} from '../assert'; import {discoverLocalRefs, getComponentAtNodeIndex, getDirectivesAtNodeIndex, getLContext} from '../context_discovery'; import {NodeInjector} from '../di'; import {buildDebugNode} from '../instructions/lview_debug'; +import {getLViewById} from '../instructions/lview_tracking'; import {LContext} from '../interfaces/context'; import {DirectiveDef} from '../interfaces/definition'; import {TElementNode, TNode, TNodeProviderIndexes} from '../interfaces/node'; @@ -54,7 +55,9 @@ export function getComponent(element: Element): T|null { if (context === null) return null; if (context.component === undefined) { - context.component = getComponentAtNodeIndex(context.nodeIndex, context.lView); + const lView = getLViewById(context.lViewId)!; + ngDevMode && assertLView(lView); + context.component = getComponentAtNodeIndex(context.nodeIndex, lView); } return context.component as T; @@ -76,7 +79,8 @@ export function getComponent(element: Element): T|null { export function getContext(element: Element): T|null { assertDomElement(element); const context = loadLContext(element, false); - return context === null ? null : context.lView[CONTEXT] as T; + const lView = context === null ? null : getLViewById(context.lViewId)!; + return lView === null ? null : lView[CONTEXT] as T; } /** @@ -98,7 +102,8 @@ export function getOwningComponent(elementOrDir: Element|{}): T|null { const context = loadLContext(elementOrDir, false); if (context === null) return null; - let lView = context.lView; + let lView = getLViewById(context.lViewId)!; + ngDevMode && assertLView(lView); let parent: LView|null; ngDevMode && assertLView(lView); while (lView[TVIEW].type === TViewType.Embedded && (parent = getLViewParent(lView)!)) { @@ -136,8 +141,10 @@ export function getInjector(elementOrDir: Element|{}): Injector { const context = loadLContext(elementOrDir, false); if (context === null) return Injector.NULL; - const tNode = context.lView[TVIEW].data[context.nodeIndex] as TElementNode; - return new NodeInjector(tNode, context.lView); + const lView = getLViewById(context.lViewId)!; + ngDevMode && assertLView(lView); + const tNode = lView[TVIEW].data[context.nodeIndex] as TElementNode; + return new NodeInjector(tNode, lView); } /** @@ -148,7 +155,8 @@ export function getInjector(elementOrDir: Element|{}): Injector { export function getInjectionTokens(element: Element): any[] { const context = loadLContext(element, false); if (context === null) return []; - const lView = context.lView; + const lView = getLViewById(context.lViewId)!; + ngDevMode && assertLView(lView); const tView = lView[TVIEW]; const tNode = tView.data[context.nodeIndex] as TNode; const providerTokens: any[] = []; @@ -195,7 +203,9 @@ export function getDirectives(element: Element): {}[] { const context = loadLContext(element)!; if (context.directives === undefined) { - context.directives = getDirectivesAtNodeIndex(context.nodeIndex, context.lView, false); + const lView = getLViewById(context.lViewId)!; + ngDevMode && assertLView(lView); + context.directives = getDirectivesAtNodeIndex(context.nodeIndex, lView, false); } // The `directives` in this case are a named array called `LComponentView`. Clone the @@ -232,7 +242,9 @@ export function getLocalRefs(target: {}): {[key: string]: any} { if (context === null) return {}; if (context.localRefs === undefined) { - context.localRefs = discoverLocalRefs(context.lView, context.nodeIndex); + const lView = getLViewById(context.lViewId)!; + ngDevMode && assertLView(lView); + context.localRefs = discoverLocalRefs(lView, context.nodeIndex); } return context.localRefs || {}; @@ -325,9 +337,9 @@ export interface Listener { export function getListeners(element: Element): Listener[] { assertDomElement(element); const lContext = loadLContext(element, false); - if (lContext === null) return []; + const lView = lContext ? getLViewById(lContext.lViewId) : null; + if (lView === null) return []; - const lView = lContext.lView; const tView = lView[TVIEW]; const lCleanup = lView[CLEANUP]; const tCleanup = tView.cleanup; @@ -380,7 +392,8 @@ export function getDebugNode(element: Element): DebugNode|null { let debugNode: DebugNode|null = null; const lContext = loadLContextFromNode(element); - const lView = lContext.lView; + const lView = getLViewById(lContext.lViewId)!; + ngDevMode && assertLView(lView); const nodeIndex = lContext.nodeIndex; if (nodeIndex !== -1) { const valueInLView = lView[nodeIndex]; @@ -407,7 +420,8 @@ export function getDebugNode(element: Element): DebugNode|null { export function getComponentLView(target: any): LView { const lContext = loadLContext(target); const nodeIndx = lContext.nodeIndex; - const lView = lContext.lView; + const lView = getLViewById(lContext.lViewId)!; + ngDevMode && assertLView(lView); const componentLView = lView[nodeIndx]; ngDevMode && assertLView(componentLView); return componentLView; diff --git a/packages/core/test/acceptance/debug_spec.ts b/packages/core/test/acceptance/debug_spec.ts index 436d134d8bac8..f3962faecddba 100644 --- a/packages/core/test/acceptance/debug_spec.ts +++ b/packages/core/test/acceptance/debug_spec.ts @@ -9,6 +9,7 @@ import {Component} from '@angular/core'; import {getLContext} from '@angular/core/src/render3/context_discovery'; import {LViewDebug} from '@angular/core/src/render3/instructions/lview_debug'; +import {getLViewById} from '@angular/core/src/render3/instructions/lview_tracking'; import {TNodeType} from '@angular/core/src/render3/interfaces/node'; import {HEADER_OFFSET} from '@angular/core/src/render3/interfaces/view'; import {TestBed} from '@angular/core/testing'; @@ -27,7 +28,7 @@ onlyInIvy('Ivy specific').describe('Debug Representation', () => { const fixture = TestBed.createComponent(MyComponent); fixture.detectChanges(); - const hostView = getLContext(fixture.componentInstance)!.lView.debug!; + const hostView = getLViewById(getLContext(fixture.componentInstance)!.lViewId)!.debug!; expect(hostView.hostHTML).toEqual(null); const myCompView = hostView.childViews[0] as LViewDebug; expect(myCompView.hostHTML).toContain('
Hello World
'); @@ -47,7 +48,7 @@ onlyInIvy('Ivy specific').describe('Debug Representation', () => { const fixture = TestBed.createComponent(MyComponent); fixture.detectChanges(); - const hostView = getLContext(fixture.componentInstance)!.lView.debug!; + const hostView = getLViewById(getLContext(fixture.componentInstance)!.lViewId)!.debug!; const myComponentView = hostView.childViews[0] as LViewDebug; expect(myComponentView.decls).toEqual({ start: HEADER_OFFSET, diff --git a/packages/core/test/acceptance/i18n_spec.ts b/packages/core/test/acceptance/i18n_spec.ts index cc7fb156ce2c0..d430787feb007 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 ef07bb26caf73..f5691ff655bbd 100644 --- a/packages/core/test/acceptance/ngdevmode_debug_spec.ts +++ b/packages/core/test/acceptance/ngdevmode_debug_spec.ts @@ -8,7 +8,7 @@ import {CommonModule} from '@angular/common'; import {Component} from '@angular/core'; -import {LView} from '@angular/core/src/render3/interfaces/view'; +import {getLViewById} from '@angular/core/src/render3/instructions/lview_tracking'; import {getComponentLView, loadLContext} from '@angular/core/src/render3/util/discovery_utils'; import {createNamedArrayType} from '@angular/core/src/util/named_array_type'; import {TestBed} from '@angular/core/testing'; @@ -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 = loadLContext(fixture.nativeElement).lView; + const rootLView = getLViewById(loadLContext(fixture.nativeElement).lViewId)!; 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 = loadLContext(li).lView; + const embeddedLView = getLViewById(loadLContext(li).lViewId)!; 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 dd29afd07a730..7aa41178e5e27 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" }, @@ -353,6 +356,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 809a19e62f25a..68b5ede15ca7c 100644 --- a/packages/core/test/bundling/forms_reactive/bundle.golden_symbols.json +++ b/packages/core/test/bundling/forms_reactive/bundle.golden_symbols.json @@ -542,6 +542,9 @@ { "name": "THROW_IF_NOT_FOUND" }, + { + "name": "TRACKED_LVIEWS" + }, { "name": "TRANSITION_ID" }, @@ -968,6 +971,9 @@ { "name": "getLView" }, + { + "name": "getLViewById" + }, { "name": "getLViewParent" }, @@ -1562,6 +1568,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 65ee1ee746dc3..5efe851e897e1 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 b44c4f410be69..441b97afb824c 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 0baae6f79ec9d..9fb126502c632 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 @@ -530,6 +530,9 @@ { "name": "THROW_IF_NOT_FOUND" }, + { + "name": "TRACKED_LVIEWS" + }, { "name": "TRANSITION_ID" }, @@ -932,6 +935,9 @@ { "name": "getLView" }, + { + "name": "getLViewById" + }, { "name": "getLViewParent" }, 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 fb67ed4908cf8..3cbb8b2a97910 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 fb03c5c0c50c8..ac81bc9e2342f 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 9f68ceb496c71..86ccb607edafb 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" }, @@ -236,6 +239,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 643193002d1f8..bc6c94eb0faa7 100644 --- a/packages/core/test/bundling/router/bundle.golden_symbols.json +++ b/packages/core/test/bundling/router/bundle.golden_symbols.json @@ -776,6 +776,9 @@ { "name": "TQuery_" }, + { + "name": "TRACKED_LVIEWS" + }, { "name": "TRANSITION_ID" }, @@ -1331,6 +1334,9 @@ { "name": "getLView" }, + { + "name": "getLViewById" + }, { "name": "getLViewParent" }, @@ -2000,6 +2006,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 057cd33ab4948..8063fe00df34b 100644 --- a/packages/core/test/bundling/todo/bundle.golden_symbols.json +++ b/packages/core/test/bundling/todo/bundle.golden_symbols.json @@ -110,6 +110,9 @@ { "name": "SkipSelf" }, + { + "name": "TRACKED_LVIEWS" + }, { "name": "TemplateRef" }, @@ -380,6 +383,9 @@ { "name": "getLView" }, + { + "name": "getLViewById" + }, { "name": "getLViewParent" }, @@ -740,6 +746,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 c9efb5b83b079..de6dc37dbe138 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 b3506d457bcf4..be682aa0f2ab2 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 0025af70e594b..b20a3532b59aa 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 9e55a6df77a92..5979f08c69d66 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 bd8b360f868f8..2c1860b4a7278 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 7fb449cd96699..98781a6407567 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 2921d9a2e71e0..e0b4b4f5828f3 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 339453a1b0134..41826fbc5e5b8 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 26c2be6c2ab64..70666f4257a83 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])', + 'remove(lView[26])', + 'remove(lView[28])', ]), matchDebug([ - 'remove(lView[28])', 'remove(lView[29])', + 'remove(lView[30])', ]), matchDebug([ - 'remove(lView[31])', '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 56219550e37d8..99b7a06c7902c 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 fdd830d44b842..7a0d5a67e322b 100644 --- a/packages/core/test/render3/integration_spec.ts +++ b/packages/core/test/render3/integration_spec.ts @@ -6,6 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ +import {getLViewById} from '@angular/core/src/render3/instructions/lview_tracking'; import {RElement} from '@angular/core/src/render3/interfaces/renderer_dom'; import {RendererType2} from '../../src/render/api_flags'; import {getLContext, readPatchedData} from '../../src/render3/context_discovery'; @@ -13,7 +14,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,14 +400,14 @@ describe('element discovery', () => { const section = fixture.hostElement.querySelector('section')!; const sectionContext = getLContext(section)!; - const sectionLView = sectionContext.lView!; + const sectionLView = getLViewById(sectionContext.lViewId)!; expect(sectionContext.nodeIndex).toEqual(HEADER_OFFSET); expect(sectionLView.length).toBeGreaterThan(HEADER_OFFSET); expect(sectionContext.native).toBe(section); const div = fixture.hostElement.querySelector('div')!; const divContext = getLContext(div)!; - const divLView = divContext.lView!; + const divLView = getLViewById(divContext.lViewId)!; expect(divContext.nodeIndex).toEqual(HEADER_OFFSET + 1); expect(divLView.length).toBeGreaterThan(HEADER_OFFSET); expect(divContext.native).toBe(div); @@ -443,7 +444,7 @@ describe('element discovery', () => { expect(Array.isArray(result2)).toBeFalsy(); expect(result2).toBe(context); - expect(result2.lView).toBe(result1); + expect(result2.lViewId).toBe((result1 as LView)[ID]); }); it('should cache the element context on an intermediate element that isn\'t pre-emptively monkey-patched', @@ -608,9 +609,9 @@ describe('element discovery', () => { const shadowContext = getLContext(header)!; const projectedContext = getLContext(p)!; - const parentComponentData = parentContext.lView; - const shadowComponentData = shadowContext.lView; - const projectedComponentData = projectedContext.lView; + const parentComponentData = getLViewById(parentContext.lViewId); + const shadowComponentData = getLViewById(shadowContext.lViewId); + const projectedComponentData = getLViewById(projectedContext.lViewId); expect(projectedComponentData).toBe(parentComponentData); expect(shadowComponentData).not.toBe(parentComponentData); @@ -682,12 +683,12 @@ describe('element discovery', () => { expect(hostLView).toBe(componentLView); const context1 = getLContext(hostElm)!; - expect(context1.lView).toBe(hostLView); + expect(getLViewById(context1.lViewId)).toBe(hostLView); expect(context1.native).toEqual(hostElm); const context2 = getLContext(component)!; expect(context2).toBe(context1); - expect(context2.lView).toBe(hostLView); + expect(getLViewById(context2.lViewId)).toBe(hostLView); expect(context2.native).toEqual(hostElm); }); @@ -738,7 +739,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 = getLViewById(context.lViewId)![context.nodeIndex]; expect(componentView).toContain(myDir1Instance); expect(componentView).toContain(myDir2Instance); @@ -752,9 +753,9 @@ describe('element discovery', () => { const d2Context = getLContext(myDir2Instance)!; const d3Context = getLContext(myDir3Instance)!; - expect(d1Context.lView).toEqual(componentView); - expect(d2Context.lView).toEqual(componentView); - expect(d3Context.lView).toEqual(componentView); + expect(getLViewById(d1Context.lViewId)).toEqual(componentView); + expect(getLViewById(d2Context.lViewId)).toEqual(componentView); + expect(getLViewById(d3Context.lViewId)).toEqual(componentView); expect(readPatchedData(myDir1Instance)).toBe(d1Context); expect(readPatchedData(myDir2Instance)).toBe(d2Context); @@ -917,16 +918,16 @@ describe('element discovery', () => { const context = getLContext(child)!; expect(readPatchedData(child)).toBeTruthy(); - const componentData = context.lView[context.nodeIndex]; + const componentData = getLViewById(context.lViewId)![context.nodeIndex]; const component = componentData[CONTEXT]; expect(component instanceof ChildComp).toBeTruthy(); - expect(readPatchedData(component)).toBe(context.lView); + expect(readPatchedData(component)).toBe(getLViewById(context.lViewId)); const componentContext = getLContext(component)!; expect(readPatchedData(component)).toBe(componentContext); expect(componentContext.nodeIndex).toEqual(context.nodeIndex); expect(componentContext.native).toEqual(context.native); - expect(componentContext.lView).toEqual(context.lView); + expect(getLViewById(componentContext.lViewId)).toEqual(getLViewById(context.lViewId)); }); });