upstream: Merge remote-tracking branch 'upstream/master' into merge-upstream
# Conflicts: # CHANGELOG.md # aio/content/examples/testing/src/app/app.component.router.spec.ts # aio/content/examples/testing/src/app/dashboard/dashboard-hero.component.spec.ts # aio/content/examples/testing/src/app/dashboard/dashboard.component.spec.ts # aio/content/examples/testing/src/app/hero/hero-detail.component.spec.ts # aio/content/examples/testing/src/app/hero/hero-list.component.spec.ts # aio/content/examples/testing/src/app/twain/twain.component.spec.ts # goldens/public-api/core/testing/testing.d.ts # goldens/size-tracking/aio-payloads.json # package.json # packages/core/test/bundling/forms/bundle.golden_symbols.json # packages/forms/test/form_group_spec.ts
This commit is contained in:
@ -292,5 +292,8 @@ export {
|
||||
ɵɵsanitizeUrl,
|
||||
ɵɵsanitizeUrlOrResourceUrl,
|
||||
} from './sanitization/sanitization';
|
||||
export {
|
||||
noSideEffects as ɵnoSideEffects,
|
||||
} from './util/closure';
|
||||
|
||||
// clang-format on
|
||||
|
@ -28,6 +28,7 @@ export {ɵɵdefineNgModule} from './render3/definition';
|
||||
export {ɵɵFactoryDef} from './render3/interfaces/definition';
|
||||
export {setClassMetadata} from './render3/metadata';
|
||||
export {NgModuleFactory} from './render3/ng_module_ref';
|
||||
export {noSideEffects as ɵnoSideEffects} from './util/closure';
|
||||
|
||||
|
||||
|
||||
|
@ -6,6 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import '../util/ng_i18n_closure_mode';
|
||||
import '../util/ng_dev_mode';
|
||||
|
||||
import {DEFAULT_LOCALE_ID, getPluralCase} from '../i18n/localization';
|
||||
import {getTemplateContent, SRCSET_ATTRS, URI_ATTRS, VALID_ATTRS, VALID_ELEMENTS} from '../sanitization/html_sanitizer';
|
||||
@ -16,8 +17,8 @@ import {assertDataInRange, assertDefined, assertEqual} from '../util/assert';
|
||||
|
||||
import {bindingUpdated} from './bindings';
|
||||
import {attachPatchData} from './context_discovery';
|
||||
import {i18nMutateOpCodesToString, i18nUpdateOpCodesToString} from './i18n_debug';
|
||||
import {setDelayProjection} from './instructions/all';
|
||||
import {attachI18nOpCodesDebug} from './instructions/lview_debug';
|
||||
import {allocExpando, elementAttributeInternal, elementPropertyInternal, getOrCreateTNode, setInputsForProperty, setNgReflectProperties, textBindingInternal} from './instructions/shared';
|
||||
import {LContainer, NATIVE} from './interfaces/container';
|
||||
import {getDocument} from './interfaces/document';
|
||||
@ -29,6 +30,7 @@ import {isLContainer} from './interfaces/type_checks';
|
||||
import {HEADER_OFFSET, LView, RENDERER, T_HOST, TVIEW, TView} from './interfaces/view';
|
||||
import {appendChild, applyProjection, createTextNode, nativeRemoveNode} from './node_manipulation';
|
||||
import {getBindingIndex, getIsParent, getLView, getPreviousOrParentTNode, getTView, nextBindingIndex, setIsNotParent, setPreviousOrParentTNode} from './state';
|
||||
import {attachDebugGetter} from './util/debug_utils';
|
||||
import {renderStringify} from './util/misc_utils';
|
||||
import {getNativeByIndex, getNativeByTNode, getTNode, load} from './util/view_utils';
|
||||
|
||||
@ -267,6 +269,9 @@ function generateBindingUpdateOpCodes(
|
||||
str: string, destinationNode: number, attrName?: string,
|
||||
sanitizeFn: SanitizerFn|null = null): I18nUpdateOpCodes {
|
||||
const updateOpCodes: I18nUpdateOpCodes = [null, null]; // Alloc space for mask and size
|
||||
if (ngDevMode) {
|
||||
attachDebugGetter(updateOpCodes, i18nUpdateOpCodesToString);
|
||||
}
|
||||
const textParts = str.split(BINDING_REGEXP);
|
||||
let mask = 0;
|
||||
|
||||
@ -395,6 +400,9 @@ function i18nStartFirstPass(
|
||||
let parentIndexPointer = 0;
|
||||
parentIndexStack[parentIndexPointer] = parentIndex;
|
||||
const createOpCodes: I18nMutateOpCodes = [];
|
||||
if (ngDevMode) {
|
||||
attachDebugGetter(createOpCodes, i18nMutateOpCodesToString);
|
||||
}
|
||||
// If the previous node wasn't the direct parent then we have a translation without top level
|
||||
// element and we need to keep a reference of the previous element if there is one. We should also
|
||||
// keep track whether an element was a parent node or not, so that the logic that consumes
|
||||
@ -411,6 +419,9 @@ function i18nStartFirstPass(
|
||||
createOpCodes.push(previousTNodeIndex << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Select);
|
||||
}
|
||||
const updateOpCodes: I18nUpdateOpCodes = [];
|
||||
if (ngDevMode) {
|
||||
attachDebugGetter(updateOpCodes, i18nUpdateOpCodesToString);
|
||||
}
|
||||
const icuExpressions: TIcu[] = [];
|
||||
|
||||
if (message === '' && isRootTemplateMessage(subTemplateIndex)) {
|
||||
@ -507,10 +518,6 @@ function i18nStartFirstPass(
|
||||
allocExpando(tView, lView, i18nVarsCount);
|
||||
}
|
||||
|
||||
ngDevMode &&
|
||||
attachI18nOpCodesDebug(
|
||||
createOpCodes, updateOpCodes, icuExpressions.length ? icuExpressions : null, lView);
|
||||
|
||||
// NOTE: local var needed to properly assert the type of `TI18n`.
|
||||
const tI18n: TI18n = {
|
||||
vars: i18nVarsCount,
|
||||
@ -751,6 +758,7 @@ function createDynamicNodeAtIndex(
|
||||
const previousOrParentTNode = getPreviousOrParentTNode();
|
||||
ngDevMode && assertDataInRange(lView, index + HEADER_OFFSET);
|
||||
lView[index + HEADER_OFFSET] = native;
|
||||
// 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);
|
||||
|
||||
// We are creating a dynamic node, the previous tNode might not be pointing at this node.
|
||||
@ -780,7 +788,7 @@ function readCreateOpCodes(
|
||||
visitedNodes.push(textNodeIndex);
|
||||
setIsNotParent();
|
||||
} else if (typeof opCode == 'number') {
|
||||
switch (opCode & I18nMutateOpCode.MASK_OPCODE) {
|
||||
switch (opCode & I18nMutateOpCode.MASK_INSTRUCTION) {
|
||||
case I18nMutateOpCode.AppendChild:
|
||||
const destinationNodeIndex = opCode >>> I18nMutateOpCode.SHIFT_PARENT;
|
||||
let destinationTNode: TNode;
|
||||
@ -799,9 +807,10 @@ function readCreateOpCodes(
|
||||
appendI18nNode(tView, currentTNode!, destinationTNode, previousTNode, lView);
|
||||
break;
|
||||
case I18nMutateOpCode.Select:
|
||||
// Negative indicies indicate that a given TNode is a sibling node, not a parent node
|
||||
// Negative indices indicate that a given TNode is a sibling node, not a parent node
|
||||
// (see `i18nStartFirstPass` for additional information).
|
||||
const isParent = opCode >= 0;
|
||||
// FIXME(misko): This SHIFT_REF looks suspect as it does not have mask.
|
||||
const nodeIndex = (isParent ? opCode : ~opCode) >>> I18nMutateOpCode.SHIFT_REF;
|
||||
visitedNodes.push(nodeIndex);
|
||||
previousTNode = currentTNode;
|
||||
@ -874,7 +883,7 @@ function readCreateOpCodes(
|
||||
|
||||
function readUpdateOpCodes(
|
||||
updateOpCodes: I18nUpdateOpCodes, icus: TIcu[]|null, bindingsStartIndex: number,
|
||||
changeMask: number, tView: TView, lView: LView, bypassCheckBit = false) {
|
||||
changeMask: number, tView: TView, lView: LView, bypassCheckBit: boolean) {
|
||||
let caseCreated = false;
|
||||
for (let i = 0; i < updateOpCodes.length; i++) {
|
||||
// bit code to check if we should apply the next update
|
||||
@ -890,13 +899,10 @@ function readUpdateOpCodes(
|
||||
value += opCode;
|
||||
} else if (typeof opCode == 'number') {
|
||||
if (opCode < 0) {
|
||||
// It's a binding index whose value is negative
|
||||
// Negative opCode represent `i18nExp` values offset.
|
||||
value += renderStringify(lView[bindingsStartIndex - opCode]);
|
||||
} else {
|
||||
const nodeIndex = opCode >>> I18nUpdateOpCode.SHIFT_REF;
|
||||
let tIcuIndex: number;
|
||||
let tIcu: TIcu;
|
||||
let icuTNode: TIcuContainerNode;
|
||||
switch (opCode & I18nUpdateOpCode.MASK_OPCODE) {
|
||||
case I18nUpdateOpCode.Attr:
|
||||
const propName = updateOpCodes[++j] as string;
|
||||
@ -909,56 +915,13 @@ function readUpdateOpCodes(
|
||||
textBindingInternal(lView, nodeIndex, value);
|
||||
break;
|
||||
case I18nUpdateOpCode.IcuSwitch:
|
||||
tIcuIndex = updateOpCodes[++j] as number;
|
||||
tIcu = icus![tIcuIndex];
|
||||
icuTNode = getTNode(tView, nodeIndex) as TIcuContainerNode;
|
||||
// If there is an active case, delete the old nodes
|
||||
if (icuTNode.activeCaseIndex !== null) {
|
||||
const removeCodes = tIcu.remove[icuTNode.activeCaseIndex];
|
||||
for (let k = 0; k < removeCodes.length; k++) {
|
||||
const removeOpCode = removeCodes[k] as number;
|
||||
switch (removeOpCode & I18nMutateOpCode.MASK_OPCODE) {
|
||||
case I18nMutateOpCode.Remove:
|
||||
const nodeIndex = removeOpCode >>> I18nMutateOpCode.SHIFT_REF;
|
||||
// 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
|
||||
// representing a new ICU case will be re-created.
|
||||
removeNode(tView, lView, nodeIndex, /* markAsDetached */ false);
|
||||
break;
|
||||
case I18nMutateOpCode.RemoveNestedIcu:
|
||||
const nestedIcuNodeIndex =
|
||||
removeCodes[k + 1] as number >>> I18nMutateOpCode.SHIFT_REF;
|
||||
const nestedIcuTNode =
|
||||
getTNode(tView, nestedIcuNodeIndex) as TIcuContainerNode;
|
||||
const activeIndex = nestedIcuTNode.activeCaseIndex;
|
||||
if (activeIndex !== null) {
|
||||
const nestedIcuTIndex = removeOpCode >>> I18nMutateOpCode.SHIFT_REF;
|
||||
const nestedTIcu = icus![nestedIcuTIndex];
|
||||
addAllToArray(nestedTIcu.remove[activeIndex], removeCodes);
|
||||
}
|
||||
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, tIcu.create[caseIndex], tView, lView);
|
||||
caseCreated = true;
|
||||
}
|
||||
caseCreated = icuSwitchCase(
|
||||
tView, updateOpCodes[++j] as number, nodeIndex, icus!, lView, value);
|
||||
break;
|
||||
case I18nUpdateOpCode.IcuUpdate:
|
||||
tIcuIndex = updateOpCodes[++j] as number;
|
||||
tIcu = icus![tIcuIndex];
|
||||
icuTNode = getTNode(tView, nodeIndex) as TIcuContainerNode;
|
||||
if (icuTNode.activeCaseIndex !== null) {
|
||||
readUpdateOpCodes(
|
||||
tIcu.update[icuTNode.activeCaseIndex], icus, bindingsStartIndex, changeMask,
|
||||
tView, lView, caseCreated);
|
||||
}
|
||||
icuUpdateCase(
|
||||
tView, lView, updateOpCodes[++j] as number, nodeIndex, bindingsStartIndex,
|
||||
icus!, caseCreated);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -969,6 +932,70 @@ function readUpdateOpCodes(
|
||||
}
|
||||
}
|
||||
|
||||
function icuUpdateCase(
|
||||
tView: TView, lView: LView, tIcuIndex: number, nodeIndex: number, bindingsStartIndex: number,
|
||||
tIcus: TIcu[], caseCreated: boolean) {
|
||||
const tIcu = tIcus[tIcuIndex];
|
||||
const icuTNode = getTNode(tView, nodeIndex) as TIcuContainerNode;
|
||||
if (icuTNode.activeCaseIndex !== null) {
|
||||
readUpdateOpCodes(
|
||||
tIcu.update[icuTNode.activeCaseIndex], tIcus, bindingsStartIndex, changeMask, tView, lView,
|
||||
caseCreated);
|
||||
}
|
||||
}
|
||||
|
||||
function icuSwitchCase(
|
||||
tView: TView, tIcuIndex: number, nodeIndex: number, tIcus: TIcu[], lView: LView,
|
||||
value: string): boolean {
|
||||
const tIcu = tIcus[tIcuIndex];
|
||||
const icuTNode = getTNode(tView, nodeIndex) as TIcuContainerNode;
|
||||
let caseCreated = false;
|
||||
// If there is an active case, delete the old nodes
|
||||
if (icuTNode.activeCaseIndex !== null) {
|
||||
const removeCodes = tIcu.remove[icuTNode.activeCaseIndex];
|
||||
for (let k = 0; k < removeCodes.length; k++) {
|
||||
const removeOpCode = removeCodes[k] as number;
|
||||
const nodeOrIcuIndex = removeOpCode >>> I18nMutateOpCode.SHIFT_REF;
|
||||
switch (removeOpCode & I18nMutateOpCode.MASK_INSTRUCTION) {
|
||||
case I18nMutateOpCode.Remove:
|
||||
// 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
|
||||
// representing a new ICU case will be re-created.
|
||||
removeNode(tView, lView, nodeOrIcuIndex, /* markAsDetached */ false);
|
||||
break;
|
||||
case I18nMutateOpCode.RemoveNestedIcu:
|
||||
removeNestedIcu(
|
||||
tView, tIcus, removeCodes, nodeOrIcuIndex,
|
||||
removeCodes[k + 1] as number >>> I18nMutateOpCode.SHIFT_REF);
|
||||
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) {
|
||||
const removedPhTNode = getTNode(tView, index);
|
||||
const removedPhRNode = getNativeByIndex(index, lView);
|
||||
@ -1044,6 +1071,9 @@ function i18nAttributesFirstPass(lView: LView, tView: TView, index: number, valu
|
||||
const previousElement = getPreviousOrParentTNode();
|
||||
const previousElementIndex = previousElement.index - HEADER_OFFSET;
|
||||
const updateOpCodes: I18nUpdateOpCodes = [];
|
||||
if (ngDevMode) {
|
||||
attachDebugGetter(updateOpCodes, i18nUpdateOpCodesToString);
|
||||
}
|
||||
for (let i = 0; i < values.length; i += 2) {
|
||||
const attrName = values[i];
|
||||
const message = values[i + 1];
|
||||
@ -1134,7 +1164,7 @@ export function ɵɵi18nApply(index: number) {
|
||||
}
|
||||
const bindingsStartIndex = getBindingIndex() - shiftsCounter - 1;
|
||||
const lView = getLView();
|
||||
readUpdateOpCodes(updateOpCodes, icus, bindingsStartIndex, changeMask, tView, lView);
|
||||
readUpdateOpCodes(updateOpCodes, icus, bindingsStartIndex, changeMask, tView, lView, false);
|
||||
|
||||
// Reset changeMask & maskBit to default for the next update cycle
|
||||
changeMask = 0b0;
|
||||
@ -1180,9 +1210,9 @@ function getCaseIndex(icuExpression: TIcu, bindingValue: string): number {
|
||||
function icuStart(
|
||||
tIcus: TIcu[], icuExpression: IcuExpression, startIndex: number,
|
||||
expandoStartIndex: number): void {
|
||||
const createCodes = [];
|
||||
const removeCodes = [];
|
||||
const updateCodes = [];
|
||||
const createCodes: I18nMutateOpCodes[] = [];
|
||||
const removeCodes: I18nMutateOpCodes[] = [];
|
||||
const updateCodes: I18nUpdateOpCodes[] = [];
|
||||
const vars = [];
|
||||
const childIcus: number[][] = [];
|
||||
for (let i = 0; i < icuExpression.values.length; i++) {
|
||||
@ -1240,6 +1270,11 @@ function parseIcuCase(
|
||||
}
|
||||
const wrapper = getTemplateContent(inertBodyElement!) as Element || inertBodyElement;
|
||||
const opCodes: IcuCase = {vars: 0, childIcus: [], create: [], remove: [], update: []};
|
||||
if (ngDevMode) {
|
||||
attachDebugGetter(opCodes.create, i18nMutateOpCodesToString);
|
||||
attachDebugGetter(opCodes.remove, i18nMutateOpCodesToString);
|
||||
attachDebugGetter(opCodes.update, i18nUpdateOpCodesToString);
|
||||
}
|
||||
parseNodes(wrapper.firstChild, opCodes, parentIndex, nestedIcus, tIcus, expandoStartIndex);
|
||||
return opCodes;
|
||||
}
|
||||
@ -1364,6 +1399,7 @@ function parseNodes(
|
||||
3, // skip 3 opCodes if not changed
|
||||
-1 - nestedIcu.mainBinding,
|
||||
nestedIcuNodeIndex << I18nUpdateOpCode.SHIFT_REF | I18nUpdateOpCode.IcuSwitch,
|
||||
// FIXME(misko): Index should be part of the opcode
|
||||
nestTIcuIndex,
|
||||
mask, // mask of all the bindings of this ICU expression
|
||||
2, // skip 2 opCodes if not changed
|
||||
@ -1371,6 +1407,7 @@ function parseNodes(
|
||||
nestTIcuIndex);
|
||||
icuCase.remove.push(
|
||||
nestTIcuIndex << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.RemoveNestedIcu,
|
||||
// FIXME(misko): Index should be part of the opcode
|
||||
nestedIcuNodeIndex << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Remove);
|
||||
}
|
||||
}
|
||||
|
186
packages/core/src/render3/i18n_debug.ts
Normal file
186
packages/core/src/render3/i18n_debug.ts
Normal file
@ -0,0 +1,186 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {assertNumber, assertString} from '../util/assert';
|
||||
|
||||
import {COMMENT_MARKER, ELEMENT_MARKER, getInstructionFromI18nMutateOpCode, getParentFromI18nMutateOpCode, getRefFromI18nMutateOpCode, I18nMutateOpCode, I18nMutateOpCodes, I18nUpdateOpCode, I18nUpdateOpCodes} from './interfaces/i18n';
|
||||
|
||||
/**
|
||||
* Converts `I18nUpdateOpCodes` array into a human readable format.
|
||||
*
|
||||
* This function is attached to the `I18nUpdateOpCodes.debug` property if `ngDevMode` is enabled.
|
||||
* This function provides a human readable view of the opcodes. This is useful when debugging the
|
||||
* application as well as writing more readable tests.
|
||||
*
|
||||
* @param this `I18nUpdateOpCodes` if attached as a method.
|
||||
* @param opcodes `I18nUpdateOpCodes` if invoked as a function.
|
||||
*/
|
||||
export function i18nUpdateOpCodesToString(
|
||||
this: I18nUpdateOpCodes|void, opcodes?: I18nUpdateOpCodes): string[] {
|
||||
const parser = new OpCodeParser(opcodes || (Array.isArray(this) ? this : []));
|
||||
let lines: string[] = [];
|
||||
|
||||
function consumeOpCode(value: number): string {
|
||||
const ref = value >>> I18nUpdateOpCode.SHIFT_REF;
|
||||
const opCode = value & I18nUpdateOpCode.MASK_OPCODE;
|
||||
switch (opCode) {
|
||||
case I18nUpdateOpCode.Text:
|
||||
return `(lView[${ref}] as Text).textContent = $$$`;
|
||||
case I18nUpdateOpCode.Attr:
|
||||
const attrName = parser.consumeString();
|
||||
const sanitizationFn = parser.consumeFunction();
|
||||
const value = sanitizationFn ? `(${sanitizationFn})($$$)` : '$$$';
|
||||
return `(lView[${ref}] as Element).setAttribute('${attrName}', ${value})`;
|
||||
case I18nUpdateOpCode.IcuSwitch:
|
||||
return `icuSwitchCase(lView[${ref}] as Comment, ${parser.consumeNumber()}, $$$)`;
|
||||
case I18nUpdateOpCode.IcuUpdate:
|
||||
return `icuUpdateCase(lView[${ref}] as Comment, ${parser.consumeNumber()})`;
|
||||
}
|
||||
throw new Error('unexpected OpCode');
|
||||
}
|
||||
|
||||
|
||||
while (parser.hasMore()) {
|
||||
let mask = parser.consumeNumber();
|
||||
let size = parser.consumeNumber();
|
||||
const end = parser.i + size;
|
||||
const statements: string[] = [];
|
||||
let statement = '';
|
||||
while (parser.i < end) {
|
||||
let value = parser.consumeNumberOrString();
|
||||
if (typeof value === 'string') {
|
||||
statement += value;
|
||||
} else if (value < 0) {
|
||||
// Negative numbers are ref indexes
|
||||
statement += '${lView[' + (0 - value) + ']}';
|
||||
} else {
|
||||
// Positive numbers are operations.
|
||||
const opCodeText = consumeOpCode(value);
|
||||
statements.push(opCodeText.replace('$$$', '`' + statement + '`') + ';');
|
||||
statement = '';
|
||||
}
|
||||
}
|
||||
lines.push(`if (mask & 0b${mask.toString(2)}) { ${statements.join(' ')} }`);
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts `I18nMutableOpCodes` array into a human readable format.
|
||||
*
|
||||
* This function is attached to the `I18nMutableOpCodes.debug` if `ngDevMode` is enabled. This
|
||||
* function provides a human readable view of the opcodes. This is useful when debugging the
|
||||
* application as well as writing more readable tests.
|
||||
*
|
||||
* @param this `I18nMutableOpCodes` if attached as a method.
|
||||
* @param opcodes `I18nMutableOpCodes` if invoked as a function.
|
||||
*/
|
||||
export function i18nMutateOpCodesToString(
|
||||
this: I18nMutateOpCodes|void, opcodes?: I18nMutateOpCodes): string[] {
|
||||
const parser = new OpCodeParser(opcodes || (Array.isArray(this) ? this : []));
|
||||
let lines: string[] = [];
|
||||
|
||||
function consumeOpCode(opCode: number): string {
|
||||
const parent = getParentFromI18nMutateOpCode(opCode);
|
||||
const ref = getRefFromI18nMutateOpCode(opCode);
|
||||
switch (getInstructionFromI18nMutateOpCode(opCode)) {
|
||||
case I18nMutateOpCode.Select:
|
||||
lastRef = ref;
|
||||
return '';
|
||||
case I18nMutateOpCode.AppendChild:
|
||||
return `(lView[${parent}] as Element).appendChild(lView[${lastRef}])`;
|
||||
case I18nMutateOpCode.Remove:
|
||||
return `(lView[${parent}] as Element).remove(lView[${ref}])`;
|
||||
case I18nMutateOpCode.Attr:
|
||||
return `(lView[${ref}] as Element).setAttribute("${parser.consumeString()}", "${
|
||||
parser.consumeString()}")`;
|
||||
case I18nMutateOpCode.ElementEnd:
|
||||
return `setPreviousOrParentTNode(tView.data[${ref}] as TNode)`;
|
||||
case I18nMutateOpCode.RemoveNestedIcu:
|
||||
return `removeNestedICU(${ref})`;
|
||||
}
|
||||
throw new Error('Unexpected OpCode');
|
||||
}
|
||||
|
||||
let lastRef = -1;
|
||||
while (parser.hasMore()) {
|
||||
let value = parser.consumeNumberStringOrMarker();
|
||||
if (value === COMMENT_MARKER) {
|
||||
const text = parser.consumeString();
|
||||
lastRef = parser.consumeNumber();
|
||||
lines.push(`lView[${lastRef}] = document.createComment("${text}")`);
|
||||
} else if (value === ELEMENT_MARKER) {
|
||||
const text = parser.consumeString();
|
||||
lastRef = parser.consumeNumber();
|
||||
lines.push(`lView[${lastRef}] = document.createElement("${text}")`);
|
||||
} else if (typeof value === 'string') {
|
||||
lastRef = parser.consumeNumber();
|
||||
lines.push(`lView[${lastRef}] = document.createTextNode("${value}")`);
|
||||
} else if (typeof value === 'number') {
|
||||
const line = consumeOpCode(value);
|
||||
line && lines.push(line);
|
||||
} else {
|
||||
throw new Error('Unexpected value');
|
||||
}
|
||||
}
|
||||
|
||||
return lines;
|
||||
}
|
||||
|
||||
|
||||
class OpCodeParser {
|
||||
i: number = 0;
|
||||
codes: any[];
|
||||
|
||||
constructor(codes: any[]) {
|
||||
this.codes = codes;
|
||||
}
|
||||
|
||||
hasMore() {
|
||||
return this.i < this.codes.length;
|
||||
}
|
||||
|
||||
consumeNumber(): number {
|
||||
let value = this.codes[this.i++];
|
||||
assertNumber(value, 'expecting number in OpCode');
|
||||
return value;
|
||||
}
|
||||
|
||||
consumeString(): string {
|
||||
let value = this.codes[this.i++];
|
||||
assertString(value, 'expecting string in OpCode');
|
||||
return value;
|
||||
}
|
||||
|
||||
consumeFunction(): Function|null {
|
||||
let value = this.codes[this.i++];
|
||||
if (value === null || typeof value === 'function') {
|
||||
return value;
|
||||
}
|
||||
throw new Error('expecting function in OpCode');
|
||||
}
|
||||
|
||||
consumeNumberOrString(): number|string {
|
||||
let value = this.codes[this.i++];
|
||||
if (typeof value === 'string') {
|
||||
return value;
|
||||
}
|
||||
assertNumber(value, 'expecting number or string in OpCode');
|
||||
return value;
|
||||
}
|
||||
|
||||
consumeNumberStringOrMarker(): number|string|COMMENT_MARKER|ELEMENT_MARKER {
|
||||
let value = this.codes[this.i++];
|
||||
if (typeof value === 'string' || typeof value === 'number' || value == COMMENT_MARKER ||
|
||||
value == ELEMENT_MARKER) {
|
||||
return value;
|
||||
}
|
||||
assertNumber(value, 'expecting number, string, COMMENT_MARKER or ELEMENT_MARKER in OpCode');
|
||||
return value;
|
||||
}
|
||||
}
|
@ -15,15 +15,14 @@ import {createNamedArrayType} from '../../util/named_array_type';
|
||||
import {initNgDevMode} from '../../util/ng_dev_mode';
|
||||
import {CONTAINER_HEADER_OFFSET, HAS_TRANSPLANTED_VIEWS, LContainer, MOVED_VIEWS, NATIVE} from '../interfaces/container';
|
||||
import {DirectiveDefList, PipeDefList, ViewQueriesFunction} from '../interfaces/definition';
|
||||
import {COMMENT_MARKER, ELEMENT_MARKER, I18nMutateOpCode, I18nMutateOpCodes, I18nUpdateOpCode, I18nUpdateOpCodes, TIcu} from '../interfaces/i18n';
|
||||
import {PropertyAliases, TConstants, TContainerNode, TElementNode, TNode as ITNode, TNodeFlags, TNodeProviderIndexes, TNodeType, TViewNode} from '../interfaces/node';
|
||||
import {PropertyAliases, TConstants, TContainerNode, TElementNode, TNode as ITNode, TNodeFlags, TNodeProviderIndexes, TNodeType, TNodeTypeAsString, TViewNode} from '../interfaces/node';
|
||||
import {SelectorFlags} from '../interfaces/projection';
|
||||
import {LQueries, TQueries} from '../interfaces/query';
|
||||
import {RComment, RElement, Renderer3, RendererFactory3, RNode} from '../interfaces/renderer';
|
||||
import {getTStylingRangeNext, getTStylingRangeNextDuplicate, getTStylingRangePrev, getTStylingRangePrevDuplicate, TStylingKey, TStylingRange} from '../interfaces/styling';
|
||||
import {CHILD_HEAD, CHILD_TAIL, CLEANUP, CONTEXT, DECLARATION_VIEW, DestroyHookData, ExpandoInstructions, FLAGS, HEADER_OFFSET, HookData, HOST, INJECTOR, LView, LViewFlags, NEXT, PARENT, QUERIES, RENDERER, RENDERER_FACTORY, SANITIZER, T_HOST, TData, TVIEW, TView as ITView, TView, TViewType} from '../interfaces/view';
|
||||
import {CHILD_HEAD, CHILD_TAIL, CLEANUP, CONTEXT, DebugNode, DECLARATION_VIEW, DestroyHookData, ExpandoInstructions, FLAGS, HEADER_OFFSET, HookData, HOST, INJECTOR, LContainerDebug as ILContainerDebug, LView, LViewDebug as ILViewDebug, LViewDebugRange, LViewDebugRangeContent, LViewFlags, NEXT, PARENT, QUERIES, RENDERER, RENDERER_FACTORY, SANITIZER, T_HOST, TData, TView as ITView, TVIEW, TView, TViewType} from '../interfaces/view';
|
||||
import {attachDebugObject} from '../util/debug_utils';
|
||||
import {getTNode, unwrapRNode} from '../util/view_utils';
|
||||
import {unwrapRNode} from '../util/view_utils';
|
||||
|
||||
const NG_DEV_MODE = ((typeof ngDevMode === 'undefined' || !!ngDevMode) && initNgDevMode());
|
||||
|
||||
@ -143,7 +142,10 @@ export const TViewConstructor = class TView implements ITView {
|
||||
public firstChild: ITNode|null, //
|
||||
public schemas: SchemaMetadata[]|null, //
|
||||
public consts: TConstants|null, //
|
||||
public incompleteFirstPass: boolean //
|
||||
public incompleteFirstPass: boolean, //
|
||||
public _decls: number, //
|
||||
public _vars: number, //
|
||||
|
||||
) {}
|
||||
|
||||
get template_(): string {
|
||||
@ -335,9 +337,9 @@ export function attachLContainerDebug(lContainer: LContainer) {
|
||||
attachDebugObject(lContainer, new LContainerDebug(lContainer));
|
||||
}
|
||||
|
||||
export function toDebug(obj: LView): LViewDebug;
|
||||
export function toDebug(obj: LView|null): LViewDebug|null;
|
||||
export function toDebug(obj: LView|LContainer|null): LViewDebug|LContainerDebug|null;
|
||||
export function toDebug(obj: LView): ILViewDebug;
|
||||
export function toDebug(obj: LView|null): ILViewDebug|null;
|
||||
export function toDebug(obj: LView|LContainer|null): ILViewDebug|ILContainerDebug|null;
|
||||
export function toDebug(obj: any): any {
|
||||
if (obj) {
|
||||
const debug = (obj as any).debug;
|
||||
@ -375,7 +377,7 @@ function toHtml(value: any, includeChildren: boolean = false): string|null {
|
||||
}
|
||||
}
|
||||
|
||||
export class LViewDebug {
|
||||
export class LViewDebug implements ILViewDebug {
|
||||
constructor(private readonly _raw_lView: LView) {}
|
||||
|
||||
/**
|
||||
@ -396,10 +398,10 @@ export class LViewDebug {
|
||||
indexWithinInitPhase: flags >> LViewFlags.IndexWithinInitPhaseShift,
|
||||
};
|
||||
}
|
||||
get parent(): LViewDebug|LContainerDebug|null {
|
||||
get parent(): ILViewDebug|ILContainerDebug|null {
|
||||
return toDebug(this._raw_lView[PARENT]);
|
||||
}
|
||||
get host(): string|null {
|
||||
get hostHTML(): string|null {
|
||||
return toHtml(this._raw_lView[HOST], true);
|
||||
}
|
||||
get html(): string {
|
||||
@ -410,10 +412,9 @@ export class LViewDebug {
|
||||
}
|
||||
/**
|
||||
* The tree of nodes associated with the current `LView`. The nodes have been normalized into
|
||||
* a
|
||||
* tree structure with relevant details pulled out for readability.
|
||||
* a tree structure with relevant details pulled out for readability.
|
||||
*/
|
||||
get nodes(): DebugNode[]|null {
|
||||
get nodes(): DebugNode[] {
|
||||
const lView = this._raw_lView;
|
||||
const tNode = lView[TVIEW].firstChild;
|
||||
return toDebugNodes(tNode, lView);
|
||||
@ -437,16 +438,16 @@ export class LViewDebug {
|
||||
get sanitizer(): Sanitizer|null {
|
||||
return this._raw_lView[SANITIZER];
|
||||
}
|
||||
get childHead(): LViewDebug|LContainerDebug|null {
|
||||
get childHead(): ILViewDebug|ILContainerDebug|null {
|
||||
return toDebug(this._raw_lView[CHILD_HEAD]);
|
||||
}
|
||||
get next(): LViewDebug|LContainerDebug|null {
|
||||
get next(): ILViewDebug|ILContainerDebug|null {
|
||||
return toDebug(this._raw_lView[NEXT]);
|
||||
}
|
||||
get childTail(): LViewDebug|LContainerDebug|null {
|
||||
get childTail(): ILViewDebug|ILContainerDebug|null {
|
||||
return toDebug(this._raw_lView[CHILD_TAIL]);
|
||||
}
|
||||
get declarationView(): LViewDebug|null {
|
||||
get declarationView(): ILViewDebug|null {
|
||||
return toDebug(this._raw_lView[DECLARATION_VIEW]);
|
||||
}
|
||||
get queries(): LQueries|null {
|
||||
@ -456,11 +457,35 @@ export class LViewDebug {
|
||||
return this._raw_lView[T_HOST];
|
||||
}
|
||||
|
||||
get decls(): LViewDebugRange {
|
||||
const tView = this.tView as any as {_decls: number, _vars: number};
|
||||
const start = HEADER_OFFSET;
|
||||
return toLViewRange(this.tView, this._raw_lView, start, start + tView._decls);
|
||||
}
|
||||
|
||||
get vars(): LViewDebugRange {
|
||||
const tView = this.tView as any as {_decls: number, _vars: number};
|
||||
const start = HEADER_OFFSET + tView._decls;
|
||||
return toLViewRange(this.tView, this._raw_lView, start, start + tView._vars);
|
||||
}
|
||||
|
||||
get i18n(): LViewDebugRange {
|
||||
const tView = this.tView as any as {_decls: number, _vars: number};
|
||||
const start = HEADER_OFFSET + tView._decls + tView._vars;
|
||||
return toLViewRange(this.tView, this._raw_lView, start, this.tView.expandoStartIndex);
|
||||
}
|
||||
|
||||
get expando(): LViewDebugRange {
|
||||
const tView = this.tView as any as {_decls: number, _vars: number};
|
||||
return toLViewRange(
|
||||
this.tView, this._raw_lView, this.tView.expandoStartIndex, this._raw_lView.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalized view of child views (and containers) attached at this location.
|
||||
*/
|
||||
get childViews(): Array<LViewDebug|LContainerDebug> {
|
||||
const childViews: Array<LViewDebug|LContainerDebug> = [];
|
||||
get childViews(): Array<ILViewDebug|ILContainerDebug> {
|
||||
const childViews: Array<ILViewDebug|ILContainerDebug> = [];
|
||||
let child = this.childHead;
|
||||
while (child) {
|
||||
childViews.push(child);
|
||||
@ -470,11 +495,12 @@ export class LViewDebug {
|
||||
}
|
||||
}
|
||||
|
||||
export interface DebugNode {
|
||||
html: string|null;
|
||||
native: Node;
|
||||
nodes: DebugNode[]|null;
|
||||
component: LViewDebug|null;
|
||||
function toLViewRange(tView: TView, lView: LView, start: number, end: number): LViewDebugRange {
|
||||
let content: LViewDebugRangeContent[] = [];
|
||||
for (let index = start; index < end; index++) {
|
||||
content.push({index: index, t: tView.data[index], l: lView[index]});
|
||||
}
|
||||
return {start: start, end: end, length: end - start, content: content};
|
||||
}
|
||||
|
||||
/**
|
||||
@ -483,7 +509,7 @@ export interface DebugNode {
|
||||
* @param tNode
|
||||
* @param lView
|
||||
*/
|
||||
export function toDebugNodes(tNode: ITNode|null, lView: LView): DebugNode[]|null {
|
||||
export function toDebugNodes(tNode: ITNode|null, lView: LView): DebugNode[] {
|
||||
if (tNode) {
|
||||
const debugNodes: DebugNode[] = [];
|
||||
let tNodeCursor: ITNode|null = tNode;
|
||||
@ -493,33 +519,32 @@ export function toDebugNodes(tNode: ITNode|null, lView: LView): DebugNode[]|null
|
||||
}
|
||||
return debugNodes;
|
||||
} else {
|
||||
return null;
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export function buildDebugNode(tNode: ITNode, lView: LView, nodeIndex: number): DebugNode {
|
||||
const rawValue = lView[nodeIndex];
|
||||
const native = unwrapRNode(rawValue);
|
||||
const componentLViewDebug = toDebug(readLViewValue(rawValue));
|
||||
return {
|
||||
html: toHtml(native),
|
||||
type: TNodeTypeAsString[tNode.type],
|
||||
native: native as any,
|
||||
nodes: toDebugNodes(tNode.child, lView),
|
||||
component: componentLViewDebug,
|
||||
children: toDebugNodes(tNode.child, lView),
|
||||
};
|
||||
}
|
||||
|
||||
export class LContainerDebug {
|
||||
export class LContainerDebug implements ILContainerDebug {
|
||||
constructor(private readonly _raw_lContainer: LContainer) {}
|
||||
|
||||
get hasTransplantedViews(): boolean {
|
||||
return this._raw_lContainer[HAS_TRANSPLANTED_VIEWS];
|
||||
}
|
||||
get views(): LViewDebug[] {
|
||||
get views(): ILViewDebug[] {
|
||||
return this._raw_lContainer.slice(CONTAINER_HEADER_OFFSET)
|
||||
.map(toDebug as (l: LView) => LViewDebug);
|
||||
.map(toDebug as (l: LView) => ILViewDebug);
|
||||
}
|
||||
get parent(): LViewDebug|LContainerDebug|null {
|
||||
get parent(): ILViewDebug|null {
|
||||
return toDebug(this._raw_lContainer[PARENT]);
|
||||
}
|
||||
get movedViews(): LView[]|null {
|
||||
@ -550,206 +575,3 @@ export function readLViewValue(value: any): LView|null {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export class I18NDebugItem {
|
||||
[key: string]: any;
|
||||
|
||||
get tNode() {
|
||||
return getTNode(this._lView[TVIEW], this.nodeIndex);
|
||||
}
|
||||
|
||||
constructor(
|
||||
public __raw_opCode: any, private _lView: LView, public nodeIndex: number,
|
||||
public type: string) {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns a list of "Create" & "Update" OpCodes into a human-readable list of operations for
|
||||
* debugging purposes.
|
||||
* @param mutateOpCodes mutation opCodes to read
|
||||
* @param updateOpCodes update opCodes to read
|
||||
* @param icus list of ICU expressions
|
||||
* @param lView The view the opCodes are acting on
|
||||
*/
|
||||
export function attachI18nOpCodesDebug(
|
||||
mutateOpCodes: I18nMutateOpCodes, updateOpCodes: I18nUpdateOpCodes, icus: TIcu[]|null,
|
||||
lView: LView) {
|
||||
attachDebugObject(mutateOpCodes, new I18nMutateOpCodesDebug(mutateOpCodes, lView));
|
||||
attachDebugObject(updateOpCodes, new I18nUpdateOpCodesDebug(updateOpCodes, icus, lView));
|
||||
|
||||
if (icus) {
|
||||
icus.forEach(icu => {
|
||||
icu.create.forEach(icuCase => {
|
||||
attachDebugObject(icuCase, new I18nMutateOpCodesDebug(icuCase, lView));
|
||||
});
|
||||
icu.update.forEach(icuCase => {
|
||||
attachDebugObject(icuCase, new I18nUpdateOpCodesDebug(icuCase, icus, lView));
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class I18nMutateOpCodesDebug implements I18nOpCodesDebug {
|
||||
constructor(private readonly __raw_opCodes: I18nMutateOpCodes, private readonly __lView: LView) {}
|
||||
|
||||
/**
|
||||
* A list of operation information about how the OpCodes will act on the view.
|
||||
*/
|
||||
get operations() {
|
||||
const {__lView, __raw_opCodes} = this;
|
||||
const results: any[] = [];
|
||||
|
||||
for (let i = 0; i < __raw_opCodes.length; i++) {
|
||||
const opCode = __raw_opCodes[i];
|
||||
let result: any;
|
||||
if (typeof opCode === 'string') {
|
||||
result = {
|
||||
__raw_opCode: opCode,
|
||||
type: 'Create Text Node',
|
||||
nodeIndex: __raw_opCodes[++i],
|
||||
text: opCode,
|
||||
};
|
||||
}
|
||||
|
||||
if (typeof opCode === 'number') {
|
||||
switch (opCode & I18nMutateOpCode.MASK_OPCODE) {
|
||||
case I18nMutateOpCode.AppendChild:
|
||||
const destinationNodeIndex = opCode >>> I18nMutateOpCode.SHIFT_PARENT;
|
||||
result = new I18NDebugItem(opCode, __lView, destinationNodeIndex, 'AppendChild');
|
||||
break;
|
||||
case I18nMutateOpCode.Select:
|
||||
const nodeIndex = opCode >>> I18nMutateOpCode.SHIFT_REF;
|
||||
result = new I18NDebugItem(opCode, __lView, nodeIndex, 'Select');
|
||||
break;
|
||||
case I18nMutateOpCode.ElementEnd:
|
||||
let elementIndex = opCode >>> I18nMutateOpCode.SHIFT_REF;
|
||||
result = new I18NDebugItem(opCode, __lView, elementIndex, 'ElementEnd');
|
||||
break;
|
||||
case I18nMutateOpCode.Attr:
|
||||
elementIndex = opCode >>> I18nMutateOpCode.SHIFT_REF;
|
||||
result = new I18NDebugItem(opCode, __lView, elementIndex, 'Attr');
|
||||
result['attrName'] = __raw_opCodes[++i];
|
||||
result['attrValue'] = __raw_opCodes[++i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
switch (opCode) {
|
||||
case COMMENT_MARKER:
|
||||
result = {
|
||||
__raw_opCode: opCode,
|
||||
type: 'COMMENT_MARKER',
|
||||
commentValue: __raw_opCodes[++i],
|
||||
nodeIndex: __raw_opCodes[++i],
|
||||
};
|
||||
break;
|
||||
case ELEMENT_MARKER:
|
||||
result = {
|
||||
__raw_opCode: opCode,
|
||||
type: 'ELEMENT_MARKER',
|
||||
};
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
result = {
|
||||
__raw_opCode: opCode,
|
||||
type: 'Unknown Op Code',
|
||||
code: opCode,
|
||||
};
|
||||
}
|
||||
|
||||
results.push(result);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
}
|
||||
|
||||
export class I18nUpdateOpCodesDebug implements I18nOpCodesDebug {
|
||||
constructor(
|
||||
private readonly __raw_opCodes: I18nUpdateOpCodes, private readonly icus: TIcu[]|null,
|
||||
private readonly __lView: LView) {}
|
||||
|
||||
/**
|
||||
* A list of operation information about how the OpCodes will act on the view.
|
||||
*/
|
||||
get operations() {
|
||||
const {__lView, __raw_opCodes, icus} = this;
|
||||
const results: any[] = [];
|
||||
|
||||
for (let i = 0; i < __raw_opCodes.length; i++) {
|
||||
// bit code to check if we should apply the next update
|
||||
const checkBit = __raw_opCodes[i] as number;
|
||||
// Number of opCodes to skip until next set of update codes
|
||||
const skipCodes = __raw_opCodes[++i] as number;
|
||||
let value = '';
|
||||
for (let j = i + 1; j <= (i + skipCodes); j++) {
|
||||
const opCode = __raw_opCodes[j];
|
||||
if (typeof opCode === 'string') {
|
||||
value += opCode;
|
||||
} else if (typeof opCode == 'number') {
|
||||
if (opCode < 0) {
|
||||
// It's a binding index whose value is negative
|
||||
// We cannot know the value of the binding so we only show the index
|
||||
value += `<EFBFBD>${- opCode - 1}<EFBFBD>`;
|
||||
} else {
|
||||
const nodeIndex = opCode >>> I18nUpdateOpCode.SHIFT_REF;
|
||||
let tIcuIndex: number;
|
||||
let tIcu: TIcu;
|
||||
switch (opCode & I18nUpdateOpCode.MASK_OPCODE) {
|
||||
case I18nUpdateOpCode.Attr:
|
||||
const attrName = __raw_opCodes[++j] as string;
|
||||
const sanitizeFn = __raw_opCodes[++j];
|
||||
results.push({
|
||||
__raw_opCode: opCode,
|
||||
checkBit,
|
||||
type: 'Attr',
|
||||
attrValue: value,
|
||||
attrName,
|
||||
sanitizeFn,
|
||||
});
|
||||
break;
|
||||
case I18nUpdateOpCode.Text:
|
||||
results.push({
|
||||
__raw_opCode: opCode,
|
||||
checkBit,
|
||||
type: 'Text',
|
||||
nodeIndex,
|
||||
text: value,
|
||||
});
|
||||
break;
|
||||
case I18nUpdateOpCode.IcuSwitch:
|
||||
tIcuIndex = __raw_opCodes[++j] as number;
|
||||
tIcu = icus![tIcuIndex];
|
||||
let result = new I18NDebugItem(opCode, __lView, nodeIndex, 'IcuSwitch');
|
||||
result['tIcuIndex'] = tIcuIndex;
|
||||
result['checkBit'] = checkBit;
|
||||
result['mainBinding'] = value;
|
||||
result['tIcu'] = tIcu;
|
||||
results.push(result);
|
||||
break;
|
||||
case I18nUpdateOpCode.IcuUpdate:
|
||||
tIcuIndex = __raw_opCodes[++j] as number;
|
||||
tIcu = icus![tIcuIndex];
|
||||
result = new I18NDebugItem(opCode, __lView, nodeIndex, 'IcuUpdate');
|
||||
result['tIcuIndex'] = tIcuIndex;
|
||||
result['checkBit'] = checkBit;
|
||||
result['tIcu'] = tIcu;
|
||||
results.push(result);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
i += skipCodes;
|
||||
}
|
||||
return results;
|
||||
}
|
||||
}
|
||||
|
||||
export interface I18nOpCodesDebug {
|
||||
operations: any[];
|
||||
}
|
||||
|
@ -236,6 +236,13 @@ export function getOrCreateTNode(
|
||||
const tNode = tView.data[adjustedIndex] as TNode ||
|
||||
createTNodeAtIndex(tView, tHostNode, adjustedIndex, type, name, attrs);
|
||||
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 &
|
||||
TProjectionNode & TIcuContainerNode;
|
||||
}
|
||||
@ -692,7 +699,9 @@ export function createTView(
|
||||
null, // firstChild: TNode|null,
|
||||
schemas, // schemas: SchemaMetadata[]|null,
|
||||
consts, // consts: TConstants|null
|
||||
false // incompleteFirstPass: boolean
|
||||
false, // incompleteFirstPass: boolean
|
||||
decls, // ngDevMode only: decls
|
||||
vars, // ngDevMode only: vars
|
||||
) :
|
||||
{
|
||||
type: type,
|
||||
|
@ -6,18 +6,31 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {SanitizerFn} from './sanitization';
|
||||
|
||||
/**
|
||||
* `I18nMutateOpCode` defines OpCodes for `I18nMutateOpCodes` array.
|
||||
*
|
||||
* OpCodes are efficient operations which can be applied to the DOM to update it. (For example to
|
||||
* update to a new ICU case requires that we clean up previous elements and create new ones.)
|
||||
*
|
||||
* OpCodes contain three parts:
|
||||
* 1) Parent node index offset.
|
||||
* 2) Reference node index offset.
|
||||
* 3) The OpCode to execute.
|
||||
* 1) Parent node index offset. (p)
|
||||
* 2) Reference node index offset. (r)
|
||||
* 3) The instruction to execute. (i)
|
||||
*
|
||||
* pppp pppp pppp pppp rrrr rrrr rrrr riii
|
||||
* 3322 2222 2222 1111 1111 1110 0000 0000
|
||||
* 1098 7654 3210 9876 5432 1098 7654 3210
|
||||
*
|
||||
* ```
|
||||
* var parent = lView[opCode >>> SHIFT_PARENT];
|
||||
* var refNode = lView[((opCode & MASK_REF) >>> SHIFT_REF)];
|
||||
* var instruction = opCode & MASK_OPCODE;
|
||||
* ```
|
||||
*
|
||||
* See: `I18nCreateOpCodes` for example of usage.
|
||||
*/
|
||||
import {SanitizerFn} from './sanitization';
|
||||
|
||||
export const enum I18nMutateOpCode {
|
||||
/**
|
||||
* Stores shift amount for bits 17-3 that contain reference index.
|
||||
@ -30,36 +43,61 @@ export const enum I18nMutateOpCode {
|
||||
/**
|
||||
* Mask for OpCode
|
||||
*/
|
||||
MASK_OPCODE = 0b111,
|
||||
MASK_INSTRUCTION = 0b111,
|
||||
|
||||
/**
|
||||
* OpCode to select a node. (next OpCode will contain the operation.)
|
||||
* Mask for the Reference node (bits 16-3)
|
||||
*/
|
||||
// FIXME(misko): Why is this not used?
|
||||
MASK_REF = 0b11111111111111000,
|
||||
// 11111110000000000
|
||||
// 65432109876543210
|
||||
|
||||
/**
|
||||
* Instruction to select a node. (next OpCode will contain the operation.)
|
||||
*/
|
||||
Select = 0b000,
|
||||
|
||||
/**
|
||||
* OpCode to append the current node to `PARENT`.
|
||||
* Instruction to append the current node to `PARENT`.
|
||||
*/
|
||||
AppendChild = 0b001,
|
||||
|
||||
/**
|
||||
* OpCode to remove the `REF` node from `PARENT`.
|
||||
* Instruction to remove the `REF` node from `PARENT`.
|
||||
*/
|
||||
Remove = 0b011,
|
||||
|
||||
/**
|
||||
* OpCode to set the attribute of a node.
|
||||
* Instruction to set the attribute of a node.
|
||||
*/
|
||||
Attr = 0b100,
|
||||
|
||||
/**
|
||||
* OpCode to simulate elementEnd()
|
||||
* Instruction to simulate elementEnd()
|
||||
*/
|
||||
ElementEnd = 0b101,
|
||||
|
||||
/**
|
||||
* OpCode to read the remove OpCodes for the nested ICU
|
||||
* Instruction to removed the nested ICU.
|
||||
*/
|
||||
RemoveNestedIcu = 0b110,
|
||||
}
|
||||
|
||||
export function getParentFromI18nMutateOpCode(mergedCode: number): number {
|
||||
return mergedCode >>> I18nMutateOpCode.SHIFT_PARENT;
|
||||
}
|
||||
|
||||
export function getRefFromI18nMutateOpCode(mergedCode: number): number {
|
||||
return (mergedCode & I18nMutateOpCode.MASK_REF) >>> I18nMutateOpCode.SHIFT_REF;
|
||||
}
|
||||
|
||||
export function getInstructionFromI18nMutateOpCode(mergedCode: number): number {
|
||||
return mergedCode & I18nMutateOpCode.MASK_INSTRUCTION;
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks that the next string is for element.
|
||||
* Marks that the next string is an element name.
|
||||
*
|
||||
* See `I18nMutateOpCodes` documentation.
|
||||
*/
|
||||
@ -71,7 +109,7 @@ export interface ELEMENT_MARKER {
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks that the next string is for comment.
|
||||
* Marks that the next string is comment text.
|
||||
*
|
||||
* See `I18nMutateOpCodes` documentation.
|
||||
*/
|
||||
@ -83,6 +121,18 @@ export interface COMMENT_MARKER {
|
||||
marker: 'comment';
|
||||
}
|
||||
|
||||
export interface I18nDebug {
|
||||
/**
|
||||
* Human readable representation of the OpCode arrays.
|
||||
*
|
||||
* NOTE: This property only exists if `ngDevMode` is set to `true` and it is not present in
|
||||
* production. Its presence is purely to help debug issue in development, and should not be relied
|
||||
* on in production application.
|
||||
*/
|
||||
debug?: string[];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Array storing OpCode for dynamically creating `i18n` blocks.
|
||||
*
|
||||
@ -92,50 +142,27 @@ export interface COMMENT_MARKER {
|
||||
* // For adding text nodes
|
||||
* // ---------------------
|
||||
* // Equivalent to:
|
||||
* // const node = lView[index++] = document.createTextNode('abc');
|
||||
* // lView[1].insertBefore(node, lView[2]);
|
||||
* 'abc', 1 << SHIFT_PARENT | 2 << SHIFT_REF | InsertBefore,
|
||||
*
|
||||
* // Equivalent to:
|
||||
* // const node = lView[index++] = document.createTextNode('xyz');
|
||||
* // lView[1].appendChild(node);
|
||||
* 'xyz', 1 << SHIFT_PARENT | AppendChild,
|
||||
* // lView[1].appendChild(lView[0] = document.createTextNode('xyz'));
|
||||
* 'xyz', 0, 1 << SHIFT_PARENT | 0 << SHIFT_REF | AppendChild,
|
||||
*
|
||||
* // For adding element nodes
|
||||
* // ---------------------
|
||||
* // Equivalent to:
|
||||
* // const node = lView[index++] = document.createElement('div');
|
||||
* // lView[1].insertBefore(node, lView[2]);
|
||||
* ELEMENT_MARKER, 'div', 1 << SHIFT_PARENT | 2 << SHIFT_REF | InsertBefore,
|
||||
*
|
||||
* // Equivalent to:
|
||||
* // const node = lView[index++] = document.createElement('div');
|
||||
* // lView[1].appendChild(node);
|
||||
* ELEMENT_MARKER, 'div', 1 << SHIFT_PARENT | AppendChild,
|
||||
* // lView[1].appendChild(lView[0] = document.createElement('div'));
|
||||
* ELEMENT_MARKER, 'div', 0, 1 << SHIFT_PARENT | 0 << SHIFT_REF | AppendChild,
|
||||
*
|
||||
* // For adding comment nodes
|
||||
* // ---------------------
|
||||
* // Equivalent to:
|
||||
* // const node = lView[index++] = document.createComment('');
|
||||
* // lView[1].insertBefore(node, lView[2]);
|
||||
* COMMENT_MARKER, '', 1 << SHIFT_PARENT | 2 << SHIFT_REF | InsertBefore,
|
||||
*
|
||||
* // Equivalent to:
|
||||
* // const node = lView[index++] = document.createComment('');
|
||||
* // lView[1].appendChild(node);
|
||||
* COMMENT_MARKER, '', 1 << SHIFT_PARENT | AppendChild,
|
||||
* // lView[1].appendChild(lView[0] = document.createComment(''));
|
||||
* COMMENT_MARKER, '', 0, 1 << SHIFT_PARENT | 0 << SHIFT_REF | AppendChild,
|
||||
*
|
||||
* // For moving existing nodes to a different location
|
||||
* // --------------------------------------------------
|
||||
* // Equivalent to:
|
||||
* // const node = lView[1];
|
||||
* // lView[2].insertBefore(node, lView[3]);
|
||||
* 1 << SHIFT_REF | Select, 2 << SHIFT_PARENT | 3 << SHIFT_REF | InsertBefore,
|
||||
*
|
||||
* // Equivalent to:
|
||||
* // const node = lView[1];
|
||||
* // lView[2].appendChild(node);
|
||||
* 1 << SHIFT_REF | Select, 2 << SHIFT_PARENT | AppendChild,
|
||||
* 1 << SHIFT_REF | Select, 2 << SHIFT_PARENT | 0 << SHIFT_REF | AppendChild,
|
||||
*
|
||||
* // For removing existing nodes
|
||||
* // --------------------------------------------------
|
||||
@ -147,18 +174,14 @@ export interface COMMENT_MARKER {
|
||||
* // --------------------------------------------------
|
||||
* // const node = lView[1];
|
||||
* // node.setAttribute('attr', 'value');
|
||||
* 1 << SHIFT_REF | Select, 'attr', 'value'
|
||||
* // NOTE: Select followed by two string (vs select followed by OpCode)
|
||||
* 1 << SHIFT_REF | Attr, 'attr', 'value'
|
||||
* ];
|
||||
* ```
|
||||
* NOTE:
|
||||
* - `index` is initial location where the extra nodes should be stored in the EXPANDO section of
|
||||
* `LVIewData`.
|
||||
*
|
||||
* See: `applyI18nCreateOpCodes`;
|
||||
*/
|
||||
export interface I18nMutateOpCodes extends Array<number|string|ELEMENT_MARKER|COMMENT_MARKER|null> {
|
||||
}
|
||||
export interface I18nMutateOpCodes extends Array<number|string|ELEMENT_MARKER|COMMENT_MARKER|null>,
|
||||
I18nDebug {}
|
||||
|
||||
export const enum I18nUpdateOpCode {
|
||||
/**
|
||||
@ -171,19 +194,19 @@ export const enum I18nUpdateOpCode {
|
||||
MASK_OPCODE = 0b11,
|
||||
|
||||
/**
|
||||
* OpCode to update a text node.
|
||||
* Instruction to update a text node.
|
||||
*/
|
||||
Text = 0b00,
|
||||
/**
|
||||
* OpCode to update a attribute of a node.
|
||||
* Instruction to update a attribute of a node.
|
||||
*/
|
||||
Attr = 0b01,
|
||||
/**
|
||||
* OpCode to switch the current ICU case.
|
||||
* Instruction to switch the current ICU case.
|
||||
*/
|
||||
IcuSwitch = 0b10,
|
||||
/**
|
||||
* OpCode to update the current ICU case.
|
||||
* Instruction to update the current ICU case.
|
||||
*/
|
||||
IcuUpdate = 0b11,
|
||||
}
|
||||
@ -197,6 +220,10 @@ export const enum I18nUpdateOpCode {
|
||||
* higher.) The OpCodes then compare its own change mask against the expression change mask to
|
||||
* determine if the OpCodes should execute.
|
||||
*
|
||||
* NOTE: 32nd bit is special as it says 32nd or higher. This way if we have more than 32 bindings
|
||||
* the code still works, but with lower efficiency. (it is unlikely that a translation would have
|
||||
* more than 32 bindings.)
|
||||
*
|
||||
* These OpCodes can be used by both the i18n block as well as ICU sub-block.
|
||||
*
|
||||
* ## Example
|
||||
@ -220,8 +247,8 @@ export const enum I18nUpdateOpCode {
|
||||
* // The following OpCodes represent: `<div i18n-title="pre{{exp1}}in{{exp2}}post">`
|
||||
* // If `changeMask & 0b11`
|
||||
* // has changed then execute update OpCodes.
|
||||
* // has NOT changed then skip `7` values and start processing next OpCodes.
|
||||
* 0b11, 7,
|
||||
* // has NOT changed then skip `8` values and start processing next OpCodes.
|
||||
* 0b11, 8,
|
||||
* // Concatenate `newValue = 'pre'+lView[bindIndex-4]+'in'+lView[bindIndex-3]+'post';`.
|
||||
* 'pre', -4, 'in', -3, 'post',
|
||||
* // Update attribute: `elementAttribute(1, 'title', sanitizerFn(newValue));`
|
||||
@ -240,8 +267,8 @@ export const enum I18nUpdateOpCode {
|
||||
* // The following OpCodes represent: `<div i18n>{exp4, plural, ... }">`
|
||||
* // If `changeMask & 0b1000`
|
||||
* // has changed then execute update OpCodes.
|
||||
* // has NOT changed then skip `4` values and start processing next OpCodes.
|
||||
* 0b1000, 4,
|
||||
* // has NOT changed then skip `2` values and start processing next OpCodes.
|
||||
* 0b1000, 2,
|
||||
* // Concatenate `newValue = lView[bindIndex -1];`.
|
||||
* -1,
|
||||
* // Switch ICU: `icuSwitchCase(lView[1], 0, newValue);`
|
||||
@ -256,7 +283,7 @@ export const enum I18nUpdateOpCode {
|
||||
* ```
|
||||
*
|
||||
*/
|
||||
export interface I18nUpdateOpCodes extends Array<string|number|SanitizerFn|null> {}
|
||||
export interface I18nUpdateOpCodes extends Array<string|number|SanitizerFn|null>, I18nDebug {}
|
||||
|
||||
/**
|
||||
* Store information for the i18n translation block.
|
||||
|
@ -7,14 +7,11 @@
|
||||
*/
|
||||
import {KeyValueArray} from '../../util/array_utils';
|
||||
import {TStylingRange} from '../interfaces/styling';
|
||||
|
||||
import {DirectiveDef} from './definition';
|
||||
import {CssSelector} from './projection';
|
||||
import {RNode} from './renderer';
|
||||
import {LView, TView} from './view';
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* TNodeType corresponds to the {@link TNode} `type` property.
|
||||
*/
|
||||
@ -45,6 +42,20 @@ export const enum TNodeType {
|
||||
IcuContainer = 5,
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts `TNodeType` into human readable text.
|
||||
* Make sure this matches with `TNodeType`
|
||||
*/
|
||||
export const TNodeTypeAsString = [
|
||||
'Container', // 0
|
||||
'Projection', // 1
|
||||
'View', // 2
|
||||
'Element', // 3
|
||||
'ElementContainer', // 4
|
||||
'IcuContainer' // 5
|
||||
] as const;
|
||||
|
||||
|
||||
/**
|
||||
* Corresponds to the TNode.flags property.
|
||||
*/
|
||||
@ -701,7 +712,9 @@ export interface TIcuContainerNode extends TNode {
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
|
@ -15,10 +15,10 @@ import {Sanitizer} from '../../sanitization/sanitizer';
|
||||
import {LContainer} from './container';
|
||||
import {ComponentDef, ComponentTemplate, DirectiveDef, DirectiveDefList, HostBindingsFunction, PipeDef, PipeDefList, ViewQueriesFunction} from './definition';
|
||||
import {I18nUpdateOpCodes, TI18n} from './i18n';
|
||||
import {TConstants, TElementNode, TNode, TViewNode} from './node';
|
||||
import {TConstants, TElementNode, TNode, TNodeTypeAsString, TViewNode} from './node';
|
||||
import {PlayerHandler} from './player';
|
||||
import {LQueries, TQueries} from './query';
|
||||
import {RElement, Renderer3, RendererFactory3} from './renderer';
|
||||
import {RComment, RElement, Renderer3, RendererFactory3} from './renderer';
|
||||
import {TStylingKey, TStylingRange} from './styling';
|
||||
|
||||
|
||||
@ -69,6 +69,15 @@ export interface OpaqueViewState {
|
||||
* don't have to edit the data array based on which views are present.
|
||||
*/
|
||||
export interface LView extends Array<any> {
|
||||
/**
|
||||
* Human readable representation of the `LView`.
|
||||
*
|
||||
* NOTE: This property only exists if `ngDevMode` is set to `true` and it is not present in
|
||||
* production. Its presence is purely to help debug issue in development, and should not be relied
|
||||
* on in production application.
|
||||
*/
|
||||
debug?: LViewDebug;
|
||||
|
||||
/**
|
||||
* The host node for this LView instance, if this is a component view.
|
||||
* If this is an embedded view, HOST will be null.
|
||||
@ -826,3 +835,190 @@ export type TData =
|
||||
// Note: This hack is necessary so we don't erroneously get a circular dependency
|
||||
// failure based on types.
|
||||
export const unusedValueExportToPlacateAjd = 1;
|
||||
|
||||
/**
|
||||
* Human readable version of the `LView`.
|
||||
*
|
||||
* `LView` is a data structure used internally to keep track of views. The `LView` is designed for
|
||||
* efficiency and so at times it is difficult to read or write tests which assert on its values. For
|
||||
* this reason when `ngDevMode` is true we patch a `LView.debug` property which points to
|
||||
* `LViewDebug` for easier debugging and test writing. It is the intent of `LViewDebug` to be used
|
||||
* in tests.
|
||||
*/
|
||||
export interface LViewDebug {
|
||||
/**
|
||||
* Flags associated with the `LView` unpacked into a more readable state.
|
||||
*
|
||||
* See `LViewFlags` for the flag meanings.
|
||||
*/
|
||||
readonly flags: {
|
||||
initPhaseState: number,
|
||||
creationMode: boolean,
|
||||
firstViewPass: boolean,
|
||||
checkAlways: boolean,
|
||||
dirty: boolean,
|
||||
attached: boolean,
|
||||
destroyed: boolean,
|
||||
isRoot: boolean,
|
||||
indexWithinInitPhase: number,
|
||||
};
|
||||
|
||||
/**
|
||||
* Parent view (or container)
|
||||
*/
|
||||
readonly parent: LViewDebug|LContainerDebug|null;
|
||||
|
||||
/**
|
||||
* Next sibling to the `LView`.
|
||||
*/
|
||||
readonly next: LViewDebug|LContainerDebug|null;
|
||||
|
||||
/**
|
||||
* The context used for evaluation of the `LView`
|
||||
*
|
||||
* (Usually the component)
|
||||
*/
|
||||
readonly context: {}|null;
|
||||
|
||||
/**
|
||||
* Hierarchical tree of nodes.
|
||||
*/
|
||||
readonly nodes: DebugNode[];
|
||||
|
||||
/**
|
||||
* HTML representation of the `LView`.
|
||||
*
|
||||
* This is only approximate to actual HTML as child `LView`s are removed.
|
||||
*/
|
||||
readonly html: string;
|
||||
|
||||
/**
|
||||
* The host element to which this `LView` is attached.
|
||||
*/
|
||||
readonly hostHTML: string|null;
|
||||
|
||||
/**
|
||||
* Child `LView`s
|
||||
*/
|
||||
readonly childViews: Array<LViewDebug|LContainerDebug>;
|
||||
|
||||
/**
|
||||
* Sub range of `LView` containing decls (DOM elements).
|
||||
*/
|
||||
readonly decls: LViewDebugRange;
|
||||
|
||||
/**
|
||||
* Sub range of `LView` containing vars (bindings).
|
||||
*/
|
||||
readonly vars: LViewDebugRange;
|
||||
|
||||
/**
|
||||
* Sub range of `LView` containing i18n (translated DOM elements).
|
||||
*/
|
||||
readonly i18n: LViewDebugRange;
|
||||
|
||||
/**
|
||||
* Sub range of `LView` containing expando (used by DI).
|
||||
*/
|
||||
readonly expando: LViewDebugRange;
|
||||
}
|
||||
|
||||
/**
|
||||
* Human readable version of the `LContainer`
|
||||
*
|
||||
* `LContainer` is a data structure used internally to keep track of child views. The `LContainer`
|
||||
* is designed for efficiency and so at times it is difficult to read or write tests which assert on
|
||||
* its values. For this reason when `ngDevMode` is true we patch a `LContainer.debug` property which
|
||||
* points to `LContainerDebug` for easier debugging and test writing. It is the intent of
|
||||
* `LContainerDebug` to be used in tests.
|
||||
*/
|
||||
export interface LContainerDebug {
|
||||
readonly native: RComment;
|
||||
/**
|
||||
* Child `LView`s.
|
||||
*/
|
||||
readonly views: LViewDebug[];
|
||||
readonly parent: LViewDebug|null;
|
||||
readonly movedViews: LView[]|null;
|
||||
readonly host: RElement|RComment|LView;
|
||||
readonly next: LViewDebug|LContainerDebug|null;
|
||||
readonly hasTransplantedViews: boolean;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* `LView` is subdivided to ranges where the actual data is stored. Some of these ranges such as
|
||||
* `decls` and `vars` are known at compile time. Other such as `i18n` and `expando` are runtime only
|
||||
* concepts.
|
||||
*/
|
||||
export interface LViewDebugRange {
|
||||
/**
|
||||
* The starting index in `LView` where the range begins. (Inclusive)
|
||||
*/
|
||||
start: number;
|
||||
|
||||
/**
|
||||
* The ending index in `LView` where the range ends. (Exclusive)
|
||||
*/
|
||||
end: number;
|
||||
|
||||
/**
|
||||
* The length of the range
|
||||
*/
|
||||
length: number;
|
||||
|
||||
/**
|
||||
* The merged content of the range. `t` contains data from `TView.data` and `l` contains `LView`
|
||||
* data at an index.
|
||||
*/
|
||||
content: LViewDebugRangeContent[];
|
||||
}
|
||||
|
||||
/**
|
||||
* For convenience the static and instance portions of `TView` and `LView` are merged into a single
|
||||
* object in `LViewRange`.
|
||||
*/
|
||||
export interface LViewDebugRangeContent {
|
||||
/**
|
||||
* Index into original `LView` or `TView.data`.
|
||||
*/
|
||||
index: number;
|
||||
|
||||
/**
|
||||
* Value from the `TView.data[index]` location.
|
||||
*/
|
||||
t: any;
|
||||
|
||||
/**
|
||||
* Value from the `LView[index]` location.
|
||||
*/
|
||||
l: any;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* A logical node which comprise into `LView`s.
|
||||
*
|
||||
*/
|
||||
export interface DebugNode {
|
||||
/**
|
||||
* HTML representation of the node.
|
||||
*/
|
||||
html: string|null;
|
||||
|
||||
/**
|
||||
* Human readable node type.
|
||||
*/
|
||||
type: typeof TNodeTypeAsString[number];
|
||||
|
||||
/**
|
||||
* DOM native node.
|
||||
*/
|
||||
native: Node;
|
||||
|
||||
/**
|
||||
* Child nodes
|
||||
*/
|
||||
children: DebugNode[];
|
||||
}
|
@ -5,6 +5,37 @@
|
||||
* 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
|
||||
*/
|
||||
export function attachDebugObject(obj: any, debug: any) {
|
||||
Object.defineProperty(obj, 'debug', {value: debug, enumerable: false});
|
||||
|
||||
/**
|
||||
* Patch a `debug` property on top of the existing object.
|
||||
*
|
||||
* NOTE: always call this method with `ngDevMode && attachDebugObject(...)`
|
||||
*
|
||||
* @param obj Object to patch
|
||||
* @param debug Value to patch
|
||||
*/
|
||||
export function attachDebugObject(obj: any, debug: any): void {
|
||||
if (ngDevMode) {
|
||||
Object.defineProperty(obj, 'debug', {value: debug, enumerable: false});
|
||||
} else {
|
||||
throw new Error(
|
||||
'This method should be guarded with `ngDevMode` so that it can be tree shaken in production!');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Patch a `debug` property getter on top of the existing object.
|
||||
*
|
||||
* NOTE: always call this method with `ngDevMode && attachDebugObject(...)`
|
||||
*
|
||||
* @param obj Object to patch
|
||||
* @param debugGetter Getter returning a value to patch
|
||||
*/
|
||||
export function attachDebugGetter(obj: any, debugGetter: () => any): void {
|
||||
if (ngDevMode) {
|
||||
Object.defineProperty(obj, 'debug', {get: debugGetter, enumerable: false});
|
||||
} else {
|
||||
throw new Error(
|
||||
'This method should be guarded with `ngDevMode` so that it can be tree shaken in production!');
|
||||
}
|
||||
}
|
||||
|
@ -10,12 +10,12 @@ import {Injector} from '../../di/injector';
|
||||
import {assertLView} from '../assert';
|
||||
import {discoverLocalRefs, getComponentAtNodeIndex, getDirectivesAtNodeIndex, getLContext} from '../context_discovery';
|
||||
import {NodeInjector} from '../di';
|
||||
import {buildDebugNode, DebugNode} from '../instructions/lview_debug';
|
||||
import {buildDebugNode} from '../instructions/lview_debug';
|
||||
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, FLAGS, HEADER_OFFSET, HOST, LView, LViewFlags, T_HOST, TVIEW} from '../interfaces/view';
|
||||
import {CLEANUP, CONTEXT, DebugNode, FLAGS, HEADER_OFFSET, HOST, LView, LViewFlags, T_HOST, TVIEW} from '../interfaces/view';
|
||||
|
||||
import {stringifyForError} from './misc_utils';
|
||||
import {getLViewParent, getRootContext} from './view_traversal_utils';
|
||||
|
@ -20,6 +20,7 @@ ts_library(
|
||||
"//packages/compiler/testing",
|
||||
"//packages/core",
|
||||
"//packages/core/src/util",
|
||||
"//packages/core/test/render3:matchers",
|
||||
"//packages/core/testing",
|
||||
"//packages/localize",
|
||||
"//packages/localize/init",
|
||||
|
@ -8,13 +8,17 @@
|
||||
|
||||
import {Component} from '@angular/core';
|
||||
import {getLContext} from '@angular/core/src/render3/context_discovery';
|
||||
import {LViewDebug, toDebug} from '@angular/core/src/render3/instructions/lview_debug';
|
||||
import {LViewDebug} from '@angular/core/src/render3/instructions/lview_debug';
|
||||
import {TNodeType} from '@angular/core/src/render3/interfaces/node';
|
||||
import {HEADER_OFFSET} from '@angular/core/src/render3/interfaces/view';
|
||||
import {TestBed} from '@angular/core/testing';
|
||||
import {expect} from '@angular/platform-browser/testing/src/matchers';
|
||||
import {onlyInIvy} from '@angular/private/testing';
|
||||
|
||||
describe('Debug Representation', () => {
|
||||
onlyInIvy('Ivy specific').it('should generate a human readable version', () => {
|
||||
import {matchDomElement, matchDomText, matchTI18n, matchTNode} from '../render3/matchers';
|
||||
|
||||
onlyInIvy('Ivy specific').describe('Debug Representation', () => {
|
||||
it('should generate a human readable version', () => {
|
||||
@Component({selector: 'my-comp', template: '<div id="123">Hello World</div>'})
|
||||
class MyComponent {
|
||||
}
|
||||
@ -23,11 +27,56 @@ describe('Debug Representation', () => {
|
||||
const fixture = TestBed.createComponent(MyComponent);
|
||||
fixture.detectChanges();
|
||||
|
||||
const hostView = toDebug(getLContext(fixture.componentInstance)!.lView);
|
||||
expect(hostView.host).toEqual(null);
|
||||
const hostView = getLContext(fixture.componentInstance)!.lView.debug!;
|
||||
expect(hostView.hostHTML).toEqual(null);
|
||||
const myCompView = hostView.childViews[0] as LViewDebug;
|
||||
expect(myCompView.host).toContain('<div id="123">Hello World</div>');
|
||||
expect(myCompView.hostHTML).toContain('<div id="123">Hello World</div>');
|
||||
expect(myCompView.nodes![0].html).toEqual('<div id="123">');
|
||||
expect(myCompView.nodes![0].nodes![0].html).toEqual('Hello World');
|
||||
expect(myCompView.nodes![0].children![0].html).toEqual('Hello World');
|
||||
});
|
||||
|
||||
describe('LViewDebug', () => {
|
||||
describe('range', () => {
|
||||
it('should show ranges', () => {
|
||||
@Component({selector: 'my-comp', template: '<div i18n>Hello {{name}}</div>'})
|
||||
class MyComponent {
|
||||
name = 'World';
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({declarations: [MyComponent]});
|
||||
const fixture = TestBed.createComponent(MyComponent);
|
||||
fixture.detectChanges();
|
||||
|
||||
const hostView = getLContext(fixture.componentInstance)!.lView.debug!;
|
||||
const myComponentView = hostView.childViews[0] as LViewDebug;
|
||||
expect(myComponentView.decls).toEqual({
|
||||
start: HEADER_OFFSET,
|
||||
end: HEADER_OFFSET + 2,
|
||||
length: 2,
|
||||
content: [
|
||||
{index: HEADER_OFFSET + 0, t: matchTNode({tagName: 'div'}), l: matchDomElement('div')},
|
||||
{index: HEADER_OFFSET + 1, t: matchTI18n(), l: null},
|
||||
]
|
||||
});
|
||||
expect(myComponentView.vars).toEqual({
|
||||
start: HEADER_OFFSET + 2,
|
||||
end: HEADER_OFFSET + 3,
|
||||
length: 1,
|
||||
content: [{index: HEADER_OFFSET + 2, t: null, l: 'World'}]
|
||||
});
|
||||
expect(myComponentView.i18n).toEqual({
|
||||
start: HEADER_OFFSET + 3,
|
||||
end: HEADER_OFFSET + 4,
|
||||
length: 1,
|
||||
content: [{
|
||||
index: HEADER_OFFSET + 3,
|
||||
t: matchTNode({type: TNodeType.Element, tagName: null}),
|
||||
l: matchDomText('Hello World')
|
||||
}]
|
||||
});
|
||||
expect(myComponentView.expando)
|
||||
.toEqual({start: HEADER_OFFSET + 4, end: HEADER_OFFSET + 4, length: 0, content: []});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {Component, Directive, forwardRef, Inject, Injectable, InjectionToken, Injector, NgModule, Optional} from '@angular/core';
|
||||
import {async, inject, TestBed} from '@angular/core/testing';
|
||||
import {inject, TestBed, waitForAsync} from '@angular/core/testing';
|
||||
import {By} from '@angular/platform-browser';
|
||||
import {expect} from '@angular/platform-browser/testing/src/matchers';
|
||||
import {modifiedInIvy, onlyInIvy} from '@angular/private/testing';
|
||||
@ -603,7 +603,7 @@ describe('providers', () => {
|
||||
});
|
||||
|
||||
it('should support injecting without bootstrapping',
|
||||
async(inject([MyComp, MyService], (comp: MyComp, service: MyService) => {
|
||||
waitForAsync(inject([MyComp, MyService], (comp: MyComp, service: MyService) => {
|
||||
expect(comp.svc.value).toEqual('some value');
|
||||
})));
|
||||
});
|
||||
|
@ -8,19 +8,19 @@
|
||||
import {Injector} from '@angular/core';
|
||||
import {APP_INITIALIZER, ApplicationInitStatus} from '@angular/core/src/application_init';
|
||||
|
||||
import {async, inject, TestBed} from '../testing';
|
||||
import {inject, TestBed, waitForAsync} from '../testing';
|
||||
|
||||
{
|
||||
describe('ApplicationInitStatus', () => {
|
||||
describe('no initializers', () => {
|
||||
it('should return true for `done`',
|
||||
async(inject([ApplicationInitStatus], (status: ApplicationInitStatus) => {
|
||||
waitForAsync(inject([ApplicationInitStatus], (status: ApplicationInitStatus) => {
|
||||
(status as any).runInitializers();
|
||||
expect(status.done).toBe(true);
|
||||
})));
|
||||
|
||||
it('should return a promise that resolves immediately for `donePromise`',
|
||||
async(inject([ApplicationInitStatus], (status: ApplicationInitStatus) => {
|
||||
waitForAsync(inject([ApplicationInitStatus], (status: ApplicationInitStatus) => {
|
||||
(status as any).runInitializers();
|
||||
status.donePromise.then(() => {
|
||||
expect(status.done).toBe(true);
|
||||
@ -58,7 +58,7 @@ import {async, inject, TestBed} from '../testing';
|
||||
});
|
||||
|
||||
it('should update the status once all async initializers are done',
|
||||
async(inject([ApplicationInitStatus], (status: ApplicationInitStatus) => {
|
||||
waitForAsync(inject([ApplicationInitStatus], (status: ApplicationInitStatus) => {
|
||||
(status as any).runInitializers();
|
||||
|
||||
setTimeout(() => {
|
||||
|
@ -19,7 +19,7 @@ import {expect} from '@angular/platform-browser/testing/src/matchers';
|
||||
import {onlyInIvy} from '@angular/private/testing';
|
||||
|
||||
import {NoopNgZone} from '../src/zone/ng_zone';
|
||||
import {async, ComponentFixtureNoNgZone, inject, TestBed, withModule} from '../testing';
|
||||
import {ComponentFixtureNoNgZone, inject, TestBed, waitForAsync, withModule} from '../testing';
|
||||
|
||||
@Component({selector: 'bootstrap-app', template: 'hello'})
|
||||
class SomeComponent {
|
||||
@ -79,62 +79,66 @@ class SomeComponent {
|
||||
}
|
||||
|
||||
it('should bootstrap a component from a child module',
|
||||
async(inject([ApplicationRef, Compiler], (app: ApplicationRef, compiler: Compiler) => {
|
||||
@Component({
|
||||
selector: 'bootstrap-app',
|
||||
template: '',
|
||||
})
|
||||
class SomeComponent {
|
||||
}
|
||||
waitForAsync(
|
||||
inject([ApplicationRef, Compiler], (app: ApplicationRef, compiler: Compiler) => {
|
||||
@Component({
|
||||
selector: 'bootstrap-app',
|
||||
template: '',
|
||||
})
|
||||
class SomeComponent {
|
||||
}
|
||||
|
||||
const helloToken = new InjectionToken<string>('hello');
|
||||
const helloToken = new InjectionToken<string>('hello');
|
||||
|
||||
@NgModule({
|
||||
providers: [{provide: helloToken, useValue: 'component'}],
|
||||
declarations: [SomeComponent],
|
||||
entryComponents: [SomeComponent],
|
||||
})
|
||||
class SomeModule {
|
||||
}
|
||||
@NgModule({
|
||||
providers: [{provide: helloToken, useValue: 'component'}],
|
||||
declarations: [SomeComponent],
|
||||
entryComponents: [SomeComponent],
|
||||
})
|
||||
class SomeModule {
|
||||
}
|
||||
|
||||
createRootEl();
|
||||
const modFactory = compiler.compileModuleSync(SomeModule);
|
||||
const module = modFactory.create(TestBed);
|
||||
const cmpFactory = module.componentFactoryResolver.resolveComponentFactory(SomeComponent)!;
|
||||
const component = app.bootstrap(cmpFactory);
|
||||
createRootEl();
|
||||
const modFactory = compiler.compileModuleSync(SomeModule);
|
||||
const module = modFactory.create(TestBed);
|
||||
const cmpFactory =
|
||||
module.componentFactoryResolver.resolveComponentFactory(SomeComponent)!;
|
||||
const component = app.bootstrap(cmpFactory);
|
||||
|
||||
// The component should see the child module providers
|
||||
expect(component.injector.get(helloToken)).toEqual('component');
|
||||
})));
|
||||
// The component should see the child module providers
|
||||
expect(component.injector.get(helloToken)).toEqual('component');
|
||||
})));
|
||||
|
||||
it('should bootstrap a component with a custom selector',
|
||||
async(inject([ApplicationRef, Compiler], (app: ApplicationRef, compiler: Compiler) => {
|
||||
@Component({
|
||||
selector: 'bootstrap-app',
|
||||
template: '',
|
||||
})
|
||||
class SomeComponent {
|
||||
}
|
||||
waitForAsync(
|
||||
inject([ApplicationRef, Compiler], (app: ApplicationRef, compiler: Compiler) => {
|
||||
@Component({
|
||||
selector: 'bootstrap-app',
|
||||
template: '',
|
||||
})
|
||||
class SomeComponent {
|
||||
}
|
||||
|
||||
const helloToken = new InjectionToken<string>('hello');
|
||||
const helloToken = new InjectionToken<string>('hello');
|
||||
|
||||
@NgModule({
|
||||
providers: [{provide: helloToken, useValue: 'component'}],
|
||||
declarations: [SomeComponent],
|
||||
entryComponents: [SomeComponent],
|
||||
})
|
||||
class SomeModule {
|
||||
}
|
||||
@NgModule({
|
||||
providers: [{provide: helloToken, useValue: 'component'}],
|
||||
declarations: [SomeComponent],
|
||||
entryComponents: [SomeComponent],
|
||||
})
|
||||
class SomeModule {
|
||||
}
|
||||
|
||||
createRootEl('custom-selector');
|
||||
const modFactory = compiler.compileModuleSync(SomeModule);
|
||||
const module = modFactory.create(TestBed);
|
||||
const cmpFactory = module.componentFactoryResolver.resolveComponentFactory(SomeComponent)!;
|
||||
const component = app.bootstrap(cmpFactory, 'custom-selector');
|
||||
createRootEl('custom-selector');
|
||||
const modFactory = compiler.compileModuleSync(SomeModule);
|
||||
const module = modFactory.create(TestBed);
|
||||
const cmpFactory =
|
||||
module.componentFactoryResolver.resolveComponentFactory(SomeComponent)!;
|
||||
const component = app.bootstrap(cmpFactory, 'custom-selector');
|
||||
|
||||
// The component should see the child module providers
|
||||
expect(component.injector.get(helloToken)).toEqual('component');
|
||||
})));
|
||||
// The component should see the child module providers
|
||||
expect(component.injector.get(helloToken)).toEqual('component');
|
||||
})));
|
||||
|
||||
describe('ApplicationRef', () => {
|
||||
beforeEach(() => {
|
||||
@ -216,7 +220,7 @@ class SomeComponent {
|
||||
defaultPlatform = _platform;
|
||||
}));
|
||||
|
||||
it('should wait for asynchronous app initializers', async(() => {
|
||||
it('should wait for asynchronous app initializers', waitForAsync(() => {
|
||||
let resolve: (result: any) => void;
|
||||
const promise: Promise<any> = new Promise((res) => {
|
||||
resolve = res;
|
||||
@ -235,7 +239,8 @@ class SomeComponent {
|
||||
});
|
||||
}));
|
||||
|
||||
it('should rethrow sync errors even if the exceptionHandler is not rethrowing', async(() => {
|
||||
it('should rethrow sync errors even if the exceptionHandler is not rethrowing',
|
||||
waitForAsync(() => {
|
||||
defaultPlatform
|
||||
.bootstrapModule(createModule([{
|
||||
provide: APP_INITIALIZER,
|
||||
@ -253,7 +258,7 @@ class SomeComponent {
|
||||
}));
|
||||
|
||||
it('should rethrow promise errors even if the exceptionHandler is not rethrowing',
|
||||
async(() => {
|
||||
waitForAsync(() => {
|
||||
defaultPlatform
|
||||
.bootstrapModule(createModule([
|
||||
{provide: APP_INITIALIZER, useValue: () => Promise.reject('Test'), multi: true}
|
||||
@ -264,7 +269,7 @@ class SomeComponent {
|
||||
});
|
||||
}));
|
||||
|
||||
it('should throw useful error when ApplicationRef is not configured', async(() => {
|
||||
it('should throw useful error when ApplicationRef is not configured', waitForAsync(() => {
|
||||
@NgModule()
|
||||
class EmptyModule {
|
||||
}
|
||||
@ -277,7 +282,7 @@ class SomeComponent {
|
||||
}));
|
||||
|
||||
it('should call the `ngDoBootstrap` method with `ApplicationRef` on the main module',
|
||||
async(() => {
|
||||
waitForAsync(() => {
|
||||
const ngDoBootstrap = jasmine.createSpy('ngDoBootstrap');
|
||||
defaultPlatform.bootstrapModule(createModule({ngDoBootstrap: ngDoBootstrap}))
|
||||
.then((moduleRef) => {
|
||||
@ -286,7 +291,7 @@ class SomeComponent {
|
||||
});
|
||||
}));
|
||||
|
||||
it('should auto bootstrap components listed in @NgModule.bootstrap', async(() => {
|
||||
it('should auto bootstrap components listed in @NgModule.bootstrap', waitForAsync(() => {
|
||||
defaultPlatform.bootstrapModule(createModule({bootstrap: [SomeComponent]}))
|
||||
.then((moduleRef) => {
|
||||
const appRef: ApplicationRef = moduleRef.injector.get(ApplicationRef);
|
||||
@ -295,7 +300,7 @@ class SomeComponent {
|
||||
}));
|
||||
|
||||
it('should error if neither `ngDoBootstrap` nor @NgModule.bootstrap was specified',
|
||||
async(() => {
|
||||
waitForAsync(() => {
|
||||
defaultPlatform.bootstrapModule(createModule({ngDoBootstrap: false}))
|
||||
.then(() => expect(false).toBe(true), (e) => {
|
||||
const expectedErrMsg =
|
||||
@ -305,12 +310,12 @@ class SomeComponent {
|
||||
});
|
||||
}));
|
||||
|
||||
it('should add bootstrapped module into platform modules list', async(() => {
|
||||
it('should add bootstrapped module into platform modules list', waitForAsync(() => {
|
||||
defaultPlatform.bootstrapModule(createModule({bootstrap: [SomeComponent]}))
|
||||
.then(module => expect((<any>defaultPlatform)._modules).toContain(module));
|
||||
}));
|
||||
|
||||
it('should bootstrap with NoopNgZone', async(() => {
|
||||
it('should bootstrap with NoopNgZone', waitForAsync(() => {
|
||||
defaultPlatform
|
||||
.bootstrapModule(createModule({bootstrap: [SomeComponent]}), {ngZone: 'noop'})
|
||||
.then((module) => {
|
||||
@ -376,7 +381,7 @@ class SomeComponent {
|
||||
createRootEl();
|
||||
defaultPlatform = _platform;
|
||||
}));
|
||||
it('should wait for asynchronous app initializers', async(() => {
|
||||
it('should wait for asynchronous app initializers', waitForAsync(() => {
|
||||
let resolve: (result: any) => void;
|
||||
const promise: Promise<any> = new Promise((res) => {
|
||||
resolve = res;
|
||||
@ -396,7 +401,8 @@ class SomeComponent {
|
||||
});
|
||||
}));
|
||||
|
||||
it('should rethrow sync errors even if the exceptionHandler is not rethrowing', async(() => {
|
||||
it('should rethrow sync errors even if the exceptionHandler is not rethrowing',
|
||||
waitForAsync(() => {
|
||||
const compilerFactory: CompilerFactory =
|
||||
defaultPlatform.injector.get(CompilerFactory, null)!;
|
||||
const moduleFactory = compilerFactory.createCompiler().compileModuleSync(createModule([{
|
||||
@ -413,7 +419,7 @@ class SomeComponent {
|
||||
}));
|
||||
|
||||
it('should rethrow promise errors even if the exceptionHandler is not rethrowing',
|
||||
async(() => {
|
||||
waitForAsync(() => {
|
||||
const compilerFactory: CompilerFactory =
|
||||
defaultPlatform.injector.get(CompilerFactory, null)!;
|
||||
const moduleFactory = compilerFactory.createCompiler().compileModuleSync(createModule(
|
||||
@ -623,25 +629,25 @@ class SomeComponent {
|
||||
});
|
||||
}
|
||||
|
||||
it('isStable should fire on synchronous component loading', async(() => {
|
||||
it('isStable should fire on synchronous component loading', waitForAsync(() => {
|
||||
expectStableTexts(SyncComp, ['1']);
|
||||
}));
|
||||
|
||||
it('isStable should fire after a microtask on init is completed', async(() => {
|
||||
it('isStable should fire after a microtask on init is completed', waitForAsync(() => {
|
||||
expectStableTexts(MicroTaskComp, ['11']);
|
||||
}));
|
||||
|
||||
it('isStable should fire after a macrotask on init is completed', async(() => {
|
||||
it('isStable should fire after a macrotask on init is completed', waitForAsync(() => {
|
||||
expectStableTexts(MacroTaskComp, ['11']);
|
||||
}));
|
||||
|
||||
it('isStable should fire only after chain of micro and macrotasks on init are completed',
|
||||
async(() => {
|
||||
waitForAsync(() => {
|
||||
expectStableTexts(MicroMacroTaskComp, ['111']);
|
||||
}));
|
||||
|
||||
it('isStable should fire only after chain of macro and microtasks on init are completed',
|
||||
async(() => {
|
||||
waitForAsync(() => {
|
||||
expectStableTexts(MacroMicroTaskComp, ['111']);
|
||||
}));
|
||||
|
||||
@ -665,7 +671,7 @@ class SomeComponent {
|
||||
});
|
||||
}
|
||||
|
||||
it('should be fired after app becomes unstable', async(() => {
|
||||
it('should be fired after app becomes unstable', waitForAsync(() => {
|
||||
const fixture = TestBed.createComponent(ClickComp);
|
||||
const appRef: ApplicationRef = TestBed.inject(ApplicationRef);
|
||||
const zone: NgZone = TestBed.inject(NgZone);
|
||||
|
@ -701,9 +701,6 @@
|
||||
{
|
||||
"name": "_keyMap"
|
||||
},
|
||||
{
|
||||
"name": "_mergeErrors"
|
||||
},
|
||||
{
|
||||
"name": "_noControlError"
|
||||
},
|
||||
@ -914,6 +911,9 @@
|
||||
{
|
||||
"name": "executeTemplate"
|
||||
},
|
||||
{
|
||||
"name": "executeValidators"
|
||||
},
|
||||
{
|
||||
"name": "executeViewQueryFn"
|
||||
},
|
||||
@ -1346,6 +1346,9 @@
|
||||
{
|
||||
"name": "mergeAll"
|
||||
},
|
||||
{
|
||||
"name": "mergeErrors"
|
||||
},
|
||||
{
|
||||
"name": "mergeHostAttribute"
|
||||
},
|
||||
@ -1401,10 +1404,7 @@
|
||||
"name": "noop"
|
||||
},
|
||||
{
|
||||
"name": "normalizeAsyncValidator"
|
||||
},
|
||||
{
|
||||
"name": "normalizeValidator"
|
||||
"name": "normalizeValidators"
|
||||
},
|
||||
{
|
||||
"name": "observable"
|
||||
|
@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
import {Component, Injectable, Input} from '@angular/core';
|
||||
import {async, ComponentFixtureAutoDetect, ComponentFixtureNoNgZone, TestBed, withModule} from '@angular/core/testing';
|
||||
import {ComponentFixtureAutoDetect, ComponentFixtureNoNgZone, TestBed, waitForAsync, withModule} from '@angular/core/testing';
|
||||
import {dispatchEvent} from '@angular/platform-browser/testing/src/browser_util';
|
||||
import {expect} from '@angular/platform-browser/testing/src/matchers';
|
||||
|
||||
@ -100,7 +100,7 @@ class NestedAsyncTimeoutComp {
|
||||
|
||||
{
|
||||
describe('ComponentFixture', () => {
|
||||
beforeEach(async(() => {
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [
|
||||
AutoDetectComp, AsyncComp, AsyncTimeoutComp, NestedAsyncTimeoutComp, AsyncChangeComp,
|
||||
@ -134,7 +134,7 @@ class NestedAsyncTimeoutComp {
|
||||
}));
|
||||
|
||||
it('should signal through whenStable when the fixture is stable (autoDetectChanges)',
|
||||
async(() => {
|
||||
waitForAsync(() => {
|
||||
const componentFixture = TestBed.createComponent(AsyncComp);
|
||||
componentFixture.autoDetectChanges();
|
||||
expect(componentFixture.nativeElement).toHaveText('1');
|
||||
@ -153,7 +153,7 @@ class NestedAsyncTimeoutComp {
|
||||
}));
|
||||
|
||||
it('should signal through isStable when the fixture is stable (no autoDetectChanges)',
|
||||
async(() => {
|
||||
waitForAsync(() => {
|
||||
const componentFixture = TestBed.createComponent(AsyncComp);
|
||||
|
||||
componentFixture.detectChanges();
|
||||
@ -174,7 +174,7 @@ class NestedAsyncTimeoutComp {
|
||||
|
||||
it('should wait for macroTask(setTimeout) while checking for whenStable ' +
|
||||
'(autoDetectChanges)',
|
||||
async(() => {
|
||||
waitForAsync(() => {
|
||||
const componentFixture = TestBed.createComponent(AsyncTimeoutComp);
|
||||
componentFixture.autoDetectChanges();
|
||||
expect(componentFixture.nativeElement).toHaveText('1');
|
||||
@ -194,7 +194,7 @@ class NestedAsyncTimeoutComp {
|
||||
|
||||
it('should wait for macroTask(setTimeout) while checking for whenStable ' +
|
||||
'(no autoDetectChanges)',
|
||||
async(() => {
|
||||
waitForAsync(() => {
|
||||
const componentFixture = TestBed.createComponent(AsyncTimeoutComp);
|
||||
componentFixture.detectChanges();
|
||||
expect(componentFixture.nativeElement).toHaveText('1');
|
||||
@ -215,7 +215,7 @@ class NestedAsyncTimeoutComp {
|
||||
|
||||
it('should wait for nested macroTasks(setTimeout) while checking for whenStable ' +
|
||||
'(autoDetectChanges)',
|
||||
async(() => {
|
||||
waitForAsync(() => {
|
||||
const componentFixture = TestBed.createComponent(NestedAsyncTimeoutComp);
|
||||
|
||||
componentFixture.autoDetectChanges();
|
||||
@ -236,7 +236,7 @@ class NestedAsyncTimeoutComp {
|
||||
|
||||
it('should wait for nested macroTasks(setTimeout) while checking for whenStable ' +
|
||||
'(no autoDetectChanges)',
|
||||
async(() => {
|
||||
waitForAsync(() => {
|
||||
const componentFixture = TestBed.createComponent(NestedAsyncTimeoutComp);
|
||||
componentFixture.detectChanges();
|
||||
expect(componentFixture.nativeElement).toHaveText('1');
|
||||
@ -255,7 +255,8 @@ class NestedAsyncTimeoutComp {
|
||||
});
|
||||
}));
|
||||
|
||||
it('should stabilize after async task in change detection (autoDetectChanges)', async(() => {
|
||||
it('should stabilize after async task in change detection (autoDetectChanges)',
|
||||
waitForAsync(() => {
|
||||
const componentFixture = TestBed.createComponent(AsyncChangeComp);
|
||||
|
||||
componentFixture.autoDetectChanges();
|
||||
@ -271,7 +272,8 @@ class NestedAsyncTimeoutComp {
|
||||
});
|
||||
}));
|
||||
|
||||
it('should stabilize after async task in change detection(no autoDetectChanges)', async(() => {
|
||||
it('should stabilize after async task in change detection(no autoDetectChanges)',
|
||||
waitForAsync(() => {
|
||||
const componentFixture = TestBed.createComponent(AsyncChangeComp);
|
||||
componentFixture.detectChanges();
|
||||
componentFixture.whenStable().then((_) => {
|
||||
@ -306,7 +308,7 @@ class NestedAsyncTimeoutComp {
|
||||
}).toThrowError(/Cannot call autoDetectChanges when ComponentFixtureNoNgZone is set/);
|
||||
});
|
||||
|
||||
it('should instantiate a component with valid DOM', async(() => {
|
||||
it('should instantiate a component with valid DOM', waitForAsync(() => {
|
||||
const componentFixture = TestBed.createComponent(SimpleComp);
|
||||
|
||||
expect(componentFixture.ngZone).toBeNull();
|
||||
@ -314,7 +316,7 @@ class NestedAsyncTimeoutComp {
|
||||
expect(componentFixture.nativeElement).toHaveText('Original Simple');
|
||||
}));
|
||||
|
||||
it('should allow changing members of the component', async(() => {
|
||||
it('should allow changing members of the component', waitForAsync(() => {
|
||||
const componentFixture = TestBed.createComponent(MyIfComp);
|
||||
|
||||
componentFixture.detectChanges();
|
||||
|
@ -10,7 +10,7 @@
|
||||
import {CommonModule, NgIfContext, ɵgetDOM as getDOM} from '@angular/common';
|
||||
import {Component, DebugElement, DebugNode, Directive, ElementRef, EmbeddedViewRef, EventEmitter, HostBinding, Injectable, Input, NO_ERRORS_SCHEMA, OnInit, Output, Renderer2, TemplateRef, ViewChild, ViewContainerRef} from '@angular/core';
|
||||
import {NgZone} from '@angular/core/src/zone';
|
||||
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
|
||||
import {By} from '@angular/platform-browser/src/dom/debug/by';
|
||||
import {createMouseEvent, hasClass} from '@angular/platform-browser/testing/src/browser_util';
|
||||
import {expect} from '@angular/platform-browser/testing/src/matchers';
|
||||
@ -264,7 +264,7 @@ class TestCmptWithPropInterpolation {
|
||||
describe('debug element', () => {
|
||||
let fixture: ComponentFixture<any>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [
|
||||
ChildComp,
|
||||
|
@ -17,7 +17,7 @@ import {TemplateRef} from '@angular/core/src/linker/template_ref';
|
||||
import {ViewContainerRef} from '@angular/core/src/linker/view_container_ref';
|
||||
import {EmbeddedViewRef} from '@angular/core/src/linker/view_ref';
|
||||
import {Attribute, Component, ContentChildren, Directive, HostBinding, HostListener, Input, Output, Pipe} from '@angular/core/src/metadata';
|
||||
import {async, fakeAsync, getTestBed, TestBed, tick} from '@angular/core/testing';
|
||||
import {fakeAsync, getTestBed, TestBed, tick, waitForAsync} from '@angular/core/testing';
|
||||
import {createMouseEvent, dispatchEvent, el, isCommentNode} from '@angular/platform-browser/testing/src/browser_util';
|
||||
import {expect} from '@angular/platform-browser/testing/src/matchers';
|
||||
import {modifiedInIvy, obsoleteInIvy, onlyInIvy} from '@angular/private/testing';
|
||||
@ -757,7 +757,7 @@ function declareTests(config?: {useJit: boolean}) {
|
||||
expect(childComponent.myHost).toBeAnInstanceOf(SomeDirective);
|
||||
});
|
||||
|
||||
it('should support events via EventEmitter on regular elements', async(() => {
|
||||
it('should support events via EventEmitter on regular elements', waitForAsync(() => {
|
||||
TestBed.configureTestingModule(
|
||||
{declarations: [MyComp, DirectiveEmittingEvent, DirectiveListeningEvent]});
|
||||
const template = '<div emitter listener></div>';
|
||||
@ -787,7 +787,7 @@ function declareTests(config?: {useJit: boolean}) {
|
||||
emitter.fireEvent('fired !');
|
||||
}));
|
||||
|
||||
it('should support events via EventEmitter on template elements', async(() => {
|
||||
it('should support events via EventEmitter on template elements', waitForAsync(() => {
|
||||
const fixture =
|
||||
TestBed
|
||||
.configureTestingModule(
|
||||
@ -819,7 +819,7 @@ function declareTests(config?: {useJit: boolean}) {
|
||||
emitter.fireEvent('fired !');
|
||||
}));
|
||||
|
||||
it('should support [()] syntax', async(() => {
|
||||
it('should support [()] syntax', waitForAsync(() => {
|
||||
TestBed.configureTestingModule({declarations: [MyComp, DirectiveWithTwoWayBinding]});
|
||||
const template = '<div [(control)]="ctxProp" two-way></div>';
|
||||
TestBed.overrideComponent(MyComp, {set: {template}});
|
||||
@ -1072,7 +1072,7 @@ function declareTests(config?: {useJit: boolean}) {
|
||||
});
|
||||
|
||||
describe('.createComponent', () => {
|
||||
it('should allow to create a component at any bound location', async(() => {
|
||||
it('should allow to create a component at any bound location', waitForAsync(() => {
|
||||
const fixture = TestBed.configureTestingModule({schemas: [NO_ERRORS_SCHEMA]})
|
||||
.createComponent(MyComp);
|
||||
const tc = fixture.debugElement.children[0].children[0];
|
||||
@ -1083,7 +1083,7 @@ function declareTests(config?: {useJit: boolean}) {
|
||||
.toHaveText('dynamic greet');
|
||||
}));
|
||||
|
||||
it('should allow to create multiple components at a location', async(() => {
|
||||
it('should allow to create multiple components at a location', waitForAsync(() => {
|
||||
const fixture = TestBed.configureTestingModule({schemas: [NO_ERRORS_SCHEMA]})
|
||||
.createComponent(MyComp);
|
||||
const tc = fixture.debugElement.children[0].children[0];
|
||||
@ -1219,7 +1219,7 @@ function declareTests(config?: {useJit: boolean}) {
|
||||
});
|
||||
|
||||
describe('.insert', () => {
|
||||
it('should throw with destroyed views', async(() => {
|
||||
it('should throw with destroyed views', waitForAsync(() => {
|
||||
const fixture = TestBed.configureTestingModule({schemas: [NO_ERRORS_SCHEMA]})
|
||||
.createComponent(MyComp);
|
||||
const tc = fixture.debugElement.children[0].children[0];
|
||||
@ -1235,7 +1235,7 @@ function declareTests(config?: {useJit: boolean}) {
|
||||
});
|
||||
|
||||
describe('.move', () => {
|
||||
it('should throw with destroyed views', async(() => {
|
||||
it('should throw with destroyed views', waitForAsync(() => {
|
||||
const fixture = TestBed.configureTestingModule({schemas: [NO_ERRORS_SCHEMA]})
|
||||
.createComponent(MyComp);
|
||||
const tc = fixture.debugElement.children[0].children[0];
|
||||
@ -2001,7 +2001,7 @@ function declareTests(config?: {useJit: boolean}) {
|
||||
});
|
||||
|
||||
describe('whitespaces in templates', () => {
|
||||
it('should not remove whitespaces by default', async(() => {
|
||||
it('should not remove whitespaces by default', waitForAsync(() => {
|
||||
@Component({
|
||||
selector: 'comp',
|
||||
template: '<span>foo</span> <span>bar</span>',
|
||||
@ -2015,7 +2015,8 @@ function declareTests(config?: {useJit: boolean}) {
|
||||
expect(f.nativeElement.childNodes.length).toBe(2);
|
||||
}));
|
||||
|
||||
it('should not remove whitespaces when explicitly requested not to do so', async(() => {
|
||||
it('should not remove whitespaces when explicitly requested not to do so',
|
||||
waitForAsync(() => {
|
||||
@Component({
|
||||
selector: 'comp',
|
||||
template: '<span>foo</span> <span>bar</span>',
|
||||
@ -2030,7 +2031,7 @@ function declareTests(config?: {useJit: boolean}) {
|
||||
expect(f.nativeElement.childNodes.length).toBe(3);
|
||||
}));
|
||||
|
||||
it('should remove whitespaces when explicitly requested to do so', async(() => {
|
||||
it('should remove whitespaces when explicitly requested to do so', waitForAsync(() => {
|
||||
@Component({
|
||||
selector: 'comp',
|
||||
template: '<span>foo</span> <span>bar</span>',
|
||||
|
@ -10,7 +10,7 @@ import {ResourceLoader} from '@angular/compiler';
|
||||
import {CompileMetadataResolver} from '@angular/compiler/src/metadata_resolver';
|
||||
import {MockResourceLoader} from '@angular/compiler/testing/src/resource_loader_mock';
|
||||
import {Component, Directive, Injectable, NgModule, OnDestroy, Pipe} from '@angular/core';
|
||||
import {async, getTestBed, TestBed} from '@angular/core/testing';
|
||||
import {getTestBed, TestBed, waitForAsync} from '@angular/core/testing';
|
||||
import {expect} from '@angular/platform-browser/testing/src/matchers';
|
||||
import {obsoleteInIvy} from '@angular/private/testing';
|
||||
|
||||
@ -135,7 +135,7 @@ import {obsoleteInIvy} from '@angular/private/testing';
|
||||
SomeService.annotations = [];
|
||||
}
|
||||
|
||||
beforeEach(async(() => {
|
||||
beforeEach(waitForAsync(() => {
|
||||
instances = new Map<any, any>();
|
||||
createSummaries().then(s => summaries = s);
|
||||
}));
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
import {AfterContentChecked, AfterContentInit, AfterViewChecked, AfterViewInit, asNativeElements, Component, ContentChild, ContentChildren, Directive, QueryList, TemplateRef, Type, ViewChild, ViewChildren, ViewContainerRef} from '@angular/core';
|
||||
import {ElementRef} from '@angular/core/src/core';
|
||||
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
|
||||
import {expect} from '@angular/platform-browser/testing/src/matchers';
|
||||
import {ivyEnabled, modifiedInIvy, onlyInIvy} from '@angular/private/testing';
|
||||
import {Subject} from 'rxjs';
|
||||
@ -414,7 +414,7 @@ describe('Query API', () => {
|
||||
});
|
||||
|
||||
describe('changes', () => {
|
||||
it('should notify query on change', async(() => {
|
||||
it('should notify query on change', waitForAsync(() => {
|
||||
const template = '<needs-query #q>' +
|
||||
'<div text="1"></div>' +
|
||||
'<div *ngIf="shouldShow" text="2"></div>' +
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
import {Compiler, SystemJsNgModuleLoader} from '@angular/core';
|
||||
import {global} from '@angular/core/src/util/global';
|
||||
import {async} from '@angular/core/testing';
|
||||
import {waitForAsync} from '@angular/core/testing';
|
||||
import {afterEach, beforeEach, describe, expect, it} from '@angular/core/testing/src/testing_internal';
|
||||
import {modifiedInIvy, onlyInIvy} from '@angular/private/testing';
|
||||
|
||||
@ -36,19 +36,19 @@ describe('SystemJsNgModuleLoader', () => {
|
||||
global['System'] = oldSystem;
|
||||
});
|
||||
|
||||
it('loads a default factory by appending the factory suffix', async(() => {
|
||||
it('loads a default factory by appending the factory suffix', waitForAsync(() => {
|
||||
const loader = new SystemJsNgModuleLoader(new Compiler());
|
||||
loader.load('test').then(contents => {
|
||||
expect(contents).toBe('test module factory' as any);
|
||||
});
|
||||
}));
|
||||
it('loads a named factory by appending the factory suffix', async(() => {
|
||||
it('loads a named factory by appending the factory suffix', waitForAsync(() => {
|
||||
const loader = new SystemJsNgModuleLoader(new Compiler());
|
||||
loader.load('test#Named').then(contents => {
|
||||
expect(contents).toBe('test NamedNgFactory' as any);
|
||||
});
|
||||
}));
|
||||
it('loads a named factory with a configured prefix and suffix', async(() => {
|
||||
it('loads a named factory with a configured prefix and suffix', waitForAsync(() => {
|
||||
const loader = new SystemJsNgModuleLoader(new Compiler(), {
|
||||
factoryPathPrefix: 'prefixed/',
|
||||
factoryPathSuffix: '/suffixed',
|
||||
@ -70,13 +70,13 @@ describe('SystemJsNgModuleLoader', () => {
|
||||
global['System'] = oldSystem;
|
||||
});
|
||||
|
||||
it('loads a default module', async(() => {
|
||||
it('loads a default module', waitForAsync(() => {
|
||||
const loader = new SystemJsNgModuleLoader(new Compiler());
|
||||
loader.load('test').then(contents => {
|
||||
expect(contents.moduleType).toBe('test module' as any);
|
||||
});
|
||||
}));
|
||||
it('loads a named module', async(() => {
|
||||
it('loads a named module', waitForAsync(() => {
|
||||
const loader = new SystemJsNgModuleLoader(new Compiler());
|
||||
loader.load('test#NamedModule').then(contents => {
|
||||
expect(contents.moduleType).toBe('test NamedModule' as any);
|
||||
|
@ -11,10 +11,13 @@ ts_library(
|
||||
"**/*_perf.ts",
|
||||
"domino.d.ts",
|
||||
"load_domino.ts",
|
||||
"is_shape_of.ts",
|
||||
"jit_spec.ts",
|
||||
"matchers.ts",
|
||||
],
|
||||
),
|
||||
deps = [
|
||||
":matchers",
|
||||
"//packages:types",
|
||||
"//packages/animations",
|
||||
"//packages/animations/browser",
|
||||
@ -34,6 +37,18 @@ ts_library(
|
||||
],
|
||||
)
|
||||
|
||||
ts_library(
|
||||
name = "matchers",
|
||||
testonly = True,
|
||||
srcs = [
|
||||
"is_shape_of.ts",
|
||||
"matchers.ts",
|
||||
],
|
||||
deps = [
|
||||
"//packages/core",
|
||||
],
|
||||
)
|
||||
|
||||
ts_library(
|
||||
name = "domino",
|
||||
testonly = True,
|
||||
|
140
packages/core/test/render3/i18n_debug_spec.ts
Normal file
140
packages/core/test/render3/i18n_debug_spec.ts
Normal file
@ -0,0 +1,140 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {i18nMutateOpCodesToString, i18nUpdateOpCodesToString} from '@angular/core/src/render3/i18n_debug';
|
||||
import {COMMENT_MARKER, ELEMENT_MARKER, I18nMutateOpCode, I18nUpdateOpCode} from '@angular/core/src/render3/interfaces/i18n';
|
||||
|
||||
describe('i18n debug', () => {
|
||||
describe('i18nUpdateOpCodesToString', () => {
|
||||
it('should print nothing', () => {
|
||||
expect(i18nUpdateOpCodesToString([])).toEqual([]);
|
||||
});
|
||||
|
||||
it('should print text opCode', () => {
|
||||
expect(i18nUpdateOpCodesToString([
|
||||
0b11,
|
||||
4,
|
||||
'pre ',
|
||||
-4,
|
||||
' post',
|
||||
1 << I18nUpdateOpCode.SHIFT_REF | I18nUpdateOpCode.Text,
|
||||
]))
|
||||
.toEqual(
|
||||
['if (mask & 0b11) { (lView[1] as Text).textContent = `pre ${lView[4]} post`; }']);
|
||||
});
|
||||
|
||||
it('should print Attribute opCode', () => {
|
||||
expect(i18nUpdateOpCodesToString([
|
||||
0b01, 8,
|
||||
'pre ', -4,
|
||||
' in ', -3,
|
||||
' post', 1 << I18nUpdateOpCode.SHIFT_REF | I18nUpdateOpCode.Attr,
|
||||
'title', null,
|
||||
0b10, 8,
|
||||
'pre ', -4,
|
||||
' in ', -3,
|
||||
' post', 1 << I18nUpdateOpCode.SHIFT_REF | I18nUpdateOpCode.Attr,
|
||||
'title', (v) => v,
|
||||
]))
|
||||
.toEqual([
|
||||
'if (mask & 0b1) { (lView[1] as Element).setAttribute(\'title\', `pre ${lView[4]} in ${lView[3]} post`); }',
|
||||
'if (mask & 0b10) { (lView[1] as Element).setAttribute(\'title\', (function (v) { return v; })(`pre ${lView[4]} in ${lView[3]} post`)); }'
|
||||
]);
|
||||
});
|
||||
|
||||
it('should print icuSwitch opCode', () => {
|
||||
expect(i18nUpdateOpCodesToString([
|
||||
0b100, 2, -5, 12 << I18nUpdateOpCode.SHIFT_REF | I18nUpdateOpCode.IcuSwitch,
|
||||
2 // FIXME(misko): Should be part of IcuSwitch
|
||||
])).toEqual(['if (mask & 0b100) { icuSwitchCase(lView[12] as Comment, 2, `${lView[5]}`); }']);
|
||||
});
|
||||
|
||||
it('should print icuUpdate opCode', () => {
|
||||
expect(i18nUpdateOpCodesToString([
|
||||
0b1000, 2, 13 << I18nUpdateOpCode.SHIFT_REF | I18nUpdateOpCode.IcuUpdate,
|
||||
3 // FIXME(misko): should be part of IcuUpdate
|
||||
])).toEqual(['if (mask & 0b1000) { icuUpdateCase(lView[13] as Comment, 3); }']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('i18nMutateOpCodesToString', () => {
|
||||
it('should print nothing', () => {
|
||||
expect(i18nMutateOpCodesToString([])).toEqual([]);
|
||||
});
|
||||
|
||||
it('should print Move', () => {
|
||||
expect(i18nMutateOpCodesToString([
|
||||
1 << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Select,
|
||||
2 << I18nMutateOpCode.SHIFT_PARENT | 0 << I18nMutateOpCode.SHIFT_REF |
|
||||
I18nMutateOpCode.AppendChild,
|
||||
])).toEqual(['(lView[2] as Element).appendChild(lView[1])']);
|
||||
});
|
||||
|
||||
it('should print text AppendChild', () => {
|
||||
expect(i18nMutateOpCodesToString([
|
||||
'xyz', 0,
|
||||
1 << I18nMutateOpCode.SHIFT_PARENT | 0 << I18nMutateOpCode.SHIFT_REF |
|
||||
I18nMutateOpCode.AppendChild
|
||||
]))
|
||||
.toEqual([
|
||||
'lView[0] = document.createTextNode("xyz")',
|
||||
'(lView[1] as Element).appendChild(lView[0])'
|
||||
]);
|
||||
});
|
||||
|
||||
|
||||
it('should print element AppendChild', () => {
|
||||
expect(i18nMutateOpCodesToString([
|
||||
ELEMENT_MARKER, 'xyz', 0,
|
||||
1 << I18nMutateOpCode.SHIFT_PARENT | 0 << I18nMutateOpCode.SHIFT_REF |
|
||||
I18nMutateOpCode.AppendChild
|
||||
]))
|
||||
.toEqual([
|
||||
'lView[0] = document.createElement("xyz")',
|
||||
'(lView[1] as Element).appendChild(lView[0])'
|
||||
]);
|
||||
});
|
||||
|
||||
it('should print comment AppendChild', () => {
|
||||
expect(i18nMutateOpCodesToString([
|
||||
COMMENT_MARKER, 'xyz', 0,
|
||||
1 << I18nMutateOpCode.SHIFT_PARENT | 0 << I18nMutateOpCode.SHIFT_REF |
|
||||
I18nMutateOpCode.AppendChild
|
||||
]))
|
||||
.toEqual([
|
||||
'lView[0] = document.createComment("xyz")',
|
||||
'(lView[1] as Element).appendChild(lView[0])'
|
||||
]);
|
||||
});
|
||||
|
||||
it('should print Remove', () => {
|
||||
expect(i18nMutateOpCodesToString([
|
||||
2 << I18nMutateOpCode.SHIFT_PARENT | 0 << I18nMutateOpCode.SHIFT_REF |
|
||||
I18nMutateOpCode.Remove
|
||||
])).toEqual(['(lView[2] as Element).remove(lView[0])']);
|
||||
});
|
||||
|
||||
it('should print Attr', () => {
|
||||
expect(i18nMutateOpCodesToString([
|
||||
1 << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Attr, 'attr', 'value'
|
||||
])).toEqual(['(lView[1] as Element).setAttribute("attr", "value")']);
|
||||
});
|
||||
|
||||
it('should print ElementEnd', () => {
|
||||
expect(i18nMutateOpCodesToString([
|
||||
1 << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.ElementEnd,
|
||||
])).toEqual(['setPreviousOrParentTNode(tView.data[1] as TNode)']);
|
||||
});
|
||||
|
||||
it('should print RemoveNestedIcu', () => {
|
||||
expect(i18nMutateOpCodesToString([
|
||||
1 << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.RemoveNestedIcu,
|
||||
])).toEqual(['removeNestedICU(1)']);
|
||||
});
|
||||
});
|
||||
});
|
@ -9,10 +9,11 @@
|
||||
import {noop} from '../../../compiler/src/render3/view/util';
|
||||
import {getTranslationForTemplate, ɵɵi18nAttributes, ɵɵi18nPostprocess, ɵɵi18nStart} from '../../src/render3/i18n';
|
||||
import {setDelayProjection, ɵɵelementEnd, ɵɵelementStart} from '../../src/render3/instructions/all';
|
||||
import {COMMENT_MARKER, ELEMENT_MARKER, I18nMutateOpCode, I18nUpdateOpCode, I18nUpdateOpCodes, TI18n} from '../../src/render3/interfaces/i18n';
|
||||
import {I18nUpdateOpCodes, TI18n, TIcu} from '../../src/render3/interfaces/i18n';
|
||||
import {HEADER_OFFSET, LView, TVIEW} from '../../src/render3/interfaces/view';
|
||||
import {getNativeByIndex} from '../../src/render3/util/view_utils';
|
||||
import {TemplateFixture} from './render_util';
|
||||
import {debugMatch} from './utils';
|
||||
|
||||
describe('Runtime i18n', () => {
|
||||
afterEach(() => {
|
||||
@ -72,25 +73,15 @@ describe('Runtime i18n', () => {
|
||||
const nbConsts = 1;
|
||||
const index = 0;
|
||||
const opCodes = getOpCodes(() => {
|
||||
ɵɵi18nStart(index, MSG_DIV);
|
||||
}, null, nbConsts, index);
|
||||
|
||||
// Check debug
|
||||
const debugOps = (opCodes as any).create.debug!.operations;
|
||||
expect(debugOps[0].__raw_opCode).toBe('simple text');
|
||||
expect(debugOps[0].type).toBe('Create Text Node');
|
||||
expect(debugOps[0].nodeIndex).toBe(1);
|
||||
expect(debugOps[0].text).toBe('simple text');
|
||||
expect(debugOps[1].__raw_opCode).toBe(1);
|
||||
expect(debugOps[1].type).toBe('AppendChild');
|
||||
expect(debugOps[1].nodeIndex).toBe(0);
|
||||
ɵɵi18nStart(index, MSG_DIV);
|
||||
}, null, nbConsts, index) as TI18n;
|
||||
|
||||
expect(opCodes).toEqual({
|
||||
vars: 1,
|
||||
create: [
|
||||
'simple text', nbConsts,
|
||||
index << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild
|
||||
],
|
||||
create: debugMatch([
|
||||
'lView[1] = document.createTextNode("simple text")',
|
||||
'(lView[0] as Element).appendChild(lView[1])'
|
||||
]),
|
||||
update: [],
|
||||
icus: null
|
||||
});
|
||||
@ -102,37 +93,28 @@ describe('Runtime i18n', () => {
|
||||
// 3 consts for the 2 divs and 1 span + 1 const for `i18nStart` = 4 consts
|
||||
const nbConsts = 4;
|
||||
const index = 1;
|
||||
const elementIndex = 2;
|
||||
const elementIndex2 = 3;
|
||||
const opCodes = getOpCodes(() => {
|
||||
ɵɵi18nStart(index, MSG_DIV);
|
||||
}, null, nbConsts, index);
|
||||
|
||||
expect(opCodes).toEqual({
|
||||
vars: 5,
|
||||
create: [
|
||||
'Hello ',
|
||||
nbConsts,
|
||||
index << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
|
||||
elementIndex << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Select,
|
||||
index << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
|
||||
'world',
|
||||
nbConsts + 1,
|
||||
elementIndex << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
|
||||
elementIndex << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.ElementEnd,
|
||||
' and ',
|
||||
nbConsts + 2,
|
||||
index << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
|
||||
elementIndex2 << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Select,
|
||||
index << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
|
||||
'universe',
|
||||
nbConsts + 3,
|
||||
elementIndex2 << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
|
||||
elementIndex2 << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.ElementEnd,
|
||||
'!',
|
||||
nbConsts + 4,
|
||||
index << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
|
||||
],
|
||||
create: debugMatch([
|
||||
'lView[4] = document.createTextNode("Hello ")',
|
||||
'(lView[1] as Element).appendChild(lView[4])',
|
||||
'(lView[1] as Element).appendChild(lView[2])',
|
||||
'lView[5] = document.createTextNode("world")',
|
||||
'(lView[2] as Element).appendChild(lView[5])',
|
||||
'setPreviousOrParentTNode(tView.data[2] as TNode)',
|
||||
'lView[6] = document.createTextNode(" and ")',
|
||||
'(lView[1] as Element).appendChild(lView[6])',
|
||||
'(lView[1] as Element).appendChild(lView[3])',
|
||||
'lView[7] = document.createTextNode("universe")',
|
||||
'(lView[3] as Element).appendChild(lView[7])',
|
||||
'setPreviousOrParentTNode(tView.data[3] as TNode)',
|
||||
'lView[8] = document.createTextNode("!")',
|
||||
'(lView[1] as Element).appendChild(lView[8])',
|
||||
]),
|
||||
update: [],
|
||||
icus: null
|
||||
});
|
||||
@ -146,21 +128,18 @@ describe('Runtime i18n', () => {
|
||||
ɵɵi18nStart(index, MSG_DIV);
|
||||
}, null, nbConsts, index);
|
||||
|
||||
expect((opCodes as any).update.debug.operations).toEqual([
|
||||
{__raw_opCode: 8, checkBit: 1, type: 'Text', nodeIndex: 2, text: 'Hello <20>0<EFBFBD>!'}
|
||||
expect((opCodes as any).update.debug).toEqual([
|
||||
'if (mask & 0b1) { (lView[2] as Text).textContent = `Hello ${lView[1]}!`; }'
|
||||
]);
|
||||
|
||||
expect(opCodes).toEqual({
|
||||
vars: 1,
|
||||
create:
|
||||
['', nbConsts, index << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild],
|
||||
update: [
|
||||
0b1, // bindings mask
|
||||
4, // if no update, skip 4
|
||||
'Hello ',
|
||||
-1, // binding index
|
||||
'!', (index + 1) << I18nUpdateOpCode.SHIFT_REF | I18nUpdateOpCode.Text
|
||||
],
|
||||
create: debugMatch([
|
||||
'lView[2] = document.createTextNode("")',
|
||||
'(lView[1] as Element).appendChild(lView[2])',
|
||||
]),
|
||||
update: debugMatch(
|
||||
['if (mask & 0b1) { (lView[2] as Text).textContent = `Hello ${lView[1]}!`; }']),
|
||||
icus: null
|
||||
});
|
||||
});
|
||||
@ -175,14 +154,12 @@ describe('Runtime i18n', () => {
|
||||
|
||||
expect(opCodes).toEqual({
|
||||
vars: 1,
|
||||
create:
|
||||
['', nbConsts, index << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild],
|
||||
update: [
|
||||
0b11, // bindings mask
|
||||
8, // if no update, skip 8
|
||||
'Hello ', -1, ' and ', -2, ', again ', -1, '!',
|
||||
(index + 1) << I18nUpdateOpCode.SHIFT_REF | I18nUpdateOpCode.Text
|
||||
],
|
||||
create: debugMatch([
|
||||
'lView[2] = document.createTextNode("")', '(lView[1] as Element).appendChild(lView[2])'
|
||||
]),
|
||||
update: debugMatch([
|
||||
'if (mask & 0b11) { (lView[2] as Text).textContent = `Hello ${lView[1]} and ${lView[2]}, again ${lView[1]}!`; }'
|
||||
]),
|
||||
icus: null
|
||||
});
|
||||
});
|
||||
@ -211,22 +188,14 @@ describe('Runtime i18n', () => {
|
||||
|
||||
expect(opCodes).toEqual({
|
||||
vars: 2,
|
||||
create: [
|
||||
'',
|
||||
nbConsts,
|
||||
index << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
|
||||
~rootTemplate << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Select,
|
||||
index << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
|
||||
'!',
|
||||
nbConsts + 1,
|
||||
index << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
|
||||
],
|
||||
update: [
|
||||
0b1, // bindings mask
|
||||
3, // if no update, skip 3
|
||||
-1, // binding index
|
||||
' is rendered as: ', firstTextNode << I18nUpdateOpCode.SHIFT_REF | I18nUpdateOpCode.Text
|
||||
],
|
||||
create: debugMatch([
|
||||
'lView[3] = document.createTextNode("")', '(lView[1] as Element).appendChild(lView[3])',
|
||||
'(lView[1] as Element).appendChild(lView[16381])',
|
||||
'lView[4] = document.createTextNode("!")', '(lView[1] as Element).appendChild(lView[4])'
|
||||
]),
|
||||
update: debugMatch([
|
||||
'if (mask & 0b1) { (lView[3] as Text).textContent = `${lView[1]} is rendered as: `; }'
|
||||
]),
|
||||
icus: null
|
||||
});
|
||||
|
||||
@ -243,19 +212,15 @@ describe('Runtime i18n', () => {
|
||||
|
||||
expect(opCodes).toEqual({
|
||||
vars: 2,
|
||||
create: [
|
||||
spanElement << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Select,
|
||||
index << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
|
||||
'before',
|
||||
nbConsts,
|
||||
spanElement << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
|
||||
~bElementSubTemplate << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Select,
|
||||
spanElement << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
|
||||
'after',
|
||||
nbConsts + 1,
|
||||
spanElement << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
|
||||
spanElement << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.ElementEnd,
|
||||
],
|
||||
create: debugMatch([
|
||||
'(lView[0] as Element).appendChild(lView[1])',
|
||||
'lView[3] = document.createTextNode("before")',
|
||||
'(lView[1] as Element).appendChild(lView[3])',
|
||||
'(lView[1] as Element).appendChild(lView[16381])',
|
||||
'lView[4] = document.createTextNode("after")',
|
||||
'(lView[1] as Element).appendChild(lView[4])',
|
||||
'setPreviousOrParentTNode(tView.data[1] as TNode)'
|
||||
]),
|
||||
update: [],
|
||||
icus: null
|
||||
});
|
||||
@ -272,14 +237,12 @@ describe('Runtime i18n', () => {
|
||||
|
||||
expect(opCodes).toEqual({
|
||||
vars: 1,
|
||||
create: [
|
||||
bElement << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Select,
|
||||
index << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
|
||||
'middle',
|
||||
nbConsts,
|
||||
bElement << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
|
||||
bElement << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.ElementEnd,
|
||||
],
|
||||
create: debugMatch([
|
||||
'(lView[0] as Element).appendChild(lView[1])',
|
||||
'lView[2] = document.createTextNode("middle")',
|
||||
'(lView[1] as Element).appendChild(lView[2])',
|
||||
'setPreviousOrParentTNode(tView.data[1] as TNode)'
|
||||
]),
|
||||
update: [],
|
||||
icus: null
|
||||
});
|
||||
@ -294,179 +257,76 @@ describe('Runtime i18n', () => {
|
||||
const nbConsts = 1;
|
||||
const index = 0;
|
||||
const opCodes = getOpCodes(() => {
|
||||
ɵɵi18nStart(index, MSG_DIV);
|
||||
}, null, nbConsts, index);
|
||||
const tIcuIndex = 0;
|
||||
const icuCommentNodeIndex = index + 1;
|
||||
const firstTextNodeIndex = index + 2;
|
||||
const bElementNodeIndex = index + 3;
|
||||
const iElementNodeIndex = index + 3;
|
||||
const spanElementNodeIndex = index + 3;
|
||||
const innerTextNode = index + 4;
|
||||
const lastTextNode = index + 5;
|
||||
|
||||
const debugOps = (opCodes as any).update.debug.operations;
|
||||
expect(debugOps[0].__raw_opCode).toBe(6);
|
||||
expect(debugOps[0].checkBit).toBe(1);
|
||||
expect(debugOps[0].type).toBe('IcuSwitch');
|
||||
expect(debugOps[0].nodeIndex).toBe(1);
|
||||
expect(debugOps[0].tIcuIndex).toBe(0);
|
||||
expect(debugOps[0].mainBinding).toBe('<27>0<EFBFBD>');
|
||||
|
||||
expect(debugOps[1].__raw_opCode).toBe(7);
|
||||
expect(debugOps[1].checkBit).toBe(3);
|
||||
expect(debugOps[1].type).toBe('IcuUpdate');
|
||||
expect(debugOps[1].nodeIndex).toBe(1);
|
||||
expect(debugOps[1].tIcuIndex).toBe(0);
|
||||
|
||||
const icuDebugOps = (opCodes as any).icus[0].create[0].debug.operations;
|
||||
let op: any;
|
||||
let i = 0;
|
||||
|
||||
op = icuDebugOps[i++];
|
||||
expect(op.__raw_opCode).toBe('no ');
|
||||
expect(op.type).toBe('Create Text Node');
|
||||
expect(op.nodeIndex).toBe(2);
|
||||
expect(op.text).toBe('no ');
|
||||
|
||||
op = icuDebugOps[i++];
|
||||
expect(op.__raw_opCode).toBe(131073);
|
||||
expect(op.type).toBe('AppendChild');
|
||||
expect(op.nodeIndex).toBe(1);
|
||||
|
||||
op = icuDebugOps[i++];
|
||||
expect(op.__raw_opCode).toEqual({marker: 'element'});
|
||||
expect(op.type).toBe('ELEMENT_MARKER');
|
||||
|
||||
op = icuDebugOps[i++];
|
||||
expect(op.__raw_opCode).toBe('b');
|
||||
expect(op.type).toBe('Create Text Node');
|
||||
expect(op.nodeIndex).toBe(3);
|
||||
expect(op.text).toBe('b');
|
||||
|
||||
op = icuDebugOps[i++];
|
||||
expect(op.__raw_opCode).toBe(131073);
|
||||
expect(op.type).toBe('AppendChild');
|
||||
expect(op.nodeIndex).toBe(1);
|
||||
|
||||
op = icuDebugOps[i++];
|
||||
expect(op.__raw_opCode).toBe(28);
|
||||
expect(op.type).toBe('Attr');
|
||||
expect(op.nodeIndex).toBe(3);
|
||||
expect(op.attrName).toBe('title');
|
||||
expect(op.attrValue).toBe('none');
|
||||
|
||||
op = icuDebugOps[i++];
|
||||
expect(op.__raw_opCode).toBe('emails');
|
||||
expect(op.type).toBe('Create Text Node');
|
||||
expect(op.nodeIndex).toBe(4);
|
||||
expect(op.text).toBe('emails');
|
||||
|
||||
op = icuDebugOps[i++];
|
||||
expect(op.__raw_opCode).toBe(393217);
|
||||
expect(op.type).toBe('AppendChild');
|
||||
expect(op.nodeIndex).toBe(3);
|
||||
|
||||
op = icuDebugOps[i++];
|
||||
expect(op.__raw_opCode).toBe('!');
|
||||
expect(op.type).toBe('Create Text Node');
|
||||
expect(op.nodeIndex).toBe(5);
|
||||
expect(op.text).toBe('!');
|
||||
|
||||
op = icuDebugOps[i++];
|
||||
expect(op.__raw_opCode).toBe(131073);
|
||||
expect(op.type).toBe('AppendChild');
|
||||
expect(op.nodeIndex).toBe(1);
|
||||
ɵɵi18nStart(index, MSG_DIV);
|
||||
}, null, nbConsts, index) as TI18n;
|
||||
|
||||
expect(opCodes).toEqual({
|
||||
vars: 5,
|
||||
create: [
|
||||
COMMENT_MARKER, 'ICU 1', icuCommentNodeIndex,
|
||||
index << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild
|
||||
],
|
||||
update: [
|
||||
0b1, // mask for ICU main binding
|
||||
3, // skip 3 if not changed
|
||||
-1, // icu main binding
|
||||
icuCommentNodeIndex << I18nUpdateOpCode.SHIFT_REF | I18nUpdateOpCode.IcuSwitch, tIcuIndex,
|
||||
0b11, // mask for all ICU bindings
|
||||
2, // skip 2 if not changed
|
||||
icuCommentNodeIndex << I18nUpdateOpCode.SHIFT_REF | I18nUpdateOpCode.IcuUpdate, tIcuIndex
|
||||
],
|
||||
icus: [{
|
||||
update: debugMatch([
|
||||
'if (mask & 0b1) { icuSwitchCase(lView[1] as Comment, 0, `${lView[1]}`); }',
|
||||
'if (mask & 0b11) { icuUpdateCase(lView[1] as Comment, 0); }',
|
||||
]),
|
||||
create: debugMatch([
|
||||
'lView[1] = document.createComment("ICU 1")',
|
||||
'(lView[0] as Element).appendChild(lView[1])',
|
||||
]),
|
||||
icus: [<TIcu>{
|
||||
type: 1,
|
||||
vars: [4, 3, 3],
|
||||
childIcus: [[], [], []],
|
||||
cases: ['0', '1', 'other'],
|
||||
create: [
|
||||
[
|
||||
'no ',
|
||||
firstTextNodeIndex,
|
||||
icuCommentNodeIndex << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
|
||||
ELEMENT_MARKER,
|
||||
'b',
|
||||
bElementNodeIndex,
|
||||
icuCommentNodeIndex << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
|
||||
bElementNodeIndex << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Attr,
|
||||
'title',
|
||||
'none',
|
||||
'emails',
|
||||
innerTextNode,
|
||||
bElementNodeIndex << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
|
||||
'!',
|
||||
lastTextNode,
|
||||
icuCommentNodeIndex << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
|
||||
],
|
||||
[
|
||||
'one ', firstTextNodeIndex,
|
||||
icuCommentNodeIndex << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
|
||||
ELEMENT_MARKER, 'i', iElementNodeIndex,
|
||||
icuCommentNodeIndex << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
|
||||
'email', innerTextNode,
|
||||
iElementNodeIndex << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild
|
||||
],
|
||||
[
|
||||
'', firstTextNodeIndex,
|
||||
icuCommentNodeIndex << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
|
||||
ELEMENT_MARKER, 'span', spanElementNodeIndex,
|
||||
icuCommentNodeIndex << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
|
||||
'emails', innerTextNode,
|
||||
spanElementNodeIndex << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild
|
||||
]
|
||||
debugMatch([
|
||||
'lView[2] = document.createTextNode("no ")',
|
||||
'(lView[1] as Element).appendChild(lView[2])',
|
||||
'lView[3] = document.createElement("b")',
|
||||
'(lView[1] as Element).appendChild(lView[3])',
|
||||
'(lView[3] as Element).setAttribute("title", "none")',
|
||||
'lView[4] = document.createTextNode("emails")',
|
||||
'(lView[3] as Element).appendChild(lView[4])',
|
||||
'lView[5] = document.createTextNode("!")',
|
||||
'(lView[1] as Element).appendChild(lView[5])'
|
||||
]),
|
||||
debugMatch([
|
||||
'lView[2] = document.createTextNode("one ")',
|
||||
'(lView[1] as Element).appendChild(lView[2])',
|
||||
'lView[3] = document.createElement("i")',
|
||||
'(lView[1] as Element).appendChild(lView[3])',
|
||||
'lView[4] = document.createTextNode("email")',
|
||||
'(lView[3] as Element).appendChild(lView[4])'
|
||||
]),
|
||||
debugMatch([
|
||||
'lView[2] = document.createTextNode("")',
|
||||
'(lView[1] as Element).appendChild(lView[2])',
|
||||
'lView[3] = document.createElement("span")',
|
||||
'(lView[1] as Element).appendChild(lView[3])',
|
||||
'lView[4] = document.createTextNode("emails")',
|
||||
'(lView[3] as Element).appendChild(lView[4])'
|
||||
])
|
||||
],
|
||||
remove: [
|
||||
[
|
||||
firstTextNodeIndex << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Remove,
|
||||
innerTextNode << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Remove,
|
||||
bElementNodeIndex << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Remove,
|
||||
lastTextNode << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Remove,
|
||||
],
|
||||
[
|
||||
firstTextNodeIndex << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Remove,
|
||||
innerTextNode << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Remove,
|
||||
iElementNodeIndex << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Remove,
|
||||
],
|
||||
[
|
||||
firstTextNodeIndex << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Remove,
|
||||
innerTextNode << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Remove,
|
||||
spanElementNodeIndex << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Remove,
|
||||
]
|
||||
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])',
|
||||
]),
|
||||
debugMatch([
|
||||
'(lView[0] as Element).remove(lView[2])',
|
||||
'(lView[0] as Element).remove(lView[4])',
|
||||
'(lView[0] as Element).remove(lView[3])',
|
||||
]),
|
||||
debugMatch([
|
||||
'(lView[0] as Element).remove(lView[2])',
|
||||
'(lView[0] as Element).remove(lView[4])',
|
||||
'(lView[0] as Element).remove(lView[3])',
|
||||
])
|
||||
],
|
||||
update: [
|
||||
[], [],
|
||||
[
|
||||
0b1, // mask for the first binding
|
||||
3, // skip 3 if not changed
|
||||
-1, // binding index
|
||||
' ', // text string to concatenate to the binding value
|
||||
firstTextNodeIndex << I18nUpdateOpCode.SHIFT_REF | I18nUpdateOpCode.Text,
|
||||
0b10, // mask for the title attribute binding
|
||||
4, // skip 4 if not changed
|
||||
-2, // binding index
|
||||
bElementNodeIndex << I18nUpdateOpCode.SHIFT_REF | I18nUpdateOpCode.Attr,
|
||||
'title', // attribute name
|
||||
null // sanitize function
|
||||
]
|
||||
debugMatch([]), debugMatch([]), debugMatch([
|
||||
'if (mask & 0b1) { (lView[2] as Text).textContent = `${lView[1]} `; }',
|
||||
'if (mask & 0b10) { (lView[3] as Element).setAttribute(\'title\', `${lView[2]}`); }'
|
||||
])
|
||||
]
|
||||
}]
|
||||
});
|
||||
@ -496,19 +356,14 @@ describe('Runtime i18n', () => {
|
||||
|
||||
expect(opCodes).toEqual({
|
||||
vars: 6,
|
||||
create: [
|
||||
COMMENT_MARKER, 'ICU 1', icuCommentNodeIndex,
|
||||
index << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild
|
||||
],
|
||||
update: [
|
||||
0b1, // mask for ICU main binding
|
||||
3, // skip 3 if not changed
|
||||
-1, // icu main binding
|
||||
icuCommentNodeIndex << I18nUpdateOpCode.SHIFT_REF | I18nUpdateOpCode.IcuSwitch, tIcuIndex,
|
||||
0b11, // mask for all ICU bindings
|
||||
2, // skip 2 if not changed
|
||||
icuCommentNodeIndex << I18nUpdateOpCode.SHIFT_REF | I18nUpdateOpCode.IcuUpdate, tIcuIndex
|
||||
],
|
||||
create: debugMatch([
|
||||
'lView[1] = document.createComment("ICU 1")',
|
||||
'(lView[0] as Element).appendChild(lView[1])'
|
||||
]),
|
||||
update: debugMatch([
|
||||
'if (mask & 0b1) { icuSwitchCase(lView[1] as Comment, 1, `${lView[1]}`); }',
|
||||
'if (mask & 0b11) { icuUpdateCase(lView[1] as Comment, 1); }'
|
||||
]),
|
||||
icus: [
|
||||
{
|
||||
type: 0,
|
||||
@ -516,28 +371,29 @@ describe('Runtime i18n', () => {
|
||||
childIcus: [[], [], []],
|
||||
cases: ['cat', 'dog', 'other'],
|
||||
create: [
|
||||
[
|
||||
'cats', nestedTextNodeIndex,
|
||||
nestedIcuCommentNodeIndex << I18nMutateOpCode.SHIFT_PARENT |
|
||||
I18nMutateOpCode.AppendChild
|
||||
],
|
||||
[
|
||||
'dogs', nestedTextNodeIndex,
|
||||
nestedIcuCommentNodeIndex << I18nMutateOpCode.SHIFT_PARENT |
|
||||
I18nMutateOpCode.AppendChild
|
||||
],
|
||||
[
|
||||
'animals', nestedTextNodeIndex,
|
||||
nestedIcuCommentNodeIndex << I18nMutateOpCode.SHIFT_PARENT |
|
||||
I18nMutateOpCode.AppendChild
|
||||
]
|
||||
debugMatch([
|
||||
'lView[5] = document.createTextNode("cats")',
|
||||
'(lView[3] as Element).appendChild(lView[5])'
|
||||
]),
|
||||
debugMatch([
|
||||
'lView[5] = document.createTextNode("dogs")',
|
||||
'(lView[3] as Element).appendChild(lView[5])'
|
||||
]),
|
||||
debugMatch([
|
||||
'lView[5] = document.createTextNode("animals")',
|
||||
'(lView[3] as Element).appendChild(lView[5])'
|
||||
]),
|
||||
],
|
||||
remove: [
|
||||
[nestedTextNodeIndex << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Remove],
|
||||
[nestedTextNodeIndex << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Remove],
|
||||
[nestedTextNodeIndex << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Remove]
|
||||
debugMatch(['(lView[0] as Element).remove(lView[5])']),
|
||||
debugMatch(['(lView[0] as Element).remove(lView[5])']),
|
||||
debugMatch(['(lView[0] as Element).remove(lView[5])'])
|
||||
],
|
||||
update: [[], [], []]
|
||||
update: [
|
||||
debugMatch([]),
|
||||
debugMatch([]),
|
||||
debugMatch([]),
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 1,
|
||||
@ -545,48 +401,33 @@ describe('Runtime i18n', () => {
|
||||
childIcus: [[], [0]],
|
||||
cases: ['0', 'other'],
|
||||
create: [
|
||||
[
|
||||
'zero', firstTextNodeIndex,
|
||||
icuCommentNodeIndex << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild
|
||||
],
|
||||
[
|
||||
'', firstTextNodeIndex,
|
||||
icuCommentNodeIndex << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
|
||||
COMMENT_MARKER, 'nested ICU 0', nestedIcuCommentNodeIndex,
|
||||
icuCommentNodeIndex << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
|
||||
'!', lastTextNodeIndex,
|
||||
icuCommentNodeIndex << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild
|
||||
]
|
||||
debugMatch([
|
||||
'lView[2] = document.createTextNode("zero")',
|
||||
'(lView[1] as Element).appendChild(lView[2])'
|
||||
]),
|
||||
debugMatch([
|
||||
'lView[2] = document.createTextNode("")',
|
||||
'(lView[1] as Element).appendChild(lView[2])',
|
||||
'lView[3] = document.createComment("nested ICU 0")',
|
||||
'(lView[1] as Element).appendChild(lView[3])',
|
||||
'lView[4] = document.createTextNode("!")',
|
||||
'(lView[1] as Element).appendChild(lView[4])'
|
||||
]),
|
||||
],
|
||||
remove: [
|
||||
[firstTextNodeIndex << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Remove],
|
||||
[
|
||||
firstTextNodeIndex << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Remove,
|
||||
lastTextNodeIndex << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Remove,
|
||||
0 << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.RemoveNestedIcu,
|
||||
nestedIcuCommentNodeIndex << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Remove,
|
||||
]
|
||||
debugMatch(['(lView[0] as Element).remove(lView[2])']),
|
||||
debugMatch([
|
||||
'(lView[0] as Element).remove(lView[2])', '(lView[0] as Element).remove(lView[4])',
|
||||
'removeNestedICU(0)', '(lView[0] as Element).remove(lView[3])'
|
||||
]),
|
||||
],
|
||||
update: [
|
||||
[],
|
||||
[
|
||||
0b1, // mask for ICU main binding
|
||||
3, // skip 3 if not changed
|
||||
-1, // binding index
|
||||
' ', // text string to concatenate to the binding value
|
||||
firstTextNodeIndex << I18nUpdateOpCode.SHIFT_REF | I18nUpdateOpCode.Text,
|
||||
0b10, // mask for inner ICU main binding
|
||||
3, // skip 3 if not changed
|
||||
-2, // inner ICU main binding
|
||||
nestedIcuCommentNodeIndex << I18nUpdateOpCode.SHIFT_REF |
|
||||
I18nUpdateOpCode.IcuSwitch,
|
||||
nestedTIcuIndex,
|
||||
0b10, // mask for all inner ICU bindings
|
||||
2, // skip 2 if not changed
|
||||
nestedIcuCommentNodeIndex << I18nUpdateOpCode.SHIFT_REF |
|
||||
I18nUpdateOpCode.IcuUpdate,
|
||||
nestedTIcuIndex
|
||||
]
|
||||
debugMatch([]),
|
||||
debugMatch([
|
||||
'if (mask & 0b1) { (lView[2] as Text).textContent = `${lView[1]} `; }',
|
||||
'if (mask & 0b10) { icuSwitchCase(lView[3] as Comment, 0, `${lView[2]}`); }',
|
||||
'if (mask & 0b10) { icuUpdateCase(lView[3] as Comment, 0); }'
|
||||
]),
|
||||
]
|
||||
}
|
||||
]
|
||||
@ -623,13 +464,9 @@ describe('Runtime i18n', () => {
|
||||
ɵɵi18nAttributes(index, MSG_div_attr);
|
||||
}, null, nbConsts, index);
|
||||
|
||||
expect(opCodes).toEqual([
|
||||
0b1, // bindings mask
|
||||
6, // if no update, skip 4
|
||||
'Hello ',
|
||||
-1, // binding index
|
||||
'!', (index - 1) << I18nUpdateOpCode.SHIFT_REF | I18nUpdateOpCode.Attr, 'title', null
|
||||
]);
|
||||
expect(opCodes).toEqual(debugMatch([
|
||||
'if (mask & 0b1) { (lView[0] as Element).setAttribute(\'title\', `Hello ${lView[1]}!`); }'
|
||||
]));
|
||||
});
|
||||
|
||||
it('for multiple bindings', () => {
|
||||
@ -641,12 +478,9 @@ describe('Runtime i18n', () => {
|
||||
ɵɵi18nAttributes(index, MSG_div_attr);
|
||||
}, null, nbConsts, index);
|
||||
|
||||
expect(opCodes).toEqual([
|
||||
0b11, // bindings mask
|
||||
10, // size
|
||||
'Hello ', -1, ' and ', -2, ', again ', -1, '!',
|
||||
(index - 1) << I18nUpdateOpCode.SHIFT_REF | I18nUpdateOpCode.Attr, 'title', null
|
||||
]);
|
||||
expect(opCodes).toEqual(debugMatch([
|
||||
'if (mask & 0b11) { (lView[0] as Element).setAttribute(\'title\', `Hello ${lView[1]} and ${lView[2]}, again ${lView[1]}!`); }'
|
||||
]));
|
||||
});
|
||||
|
||||
it('for multiple attributes', () => {
|
||||
@ -658,18 +492,10 @@ describe('Runtime i18n', () => {
|
||||
ɵɵi18nAttributes(index, MSG_div_attr);
|
||||
}, null, nbConsts, index);
|
||||
|
||||
expect(opCodes).toEqual([
|
||||
0b1, // bindings mask
|
||||
6, // if no update, skip 4
|
||||
'Hello ',
|
||||
-1, // binding index
|
||||
'!', (index - 1) << I18nUpdateOpCode.SHIFT_REF | I18nUpdateOpCode.Attr, 'title', null,
|
||||
0b1, // bindings mask
|
||||
6, // if no update, skip 4
|
||||
'Hello ',
|
||||
-1, // binding index
|
||||
'!', (index - 1) << I18nUpdateOpCode.SHIFT_REF | I18nUpdateOpCode.Attr, 'aria-label', null
|
||||
]);
|
||||
expect(opCodes).toEqual(debugMatch([
|
||||
'if (mask & 0b1) { (lView[0] as Element).setAttribute(\'title\', `Hello ${lView[1]}!`); }',
|
||||
'if (mask & 0b1) { (lView[0] as Element).setAttribute(\'aria-label\', `Hello ${lView[1]}!`); }'
|
||||
]));
|
||||
});
|
||||
});
|
||||
|
||||
|
22
packages/core/test/render3/interfaces/node_spec.ts
Normal file
22
packages/core/test/render3/interfaces/node_spec.ts
Normal file
@ -0,0 +1,22 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {TNodeType, TNodeTypeAsString} from '@angular/core/src/render3/interfaces/node';
|
||||
|
||||
describe('node interfaces', () => {
|
||||
describe('TNodeType', () => {
|
||||
it('should agree with TNodeTypeAsString', () => {
|
||||
expect(TNodeTypeAsString[TNodeType.Container]).toEqual('Container');
|
||||
expect(TNodeTypeAsString[TNodeType.Projection]).toEqual('Projection');
|
||||
expect(TNodeTypeAsString[TNodeType.View]).toEqual('View');
|
||||
expect(TNodeTypeAsString[TNodeType.Element]).toEqual('Element');
|
||||
expect(TNodeTypeAsString[TNodeType.ElementContainer]).toEqual('ElementContainer');
|
||||
expect(TNodeTypeAsString[TNodeType.IcuContainer]).toEqual('IcuContainer');
|
||||
});
|
||||
});
|
||||
});
|
186
packages/core/test/render3/is_shape_of.ts
Normal file
186
packages/core/test/render3/is_shape_of.ts
Normal file
@ -0,0 +1,186 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {TI18n} from '@angular/core/src/render3/interfaces/i18n';
|
||||
import {TNode} from '@angular/core/src/render3/interfaces/node';
|
||||
import {TView} from '@angular/core/src/render3/interfaces/view';
|
||||
|
||||
/**
|
||||
* A type used to create a runtime representation of a shape of object which matches the declared
|
||||
* interface at compile time.
|
||||
*
|
||||
* The purpose of this type is to ensure that the object must match all of the properties of a type.
|
||||
* This is later used by `isShapeOf` method to ensure that a particular object has a particular
|
||||
* shape.
|
||||
*
|
||||
* ```
|
||||
* interface MyShape {
|
||||
* foo: string,
|
||||
* bar: number
|
||||
* }
|
||||
*
|
||||
* const myShapeObj: {foo: '', bar: 0};
|
||||
* const ExpectedPropertiesOfShape = {foo: true, bar: true};
|
||||
*
|
||||
* isShapeOf(myShapeObj, ExpectedPropertiesOfShape);
|
||||
* ```
|
||||
*
|
||||
* The above code would verify that `myShapeObj` has `foo` and `bar` properties. However if later
|
||||
* `MyShape` is refactored to change a set of properties we would like to have a compile time error
|
||||
* that the `ExpectedPropertiesOfShape` also needs to be changed.
|
||||
*
|
||||
* ```
|
||||
* const ExpectedPropertiesOfShape = <ShapeOf<MyShape>>{foo: true, bar: true};
|
||||
* ```
|
||||
* The above code will force through compile time checks that the `ExpectedPropertiesOfShape` match
|
||||
* that of `MyShape`.
|
||||
*
|
||||
* See: `isShapeOf`
|
||||
*
|
||||
*/
|
||||
export type ShapeOf<T> = {
|
||||
[P in keyof T]: true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Determines if a particular object is of a given shape (duck-type version of `instanceof`.)
|
||||
*
|
||||
* ```
|
||||
* isShapeOf(someObj, {foo: true, bar: true});
|
||||
* ```
|
||||
*
|
||||
* The above code will be true if the `someObj` has both `foo` and `bar` property
|
||||
*
|
||||
* @param obj Object to test for.
|
||||
* @param shapeOf Desired shape.
|
||||
*/
|
||||
export function isShapeOf<T>(obj: any, shapeOf: ShapeOf<T>): obj is T {
|
||||
if (typeof obj === 'object' && obj) {
|
||||
return Object.keys(shapeOf).reduce(
|
||||
(prev, key) => prev && obj.hasOwnProperty(key), true as boolean);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if `obj` matches the shape `TI18n`.
|
||||
* @param obj
|
||||
*/
|
||||
export function isTI18n(obj: any): obj is TI18n {
|
||||
return isShapeOf<TI18n>(obj, ShapeOfTI18n);
|
||||
}
|
||||
const ShapeOfTI18n: ShapeOf<TI18n> = {
|
||||
vars: true,
|
||||
create: true,
|
||||
update: true,
|
||||
icus: true,
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Determines if `obj` matches the shape `TView`.
|
||||
* @param obj
|
||||
*/
|
||||
export function isTView(obj: any): obj is TView {
|
||||
return isShapeOf<TView>(obj, ShapeOfTView);
|
||||
}
|
||||
const ShapeOfTView: ShapeOf<TView> = {
|
||||
type: true,
|
||||
id: true,
|
||||
blueprint: true,
|
||||
template: true,
|
||||
viewQuery: true,
|
||||
node: true,
|
||||
firstCreatePass: true,
|
||||
firstUpdatePass: true,
|
||||
data: true,
|
||||
bindingStartIndex: true,
|
||||
expandoStartIndex: true,
|
||||
staticViewQueries: true,
|
||||
staticContentQueries: true,
|
||||
firstChild: true,
|
||||
expandoInstructions: true,
|
||||
directiveRegistry: true,
|
||||
pipeRegistry: true,
|
||||
preOrderHooks: true,
|
||||
preOrderCheckHooks: true,
|
||||
contentHooks: true,
|
||||
contentCheckHooks: true,
|
||||
viewHooks: true,
|
||||
viewCheckHooks: true,
|
||||
destroyHooks: true,
|
||||
cleanup: true,
|
||||
components: true,
|
||||
queries: true,
|
||||
contentQueries: true,
|
||||
schemas: true,
|
||||
consts: true,
|
||||
incompleteFirstPass: true,
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Determines if `obj` matches the shape `TI18n`.
|
||||
* @param obj
|
||||
*/
|
||||
export function isTNode(obj: any): obj is TNode {
|
||||
return isShapeOf<TNode>(obj, ShapeOfTNode);
|
||||
}
|
||||
const ShapeOfTNode: ShapeOf<TNode> = {
|
||||
type: true,
|
||||
index: true,
|
||||
injectorIndex: true,
|
||||
directiveStart: true,
|
||||
directiveEnd: true,
|
||||
directiveStylingLast: true,
|
||||
propertyBindings: true,
|
||||
flags: true,
|
||||
providerIndexes: true,
|
||||
tagName: true,
|
||||
attrs: true,
|
||||
mergedAttrs: true,
|
||||
localNames: true,
|
||||
initialInputs: true,
|
||||
inputs: true,
|
||||
outputs: true,
|
||||
tViews: true,
|
||||
next: true,
|
||||
projectionNext: true,
|
||||
child: true,
|
||||
parent: true,
|
||||
projection: true,
|
||||
styles: true,
|
||||
stylesWithoutHost: true,
|
||||
residualStyles: true,
|
||||
classes: true,
|
||||
classesWithoutHost: true,
|
||||
residualClasses: true,
|
||||
classBindings: true,
|
||||
styleBindings: true,
|
||||
};
|
||||
|
||||
/**
|
||||
* Determines if `obj` is DOM `Node`.
|
||||
*/
|
||||
export function isDOMNode(obj: any): obj is Node {
|
||||
return obj instanceof Node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if `obj` is DOM `Text`.
|
||||
*/
|
||||
export function isDOMElement(obj: any): obj is Element {
|
||||
return obj instanceof Element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if `obj` is DOM `Text`.
|
||||
*/
|
||||
export function isDOMText(obj: any): obj is Text {
|
||||
return obj instanceof Text;
|
||||
}
|
37
packages/core/test/render3/is_shape_of_spec.ts
Normal file
37
packages/core/test/render3/is_shape_of_spec.ts
Normal file
@ -0,0 +1,37 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {isShapeOf, ShapeOf} from './is_shape_of';
|
||||
|
||||
describe('isShapeOf', () => {
|
||||
const ShapeOfEmptyObject: ShapeOf<{}> = {};
|
||||
it('should not match for non objects', () => {
|
||||
expect(isShapeOf(null, ShapeOfEmptyObject)).toBeFalse();
|
||||
expect(isShapeOf(0, ShapeOfEmptyObject)).toBeFalse();
|
||||
expect(isShapeOf(1, ShapeOfEmptyObject)).toBeFalse();
|
||||
expect(isShapeOf(true, ShapeOfEmptyObject)).toBeFalse();
|
||||
expect(isShapeOf(false, ShapeOfEmptyObject)).toBeFalse();
|
||||
expect(isShapeOf(undefined, ShapeOfEmptyObject)).toBeFalse();
|
||||
});
|
||||
|
||||
it('should match on empty object', () => {
|
||||
expect(isShapeOf({}, ShapeOfEmptyObject)).toBeTrue();
|
||||
expect(isShapeOf({extra: 'is ok'}, ShapeOfEmptyObject)).toBeTrue();
|
||||
});
|
||||
|
||||
it('should match on shape', () => {
|
||||
expect(isShapeOf({required: 1}, {required: true})).toBeTrue();
|
||||
expect(isShapeOf({required: true, extra: 'is ok'}, {required: true})).toBeTrue();
|
||||
});
|
||||
|
||||
it('should not match if missing property', () => {
|
||||
expect(isShapeOf({required: 1}, {required: true, missing: true})).toBeFalse();
|
||||
expect(isShapeOf({required: true, extra: 'is ok'}, {required: true, missing: true}))
|
||||
.toBeFalse();
|
||||
});
|
||||
});
|
218
packages/core/test/render3/matchers.ts
Normal file
218
packages/core/test/render3/matchers.ts
Normal file
@ -0,0 +1,218 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {TI18n} from '@angular/core/src/render3/interfaces/i18n';
|
||||
import {TNode} from '@angular/core/src/render3/interfaces/node';
|
||||
import {TView} from '@angular/core/src/render3/interfaces/view';
|
||||
|
||||
import {isDOMElement, isDOMText, isTI18n, isTNode, isTView} from './is_shape_of';
|
||||
|
||||
|
||||
/**
|
||||
* Generic matcher which asserts that an object is of a given shape (`shapePredicate`) and that it
|
||||
* contains a subset of properties.
|
||||
*
|
||||
* @param name Name of `shapePredicate` to display when assertion fails.
|
||||
* @param shapePredicate Predicate which verifies that the object is of correct shape.
|
||||
* @param expected Expected set of properties to be found on the object.
|
||||
*/
|
||||
export function matchObjectShape<T>(
|
||||
name: string, shapePredicate: (obj: any) => obj is T,
|
||||
expected: Partial<T> = {}): jasmine.AsymmetricMatcher<T> {
|
||||
const matcher = function() {};
|
||||
let _actual: any = null;
|
||||
|
||||
matcher.asymmetricMatch = function(actual: any) {
|
||||
_actual = actual;
|
||||
if (!shapePredicate(actual)) return false;
|
||||
for (const key in expected) {
|
||||
if (expected.hasOwnProperty(key) && !jasmine.matchersUtil.equals(actual[key], expected[key]))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
matcher.jasmineToString = function() {
|
||||
return `${toString(_actual, false)} != ${toString(expected, true)})`;
|
||||
};
|
||||
|
||||
function toString(obj: any, isExpected: boolean) {
|
||||
if (isExpected || shapePredicate(obj)) {
|
||||
const props =
|
||||
Object.keys(expected).map(key => `${key}: ${JSON.stringify((obj as any)[key])}`);
|
||||
if (isExpected === false) {
|
||||
// Push something to let the user know that there may be other ignored properties in actual
|
||||
props.push('...');
|
||||
}
|
||||
return `${name}({${props.length === 0 ? '' : '\n ' + props.join(',\n ') + '\n'}})`;
|
||||
} else {
|
||||
return JSON.stringify(obj);
|
||||
}
|
||||
}
|
||||
return matcher;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Asymmetric matcher which matches a `TView` of a given shape.
|
||||
*
|
||||
* Expected usage:
|
||||
* ```
|
||||
* expect(tNode).toEqual(matchTView({type: TViewType.Root}));
|
||||
* expect({
|
||||
* node: tNode
|
||||
* }).toEqual({
|
||||
* node: matchTNode({type: TViewType.Root})
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* @param expected optional properties which the `TView` must contain.
|
||||
*/
|
||||
export function matchTView(expected?: Partial<TView>): jasmine.AsymmetricMatcher<TView> {
|
||||
return matchObjectShape('TView', isTView, expected);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asymmetric matcher which matches a `TNode` of a given shape.
|
||||
*
|
||||
* Expected usage:
|
||||
* ```
|
||||
* expect(tNode).toEqual(matchTNode({type: TNodeType.Element}));
|
||||
* expect({
|
||||
* node: tNode
|
||||
* }).toEqual({
|
||||
* node: matchTNode({type: TNodeType.Element})
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* @param expected optional properties which the `TNode` must contain.
|
||||
*/
|
||||
export function matchTNode(expected?: Partial<TNode>): jasmine.AsymmetricMatcher<TNode> {
|
||||
return matchObjectShape('TNode', isTNode, expected);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Asymmetric matcher which matches a `T18n` of a given shape.
|
||||
*
|
||||
* Expected usage:
|
||||
* ```
|
||||
* expect(tNode).toEqual(matchT18n({vars: 0}));
|
||||
* expect({
|
||||
* node: tNode
|
||||
* }).toEqual({
|
||||
* node: matchT18n({vars: 0})
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* @param expected optional properties which the `TI18n` must contain.
|
||||
*/
|
||||
export function matchTI18n(expected?: Partial<TI18n>): jasmine.AsymmetricMatcher<TI18n> {
|
||||
return matchObjectShape('TI18n', isTI18n, expected);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Asymmetric matcher which matches a DOM Element.
|
||||
*
|
||||
* Expected usage:
|
||||
* ```
|
||||
* expect(div).toEqual(matchT18n('div', {id: '123'}));
|
||||
* expect({
|
||||
* node: div
|
||||
* }).toEqual({
|
||||
* node: matchT18n('div', {id: '123'})
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* @param expectedTagName optional DOM tag name.
|
||||
* @param expectedAttributes optional DOM element properties.
|
||||
*/
|
||||
export function matchDomElement(
|
||||
expectedTagName: string|undefined = undefined,
|
||||
expectedAttrs: {[key: string]: string|null} = {}): jasmine.AsymmetricMatcher<Element> {
|
||||
const matcher = function() {};
|
||||
let _actual: any = null;
|
||||
|
||||
matcher.asymmetricMatch = function(actual: any) {
|
||||
_actual = actual;
|
||||
if (!isDOMElement(actual)) return false;
|
||||
if (expectedTagName && (expectedTagName.toUpperCase() !== actual.tagName.toUpperCase())) {
|
||||
return false;
|
||||
}
|
||||
if (expectedAttrs) {
|
||||
for (const attrName in expectedAttrs) {
|
||||
if (expectedAttrs.hasOwnProperty(attrName)) {
|
||||
const expectedAttrValue = expectedAttrs[attrName];
|
||||
const actualAttrValue = actual.getAttribute(attrName);
|
||||
if (expectedAttrValue !== actualAttrValue) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
matcher.jasmineToString = function() {
|
||||
let actualStr = isDOMElement(_actual) ? `<${_actual.tagName}${toString(_actual.attributes)}>` :
|
||||
JSON.stringify(_actual);
|
||||
let expectedStr = `<${expectedTagName || '*'}${
|
||||
Object.keys(expectedAttrs).map(key => ` ${key}=${JSON.stringify(expectedAttrs[key])}`)}>`;
|
||||
return `[${actualStr} != ${expectedStr}]`;
|
||||
};
|
||||
|
||||
function toString(attrs: NamedNodeMap) {
|
||||
let text = '';
|
||||
for (let i = 0; i < attrs.length; i++) {
|
||||
const attr = attrs[i];
|
||||
text += ` ${attr.name}=${JSON.stringify(attr.value)}`;
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
|
||||
return matcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* Asymmetric matcher which matches DOM text node.
|
||||
*
|
||||
* Expected usage:
|
||||
* ```
|
||||
* expect(div).toEqual(matchDomText('text'));
|
||||
* expect({
|
||||
* node: div
|
||||
* }).toEqual({
|
||||
* node: matchDomText('text')
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* @param expectedText optional DOM text.
|
||||
*/
|
||||
export function matchDomText(expectedText: string|undefined = undefined):
|
||||
jasmine.AsymmetricMatcher<Text> {
|
||||
const matcher = function() {};
|
||||
let _actual: any = null;
|
||||
|
||||
matcher.asymmetricMatch = function(actual: any) {
|
||||
_actual = actual;
|
||||
if (!isDOMText(actual)) return false;
|
||||
if (expectedText && (expectedText !== actual.textContent)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
matcher.jasmineToString = function() {
|
||||
let actualStr = isDOMText(_actual) ? `#TEXT: ${JSON.stringify(_actual.textContent)}` :
|
||||
JSON.stringify(_actual);
|
||||
let expectedStr = `#TEXT: ${JSON.stringify(expectedText)}`;
|
||||
return `[${actualStr} != ${expectedStr}]`;
|
||||
};
|
||||
|
||||
return matcher;
|
||||
}
|
101
packages/core/test/render3/matchers_spec.ts
Normal file
101
packages/core/test/render3/matchers_spec.ts
Normal file
@ -0,0 +1,101 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {createTNode, createTView} from '@angular/core/src/render3/instructions/shared';
|
||||
import {TNodeType} from '@angular/core/src/render3/interfaces/node';
|
||||
import {TViewType} from '@angular/core/src/render3/interfaces/view';
|
||||
import {onlyInIvy} from '@angular/private/testing';
|
||||
|
||||
import {isShapeOf, ShapeOf} from './is_shape_of';
|
||||
import {matchDomElement, matchDomText, matchObjectShape, matchTNode, matchTView} from './matchers';
|
||||
import {dedent} from './utils';
|
||||
|
||||
describe('render3 matchers', () => {
|
||||
describe('matchObjectShape', () => {
|
||||
interface MyShape {
|
||||
propA: any;
|
||||
propB: any;
|
||||
}
|
||||
|
||||
const myShape: MyShape = {propA: 'value', propB: 3};
|
||||
function isMyShape(obj: any): obj is MyShape {
|
||||
return isShapeOf<MyShape>(obj, ShapeOfMyShape);
|
||||
}
|
||||
const ShapeOfMyShape: ShapeOf<MyShape> = {propA: true, propB: true};
|
||||
function matchMyShape(expected?: Partial<MyShape>): jasmine.AsymmetricMatcher<MyShape> {
|
||||
return matchObjectShape('MyShape', isMyShape, expected);
|
||||
}
|
||||
|
||||
it('should match', () => {
|
||||
expect(isMyShape(myShape)).toBeTrue();
|
||||
expect(myShape).toEqual(matchMyShape());
|
||||
expect(myShape).toEqual(matchMyShape({propA: 'value'}));
|
||||
expect({node: myShape}).toEqual({node: matchMyShape({propA: 'value'})});
|
||||
});
|
||||
|
||||
it('should produce human readable errors', () => {
|
||||
const matcher = matchMyShape({propA: 'different'});
|
||||
expect(matcher.asymmetricMatch(myShape, [])).toEqual(false);
|
||||
expect(matcher.jasmineToString!()).toEqual(dedent`
|
||||
MyShape({
|
||||
propA: "value",
|
||||
...
|
||||
}) != MyShape({
|
||||
propA: "different"
|
||||
}))`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('matchTView', () => {
|
||||
const tView = createTView(TViewType.Root, 1, null, 2, 3, null, null, null, null, null);
|
||||
it('should match', () => {
|
||||
expect(tView).toEqual(matchTView());
|
||||
expect(tView).toEqual(matchTView({type: TViewType.Root}));
|
||||
expect({node: tView}).toEqual({node: matchTView({type: TViewType.Root})});
|
||||
});
|
||||
});
|
||||
describe('matchTNode', () => {
|
||||
const tView = createTView(TViewType.Root, 1, null, 2, 3, null, null, null, null, null);
|
||||
const tNode = createTNode(tView, null, TNodeType.Element, 1, 'tagName', []);
|
||||
|
||||
it('should match', () => {
|
||||
expect(tNode).toEqual(matchTNode());
|
||||
expect(tNode).toEqual(matchTNode({type: TNodeType.Element, tagName: 'tagName'}));
|
||||
expect({node: tNode}).toEqual({node: matchTNode({type: TNodeType.Element})});
|
||||
});
|
||||
});
|
||||
|
||||
describe('matchDomElement', () => {
|
||||
const div = document.createElement('div');
|
||||
div.setAttribute('name', 'Name');
|
||||
it('should match', () => {
|
||||
expect(div).toEqual(matchDomElement());
|
||||
expect(div).toEqual(matchDomElement('div', {name: 'Name'}));
|
||||
});
|
||||
|
||||
it('should produce human readable error', () => {
|
||||
const matcher = matchDomElement('div', {name: 'other'});
|
||||
expect(matcher.asymmetricMatch(div, [])).toEqual(false);
|
||||
expect(matcher.jasmineToString!()).toEqual(`[<DIV name="Name"> != <div name="other">]`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('matchDomText', () => {
|
||||
const text = document.createTextNode('myText');
|
||||
it('should match', () => {
|
||||
expect(text).toEqual(matchDomText());
|
||||
expect(text).toEqual(matchDomText('myText'));
|
||||
});
|
||||
|
||||
it('should produce human readable error', () => {
|
||||
const matcher = matchDomText('other text');
|
||||
expect(matcher.asymmetricMatch(text, [])).toEqual(false);
|
||||
expect(matcher.jasmineToString!()).toEqual(`[#TEXT: "myText" != #TEXT: "other text"]`);
|
||||
});
|
||||
});
|
||||
});
|
73
packages/core/test/render3/utils.ts
Normal file
73
packages/core/test/render3/utils.ts
Normal file
@ -0,0 +1,73 @@
|
||||
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
/** Template string function that can be used to strip indentation from a given string literal. */
|
||||
export function dedent(strings: TemplateStringsArray, ...values: any[]) {
|
||||
let joinedString = '';
|
||||
for (let i = 0; i < values.length; i++) {
|
||||
joinedString += `${strings[i]}${values[i]}`;
|
||||
}
|
||||
joinedString += strings[strings.length - 1];
|
||||
const lines = joinedString.split('\n');
|
||||
while (isBlank(lines[0])) {
|
||||
lines.shift();
|
||||
}
|
||||
while (isBlank(lines[lines.length - 1])) {
|
||||
lines.pop();
|
||||
}
|
||||
let minWhitespacePrefix = lines.reduce(
|
||||
(min, line) => Math.min(min, numOfWhiteSpaceLeadingChars(line)), Number.MAX_SAFE_INTEGER);
|
||||
return lines.map((line) => line.substring(minWhitespacePrefix)).join('\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests to see if the line is blank.
|
||||
*
|
||||
* A blank line is such which contains only whitespace.
|
||||
* @param text string to test for blank-ness.
|
||||
*/
|
||||
function isBlank(text: string): boolean {
|
||||
return /^\s*$/.test(text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns number of whitespace leading characters.
|
||||
*
|
||||
* @param text
|
||||
*/
|
||||
function numOfWhiteSpaceLeadingChars(text: string): number {
|
||||
return text.match(/^\s*/)![0].length;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Jasmine AsymmetricMatcher which can be used to assert `.debug` properties.
|
||||
*
|
||||
* ```
|
||||
* expect(obj).toEqual({
|
||||
* create: debugMatch('someValue')
|
||||
* })
|
||||
* ```
|
||||
*
|
||||
* In the above example it will assert that `obj.create.debug === 'someValue'`.
|
||||
*
|
||||
* @param expected Expected value.
|
||||
*/
|
||||
export function debugMatch<T>(expected: T): any {
|
||||
const matcher = function() {};
|
||||
let actual: any = null;
|
||||
|
||||
matcher.asymmetricMatch = function(objectWithDebug: any) {
|
||||
return jasmine.matchersUtil.equals(actual = objectWithDebug.debug, expected);
|
||||
};
|
||||
matcher.jasmineToString = function() {
|
||||
return `<${JSON.stringify(actual)} != ${JSON.stringify(expected)}>`;
|
||||
};
|
||||
return matcher;
|
||||
}
|
@ -10,7 +10,7 @@ import {EventEmitter} from '@angular/core';
|
||||
import {Injectable} from '@angular/core/src/di';
|
||||
import {PendingMacrotask, Testability, TestabilityRegistry} from '@angular/core/src/testability/testability';
|
||||
import {NgZone} from '@angular/core/src/zone/ng_zone';
|
||||
import {async, fakeAsync, flush, tick} from '@angular/core/testing';
|
||||
import {fakeAsync, flush, tick, waitForAsync} from '@angular/core/testing';
|
||||
import {beforeEach, describe, expect, it, SpyObject} from '@angular/core/testing/src/testing_internal';
|
||||
|
||||
import {scheduleMicroTask} from '../../src/util/microtask';
|
||||
@ -55,7 +55,7 @@ class MockNgZone extends NgZone {
|
||||
let updateCallback: any;
|
||||
let ngZone: MockNgZone;
|
||||
|
||||
beforeEach(async(() => {
|
||||
beforeEach(waitForAsync(() => {
|
||||
ngZone = new MockNgZone();
|
||||
testability = new Testability(ngZone);
|
||||
execute = new SpyObject().spy('execute');
|
||||
@ -68,7 +68,7 @@ class MockNgZone extends NgZone {
|
||||
expect(testability.getPendingRequestCount()).toEqual(0);
|
||||
});
|
||||
|
||||
it('should fire whenstable callbacks if pending count is 0', async(() => {
|
||||
it('should fire whenstable callbacks if pending count is 0', waitForAsync(() => {
|
||||
testability.whenStable(execute);
|
||||
|
||||
microTask(() => {
|
||||
@ -81,7 +81,7 @@ class MockNgZone extends NgZone {
|
||||
expect(execute).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not call whenstable callbacks when there are pending counts', async(() => {
|
||||
it('should not call whenstable callbacks when there are pending counts', waitForAsync(() => {
|
||||
testability.increasePendingRequestCount();
|
||||
testability.increasePendingRequestCount();
|
||||
testability.whenStable(execute);
|
||||
@ -96,7 +96,7 @@ class MockNgZone extends NgZone {
|
||||
});
|
||||
}));
|
||||
|
||||
it('should fire whenstable callbacks when pending drops to 0', async(() => {
|
||||
it('should fire whenstable callbacks when pending drops to 0', waitForAsync(() => {
|
||||
testability.increasePendingRequestCount();
|
||||
testability.whenStable(execute);
|
||||
|
||||
@ -110,7 +110,8 @@ class MockNgZone extends NgZone {
|
||||
});
|
||||
}));
|
||||
|
||||
it('should not fire whenstable callbacks synchronously when pending drops to 0', async(() => {
|
||||
it('should not fire whenstable callbacks synchronously when pending drops to 0',
|
||||
waitForAsync(() => {
|
||||
testability.increasePendingRequestCount();
|
||||
testability.whenStable(execute);
|
||||
testability.decreasePendingRequestCount();
|
||||
@ -118,7 +119,7 @@ class MockNgZone extends NgZone {
|
||||
expect(execute).not.toHaveBeenCalled();
|
||||
}));
|
||||
|
||||
it('should fire whenstable callbacks with didWork if pending count is 0', async(() => {
|
||||
it('should fire whenstable callbacks with didWork if pending count is 0', waitForAsync(() => {
|
||||
microTask(() => {
|
||||
testability.whenStable(execute);
|
||||
|
||||
@ -128,7 +129,8 @@ class MockNgZone extends NgZone {
|
||||
});
|
||||
}));
|
||||
|
||||
it('should fire whenstable callbacks with didWork when pending drops to 0', async(() => {
|
||||
it('should fire whenstable callbacks with didWork when pending drops to 0',
|
||||
waitForAsync(() => {
|
||||
testability.increasePendingRequestCount();
|
||||
testability.whenStable(execute);
|
||||
|
||||
@ -165,7 +167,7 @@ class MockNgZone extends NgZone {
|
||||
clearTimeout(id);
|
||||
}));
|
||||
|
||||
it('should fire if Angular is already stable', async(() => {
|
||||
it('should fire if Angular is already stable', waitForAsync(() => {
|
||||
testability.whenStable(execute, 200);
|
||||
|
||||
microTask(() => {
|
||||
@ -363,7 +365,7 @@ class MockNgZone extends NgZone {
|
||||
let registry: TestabilityRegistry;
|
||||
let ngZone: MockNgZone;
|
||||
|
||||
beforeEach(async(() => {
|
||||
beforeEach(waitForAsync(() => {
|
||||
ngZone = new MockNgZone();
|
||||
testability1 = new Testability(ngZone);
|
||||
testability2 = new Testability(ngZone);
|
||||
|
@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
import {EventEmitter, NgZone} from '@angular/core';
|
||||
import {async, fakeAsync, flushMicrotasks} from '@angular/core/testing';
|
||||
import {fakeAsync, flushMicrotasks, waitForAsync} from '@angular/core/testing';
|
||||
import {AsyncTestCompleter, beforeEach, describe, expect, inject, it, Log, xit} from '@angular/core/testing/src/testing_internal';
|
||||
import {browserDetection} from '@angular/platform-browser/testing/src/browser_util';
|
||||
|
||||
@ -914,7 +914,7 @@ function commonTests() {
|
||||
asyncResult = null!;
|
||||
});
|
||||
|
||||
it('should async even if the NgZone was created outside.', async(() => {
|
||||
it('should async even if the NgZone was created outside.', waitForAsync(() => {
|
||||
// try to escape the current async zone by using NgZone which was created outside.
|
||||
ngZone.run(() => {
|
||||
setTimeout(() => {
|
||||
|
@ -16,7 +16,7 @@ import {asyncFallback} from './async_fallback';
|
||||
* Example:
|
||||
*
|
||||
* ```
|
||||
* it('...', async(inject([AClass], (object) => {
|
||||
* it('...', waitForAsync(inject([AClass], (object) => {
|
||||
* object.doSomething.then(() => {
|
||||
* expect(...);
|
||||
* })
|
||||
@ -25,12 +25,12 @@ import {asyncFallback} from './async_fallback';
|
||||
*
|
||||
* @publicApi
|
||||
*/
|
||||
export function async(fn: Function): (done: any) => any {
|
||||
export function waitForAsync(fn: Function): (done: any) => any {
|
||||
const _Zone: any = typeof Zone !== 'undefined' ? Zone : null;
|
||||
if (!_Zone) {
|
||||
return function() {
|
||||
return Promise.reject(
|
||||
'Zone is needed for the async() test helper but could not be found. ' +
|
||||
'Zone is needed for the waitForAsync() test helper but could not be found. ' +
|
||||
'Please make sure that your environment includes zone.js/dist/zone.js');
|
||||
};
|
||||
}
|
||||
@ -43,3 +43,12 @@ export function async(fn: Function): (done: any) => any {
|
||||
// newest version of zone.js(0.8.25)
|
||||
return asyncFallback(fn);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use `waitForAsync()`, (expected removal in v12)
|
||||
* @see {@link waitForAsync}
|
||||
* @publicApi
|
||||
* */
|
||||
export function async(fn: Function): (done: any) => any {
|
||||
return waitForAsync(fn);
|
||||
}
|
||||
|
Reference in New Issue
Block a user