refactor(core): Consistent use of `HEADER_OFFSET` (in `ɵɵ*` instructions only) (#39233)

IMPORTANT: `HEADER_OFFSET` should only be refereed to the in the `ɵɵ*` instructions to translate
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.

PR Close #39233
This commit is contained in:
Misko Hevery 2020-10-13 22:00:43 -07:00 committed by Alex Rickabaugh
parent bc5005e35b
commit 54303688fa
34 changed files with 308 additions and 319 deletions

View File

@ -21,7 +21,7 @@
"master": {
"uncompressed": {
"runtime-es2015": 1485,
"main-es2015": 147242,
"main-es2015": 146698,
"polyfills-es2015": 36571
}
}

View File

@ -12,7 +12,6 @@ import {Type} from '../core';
import {Injector} from '../di/injector';
import {Sanitizer} from '../sanitization/sanitizer';
import {assertDefined, assertIndexInRange} from '../util/assert';
import {assertComponentType} from './assert';
import {getComponentDef} from './definition';
import {diPublicInInjector, getOrCreateNodeInjectorForNode} from './di';
@ -173,12 +172,13 @@ export function createRootComponentView(
rNode: RElement|null, def: ComponentDef<any>, rootView: LView,
rendererFactory: RendererFactory3, hostRenderer: Renderer3, sanitizer?: Sanitizer|null): LView {
const tView = rootView[TVIEW];
ngDevMode && assertIndexInRange(rootView, 0 + HEADER_OFFSET);
rootView[0 + HEADER_OFFSET] = rNode;
const index = HEADER_OFFSET;
ngDevMode && assertIndexInRange(rootView, index);
rootView[index] = rNode;
// '#host' is added here as we don't know the real host DOM name (we don't want to read it) and at
// the same time we want to communicate the the debug `TNode` that this is a special `TNode`
// representing a host element.
const tNode = getOrCreateTNode(tView, 0, TNodeType.Element, '#host', null);
const tNode: TElementNode = getOrCreateTNode(tView, index, TNodeType.Element, '#host', null);
const mergedAttrs = tNode.mergedAttrs = def.hostAttrs;
if (mergedAttrs !== null) {
computeStaticStyling(tNode, mergedAttrs, true);
@ -196,7 +196,7 @@ export function createRootComponentView(
const viewRenderer = rendererFactory.createRenderer(rNode, def);
const componentView = createLView(
rootView, getOrCreateTComponentView(def), null,
def.onPush ? LViewFlags.Dirty : LViewFlags.CheckAlways, rootView[HEADER_OFFSET], tNode,
def.onPush ? LViewFlags.Dirty : LViewFlags.CheckAlways, rootView[index], tNode,
rendererFactory, viewRenderer, sanitizer || null, null);
if (tView.firstCreatePass) {
@ -208,7 +208,7 @@ export function createRootComponentView(
addToViewTree(rootView, componentView);
// Store component view at node index, with node as the HOST
return rootView[HEADER_OFFSET] = componentView;
return rootView[index] = componentView;
}
/**
@ -237,8 +237,7 @@ export function createRootComponent<T>(
ngDevMode && assertDefined(rootTNode, 'tNode should have been already created');
if (tView.firstCreatePass &&
(componentDef.hostBindings !== null || componentDef.hostAttrs !== null)) {
const elementIndex = rootTNode.index - HEADER_OFFSET;
setSelectedIndex(elementIndex);
setSelectedIndex(rootTNode.index);
const rootTView = rootLView[TVIEW];
addHostBindingsToExpandoInstructions(rootTView, componentDef);

View File

@ -27,7 +27,7 @@ import {createLView, createTView, locateHostElement, renderView} from './instruc
import {ComponentDef} from './interfaces/definition';
import {TContainerNode, TElementContainerNode, TElementNode, TNode} from './interfaces/node';
import {domRendererFactory3, RendererFactory3, RNode} from './interfaces/renderer';
import {LView, LViewFlags, TViewType} from './interfaces/view';
import {HEADER_OFFSET, LView, LViewFlags, TViewType} from './interfaces/view';
import {MATH_ML_NAMESPACE, SVG_NAMESPACE} from './namespaces';
import {createElementNode, writeDirectClass} from './node_manipulation';
import {extractAttrsAndClassesFromSelector, stringifyCSSSelectorList} from './node_selector_matcher';
@ -192,7 +192,7 @@ export class ComponentFactory<T> extends viewEngine_ComponentFactory<T> {
}
}
tElementNode = getTNode(rootTView, 0) as TElementNode;
tElementNode = getTNode(rootTView, HEADER_OFFSET) as TElementNode;
if (projectableNodes !== undefined) {
const projection: (TNode|RNode[]|null)[] = tElementNode.projection = [];

View File

@ -14,7 +14,7 @@ import {LContext, MONKEY_PATCH_KEY_NAME} from './interfaces/context';
import {TNode, TNodeFlags} from './interfaces/node';
import {RElement, RNode} from './interfaces/renderer';
import {CONTEXT, HEADER_OFFSET, HOST, LView, TVIEW} from './interfaces/view';
import {getComponentLViewByIndex, getNativeByTNodeOrNull, readPatchedData, unwrapRNode} from './util/view_utils';
import {getComponentLViewByIndex, readPatchedData, unwrapRNode} from './util/view_utils';

View File

@ -65,7 +65,7 @@ export function setMaskBit(hasChange: boolean) {
export function applyI18n(tView: TView, lView: LView, index: number) {
if (changeMaskCounter > 0) {
ngDevMode && assertDefined(tView, `tView should be defined`);
const tI18n = tView.data[index + HEADER_OFFSET] as TI18n | I18nUpdateOpCodes;
const tI18n = tView.data[index] as TI18n | I18nUpdateOpCodes;
// When `index` points to an `ɵɵi18nAttributes` then we have an array otherwise `TI18n`
const updateOpCodes: I18nUpdateOpCodes =
Array.isArray(tI18n) ? tI18n as I18nUpdateOpCodes : (tI18n as TI18n).update;
@ -195,8 +195,8 @@ export function applyMutableOpCodes(
// This code is used for ICU expressions only, since we don't support
// directives/components in ICUs, we don't need to worry about inputs here
setElementAttribute(
renderer, getNativeByIndex(elementNodeIndex - HEADER_OFFSET, lView) as RElement, null,
null, attrName, attrValue, null);
renderer, getNativeByIndex(elementNodeIndex, lView) as RElement, null, null, attrName,
attrValue, null);
break;
default:
throw new Error(`Unable to determine the type of mutate operation for "${opCode}"`);
@ -281,7 +281,7 @@ export function applyUpdateOpCodes(
const propName = updateOpCodes[++j] as string;
const sanitizeFn = updateOpCodes[++j] as SanitizerFn | null;
const tNodeOrTagName = tView.data[nodeIndex] as TNode | string;
ngDevMode && assertDefined(tNodeOrTagName, 'Expecting TNode or string');
ngDevMode && assertDefined(tNodeOrTagName, 'Experting TNode or string');
if (typeof tNodeOrTagName === 'string') {
// IF we don't have a `TNode`, then we are an element in ICU (as ICU content does
// not have TNode), in which case we know that there are no directives, and hence

View File

@ -132,7 +132,7 @@ export function i18nStartFirstCreatePass(
}
}
tView.data[index + HEADER_OFFSET] = <TI18n>{
tView.data[index] = <TI18n>{
create: createOpCodes,
update: updateOpCodes,
};
@ -247,11 +247,11 @@ export function i18nAttributesFirstPass(
// Even indexes are text (including bindings)
const hasBinding = !!value.match(BINDING_REGEXP);
if (hasBinding) {
if (tView.firstCreatePass && tView.data[index + HEADER_OFFSET] === null) {
if (tView.firstCreatePass && tView.data[index] === null) {
generateBindingUpdateOpCodes(updateOpCodes, value, previousElementIndex, attrName);
}
} else {
const tNode = getTNode(tView, previousElementIndex - HEADER_OFFSET);
const tNode = getTNode(tView, previousElementIndex);
// Set attributes for Elements only, for other types (like ElementContainer),
// only set inputs below
if (tNode.type & TNodeType.AnyRNode) {
@ -262,9 +262,7 @@ export function i18nAttributesFirstPass(
if (dataValue) {
setInputsForProperty(tView, lView, dataValue, attrName, value);
if (ngDevMode) {
const element =
getNativeByIndex(previousElementIndex - HEADER_OFFSET, lView) as RElement |
RComment;
const element = getNativeByIndex(previousElementIndex, lView) as RElement | RComment;
setNgReflectProperties(lView, element, tNode.type, dataValue, value);
}
}
@ -273,8 +271,8 @@ export function i18nAttributesFirstPass(
}
}
if (tView.firstCreatePass && tView.data[index + HEADER_OFFSET] === null) {
tView.data[index + HEADER_OFFSET] = updateOpCodes;
if (tView.firstCreatePass && tView.data[index] === null) {
tView.data[index] = updateOpCodes;
}
}
@ -329,25 +327,6 @@ export function generateBindingUpdateOpCodes(
return mask;
}
function getBindingMask(icuExpression: IcuExpression, mask = 0): number {
mask = mask | toMaskBit(icuExpression.mainBinding);
let match;
for (let i = 0; i < icuExpression.values.length; i++) {
const valueArr = icuExpression.values[i];
for (let j = 0; j < valueArr.length; j++) {
const value = valueArr[j];
if (typeof value === 'string') {
while (match = BINDING_REGEXP.exec(value)) {
mask = mask | toMaskBit(parseInt(match[1], 10));
}
} else {
mask = getBindingMask(value as IcuExpression, mask);
}
}
}
return mask;
}
/**
* Convert binding index to mask bit.

View File

@ -8,7 +8,7 @@
import {assertGreaterThan} from '../../util/assert';
import {assertIndexInDeclRange} from '../assert';
import {executeCheckHooks, executeInitAndCheckHooks} from '../hooks';
import {FLAGS, HEADER_OFFSET, InitPhaseState, LView, LViewFlags, TView} from '../interfaces/view';
import {FLAGS, InitPhaseState, LView, LViewFlags, TView} from '../interfaces/view';
import {getLView, getSelectedIndex, getTView, isInCheckNoChangesMode, setSelectedIndex} from '../state';
@ -42,7 +42,7 @@ export function ɵɵadvance(delta: number): void {
export function selectIndexInternal(
tView: TView, lView: LView, index: number, checkNoChangesMode: boolean) {
ngDevMode && assertIndexInDeclRange(lView, index + HEADER_OFFSET);
ngDevMode && assertIndexInDeclRange(lView, index);
// Flush the initial hooks for elements in the view that have been added up to this point.
// PERF WARNING: do NOT extract this to a separate function without running benchmarks

View File

@ -83,7 +83,8 @@ export function ɵɵelementStart(
const renderer = lView[RENDERER];
const native = lView[adjustedIndex] = createElementNode(renderer, name, getNamespace());
const tNode = tView.firstCreatePass ?
elementStartFirstCreatePass(index, tView, lView, native, name, attrsIndex, localRefsIndex) :
elementStartFirstCreatePass(
adjustedIndex, tView, lView, native, name, attrsIndex, localRefsIndex) :
tView.data[adjustedIndex] as TElementNode;
setCurrentTNode(tNode, true);

View File

@ -72,7 +72,8 @@ export function ɵɵelementContainerStart(
'element containers should be created before any bindings');
const tNode = tView.firstCreatePass ?
elementContainerStartFirstCreatePass(index, tView, lView, attrsIndex, localRefsIndex) :
elementContainerStartFirstCreatePass(
adjustedIndex, tView, lView, attrsIndex, localRefsIndex) :
tView.data[adjustedIndex] as TElementContainerNode;
setCurrentTNode(tNode, true);

View File

@ -49,15 +49,16 @@ export function ɵɵi18nStart(
index: number, messageIndex: number, subTemplateIndex: number = -1): void {
const tView = getTView();
const lView = getLView();
const adjustedIndex = HEADER_OFFSET + index;
ngDevMode && assertDefined(tView, `tView should be defined`);
const message = getConstant<string>(tView.consts, messageIndex)!;
const parentTNode = getCurrentParentTNode() as TElementNode | null;
if (tView.firstCreatePass) {
i18nStartFirstCreatePass(
tView, parentTNode === null ? 0 : parentTNode.index, lView, index, message,
tView, parentTNode === null ? 0 : parentTNode.index, lView, adjustedIndex, message,
subTemplateIndex);
}
const tI18n = tView.data[HEADER_OFFSET + index] as TI18n;
const tI18n = tView.data[adjustedIndex] as TI18n;
const sameViewParentTNode = parentTNode === lView[T_HOST] ? null : parentTNode;
const parentRNode = getClosestRElement(tView, sameViewParentTNode, lView);
// If `parentTNode` is an `ElementContainer` than it has `<!--ng-container--->`.
@ -125,7 +126,7 @@ export function ɵɵi18nAttributes(index: number, attrsIndex: number): void {
const tView = getTView();
ngDevMode && assertDefined(tView, `tView should be defined`);
const attrs = getConstant<string[]>(tView.consts, attrsIndex)!;
i18nAttributesFirstPass(lView, tView, index, attrs);
i18nAttributesFirstPass(lView, tView, index + HEADER_OFFSET, attrs);
}
@ -154,7 +155,7 @@ export function ɵɵi18nExp<T>(value: T): typeof ɵɵi18nExp {
* @codeGenApi
*/
export function ɵɵi18nApply(index: number) {
applyI18n(getTView(), getLView(), index);
applyI18n(getTView(), getLView(), index + HEADER_OFFSET);
}
/**

View File

@ -676,18 +676,3 @@ export class LContainerDebug implements ILContainerDebug {
return toDebug(this._raw_lContainer[NEXT]);
}
}
/**
* Return an `LView` value if found.
*
* @param value `LView` if any
*/
export function readLViewValue(value: any): LView|null {
while (Array.isArray(value)) {
// This check is not quite right, as it does not take into account `StylingContext`
// This is why it is in debug, not in util.ts
if (value.length >= HEADER_OFFSET - 1) return value as LView;
value = value[HOST];
}
return null;
}

View File

@ -8,7 +8,7 @@
import {newArray} from '../../util/array_utils';
import {TAttributes, TElementNode, TNode, TNodeFlags, TNodeType} from '../interfaces/node';
import {ProjectionSlots} from '../interfaces/projection';
import {DECLARATION_COMPONENT_VIEW, T_HOST} from '../interfaces/view';
import {DECLARATION_COMPONENT_VIEW, HEADER_OFFSET, T_HOST} from '../interfaces/view';
import {applyProjection} from '../node_manipulation';
import {getProjectAsAttrValue, isNodeMatchingSelectorList, isSelectorInSelectorList} from '../node_selector_matcher';
import {getLView, getTView, setCurrentTNodeAsNotParent} from '../state';
@ -120,7 +120,7 @@ export function ɵɵprojection(
const lView = getLView();
const tView = getTView();
const tProjectionNode =
getOrCreateTNode(tView, nodeIndex, TNodeType.Projection, null, attrs || null);
getOrCreateTNode(tView, HEADER_OFFSET + nodeIndex, TNodeType.Projection, null, attrs || null);
// We can't use viewData[HOST_NODE] because projection nodes can be nested in embedded views.
if (tProjectionNode.projection === null) tProjectionNode.projection = selectorIndex;

View File

@ -12,7 +12,7 @@ import {CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA, SchemaMetadata} from '../../me
import {ViewEncapsulation} from '../../metadata/view';
import {validateAgainstEventAttributes, validateAgainstEventProperties} from '../../sanitization/sanitization';
import {Sanitizer} from '../../sanitization/sanitizer';
import {assertDefined, assertDomNode, assertEqual, assertGreaterThan, assertIndexInRange, assertLessThan, assertNotEqual, assertNotSame, assertSame, assertString} from '../../util/assert';
import {assertDefined, assertDomNode, assertEqual, assertGreaterThan, assertGreaterThanOrEqual, assertIndexInRange, assertLessThan, assertNotEqual, assertNotSame, assertSame, assertString} from '../../util/assert';
import {createNamedArrayType} from '../../util/named_array_type';
import {initNgDevMode} from '../../util/ng_dev_mode';
import {normalizeDebugBindingName, normalizeDebugBindingValue} from '../../util/ng_reflect';
@ -214,12 +214,14 @@ export function getOrCreateTNode(
export function getOrCreateTNode(
tView: TView, index: number, type: TNodeType, name: string|null, attrs: TAttributes|null):
TElementNode&TContainerNode&TElementContainerNode&TProjectionNode&TIcuContainerNode {
ngDevMode && index !== 0 && // 0 are bogus nodes and they are OK. See `createContainerRef` in
// `view_engine_compatibility` for additional context.
assertGreaterThanOrEqual(index, HEADER_OFFSET, 'TNodes can\'t be in the LView header.');
// Keep this function short, so that the VM will inline it.
ngDevMode && assertPureTNodeType(type);
const adjustedIndex = index + HEADER_OFFSET;
let tNode = tView.data[adjustedIndex] as TNode;
let tNode = tView.data[index] as TNode;
if (tNode === null) {
tNode = createTNodeAtIndex(tView, adjustedIndex, type, name, attrs);
tNode = createTNodeAtIndex(tView, index, type, name, attrs);
if (isInI18nBlock()) {
// If we are in i18n block then all elements should be pre declared through `Placeholder`
// See `TNodeType.Placeholder` and `LFrame.inI18n` for more context.
@ -234,7 +236,7 @@ export function getOrCreateTNode(
const parent = getCurrentParentTNode();
tNode.injectorIndex = parent === null ? -1 : parent.injectorIndex;
ngDevMode && assertTNodeForTView(tNode, tView);
ngDevMode && assertEqual(index + HEADER_OFFSET, tNode.index, 'Expecting same index');
ngDevMode && assertEqual(index, tNode.index, 'Expecting same index');
}
setCurrentTNode(tNode, true);
return tNode as TElementNode & TContainerNode & TElementContainerNode & TProjectionNode &
@ -242,14 +244,13 @@ export function getOrCreateTNode(
}
export function createTNodeAtIndex(
tView: TView, adjustedIndex: number, type: TNodeType, name: string|null,
attrs: TAttributes|null) {
tView: TView, index: number, type: TNodeType, name: string|null, attrs: TAttributes|null) {
const currentTNode = getCurrentTNodePlaceholderOk();
const isParent = isCurrentTNodeParent();
const parent = isParent ? currentTNode : currentTNode && currentTNode.parent;
// Parents cannot cross component boundaries because components will be used in multiple places.
const tNode = tView.data[adjustedIndex] =
createTNode(tView, parent as TElementNode | TContainerNode, type, adjustedIndex, name, attrs);
const tNode = tView.data[index] =
createTNode(tView, parent as TElementNode | TContainerNode, type, index, name, attrs);
// Assign a pointer to the first child node of a given view. The first node is not always the one
// at index 0, in case of i18n, index 0 can be the instruction `i18nStart` and the first node has
// the index 1 or more, so we can't just check node index.
@ -546,7 +547,7 @@ function executeTemplate<T>(
if (rf & RenderFlags.Update && lView.length > HEADER_OFFSET) {
// When we're updating, inherently select 0 so we don't
// have to generate that instruction for most update blocks.
selectIndexInternal(tView, lView, 0, isInCheckNoChangesMode());
selectIndexInternal(tView, lView, HEADER_OFFSET, isInCheckNoChangesMode());
}
templateFn(rf, context);
} finally {
@ -810,7 +811,7 @@ export function storeCleanupWithContext(
* @param tView `TView` to which this `TNode` belongs (used only in `ngDevMode`)
* @param tParent Parent `TNode`
* @param type The type of the node
* @param adjustedIndex The index of the TNode in TView.data, adjusted for HEADER_OFFSET
* @param index The index of the TNode in TView.data, adjusted for HEADER_OFFSET
* @param tagName The tag name of the node
* @param attrs The attributes defined on this node
* @param tViews Any TViews attached to this node
@ -818,25 +819,28 @@ export function storeCleanupWithContext(
*/
export function createTNode(
tView: TView, tParent: TElementNode|TContainerNode|null, type: TNodeType.Container,
adjustedIndex: number, tagName: string|null, attrs: TAttributes|null): TContainerNode;
index: number, tagName: string|null, attrs: TAttributes|null): TContainerNode;
export function createTNode(
tView: TView, tParent: TElementNode|TContainerNode|null, type: TNodeType.Element|TNodeType.Text,
adjustedIndex: number, tagName: string|null, attrs: TAttributes|null): TElementNode;
index: number, tagName: string|null, attrs: TAttributes|null): TElementNode;
export function createTNode(
tView: TView, tParent: TElementNode|TContainerNode|null, type: TNodeType.ElementContainer,
adjustedIndex: number, tagName: string|null, attrs: TAttributes|null): TElementContainerNode;
index: number, tagName: string|null, attrs: TAttributes|null): TElementContainerNode;
export function createTNode(
tView: TView, tParent: TElementNode|TContainerNode|null, type: TNodeType.Icu,
adjustedIndex: number, tagName: string|null, attrs: TAttributes|null): TIcuContainerNode;
tView: TView, tParent: TElementNode|TContainerNode|null, type: TNodeType.Icu, index: number,
tagName: string|null, attrs: TAttributes|null): TIcuContainerNode;
export function createTNode(
tView: TView, tParent: TElementNode|TContainerNode|null, type: TNodeType.Projection,
adjustedIndex: number, tagName: string|null, attrs: TAttributes|null): TProjectionNode;
index: number, tagName: string|null, attrs: TAttributes|null): TProjectionNode;
export function createTNode(
tView: TView, tParent: TElementNode|TContainerNode|null, type: TNodeType, adjustedIndex: number,
tView: TView, tParent: TElementNode|TContainerNode|null, type: TNodeType, index: number,
tagName: string|null, attrs: TAttributes|null): TNode;
export function createTNode(
tView: TView, tParent: TElementNode|TContainerNode|null, type: TNodeType, adjustedIndex: number,
tView: TView, tParent: TElementNode|TContainerNode|null, type: TNodeType, index: number,
value: string|null, attrs: TAttributes|null): TNode {
ngDevMode && index !== 0 && // 0 are bogus nodes and they are OK. See `createContainerRef` in
// `view_engine_compatibility` for additional context.
assertGreaterThanOrEqual(index, HEADER_OFFSET, 'TNodes can\'t be in the LView header.');
ngDevMode && assertNotSame(attrs, undefined, '\'undefined\' is not valid value for \'attrs\'');
ngDevMode && ngDevMode.tNode++;
ngDevMode && tParent && assertTNodeForTView(tParent, tView);
@ -845,7 +849,7 @@ export function createTNode(
new TNodeDebug(
tView, // tView_: TView
type, // type: TNodeType
adjustedIndex, // index: number
index, // index: number
null, // insertBeforeIndex: null|-1|number|number[]
injectorIndex, // injectorIndex: number
-1, // directiveStart: number
@ -877,10 +881,10 @@ export function createTNode(
0 as any, // styleBindings: TStylingRange;
) :
{
type: type,
index: adjustedIndex,
type,
index,
insertBeforeIndex: null,
injectorIndex: injectorIndex,
injectorIndex,
directiveStart: -1,
directiveEnd: -1,
directiveStylingLast: -1,
@ -1213,13 +1217,12 @@ export function resolveDirectives(
// We will push the actual hook function into this array later during dir instantiation.
// We cannot do it now because we must ensure hooks are registered in the same
// order that directives are created (i.e. injection order).
(tView.preOrderHooks || (tView.preOrderHooks = [])).push(tNode.index - HEADER_OFFSET);
(tView.preOrderHooks || (tView.preOrderHooks = [])).push(tNode.index);
preOrderHooksFound = true;
}
if (!preOrderCheckHooksFound && (lifeCycleHooks.ngOnChanges || lifeCycleHooks.ngDoCheck)) {
(tView.preOrderCheckHooks || (tView.preOrderCheckHooks = []))
.push(tNode.index - HEADER_OFFSET);
(tView.preOrderCheckHooks || (tView.preOrderCheckHooks = [])).push(tNode.index);
preOrderCheckHooksFound = true;
}
@ -1325,7 +1328,7 @@ function invokeDirectivesHostBindings(tView: TView, lView: LView, tNode: TNode)
const end = tNode.directiveEnd;
const expando = tView.expandoInstructions!;
const firstCreatePass = tView.firstCreatePass;
const elementIndex = tNode.index - HEADER_OFFSET;
const elementIndex = tNode.index;
const currentDirectiveIndex = getCurrentDirectiveIndex();
try {
setSelectedIndex(elementIndex);
@ -1373,7 +1376,7 @@ export function generateExpandoInstructionBlock(
// Important: In JS `-x` and `0-x` is not the same! If `x===0` then `-x` will produce `-0` which
// requires non standard math arithmetic and it can prevent VM optimizations.
// `0-0` will always produce `0` and will not cause a potential deoptimization in VM.
const elementIndex = HEADER_OFFSET - tNode.index;
const elementIndex = 0 - tNode.index;
const providerStartIndex = tNode.providerIndexes & TNodeProviderIndexes.ProvidersStartIndexMask;
const providerCount = tView.data.length - providerStartIndex;
(tView.expandoInstructions || (tView.expandoInstructions = []))
@ -2087,7 +2090,7 @@ export function setInputsForProperty(
export function textBindingInternal(lView: LView, index: number, value: string): void {
ngDevMode && assertString(value, 'Value should be a string');
ngDevMode && assertNotSame(value, NO_CHANGE as any, 'value should not be NO_CHANGE');
ngDevMode && assertIndexInRange(lView, index + HEADER_OFFSET);
ngDevMode && assertIndexInRange(lView, index);
const element = getNativeByIndex(index, lView) as any as RText;
ngDevMode && assertDefined(element, 'native element should exist');
updateTextNode(lView[RENDERER], element, value);

View File

@ -14,12 +14,11 @@ import {load} from '../util/view_utils';
export function store<T>(tView: TView, lView: LView, index: number, value: T): void {
// We don't store any static data for local variables, so the first time
// we see the template, we should store as null to avoid a sparse array
const adjustedIndex = index + HEADER_OFFSET;
if (adjustedIndex >= tView.data.length) {
tView.data[adjustedIndex] = null;
tView.blueprint[adjustedIndex] = null;
if (index >= tView.data.length) {
tView.data[index] = null;
tView.blueprint[index] = null;
}
lView[adjustedIndex] = value;
lView[index] = value;
}
/**
@ -34,5 +33,5 @@ export function store<T>(tView: TView, lView: LView, index: number, value: T): v
*/
export function ɵɵreference<T>(index: number) {
const contextLView = getContextLView();
return load<T>(contextLView, index);
return load<T>(contextLView, HEADER_OFFSET + index);
}

View File

@ -17,14 +17,13 @@ import {DirectiveDef} from '../interfaces/definition';
import {AttributeMarker, TAttributes, TNode, TNodeFlags, TNodeType} from '../interfaces/node';
import {RElement, Renderer3} from '../interfaces/renderer';
import {getTStylingRangeNext, getTStylingRangeNextDuplicate, getTStylingRangePrev, getTStylingRangePrevDuplicate, TStylingKey, TStylingRange} from '../interfaces/styling';
import {HEADER_OFFSET, LView, RENDERER, TData, TView} from '../interfaces/view';
import {LView, RENDERER, TData, TView} from '../interfaces/view';
import {applyStyling} from '../node_manipulation';
import {getCurrentDirectiveDef, getLView, getSelectedIndex, getTView, incrementBindingIndex} from '../state';
import {insertTStylingBinding} from '../styling/style_binding_list';
import {getLastParsedKey, getLastParsedValue, parseClassName, parseClassNameNext, parseStyle, parseStyleNext} from '../styling/styling_parser';
import {NO_CHANGE} from '../tokens';
import {getNativeByIndex} from '../util/view_utils';
import {setDirectiveInputsWhichShadowsStyling} from './property';
@ -174,7 +173,7 @@ export function checkStylingProperty(
stylingFirstUpdatePass(tView, prop, bindingIndex, isClassBased);
}
if (value !== NO_CHANGE && bindingUpdated(lView, bindingIndex, value)) {
const tNode = tView.data[getSelectedIndex() + HEADER_OFFSET] as TNode;
const tNode = tView.data[getSelectedIndex()] as TNode;
updateStyling(
tView, tNode, lView, lView[RENDERER], prop,
lView[bindingIndex + 1] = normalizeSuffix(value, suffix), isClassBased, bindingIndex);
@ -204,7 +203,7 @@ export function checkStylingMap(
if (value !== NO_CHANGE && bindingUpdated(lView, bindingIndex, value)) {
// `getSelectedIndex()` should be here (rather than in instruction) so that it is guarded by the
// if so as not to read unnecessarily.
const tNode = tView.data[getSelectedIndex() + HEADER_OFFSET] as TNode;
const tNode = tView.data[getSelectedIndex()] as TNode;
if (hasStylingInputShadow(tNode, isClassBased) && !isInHostBindings(tView, bindingIndex)) {
if (ngDevMode) {
// verify that if we are shadowing then `TData` is appropriately marked so that we skip
@ -271,7 +270,8 @@ function stylingFirstUpdatePass(
// itself to the list.
// `getSelectedIndex()` should be here (rather than in instruction) so that it is guarded by the
// if so as not to read unnecessarily.
const tNode = tData[getSelectedIndex() + HEADER_OFFSET] as TNode;
const tNode = tData[getSelectedIndex()] as TNode;
ngDevMode && assertDefined(tNode, 'TNode expected');
const isHostBindings = isInHostBindings(tView, bindingIndex);
if (hasStylingInputShadow(tNode, isClassBased) && tStylingKey === null && !isHostBindings) {
// `tStylingKey === null` implies that we are either `[style]` or `[class]` binding.

View File

@ -73,9 +73,9 @@ export function ɵɵtemplate(
const tView = getTView();
const adjustedIndex = index + HEADER_OFFSET;
const tNode = tView.firstCreatePass ?
templateFirstCreatePass(
index, tView, lView, templateFn, decls, vars, tagName, attrsIndex, localRefsIndex) :
const tNode = tView.firstCreatePass ? templateFirstCreatePass(
adjustedIndex, tView, lView, templateFn, decls, vars,
tagName, attrsIndex, localRefsIndex) :
tView.data[adjustedIndex] as TContainerNode;
setCurrentTNode(tNode, false);

View File

@ -35,7 +35,7 @@ export function ɵɵtext(index: number, value: string = ''): void {
ngDevMode && assertIndexInRange(lView, adjustedIndex);
const tNode = tView.firstCreatePass ?
getOrCreateTNode(tView, index, TNodeType.Text, value, null) :
getOrCreateTNode(tView, adjustedIndex, TNodeType.Text, value, null) :
tView.data[adjustedIndex] as TElementNode;
const textNative = lView[adjustedIndex] = createTextNode(lView[RENDERER], value);

View File

@ -47,7 +47,13 @@ export const DECLARATION_COMPONENT_VIEW = 16;
export const DECLARATION_LCONTAINER = 17;
export const PREORDER_HOOK_FLAGS = 18;
export const QUERIES = 19;
/** Size of LView's header. Necessary to adjust for it when setting slots. */
/**
* Size of LView's header. Necessary to adjust for it when setting slots.
*
* IMPORTANT: `HEADER_OFFSET` should only be referred to the in the `ɵɵ*` instructions to translate
* 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;

View File

@ -54,7 +54,7 @@ export function ɵɵpipe(index: number, pipeName: string): any {
const previousIncludeViewProviders = setIncludeViewProviders(false);
const pipeInstance = pipeFactory();
setIncludeViewProviders(previousIncludeViewProviders);
store(tView, getLView(), index, pipeInstance);
store(tView, getLView(), adjustedIndex, pipeInstance);
return pipeInstance;
} finally {
// we have to restore the injector implementation in finally, just in case the creation of the
@ -96,11 +96,12 @@ function getPipeDef(name: string, registry: PipeDefList|null): PipeDef<any> {
* @codeGenApi
*/
export function ɵɵpipeBind1(index: number, slotOffset: number, v1: any): any {
const adjustedIndex = index + HEADER_OFFSET;
const lView = getLView();
const pipeInstance = load<PipeTransform>(lView, index);
const pipeInstance = load<PipeTransform>(lView, adjustedIndex);
return unwrapValue(
lView,
isPure(lView, index) ?
isPure(lView, adjustedIndex) ?
pureFunction1Internal(
lView, getBindingRoot(), slotOffset, pipeInstance.transform, v1, pipeInstance) :
pipeInstance.transform(v1));
@ -120,11 +121,12 @@ export function ɵɵpipeBind1(index: number, slotOffset: number, v1: any): any {
* @codeGenApi
*/
export function ɵɵpipeBind2(index: number, slotOffset: number, v1: any, v2: any): any {
const adjustedIndex = index + HEADER_OFFSET;
const lView = getLView();
const pipeInstance = load<PipeTransform>(lView, index);
const pipeInstance = load<PipeTransform>(lView, adjustedIndex);
return unwrapValue(
lView,
isPure(lView, index) ?
isPure(lView, adjustedIndex) ?
pureFunction2Internal(
lView, getBindingRoot(), slotOffset, pipeInstance.transform, v1, v2, pipeInstance) :
pipeInstance.transform(v1, v2));
@ -145,13 +147,14 @@ export function ɵɵpipeBind2(index: number, slotOffset: number, v1: any, v2: an
* @codeGenApi
*/
export function ɵɵpipeBind3(index: number, slotOffset: number, v1: any, v2: any, v3: any): any {
const adjustedIndex = index + HEADER_OFFSET;
const lView = getLView();
const pipeInstance = load<PipeTransform>(lView, index);
const pipeInstance = load<PipeTransform>(lView, adjustedIndex);
return unwrapValue(
lView,
isPure(lView, index) ? pureFunction3Internal(
lView, getBindingRoot(), slotOffset, pipeInstance.transform, v1,
v2, v3, pipeInstance) :
isPure(lView, adjustedIndex) ? pureFunction3Internal(
lView, getBindingRoot(), slotOffset,
pipeInstance.transform, v1, v2, v3, pipeInstance) :
pipeInstance.transform(v1, v2, v3));
}
@ -172,13 +175,14 @@ export function ɵɵpipeBind3(index: number, slotOffset: number, v1: any, v2: an
*/
export function ɵɵpipeBind4(
index: number, slotOffset: number, v1: any, v2: any, v3: any, v4: any): any {
const adjustedIndex = index + HEADER_OFFSET;
const lView = getLView();
const pipeInstance = load<PipeTransform>(lView, index);
const pipeInstance = load<PipeTransform>(lView, adjustedIndex);
return unwrapValue(
lView,
isPure(lView, index) ? pureFunction4Internal(
lView, getBindingRoot(), slotOffset, pipeInstance.transform, v1,
v2, v3, v4, pipeInstance) :
isPure(lView, adjustedIndex) ? pureFunction4Internal(
lView, getBindingRoot(), slotOffset,
pipeInstance.transform, v1, v2, v3, v4, pipeInstance) :
pipeInstance.transform(v1, v2, v3, v4));
}
@ -195,18 +199,19 @@ export function ɵɵpipeBind4(
* @codeGenApi
*/
export function ɵɵpipeBindV(index: number, slotOffset: number, values: [any, ...any[]]): any {
const adjustedIndex = index + HEADER_OFFSET;
const lView = getLView();
const pipeInstance = load<PipeTransform>(lView, index);
const pipeInstance = load<PipeTransform>(lView, adjustedIndex);
return unwrapValue(
lView,
isPure(lView, index) ?
isPure(lView, adjustedIndex) ?
pureFunctionVInternal(
lView, getBindingRoot(), slotOffset, pipeInstance.transform, values, pipeInstance) :
pipeInstance.transform.apply(pipeInstance, values));
}
function isPure(lView: LView, index: number): boolean {
return (<PipeDef<any>>lView[TVIEW].data[index + HEADER_OFFSET]).pure;
return (<PipeDef<any>>lView[TVIEW].data[index]).pure;
}
/**

View File

@ -6,11 +6,11 @@
* found in the LICENSE file at https://angular.io/license
*/
import {assertDefined, assertEqual, assertNotEqual} from '../util/assert';
import {assertDefined, assertEqual, assertGreaterThanOrEqual, assertLessThan, assertNotEqual} from '../util/assert';
import {assertLViewOrUndefined, assertTNodeForTView} from './assert';
import {DirectiveDef} from './interfaces/definition';
import {TNode, TNodeType} from './interfaces/node';
import {CONTEXT, DECLARATION_VIEW, LView, OpaqueViewState, TData, TVIEW, TView} from './interfaces/view';
import {CONTEXT, DECLARATION_VIEW, HEADER_OFFSET, LView, OpaqueViewState, TData, TVIEW, TView} from './interfaces/view';
import {MATH_ML_NAMESPACE, SVG_NAMESPACE} from './namespaces';
import {assertTNodeType} from './node_assert';
import {getTNode} from './util/view_utils';
@ -458,13 +458,14 @@ export function enterDI(newView: LView, tNode: TNode) {
* @returns the previously active lView;
*/
export function enterView(newView: LView): void {
ngDevMode && assertNotEqual(newView[0], newView[1] as any, '????');
ngDevMode && assertLViewOrUndefined(newView);
const newLFrame = allocLFrame();
if (ngDevMode) {
assertEqual(newLFrame.isParent, true, 'Expected clean LFrame');
assertEqual(newLFrame.lView, null, 'Expected clean LFrame');
assertEqual(newLFrame.tView, null, 'Expected clean LFrame');
assertEqual(newLFrame.selectedIndex, 0, 'Expected clean LFrame');
assertEqual(newLFrame.selectedIndex, -1, 'Expected clean LFrame');
assertEqual(newLFrame.elementDepthCount, 0, 'Expected clean LFrame');
assertEqual(newLFrame.currentDirectiveIndex, -1, 'Expected clean LFrame');
assertEqual(newLFrame.currentNamespace, null, 'Expected clean LFrame');
@ -498,7 +499,7 @@ function createLFrame(parent: LFrame|null): LFrame {
isParent: true,
lView: null!,
tView: null!,
selectedIndex: 0,
selectedIndex: -1,
contextLView: null!,
elementDepthCount: 0,
currentNamespace: null,
@ -551,7 +552,7 @@ export function leaveView() {
const oldLFrame = leaveViewLight();
oldLFrame.isParent = true;
oldLFrame.tView = null!;
oldLFrame.selectedIndex = 0;
oldLFrame.selectedIndex = -1;
oldLFrame.contextLView = null!;
oldLFrame.elementDepthCount = 0;
oldLFrame.currentDirectiveIndex = -1;
@ -599,6 +600,11 @@ export function getSelectedIndex() {
* run if and when the provided `index` value is different from the current selected index value.)
*/
export function setSelectedIndex(index: number) {
ngDevMode && index !== -1 &&
assertGreaterThanOrEqual(index, HEADER_OFFSET, 'Index must be past HEADER_OFFSET (or -1).');
ngDevMode &&
assertLessThan(
index, instructionState.lFrame.lView.length, 'Can\'t set index passed end of LView');
instructionState.lFrame.selectedIndex = index;
}

View File

@ -16,7 +16,7 @@ import {LContext} from '../interfaces/context';
import {DirectiveDef} from '../interfaces/definition';
import {TElementNode, TNode, TNodeProviderIndexes} from '../interfaces/node';
import {isLView} from '../interfaces/type_checks';
import {CLEANUP, CONTEXT, DebugNode, FLAGS, HEADER_OFFSET, HOST, LView, LViewFlags, T_HOST, TVIEW, TViewType} from '../interfaces/view';
import {CLEANUP, CONTEXT, DebugNode, FLAGS, LView, LViewFlags, T_HOST, TVIEW, TViewType} from '../interfaces/view';
import {stringifyForError} from './misc_utils';
import {getLViewParent, getRootContext} from './view_traversal_utils';
@ -387,8 +387,8 @@ export function getDebugNode(element: Element): DebugNode|null {
const valueInLView = lView[nodeIndex];
// this means that value in the lView is a component with its own
// data. In this situation the TNode is not accessed at the same spot.
const tNode = isLView(valueInLView) ? (valueInLView[T_HOST] as TNode) :
getTNode(lView[TVIEW], nodeIndex - HEADER_OFFSET);
const tNode =
isLView(valueInLView) ? (valueInLView[T_HOST] as TNode) : getTNode(lView[TVIEW], nodeIndex);
ngDevMode &&
assertEqual(tNode.index, nodeIndex, 'Expecting that TNode at index is same as index');
debugNode = buildDebugNode(tNode, lView);

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {assertDefined, assertDomNode, assertGreaterThan, assertIndexInRange, assertLessThan} from '../../util/assert';
import {assertDefined, assertDomNode, assertGreaterThan, assertGreaterThanOrEqual, assertIndexInRange, assertLessThan} from '../../util/assert';
import {assertTNode, assertTNodeForLView} from '../assert';
import {LContainer, TYPE} from '../interfaces/container';
import {LContext, MONKEY_PATCH_KEY_NAME} from '../interfaces/context';
@ -78,7 +78,9 @@ export function unwrapLContainer(value: RNode|LView|LContainer): LContainer|null
* from any containers, component views, or style contexts.
*/
export function getNativeByIndex(index: number, lView: LView): RNode {
return unwrapRNode(lView[index + HEADER_OFFSET]);
ngDevMode && assertIndexInRange(lView, index);
ngDevMode && assertGreaterThanOrEqual(index, HEADER_OFFSET, 'Expected to be past HEADER_OFFSET');
return unwrapRNode(lView[index]);
}
/**
@ -120,16 +122,16 @@ export function getNativeByTNodeOrNull(tNode: TNode|null, lView: LView): RNode|n
// fixme(misko): The return Type should be `TNode|null`
export function getTNode(tView: TView, index: number): TNode {
ngDevMode && assertGreaterThan(index, -1, 'wrong index for TNode');
ngDevMode && assertLessThan(index, tView.data.length - HEADER_OFFSET, 'wrong index for TNode');
const tNode = tView.data[index + HEADER_OFFSET] as TNode;
ngDevMode && assertLessThan(index, tView.data.length, 'wrong index for TNode');
const tNode = tView.data[index] as TNode;
ngDevMode && tNode !== null && assertTNode(tNode);
return tNode;
}
/** Retrieves a value from any `LView` or `TData`. */
export function load<T>(view: LView|TData, index: number): T {
ngDevMode && assertIndexInRange(view, index + HEADER_OFFSET);
return view[index + HEADER_OFFSET];
ngDevMode && assertIndexInRange(view, index);
return view[index];
}
export function getComponentLViewByIndex(nodeIndex: number, hostView: LView): LView {

View File

@ -391,8 +391,8 @@ export function createContainerRef(
} else {
// The TNode created here is bogus, in that it is not added to the TView. It is only created
// to allow us to create a dynamic Comment node.
const commentTNode = createTNode(
hostView[TVIEW], hostTNode.parent, TNodeType.Container, hostTNode.type, null, null);
const commentTNode =
createTNode(hostView[TVIEW], hostTNode.parent, TNodeType.Container, 0, null, null);
appendChild(hostView[TVIEW], hostView, commentNode, commentTNode);
}
}

View File

@ -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(<!--ICU 0:0-->)']);
expect(lViewDebug.nodes.map(toTypeContent)).toEqual(['IcuContainer(<!--ICU 20:0-->)']);
// 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<!--ICU 0:0-->');
expect(fixture.nativeElement.innerHTML).toEqual('just now<!--ICU 20:0-->');
});
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(<!--ICU 0:0-->)',
'IcuContainer(<!--ICU 1:0-->)',
'IcuContainer(<!--ICU 20:0-->)',
'IcuContainer(<!--ICU 21:0-->)',
]);
// 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 now<!--ICU 0:0-->Mr. Angular<!--ICU 1:0-->');
.toEqual('just now<!--ICU 20:0-->Mr. Angular<!--ICU 21:0-->');
});
});
});
@ -778,19 +778,19 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
other {({{name}})}
}</div>`);
expect(fixture.nativeElement.innerHTML)
.toEqual(`<div>aucun <b>email</b>!<!--ICU 1:0--> - (Angular)<!--ICU 1:3--></div>`);
.toEqual(`<div>aucun <b>email</b>!<!--ICU 21:0--> - (Angular)<!--ICU 21:3--></div>`);
fixture.componentRef.instance.count = 4;
fixture.detectChanges();
expect(fixture.nativeElement.innerHTML)
.toEqual(
`<div>4 <span title="Angular">emails</span><!--ICU 1:0--> - (Angular)<!--ICU 1:3--></div>`);
`<div>4 <span title="Angular">emails</span><!--ICU 21:0--> - (Angular)<!--ICU 21:3--></div>`);
fixture.componentRef.instance.count = 0;
fixture.componentRef.instance.name = 'John';
fixture.detectChanges();
expect(fixture.nativeElement.innerHTML)
.toEqual(`<div>aucun <b>email</b>!<!--ICU 1:0--> - (John)<!--ICU 1:3--></div>`);
.toEqual(`<div>aucun <b>email</b>!<!--ICU 21:0--> - (John)<!--ICU 21:3--></div>`);
});
it('with custom interpolation config', () => {
@ -829,9 +829,9 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
expect(fixture.nativeElement.innerHTML)
.toEqual(
`<div>` +
`<span>aucun <b>email</b>!<!--ICU 1:0--></span>` +
`<span>aucun <b>email</b>!<!--ICU 21:0--></span>` +
` - ` +
`<span>(Angular)<!--ICU 1:3--></span>` +
`<span>(Angular)<!--ICU 21:3--></span>` +
`</div>`);
fixture.componentRef.instance.count = 4;
@ -839,9 +839,9 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
expect(fixture.nativeElement.innerHTML)
.toEqual(
`<div>` +
`<span>4 <span title="Angular">emails</span><!--ICU 1:0--></span>` +
`<span>4 <span title="Angular">emails</span><!--ICU 21:0--></span>` +
` - ` +
`<span>(Angular)<!--ICU 1:3--></span>` +
`<span>(Angular)<!--ICU 21:3--></span>` +
`</div>`);
fixture.componentRef.instance.count = 0;
@ -850,9 +850,9 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
expect(fixture.nativeElement.innerHTML)
.toEqual(
`<div>` +
`<span>aucun <b>email</b>!<!--ICU 1:0--></span>` +
`<span>aucun <b>email</b>!<!--ICU 21:0--></span>` +
` - ` +
`<span>(John)<!--ICU 1:3--></span>` +
`<span>(John)<!--ICU 21:3--></span>` +
`</div>`);
});
@ -867,7 +867,7 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
other {({{name}})}
}</span></div>`);
expect(fixture.nativeElement.innerHTML)
.toEqual(`<div><span>(Angular)<!--ICU 0:0--></span><!--bindings={
.toEqual(`<div><span>(Angular)<!--ICU 20:0--></span><!--bindings={
"ng-reflect-ng-if": "true"
}--></div>`);
@ -886,7 +886,8 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
const fixture = initWithTemplate(AppComp, `<ng-container i18n>{name, select,
other {({{name}})}
}</ng-container>`);
expect(fixture.nativeElement.innerHTML).toEqual(`(Angular)<!--ICU 1:0--><!--ng-container-->`);
expect(fixture.nativeElement.innerHTML)
.toEqual(`(Angular)<!--ICU 21:0--><!--ng-container-->`);
});
it('inside <ng-template>', () => {
@ -921,12 +922,12 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
other {animals}
}!}
}</div>`);
expect(fixture.nativeElement.innerHTML).toEqual(`<div>zero<!--ICU 1:1--></div>`);
expect(fixture.nativeElement.innerHTML).toEqual(`<div>zero<!--ICU 21:1--></div>`);
fixture.componentRef.instance.count = 4;
fixture.detectChanges();
expect(fixture.nativeElement.innerHTML)
.toEqual(`<div>4 animaux<!--nested ICU 0-->!<!--ICU 1:1--></div>`);
.toEqual(`<div>4 animaux<!--nested ICU 0-->!<!--ICU 21:1--></div>`);
});
it('nested with interpolations in "other" blocks', () => {
@ -946,16 +947,16 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
}!}
other {other - {{count}}}
}</div>`);
expect(fixture.nativeElement.innerHTML).toEqual(`<div>zero<!--ICU 1:1--></div>`);
expect(fixture.nativeElement.innerHTML).toEqual(`<div>zero<!--ICU 21:1--></div>`);
fixture.componentRef.instance.count = 2;
fixture.detectChanges();
expect(fixture.nativeElement.innerHTML)
.toEqual(`<div>2 animaux<!--nested ICU 0-->!<!--ICU 1:1--></div>`);
.toEqual(`<div>2 animaux<!--nested ICU 0-->!<!--ICU 21:1--></div>`);
fixture.componentRef.instance.count = 4;
fixture.detectChanges();
expect(fixture.nativeElement.innerHTML).toEqual(`<div>autre - 4<!--ICU 1:1--></div>`);
expect(fixture.nativeElement.innerHTML).toEqual(`<div>autre - 4<!--ICU 21:1--></div>`);
});
it('should return the correct plural form for ICU expressions when using "ro" locale', () => {
@ -988,31 +989,31 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
=other {lots of emails}
}`);
expect(fixture.nativeElement.innerHTML).toEqual('no email<!--ICU 0:0-->');
expect(fixture.nativeElement.innerHTML).toEqual('no email<!--ICU 20:0-->');
// Change detection cycle, no model changes
fixture.detectChanges();
expect(fixture.nativeElement.innerHTML).toEqual('no email<!--ICU 0:0-->');
expect(fixture.nativeElement.innerHTML).toEqual('no email<!--ICU 20:0-->');
fixture.componentInstance.count = 3;
fixture.detectChanges();
expect(fixture.nativeElement.innerHTML).toEqual('a few emails<!--ICU 0:0-->');
expect(fixture.nativeElement.innerHTML).toEqual('a few emails<!--ICU 20:0-->');
fixture.componentInstance.count = 1;
fixture.detectChanges();
expect(fixture.nativeElement.innerHTML).toEqual('one email<!--ICU 0:0-->');
expect(fixture.nativeElement.innerHTML).toEqual('one email<!--ICU 20:0-->');
fixture.componentInstance.count = 10;
fixture.detectChanges();
expect(fixture.nativeElement.innerHTML).toEqual('a few emails<!--ICU 0:0-->');
expect(fixture.nativeElement.innerHTML).toEqual('a few emails<!--ICU 20:0-->');
fixture.componentInstance.count = 20;
fixture.detectChanges();
expect(fixture.nativeElement.innerHTML).toEqual('lots of emails<!--ICU 0:0-->');
expect(fixture.nativeElement.innerHTML).toEqual('lots of emails<!--ICU 20:0-->');
fixture.componentInstance.count = 0;
fixture.detectChanges();
expect(fixture.nativeElement.innerHTML).toEqual('no email<!--ICU 0:0-->');
expect(fixture.nativeElement.innerHTML).toEqual('no email<!--ICU 20:0-->');
});
it(`should return the correct plural form for ICU expressions when using "es" locale`, () => {
@ -1039,31 +1040,31 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
=other {lots of emails}
}`);
expect(fixture.nativeElement.innerHTML).toEqual('no email<!--ICU 0:0-->');
expect(fixture.nativeElement.innerHTML).toEqual('no email<!--ICU 20:0-->');
// Change detection cycle, no model changes
fixture.detectChanges();
expect(fixture.nativeElement.innerHTML).toEqual('no email<!--ICU 0:0-->');
expect(fixture.nativeElement.innerHTML).toEqual('no email<!--ICU 20:0-->');
fixture.componentInstance.count = 3;
fixture.detectChanges();
expect(fixture.nativeElement.innerHTML).toEqual('lots of emails<!--ICU 0:0-->');
expect(fixture.nativeElement.innerHTML).toEqual('lots of emails<!--ICU 20:0-->');
fixture.componentInstance.count = 1;
fixture.detectChanges();
expect(fixture.nativeElement.innerHTML).toEqual('one email<!--ICU 0:0-->');
expect(fixture.nativeElement.innerHTML).toEqual('one email<!--ICU 20:0-->');
fixture.componentInstance.count = 10;
fixture.detectChanges();
expect(fixture.nativeElement.innerHTML).toEqual('lots of emails<!--ICU 0:0-->');
expect(fixture.nativeElement.innerHTML).toEqual('lots of emails<!--ICU 20:0-->');
fixture.componentInstance.count = 20;
fixture.detectChanges();
expect(fixture.nativeElement.innerHTML).toEqual('lots of emails<!--ICU 0:0-->');
expect(fixture.nativeElement.innerHTML).toEqual('lots of emails<!--ICU 20:0-->');
fixture.componentInstance.count = 0;
fixture.detectChanges();
expect(fixture.nativeElement.innerHTML).toEqual('no email<!--ICU 0:0-->');
expect(fixture.nativeElement.innerHTML).toEqual('no email<!--ICU 20:0-->');
});
it('projection', () => {
@ -1158,12 +1159,12 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
const fixture = TestBed.createComponent(App);
fixture.detectChanges();
expect(fixture.debugElement.nativeElement.innerHTML)
.toContain('<my-cmp><div>ONE<!--ICU 1:0--></div><!--container--></my-cmp>');
.toContain('<my-cmp><div>ONE<!--ICU 21:0--></div><!--container--></my-cmp>');
fixture.componentRef.instance.count = 2;
fixture.detectChanges();
expect(fixture.debugElement.nativeElement.innerHTML)
.toContain('<my-cmp><div>OTHER<!--ICU 1:0--></div><!--container--></my-cmp>');
.toContain('<my-cmp><div>OTHER<!--ICU 21:0--></div><!--container--></my-cmp>');
// destroy component
fixture.componentInstance.condition = false;
@ -1175,7 +1176,7 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
fixture.componentInstance.count = 1;
fixture.detectChanges();
expect(fixture.debugElement.nativeElement.innerHTML)
.toContain('<my-cmp><div>ONE<!--ICU 1:0--></div><!--container--></my-cmp>');
.toContain('<my-cmp><div>ONE<!--ICU 21:0--></div><!--container--></my-cmp>');
});
it('with nested ICU expression and inside a container when creating a view via vcr.createEmbeddedView',
@ -1247,12 +1248,12 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
fixture.detectChanges();
expect(fixture.debugElement.nativeElement.innerHTML)
.toBe(
'<my-cmp><div>2 animals<!--nested ICU 0-->!<!--ICU 1:1--></div><!--container--></my-cmp>');
'<my-cmp><div>2 animals<!--nested ICU 0-->!<!--ICU 21:1--></div><!--container--></my-cmp>');
fixture.componentRef.instance.count = 1;
fixture.detectChanges();
expect(fixture.debugElement.nativeElement.innerHTML)
.toBe('<my-cmp><div>ONE<!--ICU 1:1--></div><!--container--></my-cmp>');
.toBe('<my-cmp><div>ONE<!--ICU 21:1--></div><!--container--></my-cmp>');
});
it('with nested containers', () => {
@ -2369,13 +2370,13 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
fixture.detectChanges();
expect(fixture.nativeElement.innerHTML)
.toEqual(
`<child><div>Contenu enfant et projection depuis Parent<!--ICU 1:0--></div></child>`);
`<child><div>Contenu enfant et projection depuis Parent<!--ICU 21:0--></div></child>`);
fixture.componentRef.instance.name = 'angular';
fixture.detectChanges();
expect(fixture.nativeElement.innerHTML)
.toEqual(
`<child><div>Contenu enfant et projection depuis Angular<!--ICU 1:0--></div></child>`);
`<child><div>Contenu enfant et projection depuis Angular<!--ICU 21:0--></div></child>`);
});
it(`shouldn't project deleted projections in i18n blocks`, () => {

View File

@ -54,44 +54,44 @@ describe('addTNodeAndUpdateInsertBeforeIndex', () => {
describe('whose index is smaller than current nodes', () => {
it('should update the previous insertBeforeIndex', () => {
const previousTNodes: TNode[] = [
tPlaceholderElementNode(20),
tPlaceholderElementNode(21),
tPlaceholderElementNode(22),
];
addTNodeAndUpdateInsertBeforeIndex(previousTNodes, tPlaceholderElementNode(19));
addTNodeAndUpdateInsertBeforeIndex(previousTNodes, tPlaceholderElementNode(20));
expect(previousTNodes).toEqual([
matchTNode({index: 20, insertBeforeIndex: 19}),
matchTNode({index: 21, insertBeforeIndex: 19}),
matchTNode({index: 19, insertBeforeIndex: null}),
matchTNode({index: 21, insertBeforeIndex: 20}),
matchTNode({index: 22, insertBeforeIndex: 20}),
matchTNode({index: 20, insertBeforeIndex: null}),
]);
});
it('should not update the previous insertBeforeIndex if it is already set', () => {
const previousTNodes: TNode[] = [
tPlaceholderElementNode(20, 19),
tPlaceholderElementNode(21, 19),
tPlaceholderElementNode(19),
tPlaceholderElementNode(22, 21),
tPlaceholderElementNode(23, 21),
tPlaceholderElementNode(21),
];
addTNodeAndUpdateInsertBeforeIndex(previousTNodes, tPlaceholderElementNode(18));
addTNodeAndUpdateInsertBeforeIndex(previousTNodes, tPlaceholderElementNode(20));
expect(previousTNodes).toEqual([
matchTNode({index: 20, insertBeforeIndex: 19}),
matchTNode({index: 21, insertBeforeIndex: 19}),
matchTNode({index: 19, insertBeforeIndex: 18}),
matchTNode({index: 18, insertBeforeIndex: null}),
matchTNode({index: 22, insertBeforeIndex: 21}),
matchTNode({index: 23, insertBeforeIndex: 21}),
matchTNode({index: 21, insertBeforeIndex: 20}),
matchTNode({index: 20, insertBeforeIndex: null}),
]);
});
it('should not update the previous insertBeforeIndex if it is created after', () => {
const previousTNodes: TNode[] = [
tPlaceholderElementNode(20, 15),
tPlaceholderElementNode(21, 15),
tPlaceholderElementNode(15),
tPlaceholderElementNode(25, 20),
tPlaceholderElementNode(26, 20),
tPlaceholderElementNode(20),
];
addTNodeAndUpdateInsertBeforeIndex(previousTNodes, tPlaceholderElementNode(18));
addTNodeAndUpdateInsertBeforeIndex(previousTNodes, tPlaceholderElementNode(23));
expect(previousTNodes).toEqual([
matchTNode({index: 20, insertBeforeIndex: 15}),
matchTNode({index: 21, insertBeforeIndex: 15}),
matchTNode({index: 15, insertBeforeIndex: null}),
matchTNode({index: 18, insertBeforeIndex: null}),
matchTNode({index: 25, insertBeforeIndex: 20}),
matchTNode({index: 26, insertBeforeIndex: 20}),
matchTNode({index: 20, insertBeforeIndex: null}),
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(20),
tPlaceholderElementNode(21),
tPlaceholderElementNode(22),
];
addTNodeAndUpdateInsertBeforeIndex(previousTNodes, tI18NTextNode(19));
addTNodeAndUpdateInsertBeforeIndex(previousTNodes, tI18NTextNode(20));
expect(previousTNodes).toEqual([
matchTNode({index: 20, insertBeforeIndex: 19}),
matchTNode({index: 21, insertBeforeIndex: 19}),
matchTNode({index: 19, insertBeforeIndex: null}),
matchTNode({index: 21, insertBeforeIndex: 20}),
matchTNode({index: 22, insertBeforeIndex: 20}),
matchTNode({index: 20, insertBeforeIndex: null}),
]);
});
it('should not update the previous insertBeforeIndex if it is already set', () => {
const previousTNodes: TNode[] = [
tPlaceholderElementNode(20, 19),
tPlaceholderElementNode(21, 19),
tPlaceholderElementNode(19),
tPlaceholderElementNode(22, 21),
tPlaceholderElementNode(23, 21),
tPlaceholderElementNode(21),
];
addTNodeAndUpdateInsertBeforeIndex(previousTNodes, tI18NTextNode(18));
addTNodeAndUpdateInsertBeforeIndex(previousTNodes, tI18NTextNode(20));
expect(previousTNodes).toEqual([
matchTNode({index: 20, insertBeforeIndex: 19}),
matchTNode({index: 21, insertBeforeIndex: 19}),
matchTNode({index: 19, insertBeforeIndex: 18}),
matchTNode({index: 18, insertBeforeIndex: null}),
matchTNode({index: 22, insertBeforeIndex: 21}),
matchTNode({index: 23, insertBeforeIndex: 21}),
matchTNode({index: 21, insertBeforeIndex: 20}),
matchTNode({index: 20, insertBeforeIndex: null}),
]);
});
it('should not update the previous insertBeforeIndex if it is created after', () => {
const previousTNodes: TNode[] = [
tPlaceholderElementNode(20, 15),
tPlaceholderElementNode(21, 15),
tPlaceholderElementNode(15),
tPlaceholderElementNode(25, 20),
tPlaceholderElementNode(26, 20),
tPlaceholderElementNode(20),
];
addTNodeAndUpdateInsertBeforeIndex(previousTNodes, tI18NTextNode(18));
addTNodeAndUpdateInsertBeforeIndex(previousTNodes, tI18NTextNode(23));
expect(previousTNodes).toEqual([
matchTNode({index: 20, insertBeforeIndex: 15}),
matchTNode({index: 21, insertBeforeIndex: 15}),
matchTNode({index: 15, insertBeforeIndex: 18}),
matchTNode({index: 18, insertBeforeIndex: null}),
matchTNode({index: 25, insertBeforeIndex: 20}),
matchTNode({index: 26, insertBeforeIndex: 20}),
matchTNode({index: 20, insertBeforeIndex: 23}),
matchTNode({index: 23, insertBeforeIndex: null}),
]);
});
});

View File

@ -45,7 +45,7 @@ describe('i18n_parse', () => {
// 21: Binding for ICU |
// ----- EXPANDO -----
// 22: null | #text(before|)
// 23: TIcu | <!-- ICU 0:0 -->
// 23: TIcu | <!-- ICU 20:0 -->
// 24: null | currently selected ICU case
// 25: null | #text(caseA)
// 26: null | #text(otherCase)
@ -59,7 +59,7 @@ describe('i18n_parse', () => {
create: matchDebug([
'lView[22] = document.createText("before|");',
'parent.appendChild(lView[22]);',
'lView[23] = document.createComment("ICU 0:0");',
'lView[23] = document.createComment("ICU 20:0");',
'parent.appendChild(lView[23]);',
'lView[27] = document.createText("|after");',
'parent.appendChild(lView[27]);',
@ -95,22 +95,22 @@ describe('i18n_parse', () => {
fixture.apply(() => {
applyCreateOpCodes(fixture.lView, tI18n.create, fixture.host, null);
expect(fixture.host.innerHTML).toEqual('before|<!--ICU 0:0-->|after');
expect(fixture.host.innerHTML).toEqual('before|<!--ICU 20:0-->|after');
});
fixture.apply(() => {
ɵɵi18nExp('A');
ɵɵi18nApply(0); // index 0 + HEADER_OFFSET = 20;
expect(fixture.host.innerHTML).toEqual('before|caseA<!--ICU 0:0-->|after');
expect(fixture.host.innerHTML).toEqual('before|caseA<!--ICU 20:0-->|after');
});
fixture.apply(() => {
ɵɵi18nExp('x');
ɵɵi18nApply(0); // index 0 + HEADER_OFFSET = 20;
expect(fixture.host.innerHTML).toEqual('before|otherCase<!--ICU 0:0-->|after');
expect(fixture.host.innerHTML).toEqual('before|otherCase<!--ICU 20:0-->|after');
});
fixture.apply(() => {
ɵɵi18nExp('A');
ɵɵi18nApply(0); // index 0 + HEADER_OFFSET = 20;
expect(fixture.host.innerHTML).toEqual('before|caseA<!--ICU 0:0-->|after');
expect(fixture.host.innerHTML).toEqual('before|caseA<!--ICU 20:0-->|after');
});
});
@ -122,23 +122,23 @@ describe('i18n_parse', () => {
}`);
fixture.apply(() => {
applyCreateOpCodes(fixture.lView, tI18n.create, fixture.host, null);
expect(fixture.host.innerHTML).toEqual('<!--ICU 0:0-->');
expect(fixture.host.innerHTML).toEqual('<!--ICU 20:0-->');
});
fixture.apply(() => {
ɵɵi18nExp('A');
ɵɵi18nApply(0); // index 0 + HEADER_OFFSET = 20;
expect(fixture.host.innerHTML).toEqual('Hello <b>world<i>!</i></b><!--ICU 0:0-->');
expect(fixture.host.innerHTML).toEqual('Hello <b>world<i>!</i></b><!--ICU 20:0-->');
});
fixture.apply(() => {
ɵɵi18nExp('x');
ɵɵi18nApply(0); // index 0 + HEADER_OFFSET = 20;
expect(fixture.host.innerHTML)
.toEqual('<div>nestedOther<!--nested ICU 0--></div><!--ICU 0:0-->');
.toEqual('<div>nestedOther<!--nested ICU 0--></div><!--ICU 20:0-->');
});
fixture.apply(() => {
ɵɵi18nExp('A');
ɵɵi18nApply(0); // index 0 + HEADER_OFFSET = 20;
expect(fixture.host.innerHTML).toEqual('Hello <b>world<i>!</i></b><!--ICU 0:0-->');
expect(fixture.host.innerHTML).toEqual('Hello <b>world<i>!</i></b><!--ICU 20:0-->');
});
});
@ -154,7 +154,7 @@ describe('i18n_parse', () => {
// 22: Binding for child ICU |
// 23: Binding for child ICU |
// ----- EXPANDO -----
// 24: TIcu (parent) | <!-- ICU 0:0 -->
// 24: TIcu (parent) | <!-- ICU 20:0 -->
// 25: null | currently selected ICU case
// 26: null | #text( parentA )
// 27: TIcu (child) | <!-- nested ICU 0 -->
@ -170,7 +170,7 @@ describe('i18n_parse', () => {
}`);
expect(tI18n).toEqual(matchTI18n({
create: matchDebug([
'lView[24] = document.createComment("ICU 0:0");',
'lView[24] = document.createComment("ICU 20:0");',
'parent.appendChild(lView[24]);',
]),
update: matchDebug([
@ -244,45 +244,46 @@ describe('i18n_parse', () => {
fixture.apply(() => {
applyCreateOpCodes(fixture.lView, tI18n.create, fixture.host, null);
expect(fixture.host.innerHTML).toEqual('<!--ICU 0:0-->');
expect(fixture.host.innerHTML).toEqual('<!--ICU 20:0-->');
});
fixture.apply(() => {
ɵɵi18nExp('A');
ɵɵi18nExp('0');
ɵɵi18nExp('value1');
ɵɵi18nApply(0); // index 0 + HEADER_OFFSET = 20;
expect(fixture.host.innerHTML).toEqual('parentA nested0<!--nested ICU 0-->!<!--ICU 0:0-->');
expect(fixture.host.innerHTML)
.toEqual('parentA nested0<!--nested ICU 0-->!<!--ICU 20:0-->');
});
fixture.apply(() => {
ɵɵi18nExp('A');
ɵɵi18nExp('x');
ɵɵi18nExp('value1');
ɵɵi18nApply(0); // index 0 + HEADER_OFFSET = 20;
expect(fixture.host.innerHTML).toEqual('parentA value1<!--nested ICU 0-->!<!--ICU 0:0-->');
expect(fixture.host.innerHTML).toEqual('parentA value1<!--nested ICU 0-->!<!--ICU 20:0-->');
});
fixture.apply(() => {
ɵɵi18nExp('x');
ɵɵi18nExp('x');
ɵɵi18nExp('value2');
ɵɵi18nApply(0); // index 0 + HEADER_OFFSET = 20;
expect(fixture.host.innerHTML).toEqual('parentOther<!--ICU 0:0-->');
expect(fixture.host.innerHTML).toEqual('parentOther<!--ICU 20:0-->');
});
fixture.apply(() => {
ɵɵi18nExp('A');
ɵɵi18nExp('A');
ɵɵi18nExp('value2');
ɵɵi18nApply(0); // index 0 + HEADER_OFFSET = 20;
expect(fixture.host.innerHTML).toEqual('parentA value2<!--nested ICU 0-->!<!--ICU 0:0-->');
expect(fixture.host.innerHTML).toEqual('parentA value2<!--nested ICU 0-->!<!--ICU 20:0-->');
});
});
});
function toT18n(text: string) {
const tNodeIndex = 0;
const tNodeIndex = HEADER_OFFSET;
fixture.enterView();
i18nStartFirstCreatePass(fixture.tView, 0, fixture.lView, tNodeIndex, text, -1);
fixture.leaveView();
const tI18n = fixture.tView.data[tNodeIndex + HEADER_OFFSET] as TI18n;
const tI18n = fixture.tView.data[tNodeIndex] as TI18n;
expect(tI18n).toEqual(matchTI18n({}));
return tI18n;
}

View File

@ -67,7 +67,7 @@ describe('Runtime i18n', () => {
const fixture = new TemplateFixture(
{create: createTemplate, update: updateTemplate, decls: nbDecls, consts: [messageOrAtrs]});
tView = fixture.hostView[TVIEW];
return tView.data[index + HEADER_OFFSET] as TI18n;
return tView.data[index] as TI18n | I18nUpdateOpCodes;
}
describe('i18nStart', () => {
@ -79,7 +79,7 @@ describe('Runtime i18n', () => {
ɵɵelementStart(0, 'div');
ɵɵi18nStart(index, 0);
ɵɵelementEnd();
}, undefined, nbConsts, index) as TI18n;
}, undefined, nbConsts, HEADER_OFFSET + index) as TI18n;
expect(opCodes).toEqual({
create: matchDebug([
@ -100,7 +100,7 @@ describe('Runtime i18n', () => {
ɵɵelementStart(0, 'div');
ɵɵi18nStart(index, 0);
ɵɵelementEnd();
}, undefined, nbConsts, index);
}, undefined, nbConsts, HEADER_OFFSET + index);
expect(opCodes).toEqual({
create: matchDebug([
@ -125,7 +125,7 @@ describe('Runtime i18n', () => {
ɵɵelementStart(0, 'div');
ɵɵi18nStart(index, 0);
ɵɵelementEnd();
}, undefined, nbConsts, index);
}, undefined, nbConsts, HEADER_OFFSET + index);
expect((opCodes as any).update.debug).toEqual([
'if (mask & 0b1) { (lView[22] as Text).textContent = `Hello ${lView[i-1]}!`; }'
@ -150,7 +150,7 @@ describe('Runtime i18n', () => {
ɵɵelementStart(0, 'div');
ɵɵi18nStart(index, 0);
ɵɵelementEnd();
}, undefined, nbConsts, index);
}, undefined, nbConsts, HEADER_OFFSET + index);
expect(opCodes).toEqual({
create: matchDebug([
@ -183,7 +183,7 @@ describe('Runtime i18n', () => {
ɵɵelementStart(0, 'div');
ɵɵi18nStart(index, 0);
ɵɵelementEnd();
}, undefined, nbConsts, index);
}, undefined, nbConsts, HEADER_OFFSET + index);
expect(opCodes).toEqual({
create: matchDebug([
@ -205,7 +205,7 @@ describe('Runtime i18n', () => {
opCodes = getOpCodes(message, () => {
ɵɵelementStart(0, 'div');
ɵɵi18nStart(index, 0, 1);
}, undefined, nbConsts, index);
}, undefined, nbConsts, index + HEADER_OFFSET);
expect(opCodes).toEqual({
create: matchDebug([
@ -223,7 +223,7 @@ describe('Runtime i18n', () => {
opCodes = getOpCodes(message, () => {
ɵɵelementStart(0, 'div');
ɵɵi18nStart(index, 0, 2);
}, undefined, nbConsts, index);
}, undefined, nbConsts, index + HEADER_OFFSET);
expect(opCodes).toEqual({
create: matchDebug([
@ -245,11 +245,11 @@ describe('Runtime i18n', () => {
ɵɵelementStart(0, 'div');
ɵɵi18nStart(index, 0);
ɵɵelementEnd();
}, undefined, nbConsts, index) as TI18n;
}, undefined, nbConsts, HEADER_OFFSET + index) as TI18n;
expect(opCodes).toEqual({
create: matchDebug([
`lView[${HEADER_OFFSET + 2}] = document.createComment("ICU 1:0");`,
`lView[${HEADER_OFFSET + 2}] = document.createComment("ICU 21:0");`,
`parent.appendChild(lView[${HEADER_OFFSET + 2}]);`,
]),
update: matchDebug([
@ -332,11 +332,11 @@ describe('Runtime i18n', () => {
ɵɵelementStart(0, 'div');
ɵɵi18n(index, 0);
ɵɵelementEnd();
}, undefined, nbConsts, index);
}, undefined, nbConsts, HEADER_OFFSET + index);
expect(opCodes).toEqual({
create: matchDebug([
`lView[${HEADER_OFFSET + 2}] = document.createComment("ICU 1:0");`,
`lView[${HEADER_OFFSET + 2}] = document.createComment("ICU 21:0");`,
`parent.appendChild(lView[${HEADER_OFFSET + 2}]);`,
]),
update: matchDebug([
@ -432,11 +432,11 @@ describe('Runtime i18n', () => {
consts: [attrs],
});
const tView = fixture.hostView[TVIEW];
const opCodes = tView.data[index + HEADER_OFFSET] as I18nUpdateOpCodes;
const opCodes = tView.data[HEADER_OFFSET + index] as I18nUpdateOpCodes;
expect(opCodes).toEqual([]);
expect(
(getNativeByIndex(0, fixture.hostView as LView) as any as Element).getAttribute('title'))
expect((getNativeByIndex(HEADER_OFFSET, fixture.hostView as LView) as any as Element)
.getAttribute('title'))
.toEqual(message);
});
@ -449,7 +449,7 @@ describe('Runtime i18n', () => {
ɵɵelementStart(0, 'div');
ɵɵi18nAttributes(index, 0);
ɵɵelementEnd();
}, undefined, nbConsts, index);
}, undefined, nbConsts, HEADER_OFFSET + index);
expect(opCodes).toEqual(matchDebug([
'if (mask & 0b1) { (lView[20] as Element).setAttribute(\'title\', `Hello ${lView[i-1]}!`); }',
@ -465,7 +465,7 @@ describe('Runtime i18n', () => {
ɵɵelementStart(0, 'div');
ɵɵi18nAttributes(index, 0);
ɵɵelementEnd();
}, undefined, nbConsts, index);
}, 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]}!`); }',
@ -481,7 +481,7 @@ describe('Runtime i18n', () => {
ɵɵelementStart(0, 'div');
ɵɵi18nAttributes(index, 0);
ɵɵelementEnd();
}, undefined, nbConsts, index);
}, undefined, nbConsts, HEADER_OFFSET + index);
expect(opCodes).toEqual(matchDebug([
'if (mask & 0b1) { (lView[20] as Element).setAttribute(\'title\', `Hello ${lView[i-1]}!`); }',
@ -631,14 +631,12 @@ describe('Runtime i18n', () => {
describe('i18nStartFirstCreatePass', () => {
let fixture: ViewFixture;
let divTNode: TElementNode;
const DECLS = 20;
const VARS = 10;
beforeEach(() => {
fixture = new ViewFixture({decls: DECLS, vars: VARS});
fixture.enterView();
ɵɵelementStart(0, 'div');
divTNode = getCurrentTNode() as TElementNode;
});
afterEach(ViewFixture.cleanUp);
@ -661,7 +659,8 @@ describe('Runtime i18n', () => {
}
it('should process text node with no siblings and no children', () => {
i18nStartFirstCreatePass(fixture.tView, 0, fixture.lView, 1, 'Hello World!', -1);
i18nStartFirstCreatePass(
fixture.tView, 0, fixture.lView, HEADER_OFFSET + 1, 'Hello World!', -1);
const ti18n = fixture.tView.data[HEADER_OFFSET + 1] as TI18n;
// Expect that we only create the `Hello World!` text node and nothing else.
expect(ti18n.create).toEqual([
@ -672,7 +671,8 @@ describe('Runtime i18n', () => {
});
it('should process text with a child node', () => {
i18nStartFirstCreatePass(fixture.tView, 0, fixture.lView, 1, 'Hello <20>#2<><32>/#2<>!', -1);
i18nStartFirstCreatePass(
fixture.tView, 0, fixture.lView, HEADER_OFFSET + 1, 'Hello <20>#2<><32>/#2<>!', -1);
const ti18n = fixture.tView.data[HEADER_OFFSET + 1] as TI18n;
expect(ti18n.create).toEqual([
i18nRangeOffsetOpcode(0), 'Hello ', //
@ -689,7 +689,8 @@ describe('Runtime i18n', () => {
});
it('should process text with a child node that has text', () => {
i18nStartFirstCreatePass(fixture.tView, 0, fixture.lView, 1, 'Hello <20>#2<>World<6C>/#2<>!', -1);
i18nStartFirstCreatePass(
fixture.tView, 0, fixture.lView, HEADER_OFFSET + 1, 'Hello <20>#2<>World<6C>/#2<>!', -1);
const ti18n = fixture.tView.data[HEADER_OFFSET + 1] as TI18n;
expect(ti18n.create).toEqual([
i18nRangeOffsetOpcode(0), 'Hello ', //
@ -708,7 +709,7 @@ describe('Runtime i18n', () => {
it('should process text with a child node that has text and with bindings', () => {
i18nStartFirstCreatePass(
fixture.tView, 0, fixture.lView, 1,
fixture.tView, 0, fixture.lView, HEADER_OFFSET + 1,
'<27>0<EFBFBD> <20>#2<><32>1<EFBFBD><31>/#2<>!' /* {{salutation}} <b>{{name}}</b>! */, -1);
const ti18n = fixture.tView.data[HEADER_OFFSET + 1] as TI18n;
expect(ti18n.create).toEqual([
@ -733,7 +734,8 @@ describe('Runtime i18n', () => {
});
it('should process text with a child template', () => {
i18nStartFirstCreatePass(fixture.tView, 0, fixture.lView, 1, 'Hello <20>*2:1<>World<6C>/*2:1<>!', -1);
i18nStartFirstCreatePass(
fixture.tView, 0, fixture.lView, HEADER_OFFSET + 1, 'Hello <20>*2:1<>World<6C>/*2:1<>!', -1);
const ti18n = fixture.tView.data[HEADER_OFFSET + 1] as TI18n;
expect(ti18n.create.debug).toEqual([
'lView[50] = document.createText("Hello ");',

View File

@ -10,7 +10,7 @@ import {createLView, createTNode, createTView} from '@angular/core/src/render3/i
import {TNodeType} from '@angular/core/src/render3/interfaces/node';
import {domRendererFactory3} from '@angular/core/src/render3/interfaces/renderer';
import {HEADER_OFFSET, LViewFlags, TVIEW, TViewType} from '@angular/core/src/render3/interfaces/view';
import {enterView, getBindingRoot, getLView, setBindingIndex} from '@angular/core/src/render3/state';
import {enterView, getBindingRoot, getLView, setBindingIndex, setSelectedIndex} from '@angular/core/src/render3/state';
@ -43,9 +43,10 @@ export function enterViewWithOneDiv() {
const lView = createLView(
null, tView, null, LViewFlags.CheckAlways, null, null, domRendererFactory3, renderer, null,
null);
lView[0 + HEADER_OFFSET] = div;
tView.data[0 + HEADER_OFFSET] = tNode;
lView[HEADER_OFFSET] = div;
tView.data[HEADER_OFFSET] = tNode;
enterView(lView);
setSelectedIndex(HEADER_OFFSET);
}
export function clearFirstUpdatePass() {

View File

@ -14,8 +14,6 @@ import {getTStylingRangeNext, getTStylingRangeNextDuplicate, getTStylingRangePre
import {HEADER_OFFSET, TVIEW} from '@angular/core/src/render3/interfaces/view';
import {getLView, leaveView, setBindingRootForHostBindings} from '@angular/core/src/render3/state';
import {getNativeByIndex} from '@angular/core/src/render3/util/view_utils';
import {bypassSanitizationTrustStyle} from '@angular/core/src/sanitization/bypass';
import {ɵɵsanitizeStyle} from '@angular/core/src/sanitization/sanitization';
import {keyValueArraySet} from '@angular/core/src/util/array_utils';
import {ngDevModeResetPerfCounters} from '@angular/core/src/util/ng_dev_mode';
import {getElementClasses, getElementStyles} from '@angular/core/testing/src/styling';
@ -28,7 +26,7 @@ describe('styling', () => {
afterEach(leaveView);
let div!: HTMLElement;
beforeEach(() => div = getNativeByIndex(0, getLView()) as HTMLElement);
beforeEach(() => div = getNativeByIndex(HEADER_OFFSET, getLView()) as HTMLElement);
it('should do set basic style', () => {
ɵɵstyleProp('color', 'red');

View File

@ -6,14 +6,13 @@
* found in the LICENSE file at https://angular.io/license
*/
import {HEADER_OFFSET} from '@angular/core/src/render3/interfaces/view';
import {dispatchEvent} from '@angular/platform-browser/testing/src/browser_util';
import {ɵɵdefineComponent, ɵɵdefineDirective, ɵɵreference, ɵɵresolveBody, ɵɵresolveDocument} from '../../src/render3/index';
import {ɵɵelement, ɵɵelementEnd, ɵɵelementStart, ɵɵgetCurrentView, ɵɵlistener, ɵɵtext} from '../../src/render3/instructions/all';
import {RenderFlags} from '../../src/render3/interfaces/definition';
import {GlobalTargetResolver} from '../../src/render3/interfaces/renderer';
import {ɵɵrestoreView} from '../../src/render3/state';
import {getRendererFactory2} from './imported_renderer2';
import {ComponentFixture, containerEl, createComponent, getDirectiveOnNode, renderToHtml, TemplateFixture} from './render_util';
@ -497,7 +496,7 @@ describe('event listeners', () => {
}
// testing only
compInstance = getDirectiveOnNode(0);
compInstance = getDirectiveOnNode(HEADER_OFFSET);
},
directives: [Comp]
});

View File

@ -56,7 +56,7 @@ describe('render3 matchers', () => {
});
describe('matchTNode', () => {
const tView = createTView(TViewType.Root, null, null, 2, 3, null, null, null, null, null);
const tNode = createTNode(tView, null, TNodeType.Element, 1, 'tagName', []);
const tNode = createTNode(tView, null, TNodeType.Element, 0, 'tagName', []);
it('should match', () => {
expect(tNode).toEqual(matchTNode());

View File

@ -7,6 +7,7 @@
*/
import {ElementRef, QueryList, TemplateRef, ViewContainerRef} from '@angular/core';
import {HEADER_OFFSET} from '@angular/core/src/render3/interfaces/view';
import {AttributeMarker, ɵɵdefineComponent, ɵɵdefineDirective, ɵɵProvidersFeature} from '../../src/render3/index';
import {ɵɵdirectiveInject, ɵɵelement, ɵɵelementContainerEnd, ɵɵelementContainerStart, ɵɵelementEnd, ɵɵelementStart, ɵɵtemplate, ɵɵtext} from '../../src/render3/instructions/all';
@ -72,8 +73,8 @@ describe('query', () => {
ɵɵelementEnd();
}
if (rf & RenderFlags.Update) {
child1 = getDirectiveOnNode(0);
child2 = getDirectiveOnNode(1);
child1 = getDirectiveOnNode(HEADER_OFFSET);
child2 = getDirectiveOnNode(HEADER_OFFSET + 1);
}
},
2, 0, [Child], [],
@ -112,7 +113,7 @@ describe('query', () => {
function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
ɵɵelement(0, 'div', 0);
elToQuery = getNativeByIndex(0, getLView());
elToQuery = getNativeByIndex(HEADER_OFFSET, getLView());
}
},
1, 0, [Child], [],
@ -150,7 +151,7 @@ describe('query', () => {
function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
ɵɵelementStart(0, 'div', 0);
{ otherChildInstance = getDirectiveOnNode(0, 1); }
{ otherChildInstance = getDirectiveOnNode(HEADER_OFFSET, 1); }
ɵɵelementEnd();
}
},
@ -345,7 +346,7 @@ describe('query', () => {
function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
ɵɵelement(0, 'div', null, 0);
elToQuery = getNativeByIndex(0, getLView());
elToQuery = getNativeByIndex(HEADER_OFFSET, getLView());
ɵɵelement(2, 'div');
}
},
@ -384,7 +385,7 @@ describe('query', () => {
function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
ɵɵelement(0, 'div', null, 0);
elToQuery = getNativeByIndex(0, getLView());
elToQuery = getNativeByIndex(HEADER_OFFSET, getLView());
ɵɵelement(3, 'div');
}
},
@ -431,10 +432,10 @@ describe('query', () => {
function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
ɵɵelement(0, 'div', null, 0);
el1ToQuery = getNativeByIndex(0, getLView());
el1ToQuery = getNativeByIndex(HEADER_OFFSET, getLView());
ɵɵelement(2, 'div');
ɵɵelement(3, 'div', null, 1);
el2ToQuery = getNativeByIndex(3, getLView());
el2ToQuery = getNativeByIndex(HEADER_OFFSET + 3, getLView());
}
},
5, 0, [], [],
@ -471,7 +472,7 @@ describe('query', () => {
function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
ɵɵelement(0, 'div', null, 0);
elToQuery = getNativeByIndex(0, getLView());
elToQuery = getNativeByIndex(HEADER_OFFSET, getLView());
ɵɵelement(2, 'div');
}
},
@ -509,7 +510,7 @@ describe('query', () => {
function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
ɵɵelementContainerStart(0, null, 0);
elToQuery = getNativeByIndex(0, getLView());
elToQuery = getNativeByIndex(HEADER_OFFSET, getLView());
ɵɵelementContainerEnd();
}
},
@ -546,7 +547,7 @@ describe('query', () => {
function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
ɵɵelementContainerStart(0, null, 0);
elToQuery = getNativeByIndex(0, getLView());
elToQuery = getNativeByIndex(HEADER_OFFSET, getLView());
ɵɵelementContainerEnd();
}
},
@ -756,7 +757,7 @@ describe('query', () => {
ɵɵelement(0, 'child', null, 0);
}
if (rf & RenderFlags.Update) {
childInstance = getDirectiveOnNode(0);
childInstance = getDirectiveOnNode(HEADER_OFFSET);
}
},
2, 0, [Child], [],
@ -843,7 +844,7 @@ describe('query', () => {
ɵɵelement(0, 'div', 0, 1);
}
if (rf & RenderFlags.Update) {
childInstance = getDirectiveOnNode(0);
childInstance = getDirectiveOnNode(HEADER_OFFSET);
}
},
2, 0, [Child], [],
@ -883,8 +884,8 @@ describe('query', () => {
ɵɵelement(0, 'div', 0, 1);
}
if (rf & RenderFlags.Update) {
child1Instance = getDirectiveOnNode(0, 0);
child2Instance = getDirectiveOnNode(0, 1);
child1Instance = getDirectiveOnNode(HEADER_OFFSET, 0);
child2Instance = getDirectiveOnNode(HEADER_OFFSET, 1);
}
},
3, 0, [Child1, Child2], [],
@ -925,7 +926,7 @@ describe('query', () => {
ɵɵelement(0, 'div', 0, 1);
}
if (rf & RenderFlags.Update) {
childInstance = getDirectiveOnNode(0);
childInstance = getDirectiveOnNode(HEADER_OFFSET);
}
},
3, 0, [Child], [],
@ -970,7 +971,7 @@ describe('query', () => {
function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
ɵɵelement(0, 'div', 0, 1);
div = getNativeByIndex(0, getLView());
div = getNativeByIndex(HEADER_OFFSET, getLView());
}
},
2, 0, [Child], [],
@ -1007,10 +1008,10 @@ describe('query', () => {
function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
ɵɵelement(0, 'div', 0, 1);
div = getNativeByIndex(0, getLView());
div = getNativeByIndex(HEADER_OFFSET, getLView());
}
if (rf & RenderFlags.Update) {
childInstance = getDirectiveOnNode(0);
childInstance = getDirectiveOnNode(HEADER_OFFSET);
}
},
3, 0, [Child], [],
@ -1570,8 +1571,8 @@ describe('query', () => {
}
if (rf & RenderFlags.Update) {
const lView = getLView();
outInstance = load<QueryDirective>(lView, 1);
inInstance = load<QueryDirective>(lView, 5);
outInstance = load<QueryDirective>(lView, HEADER_OFFSET + 1);
inInstance = load<QueryDirective>(lView, HEADER_OFFSET + 5);
}
},
10, 0, [QueryDirective], [], null, [], [], undefined, [
@ -1632,8 +1633,8 @@ describe('query', () => {
}
if (rf & RenderFlags.Update) {
const lView = getLView();
outInstance = load<QueryDirective>(lView, 1);
inInstance = load<QueryDirective>(lView, 3);
outInstance = load<QueryDirective>(lView, HEADER_OFFSET + 1);
inInstance = load<QueryDirective>(lView, HEADER_OFFSET + 3);
}
},
7, 0, [QueryDirective], [], null, [], [], undefined, [
@ -1692,8 +1693,8 @@ describe('query', () => {
}
if (rf & RenderFlags.Update) {
const lView = getLView();
outInstance = load<QueryDirective>(lView, 1);
inInstance = load<QueryDirective>(lView, 3);
outInstance = load<QueryDirective>(lView, HEADER_OFFSET + 1);
inInstance = load<QueryDirective>(lView, HEADER_OFFSET + 3);
}
},
7, 0, [QueryDirective], [], null, [], [], undefined, [
@ -1783,8 +1784,8 @@ describe('query', () => {
}
if (rf & RenderFlags.Update) {
const lView = getLView();
shallowInstance = load<ShallowQueryDirective>(lView, 1);
deepInstance = load<DeepQueryDirective>(lView, 2);
shallowInstance = load<ShallowQueryDirective>(lView, HEADER_OFFSET + 1);
deepInstance = load<DeepQueryDirective>(lView, HEADER_OFFSET + 2);
}
},
8, 0, [ShallowQueryDirective, DeepQueryDirective], [], null, [], [], undefined, [

View File

@ -17,7 +17,6 @@ import {TConstants, TNodeType} from '@angular/core/src/render3/interfaces/node';
import {enterView, getLView} from '@angular/core/src/render3/state';
import {EMPTY_ARRAY} from '@angular/core/src/util/empty';
import {stringifyElement} from '@angular/platform-browser/testing/src/browser_util';
import {SWITCH_CHANGE_DETECTOR_REF_FACTORY__POST_R3__ as R3_CHANGE_DETECTOR_REF_FACTORY} from '../../src/change_detection/change_detector_ref';
import {Injector} from '../../src/di/injector';
import {Type} from '../../src/interface/type';
@ -33,11 +32,10 @@ import {ComponentDef, ComponentTemplate, ComponentType, DirectiveDef, DirectiveT
import {DirectiveDefList, DirectiveDefListOrFactory, DirectiveTypesOrFactory, HostBindingsFunction, PipeDef, PipeDefList, PipeDefListOrFactory, PipeTypesOrFactory} from '../../src/render3/interfaces/definition';
import {PlayerHandler} from '../../src/render3/interfaces/player';
import {domRendererFactory3, ProceduralRenderer3, RComment, RElement, Renderer3, RendererFactory3, RendererStyleFlags3, RNode, RText} from '../../src/render3/interfaces/renderer';
import {HEADER_OFFSET, LView, LViewFlags, T_HOST, TVIEW, TViewType} from '../../src/render3/interfaces/view';
import {HEADER_OFFSET, LView, LViewFlags, TVIEW, TViewType} from '../../src/render3/interfaces/view';
import {destroyLView} from '../../src/render3/node_manipulation';
import {getRootView} from '../../src/render3/util/view_traversal_utils';
import {Sanitizer} from '../../src/sanitization/sanitizer';
import {getRendererFactory2} from './imported_renderer2';
@ -434,7 +432,7 @@ export function createDirective(
/** Gets the directive on the given node at the given index */
export function getDirectiveOnNode(nodeIndex: number, dirIndex: number = 0) {
const directives = getDirectivesAtNodeIndex(nodeIndex + HEADER_OFFSET, getLView(), true);
const directives = getDirectivesAtNodeIndex(nodeIndex, getLView(), true);
if (directives == null) {
throw new Error(`No directives exist on node in slot ${nodeIndex}`);
}

View File

@ -6,6 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {HEADER_OFFSET} from '@angular/core/src/render3/interfaces/view';
import {ChangeDetectorRef, Component as _Component, ComponentFactoryResolver, ElementRef, QueryList, TemplateRef, ViewContainerRef, ViewRef} from '../../src/core';
import {ViewEncapsulation} from '../../src/metadata';
import {injectComponentFactoryResolver, ɵɵdefineComponent, ɵɵdefineDirective, ɵɵlistener, ɵɵloadQuery, ɵɵqueryRefresh, ɵɵviewQuery} from '../../src/render3/index';
@ -14,9 +15,9 @@ import {RenderFlags} from '../../src/render3/interfaces/definition';
import {RElement} from '../../src/render3/interfaces/renderer';
import {getLView} from '../../src/render3/state';
import {getNativeByIndex} from '../../src/render3/util/view_utils';
import {ComponentFixture, createComponent, TemplateFixture} from './render_util';
const Component: typeof _Component = function(...args: any[]): any {
// In test we use @Component for documentation only so it's safe to mock out the implementation.
return () => undefined;
@ -362,7 +363,7 @@ describe('ViewContainerRef', () => {
ɵɵelement(0, 'div', 1, 0);
}
// testing only
fooEl = getNativeByIndex(0, getLView()) as RElement;
fooEl = getNativeByIndex(HEADER_OFFSET, getLView()) as RElement;
},
viewQuery:
function(rf: RenderFlags, ctx: any) {