fix(core): Store the currently selected ICU in `LView` (#38345)
The currently selected ICU was incorrectly being stored it `TNode` rather than in `LView`. Remove: `TIcuContainerNode.activeCaseIndex` Add: `LView[TIcu.currentCaseIndex]` PR Close #38345
This commit is contained in:
parent
6ff28ac944
commit
6d8c73a4d6
|
@ -62,7 +62,7 @@
|
||||||
"bundle": "TODO(i): we should define ngDevMode to false in Closure, but --define only works in the global scope.",
|
"bundle": "TODO(i): we should define ngDevMode to false in Closure, but --define only works in the global scope.",
|
||||||
"bundle": "TODO(i): (FW-2164) TS 3.9 new class shape seems to have broken Closure in big ways. The size went from 169991 to 252338",
|
"bundle": "TODO(i): (FW-2164) TS 3.9 new class shape seems to have broken Closure in big ways. The size went from 169991 to 252338",
|
||||||
"bundle": "TODO(i): after removal of tsickle from ngc-wrapped / ng_package, we had to switch to SIMPLE optimizations which increased the size from 252338 to 1198917, see PR#37221 and PR#37317 for more info",
|
"bundle": "TODO(i): after removal of tsickle from ngc-wrapped / ng_package, we had to switch to SIMPLE optimizations which increased the size from 252338 to 1198917, see PR#37221 and PR#37317 for more info",
|
||||||
"bundle": 1213130
|
"bundle": 1213769
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {devModeEqual} from '../change_detection/change_detection_util';
|
import {devModeEqual} from '../change_detection/change_detection_util';
|
||||||
import {assertDataInRange, assertLessThan, assertNotSame} from '../util/assert';
|
import {assertIndexInRange, assertLessThan, assertNotSame} from '../util/assert';
|
||||||
|
|
||||||
import {getExpressionChangedErrorDetails, throwErrorIfNoChangesMode} from './errors';
|
import {getExpressionChangedErrorDetails, throwErrorIfNoChangesMode} from './errors';
|
||||||
import {LView} from './interfaces/view';
|
import {LView} from './interfaces/view';
|
||||||
|
@ -24,7 +24,7 @@ export function updateBinding(lView: LView, bindingIndex: number, value: any): a
|
||||||
|
|
||||||
/** Gets the current binding value. */
|
/** Gets the current binding value. */
|
||||||
export function getBinding(lView: LView, bindingIndex: number): any {
|
export function getBinding(lView: LView, bindingIndex: number): any {
|
||||||
ngDevMode && assertDataInRange(lView, bindingIndex);
|
ngDevMode && assertIndexInRange(lView, bindingIndex);
|
||||||
ngDevMode &&
|
ngDevMode &&
|
||||||
assertNotSame(lView[bindingIndex], NO_CHANGE, 'Stored value should never be NO_CHANGE.');
|
assertNotSame(lView[bindingIndex], NO_CHANGE, 'Stored value should never be NO_CHANGE.');
|
||||||
return lView[bindingIndex];
|
return lView[bindingIndex];
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
import {Type} from '../core';
|
import {Type} from '../core';
|
||||||
import {Injector} from '../di/injector';
|
import {Injector} from '../di/injector';
|
||||||
import {Sanitizer} from '../sanitization/sanitizer';
|
import {Sanitizer} from '../sanitization/sanitizer';
|
||||||
import {assertDataInRange} from '../util/assert';
|
import {assertIndexInRange} from '../util/assert';
|
||||||
|
|
||||||
import {assertComponentType} from './assert';
|
import {assertComponentType} from './assert';
|
||||||
import {getComponentDef} from './definition';
|
import {getComponentDef} from './definition';
|
||||||
|
@ -172,7 +172,7 @@ export function createRootComponentView(
|
||||||
rNode: RElement|null, def: ComponentDef<any>, rootView: LView,
|
rNode: RElement|null, def: ComponentDef<any>, rootView: LView,
|
||||||
rendererFactory: RendererFactory3, hostRenderer: Renderer3, sanitizer?: Sanitizer|null): LView {
|
rendererFactory: RendererFactory3, hostRenderer: Renderer3, sanitizer?: Sanitizer|null): LView {
|
||||||
const tView = rootView[TVIEW];
|
const tView = rootView[TVIEW];
|
||||||
ngDevMode && assertDataInRange(rootView, 0 + HEADER_OFFSET);
|
ngDevMode && assertIndexInRange(rootView, 0 + HEADER_OFFSET);
|
||||||
rootView[0 + HEADER_OFFSET] = rNode;
|
rootView[0 + HEADER_OFFSET] = rNode;
|
||||||
const tNode: TElementNode = getOrCreateTNode(tView, null, 0, TNodeType.Element, null, null);
|
const tNode: TElementNode = getOrCreateTNode(tView, null, 0, TNodeType.Element, null, null);
|
||||||
const mergedAttrs = tNode.mergedAttrs = def.hostAttrs;
|
const mergedAttrs = tNode.mergedAttrs = def.hostAttrs;
|
||||||
|
|
|
@ -13,13 +13,13 @@ import {getTemplateContent, SRCSET_ATTRS, URI_ATTRS, VALID_ATTRS, VALID_ELEMENTS
|
||||||
import {getInertBodyHelper} from '../sanitization/inert_body';
|
import {getInertBodyHelper} from '../sanitization/inert_body';
|
||||||
import {_sanitizeUrl, sanitizeSrcset} from '../sanitization/url_sanitizer';
|
import {_sanitizeUrl, sanitizeSrcset} from '../sanitization/url_sanitizer';
|
||||||
import {addAllToArray} from '../util/array_utils';
|
import {addAllToArray} from '../util/array_utils';
|
||||||
import {assertDataInRange, assertDefined, assertEqual} from '../util/assert';
|
import {assertDefined, assertEqual, assertGreaterThan, assertIndexInRange} from '../util/assert';
|
||||||
|
|
||||||
import {bindingUpdated} from './bindings';
|
import {bindingUpdated} from './bindings';
|
||||||
import {attachPatchData} from './context_discovery';
|
import {attachPatchData} from './context_discovery';
|
||||||
import {i18nMutateOpCodesToString, i18nUpdateOpCodesToString} from './i18n_debug';
|
import {i18nMutateOpCodesToString, i18nUpdateOpCodesToString} from './i18n_debug';
|
||||||
import {setDelayProjection} from './instructions/all';
|
import {setDelayProjection} from './instructions/all';
|
||||||
import {allocExpando, elementAttributeInternal, elementPropertyInternal, getOrCreateTNode, setInputsForProperty, setNgReflectProperties, textBindingInternal} from './instructions/shared';
|
import {allocExpando, elementAttributeInternal, elementPropertyInternal, getOrCreateTNode, setInputsForProperty, setNgReflectProperties, textBindingInternal as applyTextBinding} from './instructions/shared';
|
||||||
import {LContainer, NATIVE} from './interfaces/container';
|
import {LContainer, NATIVE} from './interfaces/container';
|
||||||
import {getDocument} from './interfaces/document';
|
import {getDocument} from './interfaces/document';
|
||||||
import {COMMENT_MARKER, ELEMENT_MARKER, I18nMutateOpCode, I18nMutateOpCodes, I18nUpdateOpCode, I18nUpdateOpCodes, IcuType, TI18n, TIcu} from './interfaces/i18n';
|
import {COMMENT_MARKER, ELEMENT_MARKER, I18nMutateOpCode, I18nMutateOpCodes, I18nUpdateOpCode, I18nUpdateOpCodes, IcuType, TI18n, TIcu} from './interfaces/i18n';
|
||||||
|
@ -726,7 +726,7 @@ function i18nEndFirstPass(tView: TView, lView: LView) {
|
||||||
const lastCreatedNode = getPreviousOrParentTNode();
|
const lastCreatedNode = getPreviousOrParentTNode();
|
||||||
|
|
||||||
// Read the instructions to insert/move/remove DOM elements
|
// Read the instructions to insert/move/remove DOM elements
|
||||||
const visitedNodes = readCreateOpCodes(rootIndex, tI18n.create, tView, lView);
|
const visitedNodes = applyCreateOpCodes(tView, rootIndex, tI18n.create, lView);
|
||||||
|
|
||||||
// Remove deleted nodes
|
// Remove deleted nodes
|
||||||
let index = rootIndex + 1;
|
let index = rootIndex + 1;
|
||||||
|
@ -756,7 +756,7 @@ function createDynamicNodeAtIndex(
|
||||||
tView: TView, lView: LView, index: number, type: TNodeType, native: RElement|RText|null,
|
tView: TView, lView: LView, index: number, type: TNodeType, native: RElement|RText|null,
|
||||||
name: string|null): TElementNode|TIcuContainerNode {
|
name: string|null): TElementNode|TIcuContainerNode {
|
||||||
const previousOrParentTNode = getPreviousOrParentTNode();
|
const previousOrParentTNode = getPreviousOrParentTNode();
|
||||||
ngDevMode && assertDataInRange(lView, index + HEADER_OFFSET);
|
ngDevMode && assertIndexInRange(lView, index + HEADER_OFFSET);
|
||||||
lView[index + HEADER_OFFSET] = native;
|
lView[index + HEADER_OFFSET] = native;
|
||||||
// FIXME(misko): Why does this create A TNode??? I would not expect this to be here.
|
// FIXME(misko): Why does this create A TNode??? I would not expect this to be here.
|
||||||
const tNode = getOrCreateTNode(tView, lView[T_HOST], index, type as any, name, null);
|
const tNode = getOrCreateTNode(tView, lView[T_HOST], index, type as any, name, null);
|
||||||
|
@ -770,8 +770,16 @@ function createDynamicNodeAtIndex(
|
||||||
return tNode;
|
return tNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
function readCreateOpCodes(
|
/**
|
||||||
index: number, createOpCodes: I18nMutateOpCodes, tView: TView, lView: LView): number[] {
|
* Apply `I18nMutateOpCodes` OpCodes.
|
||||||
|
*
|
||||||
|
* @param tView Current `TView`
|
||||||
|
* @param rootIndex Pointer to the root (parent) tNode for the i18n.
|
||||||
|
* @param createOpCodes OpCodes to process
|
||||||
|
* @param lView Current `LView`
|
||||||
|
*/
|
||||||
|
function applyCreateOpCodes(
|
||||||
|
tView: TView, rootindex: number, createOpCodes: I18nMutateOpCodes, lView: LView): number[] {
|
||||||
const renderer = lView[RENDERER];
|
const renderer = lView[RENDERER];
|
||||||
let currentTNode: TNode|null = null;
|
let currentTNode: TNode|null = null;
|
||||||
let previousTNode: TNode|null = null;
|
let previousTNode: TNode|null = null;
|
||||||
|
@ -792,7 +800,7 @@ function readCreateOpCodes(
|
||||||
case I18nMutateOpCode.AppendChild:
|
case I18nMutateOpCode.AppendChild:
|
||||||
const destinationNodeIndex = opCode >>> I18nMutateOpCode.SHIFT_PARENT;
|
const destinationNodeIndex = opCode >>> I18nMutateOpCode.SHIFT_PARENT;
|
||||||
let destinationTNode: TNode;
|
let destinationTNode: TNode;
|
||||||
if (destinationNodeIndex === index) {
|
if (destinationNodeIndex === rootindex) {
|
||||||
// If the destination node is `i18nStart`, we don't have a
|
// If the destination node is `i18nStart`, we don't have a
|
||||||
// top-level node and we should use the host node instead
|
// top-level node and we should use the host node instead
|
||||||
destinationTNode = lView[T_HOST]!;
|
destinationTNode = lView[T_HOST]!;
|
||||||
|
@ -852,7 +860,6 @@ function readCreateOpCodes(
|
||||||
tView, lView, commentNodeIndex, TNodeType.IcuContainer, commentRNode, null);
|
tView, lView, commentNodeIndex, TNodeType.IcuContainer, commentRNode, null);
|
||||||
visitedNodes.push(commentNodeIndex);
|
visitedNodes.push(commentNodeIndex);
|
||||||
attachPatchData(commentRNode, lView);
|
attachPatchData(commentRNode, lView);
|
||||||
(currentTNode as TIcuContainerNode).activeCaseIndex = null;
|
|
||||||
// We will add the case nodes later, during the update phase
|
// We will add the case nodes later, during the update phase
|
||||||
setIsNotParent();
|
setIsNotParent();
|
||||||
break;
|
break;
|
||||||
|
@ -881,16 +888,27 @@ function readCreateOpCodes(
|
||||||
return visitedNodes;
|
return visitedNodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
function readUpdateOpCodes(
|
/**
|
||||||
updateOpCodes: I18nUpdateOpCodes, icus: TIcu[]|null, bindingsStartIndex: number,
|
* Apply `I18nUpdateOpCodes` OpCodes
|
||||||
changeMask: number, tView: TView, lView: LView, bypassCheckBit: boolean) {
|
*
|
||||||
|
* @param tView Current `TView`
|
||||||
|
* @param tIcus If ICUs present than this contains them.
|
||||||
|
* @param lView Current `LView`
|
||||||
|
* @param updateOpCodes OpCodes to process
|
||||||
|
* @param bindingsStartIndex Location of the first `ɵɵi18nApply`
|
||||||
|
* @param changeMask Each bit corresponds to a `ɵɵi18nExp` (Counting backwards from
|
||||||
|
* `bindingsStartIndex`)
|
||||||
|
*/
|
||||||
|
function applyUpdateOpCodes(
|
||||||
|
tView: TView, tIcus: TIcu[]|null, lView: LView, updateOpCodes: I18nUpdateOpCodes,
|
||||||
|
bindingsStartIndex: number, changeMask: number) {
|
||||||
let caseCreated = false;
|
let caseCreated = false;
|
||||||
for (let i = 0; i < updateOpCodes.length; i++) {
|
for (let i = 0; i < updateOpCodes.length; i++) {
|
||||||
// bit code to check if we should apply the next update
|
// bit code to check if we should apply the next update
|
||||||
const checkBit = updateOpCodes[i] as number;
|
const checkBit = updateOpCodes[i] as number;
|
||||||
// Number of opCodes to skip until next set of update codes
|
// Number of opCodes to skip until next set of update codes
|
||||||
const skipCodes = updateOpCodes[++i] as number;
|
const skipCodes = updateOpCodes[++i] as number;
|
||||||
if (bypassCheckBit || (checkBit & changeMask)) {
|
if (checkBit & changeMask) {
|
||||||
// The value has been updated since last checked
|
// The value has been updated since last checked
|
||||||
let value = '';
|
let value = '';
|
||||||
for (let j = i + 1; j <= (i + skipCodes); j++) {
|
for (let j = i + 1; j <= (i + skipCodes); j++) {
|
||||||
|
@ -912,16 +930,16 @@ function readUpdateOpCodes(
|
||||||
sanitizeFn, false);
|
sanitizeFn, false);
|
||||||
break;
|
break;
|
||||||
case I18nUpdateOpCode.Text:
|
case I18nUpdateOpCode.Text:
|
||||||
textBindingInternal(lView, nodeIndex, value);
|
applyTextBinding(lView, nodeIndex, value);
|
||||||
break;
|
break;
|
||||||
case I18nUpdateOpCode.IcuSwitch:
|
case I18nUpdateOpCode.IcuSwitch:
|
||||||
caseCreated = icuSwitchCase(
|
caseCreated =
|
||||||
tView, updateOpCodes[++j] as number, nodeIndex, icus!, lView, value);
|
applyIcuSwitchCase(tView, tIcus!, updateOpCodes[++j] as number, lView, value);
|
||||||
break;
|
break;
|
||||||
case I18nUpdateOpCode.IcuUpdate:
|
case I18nUpdateOpCode.IcuUpdate:
|
||||||
icuUpdateCase(
|
applyIcuUpdateCase(
|
||||||
tView, lView, updateOpCodes[++j] as number, nodeIndex, bindingsStartIndex,
|
tView, tIcus!, updateOpCodes[++j] as number, bindingsStartIndex, lView,
|
||||||
icus!, caseCreated);
|
caseCreated);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -932,68 +950,100 @@ function readUpdateOpCodes(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function icuUpdateCase(
|
/**
|
||||||
tView: TView, lView: LView, tIcuIndex: number, nodeIndex: number, bindingsStartIndex: number,
|
* Apply OpCodes associated with updating an existing ICU.
|
||||||
tIcus: TIcu[], caseCreated: boolean) {
|
*
|
||||||
|
* @param tView Current `TView`
|
||||||
|
* @param tIcus tIcus ICUs active at this location them.
|
||||||
|
* @param tIcuIndex Index into `tIcus` to process.
|
||||||
|
* @param bindingsStartIndex Location of the first `ɵɵi18nApply`
|
||||||
|
* @param lView Current `LView`
|
||||||
|
* @param changeMask Each bit corresponds to a `ɵɵi18nExp` (Counting backwards from
|
||||||
|
* `bindingsStartIndex`)
|
||||||
|
*/
|
||||||
|
function applyIcuUpdateCase(
|
||||||
|
tView: TView, tIcus: TIcu[], tIcuIndex: number, bindingsStartIndex: number, lView: LView,
|
||||||
|
caseCreated: boolean) {
|
||||||
|
ngDevMode && assertIndexInRange(tIcus, tIcuIndex);
|
||||||
const tIcu = tIcus[tIcuIndex];
|
const tIcu = tIcus[tIcuIndex];
|
||||||
const icuTNode = getTNode(tView, nodeIndex) as TIcuContainerNode;
|
ngDevMode && assertIndexInRange(lView, tIcu.currentCaseLViewIndex);
|
||||||
if (icuTNode.activeCaseIndex !== null) {
|
const activeCaseIndex = lView[tIcu.currentCaseLViewIndex];
|
||||||
readUpdateOpCodes(
|
if (activeCaseIndex !== null) {
|
||||||
tIcu.update[icuTNode.activeCaseIndex], tIcus, bindingsStartIndex, changeMask, tView, lView,
|
const mask = caseCreated ?
|
||||||
caseCreated);
|
-1 : // -1 is same as all bits on, which simulates creation since it marks all bits dirty
|
||||||
|
changeMask;
|
||||||
|
applyUpdateOpCodes(tView, tIcus, lView, tIcu.update[activeCaseIndex], bindingsStartIndex, mask);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function icuSwitchCase(
|
/**
|
||||||
tView: TView, tIcuIndex: number, nodeIndex: number, tIcus: TIcu[], lView: LView,
|
* Apply OpCodes associated with switching a case on ICU.
|
||||||
value: string): boolean {
|
*
|
||||||
const tIcu = tIcus[tIcuIndex];
|
* This involves tearing down existing case and than building up a new case.
|
||||||
const icuTNode = getTNode(tView, nodeIndex) as TIcuContainerNode;
|
*
|
||||||
|
* @param tView Current `TView`
|
||||||
|
* @param tIcus ICUs active at this location.
|
||||||
|
* @param tIcuIndex Index into `tIcus` to process.
|
||||||
|
* @param lView Current `LView`
|
||||||
|
* @param value Value of the case to update to.
|
||||||
|
* @returns true if a new case was created (needed so that the update executes regardless of the
|
||||||
|
* bitmask)
|
||||||
|
*/
|
||||||
|
function applyIcuSwitchCase(
|
||||||
|
tView: TView, tIcus: TIcu[], tIcuIndex: number, lView: LView, value: string): boolean {
|
||||||
|
applyIcuSwitchCaseRemove(tView, tIcus, tIcuIndex, lView);
|
||||||
|
|
||||||
|
// Rebuild a new case for this ICU
|
||||||
let caseCreated = false;
|
let caseCreated = false;
|
||||||
// If there is an active case, delete the old nodes
|
ngDevMode && assertIndexInRange(tIcus, tIcuIndex);
|
||||||
if (icuTNode.activeCaseIndex !== null) {
|
const tIcu = tIcus[tIcuIndex];
|
||||||
const removeCodes = tIcu.remove[icuTNode.activeCaseIndex];
|
const caseIndex = getCaseIndex(tIcu, value);
|
||||||
|
lView[tIcu.currentCaseLViewIndex] = caseIndex !== -1 ? caseIndex : null;
|
||||||
|
if (caseIndex > -1) {
|
||||||
|
// Add the nodes for the new case
|
||||||
|
applyCreateOpCodes(
|
||||||
|
tView, -1, // -1 means we don't have parent node
|
||||||
|
tIcu.create[caseIndex], lView);
|
||||||
|
caseCreated = true;
|
||||||
|
}
|
||||||
|
return caseCreated;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply OpCodes associated with tearing down of DOM.
|
||||||
|
*
|
||||||
|
* This involves tearing down existing case and than building up a new case.
|
||||||
|
*
|
||||||
|
* @param tView Current `TView`
|
||||||
|
* @param tIcus ICUs active at this location.
|
||||||
|
* @param tIcuIndex Index into `tIcus` to process.
|
||||||
|
* @param lView Current `LView`
|
||||||
|
* @returns true if a new case was created (needed so that the update executes regardless of the
|
||||||
|
* bitmask)
|
||||||
|
*/
|
||||||
|
function applyIcuSwitchCaseRemove(tView: TView, tIcus: TIcu[], tIcuIndex: number, lView: LView) {
|
||||||
|
ngDevMode && assertIndexInRange(tIcus, tIcuIndex);
|
||||||
|
const tIcu = tIcus[tIcuIndex];
|
||||||
|
const activeCaseIndex = lView[tIcu.currentCaseLViewIndex];
|
||||||
|
if (activeCaseIndex !== null) {
|
||||||
|
const removeCodes = tIcu.remove[activeCaseIndex];
|
||||||
for (let k = 0; k < removeCodes.length; k++) {
|
for (let k = 0; k < removeCodes.length; k++) {
|
||||||
const removeOpCode = removeCodes[k] as number;
|
const removeOpCode = removeCodes[k] as number;
|
||||||
const nodeOrIcuIndex = removeOpCode >>> I18nMutateOpCode.SHIFT_REF;
|
const nodeOrIcuIndex = removeOpCode >>> I18nMutateOpCode.SHIFT_REF;
|
||||||
switch (removeOpCode & I18nMutateOpCode.MASK_INSTRUCTION) {
|
switch (removeOpCode & I18nMutateOpCode.MASK_INSTRUCTION) {
|
||||||
case I18nMutateOpCode.Remove:
|
case I18nMutateOpCode.Remove:
|
||||||
|
// FIXME(misko): this comment is wrong!
|
||||||
// Remove DOM element, but do *not* mark TNode as detached, since we are
|
// Remove DOM element, but do *not* mark TNode as detached, since we are
|
||||||
// just switching ICU cases (while keeping the same TNode), so a DOM element
|
// just switching ICU cases (while keeping the same TNode), so a DOM element
|
||||||
// representing a new ICU case will be re-created.
|
// representing a new ICU case will be re-created.
|
||||||
removeNode(tView, lView, nodeOrIcuIndex, /* markAsDetached */ false);
|
removeNode(tView, lView, nodeOrIcuIndex, /* markAsDetached */ false);
|
||||||
break;
|
break;
|
||||||
case I18nMutateOpCode.RemoveNestedIcu:
|
case I18nMutateOpCode.RemoveNestedIcu:
|
||||||
removeNestedIcu(
|
applyIcuSwitchCaseRemove(tView, tIcus, nodeOrIcuIndex, lView);
|
||||||
tView, tIcus, removeCodes, nodeOrIcuIndex,
|
|
||||||
removeCodes[k + 1] as number >>> I18nMutateOpCode.SHIFT_REF);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the active caseIndex
|
|
||||||
const caseIndex = getCaseIndex(tIcu, value);
|
|
||||||
icuTNode.activeCaseIndex = caseIndex !== -1 ? caseIndex : null;
|
|
||||||
if (caseIndex > -1) {
|
|
||||||
// Add the nodes for the new case
|
|
||||||
readCreateOpCodes(
|
|
||||||
-1 /* -1 means we don't have parent node */, tIcu.create[caseIndex], tView, lView);
|
|
||||||
caseCreated = true;
|
|
||||||
}
|
|
||||||
return caseCreated;
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeNestedIcu(
|
|
||||||
tView: TView, tIcus: TIcu[], removeCodes: I18nMutateOpCodes, nodeIndex: number,
|
|
||||||
nestedIcuNodeIndex: number) {
|
|
||||||
const nestedIcuTNode = getTNode(tView, nestedIcuNodeIndex) as TIcuContainerNode;
|
|
||||||
const activeIndex = nestedIcuTNode.activeCaseIndex;
|
|
||||||
if (activeIndex !== null) {
|
|
||||||
const nestedTIcu = tIcus[nodeIndex];
|
|
||||||
// FIXME(misko): the fact that we are adding items to parent list looks very suspect!
|
|
||||||
addAllToArray(nestedTIcu.remove[activeIndex], removeCodes);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeNode(tView: TView, lView: LView, index: number, markAsDetached: boolean) {
|
function removeNode(tView: TView, lView: LView, index: number, markAsDetached: boolean) {
|
||||||
|
@ -1153,18 +1203,18 @@ export function ɵɵi18nApply(index: number) {
|
||||||
if (shiftsCounter) {
|
if (shiftsCounter) {
|
||||||
const tView = getTView();
|
const tView = getTView();
|
||||||
ngDevMode && assertDefined(tView, `tView should be defined`);
|
ngDevMode && assertDefined(tView, `tView should be defined`);
|
||||||
const tI18n = tView.data[index + HEADER_OFFSET];
|
const tI18n = tView.data[index + HEADER_OFFSET] as TI18n | I18nUpdateOpCodes;
|
||||||
let updateOpCodes: I18nUpdateOpCodes;
|
let updateOpCodes: I18nUpdateOpCodes;
|
||||||
let icus: TIcu[]|null = null;
|
let tIcus: TIcu[]|null = null;
|
||||||
if (Array.isArray(tI18n)) {
|
if (Array.isArray(tI18n)) {
|
||||||
updateOpCodes = tI18n as I18nUpdateOpCodes;
|
updateOpCodes = tI18n as I18nUpdateOpCodes;
|
||||||
} else {
|
} else {
|
||||||
updateOpCodes = (tI18n as TI18n).update;
|
updateOpCodes = (tI18n as TI18n).update;
|
||||||
icus = (tI18n as TI18n).icus;
|
tIcus = (tI18n as TI18n).icus;
|
||||||
}
|
}
|
||||||
const bindingsStartIndex = getBindingIndex() - shiftsCounter - 1;
|
const bindingsStartIndex = getBindingIndex() - shiftsCounter - 1;
|
||||||
const lView = getLView();
|
const lView = getLView();
|
||||||
readUpdateOpCodes(updateOpCodes, icus, bindingsStartIndex, changeMask, tView, lView, false);
|
applyUpdateOpCodes(tView, tIcus, lView, updateOpCodes, bindingsStartIndex, changeMask);
|
||||||
|
|
||||||
// Reset changeMask & maskBit to default for the next update cycle
|
// Reset changeMask & maskBit to default for the next update cycle
|
||||||
changeMask = 0b0;
|
changeMask = 0b0;
|
||||||
|
@ -1215,9 +1265,10 @@ function icuStart(
|
||||||
const updateCodes: I18nUpdateOpCodes[] = [];
|
const updateCodes: I18nUpdateOpCodes[] = [];
|
||||||
const vars = [];
|
const vars = [];
|
||||||
const childIcus: number[][] = [];
|
const childIcus: number[][] = [];
|
||||||
for (let i = 0; i < icuExpression.values.length; i++) {
|
const values = icuExpression.values;
|
||||||
|
for (let i = 0; i < values.length; i++) {
|
||||||
// Each value is an array of strings & other ICU expressions
|
// Each value is an array of strings & other ICU expressions
|
||||||
const valueArr = icuExpression.values[i];
|
const valueArr = values[i];
|
||||||
const nestedIcus: IcuExpression[] = [];
|
const nestedIcus: IcuExpression[] = [];
|
||||||
for (let j = 0; j < valueArr.length; j++) {
|
for (let j = 0; j < valueArr.length; j++) {
|
||||||
const value = valueArr[j];
|
const value = valueArr[j];
|
||||||
|
@ -1239,6 +1290,9 @@ function icuStart(
|
||||||
const tIcu: TIcu = {
|
const tIcu: TIcu = {
|
||||||
type: icuExpression.type,
|
type: icuExpression.type,
|
||||||
vars,
|
vars,
|
||||||
|
currentCaseLViewIndex: HEADER_OFFSET +
|
||||||
|
expandoStartIndex // expandoStartIndex does not include the header so add it.
|
||||||
|
+ 1, // The first item stored is the `<!--ICU #-->` anchor so skip it.
|
||||||
childIcus,
|
childIcus,
|
||||||
cases: icuExpression.cases,
|
cases: icuExpression.cases,
|
||||||
create: createCodes,
|
create: createCodes,
|
||||||
|
@ -1269,7 +1323,13 @@ function parseIcuCase(
|
||||||
throw new Error('Unable to generate inert body element');
|
throw new Error('Unable to generate inert body element');
|
||||||
}
|
}
|
||||||
const wrapper = getTemplateContent(inertBodyElement!) as Element || inertBodyElement;
|
const wrapper = getTemplateContent(inertBodyElement!) as Element || inertBodyElement;
|
||||||
const opCodes: IcuCase = {vars: 0, childIcus: [], create: [], remove: [], update: []};
|
const opCodes: IcuCase = {
|
||||||
|
vars: 1, // allocate space for `TIcu.currentCaseLViewIndex`
|
||||||
|
childIcus: [],
|
||||||
|
create: [],
|
||||||
|
remove: [],
|
||||||
|
update: []
|
||||||
|
};
|
||||||
if (ngDevMode) {
|
if (ngDevMode) {
|
||||||
attachDebugGetter(opCodes.create, i18nMutateOpCodesToString);
|
attachDebugGetter(opCodes.create, i18nMutateOpCodesToString);
|
||||||
attachDebugGetter(opCodes.remove, i18nMutateOpCodesToString);
|
attachDebugGetter(opCodes.remove, i18nMutateOpCodesToString);
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
* Use of this source code is governed by an MIT-style license that can be
|
* 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
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
import {assertDataInRange, assertGreaterThan} from '../../util/assert';
|
import {assertGreaterThan, assertIndexInRange} from '../../util/assert';
|
||||||
import {executeCheckHooks, executeInitAndCheckHooks} from '../hooks';
|
import {executeCheckHooks, executeInitAndCheckHooks} from '../hooks';
|
||||||
import {FLAGS, HEADER_OFFSET, InitPhaseState, LView, LViewFlags, TView} from '../interfaces/view';
|
import {FLAGS, HEADER_OFFSET, InitPhaseState, LView, LViewFlags, TView} from '../interfaces/view';
|
||||||
import {getCheckNoChangesMode, getLView, getSelectedIndex, getTView, setSelectedIndex} from '../state';
|
import {getCheckNoChangesMode, getLView, getSelectedIndex, getTView, setSelectedIndex} from '../state';
|
||||||
|
@ -52,7 +52,7 @@ export function ɵɵselect(index: number): void {
|
||||||
export function selectIndexInternal(
|
export function selectIndexInternal(
|
||||||
tView: TView, lView: LView, index: number, checkNoChangesMode: boolean) {
|
tView: TView, lView: LView, index: number, checkNoChangesMode: boolean) {
|
||||||
ngDevMode && assertGreaterThan(index, -1, 'Invalid index');
|
ngDevMode && assertGreaterThan(index, -1, 'Invalid index');
|
||||||
ngDevMode && assertDataInRange(lView, index + HEADER_OFFSET);
|
ngDevMode && assertIndexInRange(lView, index + HEADER_OFFSET);
|
||||||
|
|
||||||
// Flush the initial hooks for elements in the view that have been added up to this point.
|
// 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
|
// PERF WARNING: do NOT extract this to a separate function without running benchmarks
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {assertDataInRange, assertDefined, assertEqual} from '../../util/assert';
|
import {assertDefined, assertEqual, assertIndexInRange} from '../../util/assert';
|
||||||
import {assertFirstCreatePass, assertHasParent} from '../assert';
|
import {assertFirstCreatePass, assertHasParent} from '../assert';
|
||||||
import {attachPatchData} from '../context_discovery';
|
import {attachPatchData} from '../context_discovery';
|
||||||
import {registerPostOrderHooks} from '../hooks';
|
import {registerPostOrderHooks} from '../hooks';
|
||||||
|
@ -79,7 +79,7 @@ export function ɵɵelementStart(
|
||||||
getBindingIndex(), tView.bindingStartIndex,
|
getBindingIndex(), tView.bindingStartIndex,
|
||||||
'elements should be created before any bindings');
|
'elements should be created before any bindings');
|
||||||
ngDevMode && ngDevMode.rendererCreateElement++;
|
ngDevMode && ngDevMode.rendererCreateElement++;
|
||||||
ngDevMode && assertDataInRange(lView, adjustedIndex);
|
ngDevMode && assertIndexInRange(lView, adjustedIndex);
|
||||||
|
|
||||||
const renderer = lView[RENDERER];
|
const renderer = lView[RENDERER];
|
||||||
const native = lView[adjustedIndex] = elementCreate(name, renderer, getNamespace());
|
const native = lView[adjustedIndex] = elementCreate(name, renderer, getNamespace());
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
* Use of this source code is governed by an MIT-style license that can be
|
* 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
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
import {assertDataInRange, assertEqual} from '../../util/assert';
|
import {assertEqual, assertIndexInRange} from '../../util/assert';
|
||||||
import {assertHasParent} from '../assert';
|
import {assertHasParent} from '../assert';
|
||||||
import {attachPatchData} from '../context_discovery';
|
import {attachPatchData} from '../context_discovery';
|
||||||
import {registerPostOrderHooks} from '../hooks';
|
import {registerPostOrderHooks} from '../hooks';
|
||||||
|
@ -66,7 +66,7 @@ export function ɵɵelementContainerStart(
|
||||||
const tView = getTView();
|
const tView = getTView();
|
||||||
const adjustedIndex = index + HEADER_OFFSET;
|
const adjustedIndex = index + HEADER_OFFSET;
|
||||||
|
|
||||||
ngDevMode && assertDataInRange(lView, adjustedIndex);
|
ngDevMode && assertIndexInRange(lView, adjustedIndex);
|
||||||
ngDevMode &&
|
ngDevMode &&
|
||||||
assertEqual(
|
assertEqual(
|
||||||
getBindingIndex(), tView.bindingStartIndex,
|
getBindingIndex(), tView.bindingStartIndex,
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
import {assertDataInRange} from '../../util/assert';
|
import {assertIndexInRange} from '../../util/assert';
|
||||||
import {isObservable} from '../../util/lang';
|
import {isObservable} from '../../util/lang';
|
||||||
import {EMPTY_OBJ} from '../empty';
|
import {EMPTY_OBJ} from '../empty';
|
||||||
import {PropertyAliasValue, TNode, TNodeFlags, TNodeType} from '../interfaces/node';
|
import {PropertyAliasValue, TNode, TNodeFlags, TNodeType} from '../interfaces/node';
|
||||||
|
@ -204,7 +204,7 @@ function listenerInternal(
|
||||||
if (propsLength) {
|
if (propsLength) {
|
||||||
for (let i = 0; i < propsLength; i += 2) {
|
for (let i = 0; i < propsLength; i += 2) {
|
||||||
const index = props[i] as number;
|
const index = props[i] as number;
|
||||||
ngDevMode && assertDataInRange(lView, index);
|
ngDevMode && assertIndexInRange(lView, index);
|
||||||
const minifiedName = props[i + 1];
|
const minifiedName = props[i + 1];
|
||||||
const directiveInstance = lView[index];
|
const directiveInstance = lView[index];
|
||||||
const output = directiveInstance[minifiedName];
|
const output = directiveInstance[minifiedName];
|
||||||
|
|
|
@ -362,19 +362,24 @@ export function toDebug(obj: any): any {
|
||||||
* (will not serialize child elements).
|
* (will not serialize child elements).
|
||||||
*/
|
*/
|
||||||
function toHtml(value: any, includeChildren: boolean = false): string|null {
|
function toHtml(value: any, includeChildren: boolean = false): string|null {
|
||||||
const node: HTMLElement|null = unwrapRNode(value) as any;
|
const node: Node|null = unwrapRNode(value) as any;
|
||||||
if (node) {
|
if (node) {
|
||||||
const isTextNode = node.nodeType === Node.TEXT_NODE;
|
switch (node.nodeType) {
|
||||||
const outerHTML = (isTextNode ? node.textContent : node.outerHTML) || '';
|
case Node.TEXT_NODE:
|
||||||
if (includeChildren || isTextNode) {
|
return node.textContent;
|
||||||
return outerHTML;
|
case Node.COMMENT_NODE:
|
||||||
} else {
|
return `<!--${(node as Comment).textContent}-->`;
|
||||||
const innerHTML = '>' + node.innerHTML + '<';
|
case Node.ELEMENT_NODE:
|
||||||
return (outerHTML.split(innerHTML)[0]) + '>';
|
const outerHTML = (node as Element).outerHTML;
|
||||||
|
if (includeChildren) {
|
||||||
|
return outerHTML;
|
||||||
|
} else {
|
||||||
|
const innerHTML = '>' + (node as Element).innerHTML + '<';
|
||||||
|
return (outerHTML.split(innerHTML)[0]) + '>';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class LViewDebug implements ILViewDebug {
|
export class LViewDebug implements ILViewDebug {
|
||||||
|
|
|
@ -12,7 +12,7 @@ import {CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA, SchemaMetadata} from '../../me
|
||||||
import {ViewEncapsulation} from '../../metadata/view';
|
import {ViewEncapsulation} from '../../metadata/view';
|
||||||
import {validateAgainstEventAttributes, validateAgainstEventProperties} from '../../sanitization/sanitization';
|
import {validateAgainstEventAttributes, validateAgainstEventProperties} from '../../sanitization/sanitization';
|
||||||
import {Sanitizer} from '../../sanitization/sanitizer';
|
import {Sanitizer} from '../../sanitization/sanitizer';
|
||||||
import {assertDataInRange, assertDefined, assertDomNode, assertEqual, assertGreaterThan, assertLessThan, assertNotEqual, assertNotSame, assertSame} from '../../util/assert';
|
import {assertDefined, assertDomNode, assertEqual, assertGreaterThan, assertIndexInRange, assertLessThan, assertNotEqual, assertNotSame, assertSame} from '../../util/assert';
|
||||||
import {createNamedArrayType} from '../../util/named_array_type';
|
import {createNamedArrayType} from '../../util/named_array_type';
|
||||||
import {initNgDevMode} from '../../util/ng_dev_mode';
|
import {initNgDevMode} from '../../util/ng_dev_mode';
|
||||||
import {normalizeDebugBindingName, normalizeDebugBindingValue} from '../../util/ng_reflect';
|
import {normalizeDebugBindingName, normalizeDebugBindingValue} from '../../util/ng_reflect';
|
||||||
|
@ -236,13 +236,6 @@ export function getOrCreateTNode(
|
||||||
const tNode = tView.data[adjustedIndex] as TNode ||
|
const tNode = tView.data[adjustedIndex] as TNode ||
|
||||||
createTNodeAtIndex(tView, tHostNode, adjustedIndex, type, name, attrs);
|
createTNodeAtIndex(tView, tHostNode, adjustedIndex, type, name, attrs);
|
||||||
setPreviousOrParentTNode(tNode, true);
|
setPreviousOrParentTNode(tNode, true);
|
||||||
if (ngDevMode) {
|
|
||||||
// For performance reasons it is important that the tNode retains the same shape during runtime.
|
|
||||||
// (To make sure that all of the code is monomorphic.) For this reason we seal the object to
|
|
||||||
// prevent class transitions.
|
|
||||||
// FIXME(misko): re-enable this once i18n code is compliant with this.
|
|
||||||
// Object.seal(tNode);
|
|
||||||
}
|
|
||||||
return tNode as TElementNode & TViewNode & TContainerNode & TElementContainerNode &
|
return tNode as TElementNode & TViewNode & TContainerNode & TElementContainerNode &
|
||||||
TProjectionNode & TIcuContainerNode;
|
TProjectionNode & TIcuContainerNode;
|
||||||
}
|
}
|
||||||
|
@ -665,44 +658,44 @@ export function createTView(
|
||||||
// that has a host binding, we will update the blueprint with that def's hostVars count.
|
// that has a host binding, we will update the blueprint with that def's hostVars count.
|
||||||
const initialViewLength = bindingStartIndex + vars;
|
const initialViewLength = bindingStartIndex + vars;
|
||||||
const blueprint = createViewBlueprint(bindingStartIndex, initialViewLength);
|
const blueprint = createViewBlueprint(bindingStartIndex, initialViewLength);
|
||||||
return blueprint[TVIEW as any] = ngDevMode ?
|
const tView = blueprint[TVIEW as any] = ngDevMode ?
|
||||||
new TViewConstructor(
|
new TViewConstructor(
|
||||||
type,
|
type,
|
||||||
viewIndex, // id: number,
|
viewIndex, // id: number,
|
||||||
blueprint, // blueprint: LView,
|
blueprint, // blueprint: LView,
|
||||||
templateFn, // template: ComponentTemplate<{}>|null,
|
templateFn, // template: ComponentTemplate<{}>|null,
|
||||||
null, // queries: TQueries|null
|
null, // queries: TQueries|null
|
||||||
viewQuery, // viewQuery: ViewQueriesFunction<{}>|null,
|
viewQuery, // viewQuery: ViewQueriesFunction<{}>|null,
|
||||||
null!, // node: TViewNode|TElementNode|null,
|
null!, // node: TViewNode|TElementNode|null,
|
||||||
cloneToTViewData(blueprint).fill(null, bindingStartIndex), // data: TData,
|
cloneToTViewData(blueprint).fill(null, bindingStartIndex), // data: TData,
|
||||||
bindingStartIndex, // bindingStartIndex: number,
|
bindingStartIndex, // bindingStartIndex: number,
|
||||||
initialViewLength, // expandoStartIndex: number,
|
initialViewLength, // expandoStartIndex: number,
|
||||||
null, // expandoInstructions: ExpandoInstructions|null,
|
null, // expandoInstructions: ExpandoInstructions|null,
|
||||||
true, // firstCreatePass: boolean,
|
true, // firstCreatePass: boolean,
|
||||||
true, // firstUpdatePass: boolean,
|
true, // firstUpdatePass: boolean,
|
||||||
false, // staticViewQueries: boolean,
|
false, // staticViewQueries: boolean,
|
||||||
false, // staticContentQueries: boolean,
|
false, // staticContentQueries: boolean,
|
||||||
null, // preOrderHooks: HookData|null,
|
null, // preOrderHooks: HookData|null,
|
||||||
null, // preOrderCheckHooks: HookData|null,
|
null, // preOrderCheckHooks: HookData|null,
|
||||||
null, // contentHooks: HookData|null,
|
null, // contentHooks: HookData|null,
|
||||||
null, // contentCheckHooks: HookData|null,
|
null, // contentCheckHooks: HookData|null,
|
||||||
null, // viewHooks: HookData|null,
|
null, // viewHooks: HookData|null,
|
||||||
null, // viewCheckHooks: HookData|null,
|
null, // viewCheckHooks: HookData|null,
|
||||||
null, // destroyHooks: DestroyHookData|null,
|
null, // destroyHooks: DestroyHookData|null,
|
||||||
null, // cleanup: any[]|null,
|
null, // cleanup: any[]|null,
|
||||||
null, // contentQueries: number[]|null,
|
null, // contentQueries: number[]|null,
|
||||||
null, // components: number[]|null,
|
null, // components: number[]|null,
|
||||||
typeof directives === 'function' ?
|
typeof directives === 'function' ?
|
||||||
directives() :
|
directives() :
|
||||||
directives, // directiveRegistry: DirectiveDefList|null,
|
directives, // directiveRegistry: DirectiveDefList|null,
|
||||||
typeof pipes === 'function' ? pipes() : pipes, // pipeRegistry: PipeDefList|null,
|
typeof pipes === 'function' ? pipes() : pipes, // pipeRegistry: PipeDefList|null,
|
||||||
null, // firstChild: TNode|null,
|
null, // firstChild: TNode|null,
|
||||||
schemas, // schemas: SchemaMetadata[]|null,
|
schemas, // schemas: SchemaMetadata[]|null,
|
||||||
consts, // consts: TConstants|null
|
consts, // consts: TConstants|null
|
||||||
false, // incompleteFirstPass: boolean
|
false, // incompleteFirstPass: boolean
|
||||||
decls, // ngDevMode only: decls
|
decls, // ngDevMode only: decls
|
||||||
vars, // ngDevMode only: vars
|
vars, // ngDevMode only: vars
|
||||||
) :
|
) :
|
||||||
{
|
{
|
||||||
type: type,
|
type: type,
|
||||||
id: viewIndex,
|
id: viewIndex,
|
||||||
|
@ -736,6 +729,13 @@ export function createTView(
|
||||||
consts: consts,
|
consts: consts,
|
||||||
incompleteFirstPass: false
|
incompleteFirstPass: false
|
||||||
};
|
};
|
||||||
|
if (ngDevMode) {
|
||||||
|
// For performance reasons it is important that the tView retains the same shape during runtime.
|
||||||
|
// (To make sure that all of the code is monomorphic.) For this reason we seal the object to
|
||||||
|
// prevent class transitions.
|
||||||
|
Object.seal(tView);
|
||||||
|
}
|
||||||
|
return tView;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createViewBlueprint(bindingStartIndex: number, initialViewLength: number): LView {
|
function createViewBlueprint(bindingStartIndex: number, initialViewLength: number): LView {
|
||||||
|
@ -825,71 +825,79 @@ export function createTNode(
|
||||||
tagName: string|null, attrs: TAttributes|null): TNode {
|
tagName: string|null, attrs: TAttributes|null): TNode {
|
||||||
ngDevMode && ngDevMode.tNode++;
|
ngDevMode && ngDevMode.tNode++;
|
||||||
let injectorIndex = tParent ? tParent.injectorIndex : -1;
|
let injectorIndex = tParent ? tParent.injectorIndex : -1;
|
||||||
return ngDevMode ? new TNodeDebug(
|
const tNode = ngDevMode ?
|
||||||
tView, // tView_: TView
|
new TNodeDebug(
|
||||||
type, // type: TNodeType
|
tView, // tView_: TView
|
||||||
adjustedIndex, // index: number
|
type, // type: TNodeType
|
||||||
injectorIndex, // injectorIndex: number
|
adjustedIndex, // index: number
|
||||||
-1, // directiveStart: number
|
injectorIndex, // injectorIndex: number
|
||||||
-1, // directiveEnd: number
|
-1, // directiveStart: number
|
||||||
-1, // directiveStylingLast: number
|
-1, // directiveEnd: number
|
||||||
null, // propertyBindings: number[]|null
|
-1, // directiveStylingLast: number
|
||||||
0, // flags: TNodeFlags
|
null, // propertyBindings: number[]|null
|
||||||
0, // providerIndexes: TNodeProviderIndexes
|
0, // flags: TNodeFlags
|
||||||
tagName, // tagName: string|null
|
0, // providerIndexes: TNodeProviderIndexes
|
||||||
attrs, // attrs: (string|AttributeMarker|(string|SelectorFlags)[])[]|null
|
tagName, // tagName: string|null
|
||||||
null, // mergedAttrs
|
attrs, // attrs: (string|AttributeMarker|(string|SelectorFlags)[])[]|null
|
||||||
null, // localNames: (string|number)[]|null
|
null, // mergedAttrs
|
||||||
undefined, // initialInputs: (string[]|null)[]|null|undefined
|
null, // localNames: (string|number)[]|null
|
||||||
null, // inputs: PropertyAliases|null
|
undefined, // initialInputs: (string[]|null)[]|null|undefined
|
||||||
null, // outputs: PropertyAliases|null
|
null, // inputs: PropertyAliases|null
|
||||||
null, // tViews: ITView|ITView[]|null
|
null, // outputs: PropertyAliases|null
|
||||||
null, // next: ITNode|null
|
null, // tViews: ITView|ITView[]|null
|
||||||
null, // projectionNext: ITNode|null
|
null, // next: ITNode|null
|
||||||
null, // child: ITNode|null
|
null, // projectionNext: ITNode|null
|
||||||
tParent, // parent: TElementNode|TContainerNode|null
|
null, // child: ITNode|null
|
||||||
null, // projection: number|(ITNode|RNode[])[]|null
|
tParent, // parent: TElementNode|TContainerNode|null
|
||||||
null, // styles: string|null
|
null, // projection: number|(ITNode|RNode[])[]|null
|
||||||
null, // stylesWithoutHost: string|null
|
null, // styles: string|null
|
||||||
undefined, // residualStyles: string|null
|
null, // stylesWithoutHost: string|null
|
||||||
null, // classes: string|null
|
undefined, // residualStyles: string|null
|
||||||
null, // classesWithoutHost: string|null
|
null, // classes: string|null
|
||||||
undefined, // residualClasses: string|null
|
null, // classesWithoutHost: string|null
|
||||||
0 as any, // classBindings: TStylingRange;
|
undefined, // residualClasses: string|null
|
||||||
0 as any, // styleBindings: TStylingRange;
|
0 as any, // classBindings: TStylingRange;
|
||||||
) :
|
0 as any, // styleBindings: TStylingRange;
|
||||||
{
|
) :
|
||||||
type: type,
|
{
|
||||||
index: adjustedIndex,
|
type: type,
|
||||||
injectorIndex: injectorIndex,
|
index: adjustedIndex,
|
||||||
directiveStart: -1,
|
injectorIndex: injectorIndex,
|
||||||
directiveEnd: -1,
|
directiveStart: -1,
|
||||||
directiveStylingLast: -1,
|
directiveEnd: -1,
|
||||||
propertyBindings: null,
|
directiveStylingLast: -1,
|
||||||
flags: 0,
|
propertyBindings: null,
|
||||||
providerIndexes: 0,
|
flags: 0,
|
||||||
tagName: tagName,
|
providerIndexes: 0,
|
||||||
attrs: attrs,
|
tagName: tagName,
|
||||||
mergedAttrs: null,
|
attrs: attrs,
|
||||||
localNames: null,
|
mergedAttrs: null,
|
||||||
initialInputs: undefined,
|
localNames: null,
|
||||||
inputs: null,
|
initialInputs: undefined,
|
||||||
outputs: null,
|
inputs: null,
|
||||||
tViews: null,
|
outputs: null,
|
||||||
next: null,
|
tViews: null,
|
||||||
projectionNext: null,
|
next: null,
|
||||||
child: null,
|
projectionNext: null,
|
||||||
parent: tParent,
|
child: null,
|
||||||
projection: null,
|
parent: tParent,
|
||||||
styles: null,
|
projection: null,
|
||||||
stylesWithoutHost: null,
|
styles: null,
|
||||||
residualStyles: undefined,
|
stylesWithoutHost: null,
|
||||||
classes: null,
|
residualStyles: undefined,
|
||||||
classesWithoutHost: null,
|
classes: null,
|
||||||
residualClasses: undefined,
|
classesWithoutHost: null,
|
||||||
classBindings: 0 as any,
|
residualClasses: undefined,
|
||||||
styleBindings: 0 as any,
|
classBindings: 0 as any,
|
||||||
};
|
styleBindings: 0 as any,
|
||||||
|
};
|
||||||
|
if (ngDevMode) {
|
||||||
|
// For performance reasons it is important that the tNode retains the same shape during runtime.
|
||||||
|
// (To make sure that all of the code is monomorphic.) For this reason we seal the object to
|
||||||
|
// prevent class transitions.
|
||||||
|
Object.seal(tNode);
|
||||||
|
}
|
||||||
|
return tNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -2040,7 +2048,7 @@ export function setInputsForProperty(
|
||||||
const index = inputs[i++] as number;
|
const index = inputs[i++] as number;
|
||||||
const privateName = inputs[i++] as string;
|
const privateName = inputs[i++] as string;
|
||||||
const instance = lView[index];
|
const instance = lView[index];
|
||||||
ngDevMode && assertDataInRange(lView, index);
|
ngDevMode && assertIndexInRange(lView, index);
|
||||||
const def = tView.data[index] as DirectiveDef<any>;
|
const def = tView.data[index] as DirectiveDef<any>;
|
||||||
if (def.setInput !== null) {
|
if (def.setInput !== null) {
|
||||||
def.setInput!(instance, value, publicName, privateName);
|
def.setInput!(instance, value, publicName, privateName);
|
||||||
|
@ -2055,7 +2063,7 @@ export function setInputsForProperty(
|
||||||
*/
|
*/
|
||||||
export function textBindingInternal(lView: LView, index: number, value: string): void {
|
export function textBindingInternal(lView: LView, index: number, value: string): void {
|
||||||
ngDevMode && assertNotSame(value, NO_CHANGE as any, 'value should not be NO_CHANGE');
|
ngDevMode && assertNotSame(value, NO_CHANGE as any, 'value should not be NO_CHANGE');
|
||||||
ngDevMode && assertDataInRange(lView, index + HEADER_OFFSET);
|
ngDevMode && assertIndexInRange(lView, index + HEADER_OFFSET);
|
||||||
const element = getNativeByIndex(index, lView) as any as RText;
|
const element = getNativeByIndex(index, lView) as any as RText;
|
||||||
ngDevMode && assertDefined(element, 'native element should exist');
|
ngDevMode && assertDefined(element, 'native element should exist');
|
||||||
ngDevMode && ngDevMode.rendererSetText++;
|
ngDevMode && ngDevMode.rendererSetText++;
|
||||||
|
|
|
@ -5,11 +5,12 @@
|
||||||
* Use of this source code is governed by an MIT-style license that can be
|
* 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
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
import {assertDataInRange, assertEqual} from '../../util/assert';
|
import {assertEqual, assertIndexInRange} from '../../util/assert';
|
||||||
import {TElementNode, TNodeType} from '../interfaces/node';
|
import {TElementNode, TNodeType} from '../interfaces/node';
|
||||||
import {HEADER_OFFSET, RENDERER, T_HOST} from '../interfaces/view';
|
import {HEADER_OFFSET, RENDERER, T_HOST} from '../interfaces/view';
|
||||||
import {appendChild, createTextNode} from '../node_manipulation';
|
import {appendChild, createTextNode} from '../node_manipulation';
|
||||||
import {getBindingIndex, getLView, getTView, setPreviousOrParentTNode} from '../state';
|
import {getBindingIndex, getLView, getTView, setPreviousOrParentTNode} from '../state';
|
||||||
|
|
||||||
import {getOrCreateTNode} from './shared';
|
import {getOrCreateTNode} from './shared';
|
||||||
|
|
||||||
|
|
||||||
|
@ -31,7 +32,7 @@ export function ɵɵtext(index: number, value: string = ''): void {
|
||||||
assertEqual(
|
assertEqual(
|
||||||
getBindingIndex(), tView.bindingStartIndex,
|
getBindingIndex(), tView.bindingStartIndex,
|
||||||
'text nodes should be created before any bindings');
|
'text nodes should be created before any bindings');
|
||||||
ngDevMode && assertDataInRange(lView, adjustedIndex);
|
ngDevMode && assertIndexInRange(lView, adjustedIndex);
|
||||||
|
|
||||||
const tNode = tView.firstCreatePass ?
|
const tNode = tView.firstCreatePass ?
|
||||||
getOrCreateTNode(tView, lView[T_HOST], index, TNodeType.Element, null, null) :
|
getOrCreateTNode(tView, lView[T_HOST], index, TNodeType.Element, null, null) :
|
||||||
|
|
|
@ -346,6 +346,14 @@ export interface TIcu {
|
||||||
*/
|
*/
|
||||||
vars: number[];
|
vars: number[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Currently selected ICU case pointer.
|
||||||
|
*
|
||||||
|
* `lView[currentCaseLViewIndex]` stores the currently selected case. This is needed to know how
|
||||||
|
* to clean up the current case when transitioning no the new case.
|
||||||
|
*/
|
||||||
|
currentCaseLViewIndex: number;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An optional array of child/sub ICUs.
|
* An optional array of child/sub ICUs.
|
||||||
*
|
*
|
||||||
|
|
|
@ -709,13 +709,6 @@ export interface TIcuContainerNode extends TNode {
|
||||||
parent: TElementNode|TElementContainerNode|null;
|
parent: TElementNode|TElementContainerNode|null;
|
||||||
tViews: null;
|
tViews: null;
|
||||||
projection: null;
|
projection: null;
|
||||||
/**
|
|
||||||
* Indicates the current active case for an ICU expression.
|
|
||||||
* It is null when there is no active case.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
// FIXME(misko): This is at a wrong location as activeCase is `LView` (not `TView`) concern
|
|
||||||
activeCaseIndex: number|null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Static data for a view */
|
/** Static data for a view */
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {assertDataInRange} from '../util/assert';
|
import {assertIndexInRange} from '../util/assert';
|
||||||
import {bindingUpdated, bindingUpdated2, bindingUpdated3, bindingUpdated4, getBinding, updateBinding} from './bindings';
|
import {bindingUpdated, bindingUpdated2, bindingUpdated3, bindingUpdated4, getBinding, updateBinding} from './bindings';
|
||||||
import {LView} from './interfaces/view';
|
import {LView} from './interfaces/view';
|
||||||
import {getBindingRoot, getLView} from './state';
|
import {getBindingRoot, getLView} from './state';
|
||||||
|
@ -287,7 +287,7 @@ export function ɵɵpureFunctionV(
|
||||||
* it to `undefined`.
|
* it to `undefined`.
|
||||||
*/
|
*/
|
||||||
function getPureFunctionReturnValue(lView: LView, returnValueIndex: number) {
|
function getPureFunctionReturnValue(lView: LView, returnValueIndex: number) {
|
||||||
ngDevMode && assertDataInRange(lView, returnValueIndex);
|
ngDevMode && assertIndexInRange(lView, returnValueIndex);
|
||||||
const lastReturnValue = lView[returnValueIndex];
|
const lastReturnValue = lView[returnValueIndex];
|
||||||
return lastReturnValue === NO_CHANGE ? undefined : lastReturnValue;
|
return lastReturnValue === NO_CHANGE ? undefined : lastReturnValue;
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@ import {ElementRef as ViewEngine_ElementRef} from '../linker/element_ref';
|
||||||
import {QueryList} from '../linker/query_list';
|
import {QueryList} from '../linker/query_list';
|
||||||
import {TemplateRef as ViewEngine_TemplateRef} from '../linker/template_ref';
|
import {TemplateRef as ViewEngine_TemplateRef} from '../linker/template_ref';
|
||||||
import {ViewContainerRef} from '../linker/view_container_ref';
|
import {ViewContainerRef} from '../linker/view_container_ref';
|
||||||
import {assertDataInRange, assertDefined, throwError} from '../util/assert';
|
import {assertDefined, assertIndexInRange, throwError} from '../util/assert';
|
||||||
import {stringify} from '../util/stringify';
|
import {stringify} from '../util/stringify';
|
||||||
|
|
||||||
import {assertFirstCreatePass, assertLContainer} from './assert';
|
import {assertFirstCreatePass, assertLContainer} from './assert';
|
||||||
|
@ -140,7 +140,7 @@ class TQueries_ implements TQueries {
|
||||||
}
|
}
|
||||||
|
|
||||||
getByIndex(index: number): TQuery {
|
getByIndex(index: number): TQuery {
|
||||||
ngDevMode && assertDataInRange(this.queries, index);
|
ngDevMode && assertIndexInRange(this.queries, index);
|
||||||
return this.queries[index];
|
return this.queries[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -358,7 +358,7 @@ function materializeViewResults<T>(
|
||||||
// null as a placeholder
|
// null as a placeholder
|
||||||
result.push(null);
|
result.push(null);
|
||||||
} else {
|
} else {
|
||||||
ngDevMode && assertDataInRange(tViewData, matchedNodeIdx);
|
ngDevMode && assertIndexInRange(tViewData, matchedNodeIdx);
|
||||||
const tNode = tViewData[matchedNodeIdx] as TNode;
|
const tNode = tViewData[matchedNodeIdx] as TNode;
|
||||||
result.push(createResultForNode(lView, tNode, tQueryMatches[i + 1], tQuery.metadata.read));
|
result.push(createResultForNode(lView, tNode, tQueryMatches[i + 1], tQuery.metadata.read));
|
||||||
}
|
}
|
||||||
|
@ -551,7 +551,7 @@ export function ɵɵloadQuery<T>(): QueryList<T> {
|
||||||
function loadQueryInternal<T>(lView: LView, queryIndex: number): QueryList<T> {
|
function loadQueryInternal<T>(lView: LView, queryIndex: number): QueryList<T> {
|
||||||
ngDevMode &&
|
ngDevMode &&
|
||||||
assertDefined(lView[QUERIES], 'LQueries should be defined when trying to load a query');
|
assertDefined(lView[QUERIES], 'LQueries should be defined when trying to load a query');
|
||||||
ngDevMode && assertDataInRange(lView[QUERIES]!.queries, queryIndex);
|
ngDevMode && assertIndexInRange(lView[QUERIES]!.queries, queryIndex);
|
||||||
return lView[QUERIES]!.queries[queryIndex].queryList;
|
return lView[QUERIES]!.queries[queryIndex].queryList;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {KeyValueArray, keyValueArrayIndexOf} from '../../util/array_utils';
|
import {KeyValueArray, keyValueArrayIndexOf} from '../../util/array_utils';
|
||||||
import {assertDataInRange, assertEqual, assertNotEqual} from '../../util/assert';
|
import {assertEqual, assertIndexInRange, assertNotEqual} from '../../util/assert';
|
||||||
import {assertFirstUpdatePass} from '../assert';
|
import {assertFirstUpdatePass} from '../assert';
|
||||||
import {TNode} from '../interfaces/node';
|
import {TNode} from '../interfaces/node';
|
||||||
import {getTStylingRangeNext, getTStylingRangePrev, setTStylingRangeNext, setTStylingRangeNextDuplicate, setTStylingRangePrev, setTStylingRangePrevDuplicate, toTStylingRange, TStylingKey, TStylingKeyPrimitive, TStylingRange} from '../interfaces/styling';
|
import {getTStylingRangeNext, getTStylingRangePrev, setTStylingRangeNext, setTStylingRangeNextDuplicate, setTStylingRangePrev, setTStylingRangePrevDuplicate, toTStylingRange, TStylingKey, TStylingKeyPrimitive, TStylingRange} from '../interfaces/styling';
|
||||||
|
@ -370,7 +370,7 @@ function markDuplicates(
|
||||||
// - we are a map in which case we have to continue searching even after we find what we were
|
// - we are a map in which case we have to continue searching even after we find what we were
|
||||||
// looking for since we are a wild card and everything needs to be flipped to duplicate.
|
// looking for since we are a wild card and everything needs to be flipped to duplicate.
|
||||||
while (cursor !== 0 && (foundDuplicate === false || isMap)) {
|
while (cursor !== 0 && (foundDuplicate === false || isMap)) {
|
||||||
ngDevMode && assertDataInRange(tData, cursor);
|
ngDevMode && assertIndexInRange(tData, cursor);
|
||||||
const tStylingValueAtCursor = tData[cursor] as TStylingKey;
|
const tStylingValueAtCursor = tData[cursor] as TStylingKey;
|
||||||
const tStyleRangeAtCursor = tData[cursor + 1] as TStylingRange;
|
const tStyleRangeAtCursor = tData[cursor + 1] as TStylingRange;
|
||||||
if (isStylingMatch(tStylingValueAtCursor, tStylingKey)) {
|
if (isStylingMatch(tStylingValueAtCursor, tStylingKey)) {
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {assertDataInRange, assertDefined, assertDomNode, assertGreaterThan, assertLessThan} from '../../util/assert';
|
import {assertDefined, assertDomNode, assertGreaterThan, assertIndexInRange, assertLessThan} from '../../util/assert';
|
||||||
import {assertTNodeForLView} from '../assert';
|
import {assertTNodeForLView} from '../assert';
|
||||||
import {LContainer, TYPE} from '../interfaces/container';
|
import {LContainer, TYPE} from '../interfaces/container';
|
||||||
import {LContext, MONKEY_PATCH_KEY_NAME} from '../interfaces/context';
|
import {LContext, MONKEY_PATCH_KEY_NAME} from '../interfaces/context';
|
||||||
|
@ -91,7 +91,7 @@ export function getNativeByIndex(index: number, lView: LView): RNode {
|
||||||
*/
|
*/
|
||||||
export function getNativeByTNode(tNode: TNode, lView: LView): RNode {
|
export function getNativeByTNode(tNode: TNode, lView: LView): RNode {
|
||||||
ngDevMode && assertTNodeForLView(tNode, lView);
|
ngDevMode && assertTNodeForLView(tNode, lView);
|
||||||
ngDevMode && assertDataInRange(lView, tNode.index);
|
ngDevMode && assertIndexInRange(lView, tNode.index);
|
||||||
const node: RNode = unwrapRNode(lView[tNode.index]);
|
const node: RNode = unwrapRNode(lView[tNode.index]);
|
||||||
ngDevMode && !isProceduralRenderer(lView[RENDERER]) && assertDomNode(node);
|
ngDevMode && !isProceduralRenderer(lView[RENDERER]) && assertDomNode(node);
|
||||||
return node;
|
return node;
|
||||||
|
@ -125,13 +125,13 @@ export function getTNode(tView: TView, index: number): TNode {
|
||||||
|
|
||||||
/** Retrieves a value from any `LView` or `TData`. */
|
/** Retrieves a value from any `LView` or `TData`. */
|
||||||
export function load<T>(view: LView|TData, index: number): T {
|
export function load<T>(view: LView|TData, index: number): T {
|
||||||
ngDevMode && assertDataInRange(view, index + HEADER_OFFSET);
|
ngDevMode && assertIndexInRange(view, index + HEADER_OFFSET);
|
||||||
return view[index + HEADER_OFFSET];
|
return view[index + HEADER_OFFSET];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getComponentLViewByIndex(nodeIndex: number, hostView: LView): LView {
|
export function getComponentLViewByIndex(nodeIndex: number, hostView: LView): LView {
|
||||||
// Could be an LView or an LContainer. If LContainer, unwrap to find LView.
|
// Could be an LView or an LContainer. If LContainer, unwrap to find LView.
|
||||||
ngDevMode && assertDataInRange(hostView, nodeIndex);
|
ngDevMode && assertIndexInRange(hostView, nodeIndex);
|
||||||
const slotValue = hostView[nodeIndex];
|
const slotValue = hostView[nodeIndex];
|
||||||
const lView = isLView(slotValue) ? slotValue : slotValue[HOST];
|
const lView = isLView(slotValue) ? slotValue : slotValue[HOST];
|
||||||
return lView;
|
return lView;
|
||||||
|
|
|
@ -110,7 +110,7 @@ export function assertDomNode(node: any): asserts node is Node {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export function assertDataInRange(arr: any[], index: number) {
|
export function assertIndexInRange(arr: any[], index: number) {
|
||||||
const maxLen = arr ? arr.length : 0;
|
const maxLen = arr ? arr.length : 0;
|
||||||
assertLessThan(index, maxLen, `Index expected to be less than ${maxLen} but got ${index}`);
|
assertLessThan(index, maxLen, `Index expected to be less than ${maxLen} but got ${index}`);
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,11 @@ import localeEs from '@angular/common/locales/es';
|
||||||
import localeRo from '@angular/common/locales/ro';
|
import localeRo from '@angular/common/locales/ro';
|
||||||
import {computeMsgId} from '@angular/compiler';
|
import {computeMsgId} from '@angular/compiler';
|
||||||
import {Component, ContentChild, ContentChildren, Directive, ElementRef, HostBinding, Input, LOCALE_ID, NO_ERRORS_SCHEMA, Pipe, PipeTransform, QueryList, RendererFactory2, TemplateRef, Type, ViewChild, ViewContainerRef, ɵsetDocument} from '@angular/core';
|
import {Component, ContentChild, ContentChildren, Directive, ElementRef, HostBinding, Input, LOCALE_ID, NO_ERRORS_SCHEMA, Pipe, PipeTransform, QueryList, RendererFactory2, TemplateRef, Type, ViewChild, ViewContainerRef, ɵsetDocument} from '@angular/core';
|
||||||
|
import {getComponentDef} from '@angular/core/src/render3/definition';
|
||||||
import {setDelayProjection} from '@angular/core/src/render3/instructions/projection';
|
import {setDelayProjection} from '@angular/core/src/render3/instructions/projection';
|
||||||
|
import {TI18n, TIcu} from '@angular/core/src/render3/interfaces/i18n';
|
||||||
|
import {DebugNode, HEADER_OFFSET, TVIEW} from '@angular/core/src/render3/interfaces/view';
|
||||||
|
import {getComponentLView, loadLContext} from '@angular/core/src/render3/util/discovery_utils';
|
||||||
import {TestBed} from '@angular/core/testing';
|
import {TestBed} from '@angular/core/testing';
|
||||||
import {clearTranslations, loadTranslations} from '@angular/localize';
|
import {clearTranslations, loadTranslations} from '@angular/localize';
|
||||||
import {By, ɵDomRendererFactory2 as DomRendererFactory2} from '@angular/platform-browser';
|
import {By, ɵDomRendererFactory2 as DomRendererFactory2} from '@angular/platform-browser';
|
||||||
|
@ -599,6 +603,217 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('dynamic TNodes', () => {
|
||||||
|
// When translation occurs the i18n system needs to create dynamic TNodes for the text
|
||||||
|
// nodes so that they can be correctly processed by the `addRemoveViewFromContainer`.
|
||||||
|
|
||||||
|
function toTypeContent(n: DebugNode): string {
|
||||||
|
return `${n.type}(${n.html})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
it('should not create dynamic TNode when no i18n', () => {
|
||||||
|
const fixture = initWithTemplate(AppComp, `Hello <b>World</b>!`);
|
||||||
|
const lView = getComponentLView(fixture.componentInstance);
|
||||||
|
const hello_ = (fixture.nativeElement as Element).firstChild!;
|
||||||
|
const b = hello_.nextSibling!;
|
||||||
|
const world = b.firstChild!;
|
||||||
|
const exclamation = b.nextSibling!;
|
||||||
|
const lViewDebug = lView.debug!;
|
||||||
|
expect(lViewDebug.nodes.map(toTypeContent)).toEqual([
|
||||||
|
'Element(Hello )', 'Element(<b>)', 'Element(!)'
|
||||||
|
]);
|
||||||
|
expect(lViewDebug.decls).toEqual({
|
||||||
|
start: HEADER_OFFSET,
|
||||||
|
end: HEADER_OFFSET + 4,
|
||||||
|
length: 4,
|
||||||
|
content: [
|
||||||
|
jasmine.objectContaining({index: HEADER_OFFSET + 0, l: hello_}),
|
||||||
|
jasmine.objectContaining({index: HEADER_OFFSET + 1, l: b}),
|
||||||
|
jasmine.objectContaining({index: HEADER_OFFSET + 2, l: world}),
|
||||||
|
jasmine.objectContaining({index: HEADER_OFFSET + 3, l: exclamation}),
|
||||||
|
]
|
||||||
|
});
|
||||||
|
expect(lViewDebug.i18n)
|
||||||
|
.toEqual(
|
||||||
|
{start: lViewDebug.vars.end, end: lViewDebug.expando.start, length: 0, content: []});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create dynamic TNode for text nodes', () => {
|
||||||
|
const fixture =
|
||||||
|
initWithTemplate(AppComp, `<ng-container i18n>Hello <b>World</b>!</ng-container>`);
|
||||||
|
const lView = getComponentLView(fixture.componentInstance);
|
||||||
|
const hello_ = (fixture.nativeElement as Element).firstChild!;
|
||||||
|
const b = hello_.nextSibling!;
|
||||||
|
const world = b.firstChild!;
|
||||||
|
const exclamation = b.nextSibling!;
|
||||||
|
const container = exclamation.nextSibling!;
|
||||||
|
const lViewDebug = lView.debug!;
|
||||||
|
expect(lViewDebug.nodes.map(toTypeContent)).toEqual([
|
||||||
|
'ElementContainer(<!--ng-container-->)'
|
||||||
|
]);
|
||||||
|
// This assertion shows that the translated nodes are correctly linked into the TNode tree.
|
||||||
|
expect(lViewDebug.nodes[0].children.map(toTypeContent)).toEqual([
|
||||||
|
'Element(Hello )', 'Element(<b>)', 'Element(!)'
|
||||||
|
]);
|
||||||
|
// This assertion shows that the translated text is not part of decls
|
||||||
|
expect(lViewDebug.decls).toEqual({
|
||||||
|
start: HEADER_OFFSET,
|
||||||
|
end: HEADER_OFFSET + 3,
|
||||||
|
length: 3,
|
||||||
|
content: [
|
||||||
|
jasmine.objectContaining({index: HEADER_OFFSET + 0, l: container}),
|
||||||
|
jasmine.objectContaining({index: HEADER_OFFSET + 1}),
|
||||||
|
jasmine.objectContaining({index: HEADER_OFFSET + 2, l: b}),
|
||||||
|
]
|
||||||
|
});
|
||||||
|
// This assertion shows that the translated DOM elements (and corresponding TNode's are stored
|
||||||
|
// in i18n section of LView)
|
||||||
|
expect(lViewDebug.i18n).toEqual({
|
||||||
|
start: lViewDebug.vars.end,
|
||||||
|
end: lViewDebug.expando.start,
|
||||||
|
length: 3,
|
||||||
|
content: [
|
||||||
|
jasmine.objectContaining({index: HEADER_OFFSET + 3, l: hello_}),
|
||||||
|
jasmine.objectContaining({index: HEADER_OFFSET + 4, l: world}),
|
||||||
|
jasmine.objectContaining({index: HEADER_OFFSET + 5, l: exclamation}),
|
||||||
|
]
|
||||||
|
});
|
||||||
|
// This assertion shows the DOM operations which the i18n subsystem performed to update the
|
||||||
|
// DOM with translated text. The offsets in the debug text should match the offsets in the
|
||||||
|
// above assertions.
|
||||||
|
expect((lView[TVIEW]!.data[HEADER_OFFSET + 1]! as TI18n).create.debug).toEqual([
|
||||||
|
'lView[3] = document.createTextNode("Hello ")',
|
||||||
|
'(lView[0] as Element).appendChild(lView[3])',
|
||||||
|
'(lView[0] as Element).appendChild(lView[2])',
|
||||||
|
'lView[4] = document.createTextNode("World")',
|
||||||
|
'(lView[2] as Element).appendChild(lView[4])',
|
||||||
|
'setPreviousOrParentTNode(tView.data[2] as TNode)',
|
||||||
|
'lView[5] = document.createTextNode("!")',
|
||||||
|
'(lView[0] as Element).appendChild(lView[5])',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('ICU', () => {
|
||||||
|
// In the case of ICUs we can't create TNodes for each ICU part, as different ICU instances
|
||||||
|
// may have different selections active and hence have different shape. In such a case
|
||||||
|
// a single `TIcuContainerNode` should be generated only.
|
||||||
|
it('should create a single dynamic TNode for ICU', () => {
|
||||||
|
const fixture = initWithTemplate(AppComp, `
|
||||||
|
{count, plural,
|
||||||
|
=0 {just now}
|
||||||
|
=1 {one minute ago}
|
||||||
|
other {{{count}} minutes ago}
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
const lView = getComponentLView(fixture.componentInstance);
|
||||||
|
const lViewDebug = lView.debug!;
|
||||||
|
expect((fixture.nativeElement as Element).textContent).toEqual('just now');
|
||||||
|
const text_just_now = (fixture.nativeElement as Element).firstChild!;
|
||||||
|
const icuComment = text_just_now.nextSibling!;
|
||||||
|
expect(lViewDebug.nodes.map(toTypeContent)).toEqual(['IcuContainer(<!--ICU 3-->)']);
|
||||||
|
// 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([
|
||||||
|
'Element(just now)', // FIXME(misko): This should not be here. The content of the ICU is
|
||||||
|
// instance specific and as such can't be encoded in the tNodes.
|
||||||
|
]);
|
||||||
|
expect(lViewDebug.decls).toEqual({
|
||||||
|
start: HEADER_OFFSET,
|
||||||
|
end: HEADER_OFFSET + 1,
|
||||||
|
length: 1,
|
||||||
|
content: [
|
||||||
|
jasmine.objectContaining({
|
||||||
|
t: jasmine.objectContaining({
|
||||||
|
vars: 3, // one slot for: the `<!--ICU 3-->`
|
||||||
|
// one slot for: the last selected ICU case.
|
||||||
|
// one slot for: the actual text node to attach.
|
||||||
|
create: jasmine.any(Object),
|
||||||
|
update: jasmine.any(Object),
|
||||||
|
icus: [jasmine.any(Object)],
|
||||||
|
}),
|
||||||
|
l: null
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
});
|
||||||
|
expect(((lViewDebug.decls.content[0].t as TI18n).create.debug)).toEqual([
|
||||||
|
'lView[3] = document.createComment("ICU 3")',
|
||||||
|
'(lView[0] as Element).appendChild(lView[3])',
|
||||||
|
]);
|
||||||
|
expect(((lViewDebug.decls.content[0].t as TI18n).update.debug)).toEqual([
|
||||||
|
'if (mask & 0b1) { icuSwitchCase(lView[3] as Comment, 0, `${lView[1]}`); }',
|
||||||
|
'if (mask & 0b11) { icuUpdateCase(lView[3] as Comment, 0); }',
|
||||||
|
]);
|
||||||
|
const tIcu = (lViewDebug.decls.content[0].t as TI18n).icus![0];
|
||||||
|
expect(tIcu.cases).toEqual(['0', '1', 'other']);
|
||||||
|
// Case: '0'
|
||||||
|
expect(tIcu.create[0].debug).toEqual([
|
||||||
|
'lView[5] = document.createTextNode("just now")',
|
||||||
|
'(lView[3] as Element).appendChild(lView[5])',
|
||||||
|
]);
|
||||||
|
expect(tIcu.remove[0].debug).toEqual(['(lView[0] as Element).remove(lView[5])']);
|
||||||
|
expect(tIcu.update[0].debug).toEqual([]);
|
||||||
|
|
||||||
|
// Case: '1'
|
||||||
|
expect(tIcu.create[1].debug).toEqual([
|
||||||
|
'lView[5] = document.createTextNode("one minute ago")',
|
||||||
|
'(lView[3] as Element).appendChild(lView[5])',
|
||||||
|
]);
|
||||||
|
expect(tIcu.remove[1].debug).toEqual(['(lView[0] as Element).remove(lView[5])']);
|
||||||
|
expect(tIcu.update[1].debug).toEqual([]);
|
||||||
|
|
||||||
|
// Case: 'other'
|
||||||
|
expect(tIcu.create[2].debug).toEqual([
|
||||||
|
'lView[5] = document.createTextNode("")',
|
||||||
|
'(lView[3] as Element).appendChild(lView[5])',
|
||||||
|
]);
|
||||||
|
expect(tIcu.remove[2].debug).toEqual(['(lView[0] as Element).remove(lView[5])']);
|
||||||
|
expect(tIcu.update[2].debug).toEqual([
|
||||||
|
'if (mask & 0b10) { (lView[5] as Text).textContent = `${lView[2]} minutes ago`; }'
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(lViewDebug.i18n).toEqual({
|
||||||
|
start: lViewDebug.vars.end,
|
||||||
|
end: lViewDebug.expando.start,
|
||||||
|
length: 3,
|
||||||
|
content: [
|
||||||
|
// ICU anchor `<!--ICU 3-->`.
|
||||||
|
jasmine.objectContaining({index: HEADER_OFFSET + 3, l: icuComment}),
|
||||||
|
// ICU `TIcu.currentCaseLViewIndex` storage location
|
||||||
|
jasmine.objectContaining({
|
||||||
|
index: HEADER_OFFSET + 4,
|
||||||
|
t: null,
|
||||||
|
l: 0, // The current ICU case
|
||||||
|
}),
|
||||||
|
jasmine.objectContaining({index: HEADER_OFFSET + 5, l: text_just_now}),
|
||||||
|
]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// FIXME(misko): re-enable and fix this use case.
|
||||||
|
xit('should support multiple ICUs', () => {
|
||||||
|
const fixture = initWithTemplate(AppComp, `
|
||||||
|
{count, plural,
|
||||||
|
=0 {just now}
|
||||||
|
=1 {one minute ago}
|
||||||
|
other {{{count}} minutes ago}
|
||||||
|
}
|
||||||
|
{count, plural,
|
||||||
|
=0 {just now}
|
||||||
|
=1 {one minute ago}
|
||||||
|
other {{{count}} minutes ago}
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
const lView = getComponentLView(fixture.componentInstance);
|
||||||
|
expect(lView.debug!.nodes.map(toTypeContent)).toEqual(['IcuContainer(<!--ICU 3-->)']);
|
||||||
|
// 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([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('should support ICU expressions', () => {
|
describe('should support ICU expressions', () => {
|
||||||
it('with no root node', () => {
|
it('with no root node', () => {
|
||||||
loadTranslations({
|
loadTranslations({
|
||||||
|
@ -690,19 +905,19 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
|
||||||
other {({{name}})}
|
other {({{name}})}
|
||||||
}</div>`);
|
}</div>`);
|
||||||
expect(fixture.nativeElement.innerHTML)
|
expect(fixture.nativeElement.innerHTML)
|
||||||
.toEqual(`<div>aucun <b>email</b>!<!--ICU 7--> - (Angular)<!--ICU 13--></div>`);
|
.toEqual(`<div>aucun <b>email</b>!<!--ICU 7--> - (Angular)<!--ICU 14--></div>`);
|
||||||
|
|
||||||
fixture.componentRef.instance.count = 4;
|
fixture.componentRef.instance.count = 4;
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
expect(fixture.nativeElement.innerHTML)
|
expect(fixture.nativeElement.innerHTML)
|
||||||
.toEqual(
|
.toEqual(
|
||||||
`<div>4 <span title="Angular">emails</span><!--ICU 7--> - (Angular)<!--ICU 13--></div>`);
|
`<div>4 <span title="Angular">emails</span><!--ICU 7--> - (Angular)<!--ICU 14--></div>`);
|
||||||
|
|
||||||
fixture.componentRef.instance.count = 0;
|
fixture.componentRef.instance.count = 0;
|
||||||
fixture.componentRef.instance.name = 'John';
|
fixture.componentRef.instance.name = 'John';
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
expect(fixture.nativeElement.innerHTML)
|
expect(fixture.nativeElement.innerHTML)
|
||||||
.toEqual(`<div>aucun <b>email</b>!<!--ICU 7--> - (John)<!--ICU 13--></div>`);
|
.toEqual(`<div>aucun <b>email</b>!<!--ICU 7--> - (John)<!--ICU 14--></div>`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('with custom interpolation config', () => {
|
it('with custom interpolation config', () => {
|
||||||
|
@ -740,20 +955,20 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
|
||||||
}</span></div>`);
|
}</span></div>`);
|
||||||
expect(fixture.nativeElement.innerHTML)
|
expect(fixture.nativeElement.innerHTML)
|
||||||
.toEqual(
|
.toEqual(
|
||||||
`<div><span>aucun <b>email</b>!<!--ICU 9--></span> - <span>(Angular)<!--ICU 15--></span></div>`);
|
`<div><span>aucun <b>email</b>!<!--ICU 9--></span> - <span>(Angular)<!--ICU 16--></span></div>`);
|
||||||
|
|
||||||
fixture.componentRef.instance.count = 4;
|
fixture.componentRef.instance.count = 4;
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
expect(fixture.nativeElement.innerHTML)
|
expect(fixture.nativeElement.innerHTML)
|
||||||
.toEqual(
|
.toEqual(
|
||||||
`<div><span>4 <span title="Angular">emails</span><!--ICU 9--></span> - <span>(Angular)<!--ICU 15--></span></div>`);
|
`<div><span>4 <span title="Angular">emails</span><!--ICU 9--></span> - <span>(Angular)<!--ICU 16--></span></div>`);
|
||||||
|
|
||||||
fixture.componentRef.instance.count = 0;
|
fixture.componentRef.instance.count = 0;
|
||||||
fixture.componentRef.instance.name = 'John';
|
fixture.componentRef.instance.name = 'John';
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
expect(fixture.nativeElement.innerHTML)
|
expect(fixture.nativeElement.innerHTML)
|
||||||
.toEqual(
|
.toEqual(
|
||||||
`<div><span>aucun <b>email</b>!<!--ICU 9--></span> - <span>(John)<!--ICU 15--></span></div>`);
|
`<div><span>aucun <b>email</b>!<!--ICU 9--></span> - <span>(John)<!--ICU 16--></span></div>`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('inside template directives', () => {
|
it('inside template directives', () => {
|
||||||
|
@ -1435,6 +1650,54 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
|
||||||
// checking the second ICU case
|
// checking the second ICU case
|
||||||
expect(fixture.nativeElement.textContent.trim()).toBe('deux articles');
|
expect(fixture.nativeElement.textContent.trim()).toBe('deux articles');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// FIXME(misko): re-enable and fix this use case. Root cause is that
|
||||||
|
// `addRemoveViewFromContainer` needs to understand ICU
|
||||||
|
xit('should handle select expressions without an `other` parameter inside a template', () => {
|
||||||
|
const fixture = initWithTemplate(AppComp, `
|
||||||
|
<ng-container *ngFor="let item of items">{item.value, select, 0 {A} 1 {B} 2 {C}}</ng-container>
|
||||||
|
`);
|
||||||
|
fixture.componentInstance.items = [{value: 0}, {value: 1}, {value: 1337}];
|
||||||
|
fixture.detectChanges();
|
||||||
|
const p = fixture.nativeElement.querySelector('p');
|
||||||
|
const lContext = loadLContext(p);
|
||||||
|
const lView = lContext.lView;
|
||||||
|
const nodeIndex = lContext.nodeIndex;
|
||||||
|
const tView = lView[TVIEW];
|
||||||
|
const i18n = tView.data[nodeIndex + 1] as unknown as TI18n;
|
||||||
|
expect(fixture.nativeElement.textContent.trim()).toBe('AB');
|
||||||
|
|
||||||
|
fixture.componentInstance.items[0].value = 2;
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(fixture.nativeElement.textContent.trim()).toBe('CB');
|
||||||
|
fail('testing');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render an element whose case did not match initially', () => {
|
||||||
|
const fixture = initWithTemplate(AppComp, `
|
||||||
|
<p *ngFor="let item of items">{item.value, select, 0 {A} 1 {B} 2 {C}}</p>
|
||||||
|
`);
|
||||||
|
fixture.componentInstance.items = [{value: 0}, {value: 1}, {value: 1337}];
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(fixture.nativeElement.textContent.trim()).toBe('AB');
|
||||||
|
|
||||||
|
fixture.componentInstance.items[2].value = 2;
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(fixture.nativeElement.textContent.trim()).toBe('ABC');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should remove an element whose case matched initially, but does not anymore', () => {
|
||||||
|
const fixture = initWithTemplate(AppComp, `
|
||||||
|
<p *ngFor="let item of items">{item.value, select, 0 {A} 1 {B} 2 {C}}</p>
|
||||||
|
`);
|
||||||
|
fixture.componentInstance.items = [{value: 0}, {value: 1}];
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(fixture.nativeElement.textContent.trim()).toBe('AB');
|
||||||
|
|
||||||
|
fixture.componentInstance.items[0].value = 1337;
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(fixture.nativeElement.textContent.trim()).toBe('B');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('should support attributes', () => {
|
describe('should support attributes', () => {
|
||||||
|
|
|
@ -261,7 +261,7 @@ describe('Runtime i18n', () => {
|
||||||
}, null, nbConsts, index) as TI18n;
|
}, null, nbConsts, index) as TI18n;
|
||||||
|
|
||||||
expect(opCodes).toEqual({
|
expect(opCodes).toEqual({
|
||||||
vars: 5,
|
vars: 6,
|
||||||
update: debugMatch([
|
update: debugMatch([
|
||||||
'if (mask & 0b1) { icuSwitchCase(lView[1] as Comment, 0, `${lView[1]}`); }',
|
'if (mask & 0b1) { icuSwitchCase(lView[1] as Comment, 0, `${lView[1]}`); }',
|
||||||
'if (mask & 0b11) { icuUpdateCase(lView[1] as Comment, 0); }',
|
'if (mask & 0b11) { icuUpdateCase(lView[1] as Comment, 0); }',
|
||||||
|
@ -272,60 +272,61 @@ describe('Runtime i18n', () => {
|
||||||
]),
|
]),
|
||||||
icus: [<TIcu>{
|
icus: [<TIcu>{
|
||||||
type: 1,
|
type: 1,
|
||||||
vars: [4, 3, 3],
|
currentCaseLViewIndex: 22,
|
||||||
|
vars: [5, 4, 4],
|
||||||
childIcus: [[], [], []],
|
childIcus: [[], [], []],
|
||||||
cases: ['0', '1', 'other'],
|
cases: ['0', '1', 'other'],
|
||||||
create: [
|
create: [
|
||||||
debugMatch([
|
debugMatch([
|
||||||
'lView[2] = document.createTextNode("no ")',
|
'lView[3] = document.createTextNode("no ")',
|
||||||
'(lView[1] as Element).appendChild(lView[2])',
|
|
||||||
'lView[3] = document.createElement("b")',
|
|
||||||
'(lView[1] as Element).appendChild(lView[3])',
|
'(lView[1] as Element).appendChild(lView[3])',
|
||||||
'(lView[3] as Element).setAttribute("title", "none")',
|
'lView[4] = document.createElement("b")',
|
||||||
'lView[4] = document.createTextNode("emails")',
|
'(lView[1] as Element).appendChild(lView[4])',
|
||||||
'(lView[3] as Element).appendChild(lView[4])',
|
'(lView[4] as Element).setAttribute("title", "none")',
|
||||||
'lView[5] = document.createTextNode("!")',
|
'lView[5] = document.createTextNode("emails")',
|
||||||
'(lView[1] as Element).appendChild(lView[5])'
|
'(lView[4] as Element).appendChild(lView[5])',
|
||||||
|
'lView[6] = document.createTextNode("!")',
|
||||||
|
'(lView[1] as Element).appendChild(lView[6])',
|
||||||
]),
|
]),
|
||||||
debugMatch([
|
debugMatch([
|
||||||
'lView[2] = document.createTextNode("one ")',
|
'lView[3] = document.createTextNode("one ")',
|
||||||
'(lView[1] as Element).appendChild(lView[2])',
|
|
||||||
'lView[3] = document.createElement("i")',
|
|
||||||
'(lView[1] as Element).appendChild(lView[3])',
|
'(lView[1] as Element).appendChild(lView[3])',
|
||||||
'lView[4] = document.createTextNode("email")',
|
'lView[4] = document.createElement("i")',
|
||||||
'(lView[3] as Element).appendChild(lView[4])'
|
'(lView[1] as Element).appendChild(lView[4])',
|
||||||
|
'lView[5] = document.createTextNode("email")',
|
||||||
|
'(lView[4] as Element).appendChild(lView[5])',
|
||||||
]),
|
]),
|
||||||
debugMatch([
|
debugMatch([
|
||||||
'lView[2] = document.createTextNode("")',
|
'lView[3] = document.createTextNode("")',
|
||||||
'(lView[1] as Element).appendChild(lView[2])',
|
|
||||||
'lView[3] = document.createElement("span")',
|
|
||||||
'(lView[1] as Element).appendChild(lView[3])',
|
'(lView[1] as Element).appendChild(lView[3])',
|
||||||
'lView[4] = document.createTextNode("emails")',
|
'lView[4] = document.createElement("span")',
|
||||||
'(lView[3] as Element).appendChild(lView[4])'
|
'(lView[1] as Element).appendChild(lView[4])',
|
||||||
|
'lView[5] = document.createTextNode("emails")',
|
||||||
|
'(lView[4] as Element).appendChild(lView[5])',
|
||||||
])
|
])
|
||||||
],
|
],
|
||||||
remove: [
|
remove: [
|
||||||
debugMatch([
|
debugMatch([
|
||||||
'(lView[0] as Element).remove(lView[2])',
|
|
||||||
'(lView[0] as Element).remove(lView[4])',
|
|
||||||
'(lView[0] as Element).remove(lView[3])',
|
'(lView[0] as Element).remove(lView[3])',
|
||||||
'(lView[0] as Element).remove(lView[5])',
|
'(lView[0] as Element).remove(lView[5])',
|
||||||
|
'(lView[0] as Element).remove(lView[4])',
|
||||||
|
'(lView[0] as Element).remove(lView[6])',
|
||||||
]),
|
]),
|
||||||
debugMatch([
|
debugMatch([
|
||||||
'(lView[0] as Element).remove(lView[2])',
|
|
||||||
'(lView[0] as Element).remove(lView[4])',
|
|
||||||
'(lView[0] as Element).remove(lView[3])',
|
'(lView[0] as Element).remove(lView[3])',
|
||||||
|
'(lView[0] as Element).remove(lView[5])',
|
||||||
|
'(lView[0] as Element).remove(lView[4])',
|
||||||
]),
|
]),
|
||||||
debugMatch([
|
debugMatch([
|
||||||
'(lView[0] as Element).remove(lView[2])',
|
|
||||||
'(lView[0] as Element).remove(lView[4])',
|
|
||||||
'(lView[0] as Element).remove(lView[3])',
|
'(lView[0] as Element).remove(lView[3])',
|
||||||
|
'(lView[0] as Element).remove(lView[5])',
|
||||||
|
'(lView[0] as Element).remove(lView[4])',
|
||||||
])
|
])
|
||||||
],
|
],
|
||||||
update: [
|
update: [
|
||||||
debugMatch([]), debugMatch([]), debugMatch([
|
debugMatch([]), debugMatch([]), debugMatch([
|
||||||
'if (mask & 0b1) { (lView[2] as Text).textContent = `${lView[1]} `; }',
|
'if (mask & 0b1) { (lView[3] as Text).textContent = `${lView[1]} `; }',
|
||||||
'if (mask & 0b10) { (lView[3] as Element).setAttribute(\'title\', `${lView[2]}`); }'
|
'if (mask & 0b10) { (lView[4] as Element).setAttribute(\'title\', `${lView[2]}`); }'
|
||||||
])
|
])
|
||||||
]
|
]
|
||||||
}]
|
}]
|
||||||
|
@ -355,7 +356,7 @@ describe('Runtime i18n', () => {
|
||||||
const nestedTIcuIndex = 0;
|
const nestedTIcuIndex = 0;
|
||||||
|
|
||||||
expect(opCodes).toEqual({
|
expect(opCodes).toEqual({
|
||||||
vars: 6,
|
vars: 9,
|
||||||
create: debugMatch([
|
create: debugMatch([
|
||||||
'lView[1] = document.createComment("ICU 1")',
|
'lView[1] = document.createComment("ICU 1")',
|
||||||
'(lView[0] as Element).appendChild(lView[1])'
|
'(lView[0] as Element).appendChild(lView[1])'
|
||||||
|
@ -367,27 +368,28 @@ describe('Runtime i18n', () => {
|
||||||
icus: [
|
icus: [
|
||||||
{
|
{
|
||||||
type: 0,
|
type: 0,
|
||||||
vars: [1, 1, 1],
|
vars: [2, 2, 2],
|
||||||
|
currentCaseLViewIndex: 26,
|
||||||
childIcus: [[], [], []],
|
childIcus: [[], [], []],
|
||||||
cases: ['cat', 'dog', 'other'],
|
cases: ['cat', 'dog', 'other'],
|
||||||
create: [
|
create: [
|
||||||
debugMatch([
|
debugMatch([
|
||||||
'lView[5] = document.createTextNode("cats")',
|
'lView[7] = document.createTextNode("cats")',
|
||||||
'(lView[3] as Element).appendChild(lView[5])'
|
'(lView[4] as Element).appendChild(lView[7])'
|
||||||
]),
|
]),
|
||||||
debugMatch([
|
debugMatch([
|
||||||
'lView[5] = document.createTextNode("dogs")',
|
'lView[7] = document.createTextNode("dogs")',
|
||||||
'(lView[3] as Element).appendChild(lView[5])'
|
'(lView[4] as Element).appendChild(lView[7])'
|
||||||
]),
|
]),
|
||||||
debugMatch([
|
debugMatch([
|
||||||
'lView[5] = document.createTextNode("animals")',
|
'lView[7] = document.createTextNode("animals")',
|
||||||
'(lView[3] as Element).appendChild(lView[5])'
|
'(lView[4] as Element).appendChild(lView[7])'
|
||||||
]),
|
]),
|
||||||
],
|
],
|
||||||
remove: [
|
remove: [
|
||||||
debugMatch(['(lView[0] as Element).remove(lView[5])']),
|
debugMatch(['(lView[0] as Element).remove(lView[7])']),
|
||||||
debugMatch(['(lView[0] as Element).remove(lView[5])']),
|
debugMatch(['(lView[0] as Element).remove(lView[7])']),
|
||||||
debugMatch(['(lView[0] as Element).remove(lView[5])'])
|
debugMatch(['(lView[0] as Element).remove(lView[7])'])
|
||||||
],
|
],
|
||||||
update: [
|
update: [
|
||||||
debugMatch([]),
|
debugMatch([]),
|
||||||
|
@ -397,36 +399,37 @@ describe('Runtime i18n', () => {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 1,
|
type: 1,
|
||||||
vars: [1, 4],
|
vars: [2, 6],
|
||||||
childIcus: [[], [0]],
|
childIcus: [[], [0]],
|
||||||
|
currentCaseLViewIndex: 22,
|
||||||
cases: ['0', 'other'],
|
cases: ['0', 'other'],
|
||||||
create: [
|
create: [
|
||||||
debugMatch([
|
debugMatch([
|
||||||
'lView[2] = document.createTextNode("zero")',
|
'lView[3] = document.createTextNode("zero")',
|
||||||
'(lView[1] as Element).appendChild(lView[2])'
|
'(lView[1] as Element).appendChild(lView[3])'
|
||||||
]),
|
]),
|
||||||
debugMatch([
|
debugMatch([
|
||||||
'lView[2] = document.createTextNode("")',
|
'lView[3] = document.createTextNode("")',
|
||||||
'(lView[1] as Element).appendChild(lView[2])',
|
|
||||||
'lView[3] = document.createComment("nested ICU 0")',
|
|
||||||
'(lView[1] as Element).appendChild(lView[3])',
|
'(lView[1] as Element).appendChild(lView[3])',
|
||||||
'lView[4] = document.createTextNode("!")',
|
'lView[4] = document.createComment("nested ICU 0")',
|
||||||
'(lView[1] as Element).appendChild(lView[4])'
|
'(lView[1] as Element).appendChild(lView[4])',
|
||||||
|
'lView[5] = document.createTextNode("!")',
|
||||||
|
'(lView[1] as Element).appendChild(lView[5])'
|
||||||
]),
|
]),
|
||||||
],
|
],
|
||||||
remove: [
|
remove: [
|
||||||
debugMatch(['(lView[0] as Element).remove(lView[2])']),
|
debugMatch(['(lView[0] as Element).remove(lView[3])']),
|
||||||
debugMatch([
|
debugMatch([
|
||||||
'(lView[0] as Element).remove(lView[2])', '(lView[0] as Element).remove(lView[4])',
|
'(lView[0] as Element).remove(lView[3])', '(lView[0] as Element).remove(lView[5])',
|
||||||
'removeNestedICU(0)', '(lView[0] as Element).remove(lView[3])'
|
'removeNestedICU(0)', '(lView[0] as Element).remove(lView[4])'
|
||||||
]),
|
]),
|
||||||
],
|
],
|
||||||
update: [
|
update: [
|
||||||
debugMatch([]),
|
debugMatch([]),
|
||||||
debugMatch([
|
debugMatch([
|
||||||
'if (mask & 0b1) { (lView[2] as Text).textContent = `${lView[1]} `; }',
|
'if (mask & 0b1) { (lView[3] as Text).textContent = `${lView[1]} `; }',
|
||||||
'if (mask & 0b10) { icuSwitchCase(lView[3] as Comment, 0, `${lView[2]}`); }',
|
'if (mask & 0b10) { icuSwitchCase(lView[4] as Comment, 0, `${lView[2]}`); }',
|
||||||
'if (mask & 0b10) { icuUpdateCase(lView[3] as Comment, 0); }'
|
'if (mask & 0b10) { icuUpdateCase(lView[4] as Comment, 0); }'
|
||||||
]),
|
]),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue