feat(ivy): add ΔpropertyInterpolate instructions (#29576)

- Adds the instructions
- Adds tests for all instructions
- Adds TODO to remove all tests when we are able to test this with TestBed after the compiler is updated

PR Close #29576
This commit is contained in:
Ben Lesh 2019-03-28 14:54:22 -07:00 committed by Igor Minar
parent b71cd7b4ee
commit a80637e9a1
6 changed files with 1320 additions and 3133 deletions

File diff suppressed because it is too large Load Diff

View File

@ -5,21 +5,14 @@
* 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 {CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA} from '../../metadata/schema';
import {validateAgainstEventProperties} from '../../sanitization/sanitization';
import {normalizeDebugBindingName, normalizeDebugBindingValue} from '../../util/ng_reflect';
import {assertLView} from '../assert';
import {bindingUpdated} from '../bindings';
import {PropertyAliasValue, PropertyAliases, TNode, TNodeType} from '../interfaces/node';
import {RComment, RElement, Renderer3, isProceduralRenderer} from '../interfaces/renderer';
import {SanitizerFn} from '../interfaces/sanitization';
import {BINDING_INDEX, FLAGS, HEADER_OFFSET, LView, LViewFlags, RENDERER, TData, TVIEW} from '../interfaces/view';
import {BINDING_INDEX} from '../interfaces/view';
import {getLView, getSelectedIndex} from '../state';
import {ANIMATION_PROP_PREFIX, isAnimationProp} from '../styling/util';
import {NO_CHANGE} from '../tokens';
import {INTERPOLATION_DELIMITER} from '../util/misc_utils';
import {getComponentViewByIndex, getNativeByIndex, getTNode, isComponent} from '../util/view_utils';
import {TsickleIssue1009, initializeTNodeInputs, loadComponentRenderer, setInputsForProperty, storeBindingMetadata} from './shared';
import {TsickleIssue1009, elementPropertyInternal, loadComponentRenderer, storeBindingMetadata} from './shared';
/**
* Update a property on a selected element.
@ -116,167 +109,3 @@ export function ΔcomponentHostSyntheticProperty<T>(
nativeOnly?: boolean) {
elementPropertyInternal(index, propName, value, sanitizer, nativeOnly, loadComponentRenderer);
}
/**
* Mapping between attributes names that don't correspond to their element property names.
*/
const ATTR_TO_PROP: {[name: string]: string} = {
'class': 'className',
'for': 'htmlFor',
'formaction': 'formAction',
'innerHtml': 'innerHTML',
'readonly': 'readOnly',
'tabindex': 'tabIndex',
};
function elementPropertyInternal<T>(
index: number, propName: string, value: T | NO_CHANGE, sanitizer?: SanitizerFn | null,
nativeOnly?: boolean,
loadRendererFn?: ((tNode: TNode, lView: LView) => Renderer3) | null): void {
if (value === NO_CHANGE) return;
const lView = getLView();
const element = getNativeByIndex(index, lView) as RElement | RComment;
const tNode = getTNode(index, lView);
let inputData: PropertyAliases|null|undefined;
let dataValue: PropertyAliasValue|undefined;
if (!nativeOnly && (inputData = initializeTNodeInputs(tNode)) &&
(dataValue = inputData[propName])) {
setInputsForProperty(lView, dataValue, value);
if (isComponent(tNode)) markDirtyIfOnPush(lView, index + HEADER_OFFSET);
if (ngDevMode) {
if (tNode.type === TNodeType.Element || tNode.type === TNodeType.Container) {
setNgReflectProperties(lView, element, tNode.type, dataValue, value);
}
}
} else if (tNode.type === TNodeType.Element) {
propName = ATTR_TO_PROP[propName] || propName;
if (ngDevMode) {
validateAgainstEventProperties(propName);
validateAgainstUnknownProperties(lView, element, propName, tNode);
ngDevMode.rendererSetProperty++;
}
savePropertyDebugData(tNode, lView, propName, lView[TVIEW].data, nativeOnly);
const renderer = loadRendererFn ? loadRendererFn(tNode, lView) : lView[RENDERER];
// It is assumed that the sanitizer is only added when the compiler determines that the property
// is risky, so sanitization can be done without further checks.
value = sanitizer != null ? (sanitizer(value, tNode.tagName || '', propName) as any) : value;
if (isProceduralRenderer(renderer)) {
renderer.setProperty(element as RElement, propName, value);
} else if (!isAnimationProp(propName)) {
(element as RElement).setProperty ? (element as any).setProperty(propName, value) :
(element as any)[propName] = value;
}
} else if (tNode.type === TNodeType.Container) {
// If the node is a container and the property didn't
// match any of the inputs or schemas we should throw.
if (ngDevMode && !matchingSchemas(lView, tNode.tagName)) {
throw createUnknownPropertyError(propName, tNode);
}
}
}
/** If node is an OnPush component, marks its LView dirty. */
function markDirtyIfOnPush(lView: LView, viewIndex: number): void {
ngDevMode && assertLView(lView);
const childComponentLView = getComponentViewByIndex(viewIndex, lView);
if (!(childComponentLView[FLAGS] & LViewFlags.CheckAlways)) {
childComponentLView[FLAGS] |= LViewFlags.Dirty;
}
}
function setNgReflectProperties(
lView: LView, element: RElement | RComment, type: TNodeType, inputs: PropertyAliasValue,
value: any) {
for (let i = 0; i < inputs.length; i += 3) {
const renderer = lView[RENDERER];
const attrName = normalizeDebugBindingName(inputs[i + 2] as string);
const debugValue = normalizeDebugBindingValue(value);
if (type === TNodeType.Element) {
isProceduralRenderer(renderer) ?
renderer.setAttribute((element as RElement), attrName, debugValue) :
(element as RElement).setAttribute(attrName, debugValue);
} else if (value !== undefined) {
const value = `bindings=${JSON.stringify({[attrName]: debugValue}, null, 2)}`;
if (isProceduralRenderer(renderer)) {
renderer.setValue((element as RComment), value);
} else {
(element as RComment).textContent = value;
}
}
}
}
function validateAgainstUnknownProperties(
hostView: LView, element: RElement | RComment, propName: string, tNode: TNode) {
// If the tag matches any of the schemas we shouldn't throw.
if (matchingSchemas(hostView, tNode.tagName)) {
return;
}
// If prop is not a known property of the HTML element...
if (!(propName in element) &&
// and we are in a browser context... (web worker nodes should be skipped)
typeof Node === 'function' && element instanceof Node &&
// and isn't a synthetic animation property...
propName[0] !== ANIMATION_PROP_PREFIX) {
// ... it is probably a user error and we should throw.
throw createUnknownPropertyError(propName, tNode);
}
}
function matchingSchemas(hostView: LView, tagName: string | null): boolean {
const schemas = hostView[TVIEW].schemas;
if (schemas !== null) {
for (let i = 0; i < schemas.length; i++) {
const schema = schemas[i];
if (schema === NO_ERRORS_SCHEMA ||
schema === CUSTOM_ELEMENTS_SCHEMA && tagName && tagName.indexOf('-') > -1) {
return true;
}
}
}
return false;
}
/**
* Stores debugging data for this property binding on first template pass.
* This enables features like DebugElement.properties.
*/
function savePropertyDebugData(
tNode: TNode, lView: LView, propName: string, tData: TData,
nativeOnly: boolean | undefined): void {
const lastBindingIndex = lView[BINDING_INDEX] - 1;
// Bind/interpolation functions save binding metadata in the last binding index,
// but leave the property name blank. If the interpolation delimiter is at the 0
// index, we know that this is our first pass and the property name still needs to
// be set.
const bindingMetadata = tData[lastBindingIndex] as string;
if (bindingMetadata[0] == INTERPOLATION_DELIMITER) {
tData[lastBindingIndex] = propName + bindingMetadata;
// We don't want to store indices for host bindings because they are stored in a
// different part of LView (the expando section).
if (!nativeOnly) {
if (tNode.propertyMetadataStartIndex == -1) {
tNode.propertyMetadataStartIndex = lastBindingIndex;
}
tNode.propertyMetadataEndIndex = lastBindingIndex + 1;
}
}
}
/**
* Creates an error that should be thrown when encountering an unknown property on an element.
* @param propName Name of the invalid property.
* @param tNode Node on which we encountered the error.
*/
function createUnknownPropertyError(propName: string, tNode: TNode): Error {
return new Error(
`Template error: Can't bind to '${propName}' since it isn't a known property of '${tNode.tagName}'.`);
}

View File

@ -8,10 +8,12 @@
import {assertEqual, assertLessThan} from '../../util/assert';
import {bindingUpdated, bindingUpdated2, bindingUpdated3, bindingUpdated4} from '../bindings';
import {BINDING_INDEX, TVIEW} from '../interfaces/view';
import {getLView} from '../state';
import {getLView, getSelectedIndex} from '../state';
import {NO_CHANGE} from '../tokens';
import {renderStringify} from '../util/misc_utils';
import {storeBindingMetadata} from './shared';
import {TsickleIssue1009, elementPropertyInternal, storeBindingMetadata} from './shared';
/**
* Create interpolation bindings with a variable number of expressions.
@ -283,3 +285,423 @@ export function Δinterpolation8(
renderStringify(v6) + i6 + renderStringify(v7) + suffix :
NO_CHANGE;
}
/////////////////////////////////////////////////////////////////////
/// NEW INSTRUCTIONS
/////////////////////////////////////////////////////////////////////
/**
* Shared reference to a string, used in `ΔpropertyInterpolate`.
*/
const EMPTY_STRING = '';
/**
*
* Update an interpolated property on an element with a lone bound value
*
* Used when the value passed to a property has 1 interpolated value in it, an no additional text
* surrounds that interpolated value:
*
* ```html
* <div title="{{v0}}"></div>
* ```
*
* Its compiled representation is::
*
* ```ts
* ΔpropertyInterpolate('title', v0);
* ```
*
* If the property name also exists as an input property on one of the element's directives,
* the component property will be set instead of the element property. This check must
* be conducted at runtime so child components that add new `@Inputs` don't have to be re-compiled.
*
* @param propName The name of the property to update
* @param prefix Static value used for concatenation only.
* @param v0 Value checked for change.
* @param suffix Static value used for concatenation only.
* @returns itself, so that it may be chained.
*/
export function ΔpropertyInterpolate(propName: string, v0: any): TsickleIssue1009 {
ΔpropertyInterpolate1(propName, EMPTY_STRING, v0, EMPTY_STRING);
return ΔpropertyInterpolate;
}
/**
*
* Update an interpolated property on an element with single bound value surrounded by text.
*
* Used when the value passed to a property has 1 interpolated value in it:
*
* ```html
* <div title="prefix{{v0}}suffix"></div>
* ```
*
* Its compiled representation is::
*
* ```ts
* ΔpropertyInterpolate1('title', 'prefix', v0, 'suffix');
* ```
*
* If the property name also exists as an input property on one of the element's directives,
* the component property will be set instead of the element property. This check must
* be conducted at runtime so child components that add new `@Inputs` don't have to be re-compiled.
*
* @param propName The name of the property to update
* @param prefix Static value used for concatenation only.
* @param v0 Value checked for change.
* @param suffix Static value used for concatenation only.
* @returns itself, so that it may be chained.
*/
export function ΔpropertyInterpolate1(
propName: string, prefix: string, v0: any, suffix: string): TsickleIssue1009 {
const index = getSelectedIndex();
elementPropertyInternal(index, propName, Δinterpolation1(prefix, v0, suffix));
return ΔpropertyInterpolate1;
}
/**
*
* Update an interpolated property on an element with 2 bound values surrounded by text.
*
* Used when the value passed to a property has 2 interpolated values in it:
*
* ```html
* <div title="prefix{{v0}}-{{v1}}suffix"></div>
* ```
*
* Its compiled representation is::
*
* ```ts
* ΔpropertyInterpolate2('title', 'prefix', v0, '-', v1, 'suffix');
* ```
*
* If the property name also exists as an input property on one of the element's directives,
* the component property will be set instead of the element property. This check must
* be conducted at runtime so child components that add new `@Inputs` don't have to be re-compiled.
*
* @param propName The name of the property to update
* @param prefix Static value used for concatenation only.
* @param v0 Value checked for change.
* @param i0 Static value used for concatenation only.
* @param v1 Value checked for change.
* @param suffix Static value used for concatenation only.
* @returns itself, so that it may be chained.
*/
export function ΔpropertyInterpolate2(
propName: string, prefix: string, v0: any, i0: string, v1: any,
suffix: string): TsickleIssue1009 {
const index = getSelectedIndex();
elementPropertyInternal(index, propName, Δinterpolation2(prefix, v0, i0, v1, suffix));
return ΔpropertyInterpolate2;
}
/**
*
* Update an interpolated property on an element with 3 bound values surrounded by text.
*
* Used when the value passed to a property has 3 interpolated values in it:
*
* ```html
* <div title="prefix{{v0}}-{{v1}}-{{v2}}suffix"></div>
* ```
*
* Its compiled representation is::
*
* ```ts
* ΔpropertyInterpolate3(
* 'title', 'prefix', v0, '-', v1, '-', v2, 'suffix');
* ```
*
* If the property name also exists as an input property on one of the element's directives,
* the component property will be set instead of the element property. This check must
* be conducted at runtime so child components that add new `@Inputs` don't have to be re-compiled.
*
* @param propName The name of the property to update
* @param prefix Static value used for concatenation only.
* @param v0 Value checked for change.
* @param i0 Static value used for concatenation only.
* @param v1 Value checked for change.
* @param i1 Static value used for concatenation only.
* @param v2 Value checked for change.
* @param suffix Static value used for concatenation only.
* @returns itself, so that it may be chained.
*/
export function ΔpropertyInterpolate3(
propName: string, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any,
suffix: string): TsickleIssue1009 {
const index = getSelectedIndex();
elementPropertyInternal(index, propName, Δinterpolation3(prefix, v0, i0, v1, i1, v2, suffix));
return ΔpropertyInterpolate3;
}
/**
*
* Update an interpolated property on an element with 4 bound values surrounded by text.
*
* Used when the value passed to a property has 4 interpolated values in it:
*
* ```html
* <div title="prefix{{v0}}-{{v1}}-{{v2}}-{{v3}}suffix"></div>
* ```
*
* Its compiled representation is::
*
* ```ts
* ΔpropertyInterpolate4(
* 'title', 'prefix', v0, '-', v1, '-', v2, '-', v3, 'suffix');
* ```
*
* If the property name also exists as an input property on one of the element's directives,
* the component property will be set instead of the element property. This check must
* be conducted at runtime so child components that add new `@Inputs` don't have to be re-compiled.
*
* @param propName The name of the property to update
* @param prefix Static value used for concatenation only.
* @param v0 Value checked for change.
* @param i0 Static value used for concatenation only.
* @param v1 Value checked for change.
* @param i1 Static value used for concatenation only.
* @param v2 Value checked for change.
* @param i2 Static value used for concatenation only.
* @param v3 Value checked for change.
* @param suffix Static value used for concatenation only.
* @returns itself, so that it may be chained.
*/
export function ΔpropertyInterpolate4(
propName: string, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string,
v3: any, suffix: string): TsickleIssue1009 {
const index = getSelectedIndex();
elementPropertyInternal(
index, propName, Δinterpolation4(prefix, v0, i0, v1, i1, v2, i2, v3, suffix));
return ΔpropertyInterpolate4;
}
/**
*
* Update an interpolated property on an element with 5 bound values surrounded by text.
*
* Used when the value passed to a property has 5 interpolated values in it:
*
* ```html
* <div title="prefix{{v0}}-{{v1}}-{{v2}}-{{v3}}-{{v4}}suffix"></div>
* ```
*
* Its compiled representation is::
*
* ```ts
* ΔpropertyInterpolate5(
* 'title', 'prefix', v0, '-', v1, '-', v2, '-', v3, '-', v4, 'suffix');
* ```
*
* If the property name also exists as an input property on one of the element's directives,
* the component property will be set instead of the element property. This check must
* be conducted at runtime so child components that add new `@Inputs` don't have to be re-compiled.
*
* @param propName The name of the property to update
* @param prefix Static value used for concatenation only.
* @param v0 Value checked for change.
* @param i0 Static value used for concatenation only.
* @param v1 Value checked for change.
* @param i1 Static value used for concatenation only.
* @param v2 Value checked for change.
* @param i2 Static value used for concatenation only.
* @param v3 Value checked for change.
* @param i3 Static value used for concatenation only.
* @param v4 Value checked for change.
* @param suffix Static value used for concatenation only.
* @returns itself, so that it may be chained.
*/
export function ΔpropertyInterpolate5(
propName: string, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string,
v3: any, i3: string, v4: any, suffix: string): TsickleIssue1009 {
const index = getSelectedIndex();
elementPropertyInternal(
index, propName, Δinterpolation5(prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, suffix));
return ΔpropertyInterpolate5;
}
/**
*
* Update an interpolated property on an element with 6 bound values surrounded by text.
*
* Used when the value passed to a property has 6 interpolated values in it:
*
* ```html
* <div title="prefix{{v0}}-{{v1}}-{{v2}}-{{v3}}-{{v4}}-{{v5}}suffix"></div>
* ```
*
* Its compiled representation is::
*
* ```ts
* ΔpropertyInterpolate6(
* 'title', 'prefix', v0, '-', v1, '-', v2, '-', v3, '-', v4, '-', v5, 'suffix');
* ```
*
* If the property name also exists as an input property on one of the element's directives,
* the component property will be set instead of the element property. This check must
* be conducted at runtime so child components that add new `@Inputs` don't have to be re-compiled.
*
* @param propName The name of the property to update
* @param prefix Static value used for concatenation only.
* @param v0 Value checked for change.
* @param i0 Static value used for concatenation only.
* @param v1 Value checked for change.
* @param i1 Static value used for concatenation only.
* @param v2 Value checked for change.
* @param i2 Static value used for concatenation only.
* @param v3 Value checked for change.
* @param i3 Static value used for concatenation only.
* @param v4 Value checked for change.
* @param i4 Static value used for concatenation only.
* @param v5 Value checked for change.
* @param suffix Static value used for concatenation only.
* @returns itself, so that it may be chained.
*/
export function ΔpropertyInterpolate6(
propName: string, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string,
v3: any, i3: string, v4: any, i4: string, v5: any, suffix: string): TsickleIssue1009 {
const index = getSelectedIndex();
elementPropertyInternal(
index, propName, Δinterpolation6(prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, suffix));
return ΔpropertyInterpolate6;
}
/**
*
* Update an interpolated property on an element with 7 bound values surrounded by text.
*
* Used when the value passed to a property has 7 interpolated values in it:
*
* ```html
* <div title="prefix{{v0}}-{{v1}}-{{v2}}-{{v3}}-{{v4}}-{{v5}}-{{v6}}suffix"></div>
* ```
*
* Its compiled representation is::
*
* ```ts
* ΔpropertyInterpolate7(
* 'title', 'prefix', v0, '-', v1, '-', v2, '-', v3, '-', v4, '-', v5, '-', v6, 'suffix');
* ```
*
* If the property name also exists as an input property on one of the element's directives,
* the component property will be set instead of the element property. This check must
* be conducted at runtime so child components that add new `@Inputs` don't have to be re-compiled.
*
* @param propName The name of the property to update
* @param prefix Static value used for concatenation only.
* @param v0 Value checked for change.
* @param i0 Static value used for concatenation only.
* @param v1 Value checked for change.
* @param i1 Static value used for concatenation only.
* @param v2 Value checked for change.
* @param i2 Static value used for concatenation only.
* @param v3 Value checked for change.
* @param i3 Static value used for concatenation only.
* @param v4 Value checked for change.
* @param i4 Static value used for concatenation only.
* @param v5 Value checked for change.
* @param i5 Static value used for concatenation only.
* @param v6 Value checked for change.
* @param suffix Static value used for concatenation only.
* @returns itself, so that it may be chained.
*/
export function ΔpropertyInterpolate7(
propName: string, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string,
v3: any, i3: string, v4: any, i4: string, v5: any, i5: string, v6: any,
suffix: string): TsickleIssue1009 {
const index = getSelectedIndex();
elementPropertyInternal(
index, propName,
Δinterpolation7(prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, i5, v6, suffix));
return ΔpropertyInterpolate7;
}
/**
*
* Update an interpolated property on an element with 8 bound values surrounded by text.
*
* Used when the value passed to a property has 8 interpolated values in it:
*
* ```html
* <div title="prefix{{v0}}-{{v1}}-{{v2}}-{{v3}}-{{v4}}-{{v5}}-{{v6}}-{{v7}}suffix"></div>
* ```
*
* Its compiled representation is::
*
* ```ts
* ΔpropertyInterpolate8(
* 'title', 'prefix', v0, '-', v1, '-', v2, '-', v3, '-', v4, '-', v5, '-', v6, '-', v7, 'suffix');
* ```
*
* If the property name also exists as an input property on one of the element's directives,
* the component property will be set instead of the element property. This check must
* be conducted at runtime so child components that add new `@Inputs` don't have to be re-compiled.
*
* @param propName The name of the property to update
* @param prefix Static value used for concatenation only.
* @param v0 Value checked for change.
* @param i0 Static value used for concatenation only.
* @param v1 Value checked for change.
* @param i1 Static value used for concatenation only.
* @param v2 Value checked for change.
* @param i2 Static value used for concatenation only.
* @param v3 Value checked for change.
* @param i3 Static value used for concatenation only.
* @param v4 Value checked for change.
* @param i4 Static value used for concatenation only.
* @param v5 Value checked for change.
* @param i5 Static value used for concatenation only.
* @param v6 Value checked for change.
* @param i6 Static value used for concatenation only.
* @param v7 Value checked for change.
* @param suffix Static value used for concatenation only.
* @returns itself, so that it may be chained.
*/
export function ΔpropertyInterpolate8(
propName: string, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string,
v3: any, i3: string, v4: any, i4: string, v5: any, i5: string, v6: any, i6: string, v7: any,
suffix: string): TsickleIssue1009 {
const index = getSelectedIndex();
elementPropertyInternal(
index, propName,
Δinterpolation8(prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, i5, v6, i6, v7, suffix));
return ΔpropertyInterpolate8;
}
/**
* Update an interpolated property on an element with 8 or more bound values surrounded by text.
*
* Used when the number of interpolated values exceeds 7.
*
* ```html
* <div
* title="prefix{{v0}}-{{v1}}-{{v2}}-{{v3}}-{{v4}}-{{v5}}-{{v6}}-{{v7}}-{{v8}}-{{v9}}suffix"></div>
* ```
*
* Its compiled representation is::
*
* ```ts
* ΔpropertyInterpolateV(
* 'title', ['prefix', v0, '-', v1, '-', v2, '-', v3, '-', v4, '-', v5, '-', v6, '-', v7, '-', v9,
* 'suffix']);
* ```
*
* If the property name also exists as an input property on one of the element's directives,
* the component property will be set instead of the element property. This check must
* be conducted at runtime so child components that add new `@Inputs` don't have to be re-compiled.
*
* @param propName The name of the property to update.
* @param values The a collection of values and the strings inbetween those values, beginning with a
* string prefix and ending with a string suffix.
* (e.g. `['prefix', value0, '-', value1, '-', value2, ..., value99, 'suffix']`)
* @returns itself, so that it may be chained.
*/
export function ΔpropertyInterpolateV(propName: string, values: any[]): TsickleIssue1009 {
const index = getSelectedIndex();
elementPropertyInternal(index, propName, ΔinterpolationV(values));
return ΔpropertyInterpolateV;
}

View File

@ -11,7 +11,7 @@ import {HEADER_OFFSET, TVIEW} from '../interfaces/view';
import {getCheckNoChangesMode, getLView, setSelectedIndex} from '../state';
/**
* Selects an index of an item to act on and flushes lifecycle hooks up to this point
* Selects an element for later binding instructions.
*
* Used in conjunction with instructions like {@link property} to act on elements with specified
* indices, for example those created with {@link element} or {@link elementStart}.

View File

@ -8,9 +8,11 @@
import {Injector} from '../../di';
import {ErrorHandler} from '../../error_handler';
import {Type} from '../../interface/type';
import {SchemaMetadata} from '../../metadata/schema';
import {CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA, SchemaMetadata} from '../../metadata/schema';
import {validateAgainstEventProperties} from '../../sanitization/sanitization';
import {Sanitizer} from '../../sanitization/security';
import {assertDataInRange, assertDefined, assertDomNode, assertEqual, assertLessThan, assertNotEqual} from '../../util/assert';
import {normalizeDebugBindingName, normalizeDebugBindingValue} from '../../util/ng_reflect';
import {assertLView, assertPreviousIsParent} from '../assert';
import {attachPatchData, getComponentViewByInstance} from '../context_discovery';
import {attachLContainerDebug, attachLViewDebug} from '../debug';
@ -23,17 +25,19 @@ import {INJECTOR_BLOOM_PARENT_SIZE, NodeInjectorFactory} from '../interfaces/inj
import {AttributeMarker, InitialInputData, InitialInputs, LocalRefExtractor, PropertyAliasValue, PropertyAliases, TAttributes, TContainerNode, TElementContainerNode, TElementNode, TIcuContainerNode, TNode, TNodeFlags, TNodeProviderIndexes, TNodeType, TProjectionNode, TViewNode} from '../interfaces/node';
import {LQueries} from '../interfaces/query';
import {RComment, RElement, RText, Renderer3, RendererFactory3, isProceduralRenderer} from '../interfaces/renderer';
import {SanitizerFn} from '../interfaces/sanitization';
import {StylingContext} from '../interfaces/styling';
import {BINDING_INDEX, CHILD_HEAD, CHILD_TAIL, CLEANUP, CONTEXT, DECLARATION_VIEW, ExpandoInstructions, FLAGS, HEADER_OFFSET, HOST, INJECTOR, InitPhaseState, LView, LViewFlags, NEXT, PARENT, QUERIES, RENDERER, RENDERER_FACTORY, RootContext, RootContextFlags, SANITIZER, TVIEW, TView, T_HOST} from '../interfaces/view';
import {BINDING_INDEX, CHILD_HEAD, CHILD_TAIL, CLEANUP, CONTEXT, DECLARATION_VIEW, ExpandoInstructions, FLAGS, HEADER_OFFSET, HOST, INJECTOR, InitPhaseState, LView, LViewFlags, NEXT, PARENT, QUERIES, RENDERER, RENDERER_FACTORY, RootContext, RootContextFlags, SANITIZER, TData, TVIEW, TView, T_HOST} from '../interfaces/view';
import {assertNodeOfPossibleTypes, assertNodeType} from '../node_assert';
import {isNodeMatchingSelectorList} from '../node_selector_matcher';
import {enterView, getBindingsEnabled, getCheckNoChangesMode, getIsParent, getLView, getNamespace, getPreviousOrParentTNode, incrementActiveDirectiveId, isCreationMode, leaveView, resetComponentState, setActiveHostElement, setBindingRoot, setCheckNoChangesMode, setCurrentDirectiveDef, setCurrentQueryIndex, setIsParent, setPreviousOrParentTNode, setSelectedIndex, ΔnamespaceHTML} from '../state';
import {initializeStaticContext as initializeStaticStylingContext} from '../styling/class_and_style_bindings';
import {ANIMATION_PROP_PREFIX, isAnimationProp} from '../styling/util';
import {NO_CHANGE} from '../tokens';
import {attrsStylingIndexOf} from '../util/attrs_utils';
import {INTERPOLATION_DELIMITER, renderStringify} from '../util/misc_utils';
import {getLViewParent, getRootContext} from '../util/view_traversal_utils';
import {getComponentViewByIndex, getNativeByTNode, isComponentDef, isContentQueryHost, isRootView, readPatchedLView, resetPreOrderHookFlags, unwrapRNode, viewAttachedToChangeDetector} from '../util/view_utils';
import {getComponentViewByIndex, getNativeByIndex, getNativeByTNode, getTNode, isComponent, isComponentDef, isContentQueryHost, isRootView, readPatchedLView, resetPreOrderHookFlags, unwrapRNode, viewAttachedToChangeDetector} from '../util/view_utils';
@ -430,7 +434,7 @@ export function renderEmbeddedTemplate<T>(viewToRender: LView, tView: TView, con
ΔnamespaceHTML();
// Reset the selected index so we can assert that `select` was called later
ngDevMode && setSelectedIndex(-1);
setSelectedIndex(-1);
tView.template !(getRenderFlags(viewToRender), context);
// This must be set to false immediately after the first creation run because in an
@ -465,7 +469,7 @@ function renderComponentOrTemplate<T>(
ΔnamespaceHTML();
// Reset the selected index so we can assert that `select` was called later
ngDevMode && setSelectedIndex(-1);
setSelectedIndex(-1);
templateFn(RenderFlags.Create, context);
}
@ -810,16 +814,169 @@ export function generatePropertyAliases(tNode: TNode, direction: BindingDirectio
return propStore;
}
/**
* Mapping between attributes names that don't correspond to their element property names.
*/
const ATTR_TO_PROP: {[name: string]: string} = {
'class': 'className',
'for': 'htmlFor',
'formaction': 'formAction',
'innerHtml': 'innerHTML',
'readonly': 'readOnly',
'tabindex': 'tabIndex',
};
//////////////////////////
//// Text
//////////////////////////
export function elementPropertyInternal<T>(
index: number, propName: string, value: T | NO_CHANGE, sanitizer?: SanitizerFn | null,
nativeOnly?: boolean,
loadRendererFn?: ((tNode: TNode, lView: LView) => Renderer3) | null): void {
if (value === NO_CHANGE) return;
const lView = getLView();
const element = getNativeByIndex(index, lView) as RElement | RComment;
const tNode = getTNode(index, lView);
let inputData: PropertyAliases|null|undefined;
let dataValue: PropertyAliasValue|undefined;
if (!nativeOnly && (inputData = initializeTNodeInputs(tNode)) &&
(dataValue = inputData[propName])) {
setInputsForProperty(lView, dataValue, value);
if (isComponent(tNode)) markDirtyIfOnPush(lView, index + HEADER_OFFSET);
if (ngDevMode) {
if (tNode.type === TNodeType.Element || tNode.type === TNodeType.Container) {
setNgReflectProperties(lView, element, tNode.type, dataValue, value);
}
}
} else if (tNode.type === TNodeType.Element) {
propName = ATTR_TO_PROP[propName] || propName;
if (ngDevMode) {
validateAgainstEventProperties(propName);
validateAgainstUnknownProperties(lView, element, propName, tNode);
ngDevMode.rendererSetProperty++;
}
savePropertyDebugData(tNode, lView, propName, lView[TVIEW].data, nativeOnly);
//////////////////////////
//// Directive
//////////////////////////
const renderer = loadRendererFn ? loadRendererFn(tNode, lView) : lView[RENDERER];
// It is assumed that the sanitizer is only added when the compiler determines that the property
// is risky, so sanitization can be done without further checks.
value = sanitizer != null ? (sanitizer(value, tNode.tagName || '', propName) as any) : value;
if (isProceduralRenderer(renderer)) {
renderer.setProperty(element as RElement, propName, value);
} else if (!isAnimationProp(propName)) {
(element as RElement).setProperty ? (element as any).setProperty(propName, value) :
(element as any)[propName] = value;
}
} else if (tNode.type === TNodeType.Container) {
// If the node is a container and the property didn't
// match any of the inputs or schemas we should throw.
if (ngDevMode && !matchingSchemas(lView, tNode.tagName)) {
throw createUnknownPropertyError(propName, tNode);
}
}
}
/** If node is an OnPush component, marks its LView dirty. */
function markDirtyIfOnPush(lView: LView, viewIndex: number): void {
ngDevMode && assertLView(lView);
const childComponentLView = getComponentViewByIndex(viewIndex, lView);
if (!(childComponentLView[FLAGS] & LViewFlags.CheckAlways)) {
childComponentLView[FLAGS] |= LViewFlags.Dirty;
}
}
function setNgReflectProperties(
lView: LView, element: RElement | RComment, type: TNodeType, inputs: PropertyAliasValue,
value: any) {
for (let i = 0; i < inputs.length; i += 3) {
const renderer = lView[RENDERER];
const attrName = normalizeDebugBindingName(inputs[i + 2] as string);
const debugValue = normalizeDebugBindingValue(value);
if (type === TNodeType.Element) {
isProceduralRenderer(renderer) ?
renderer.setAttribute((element as RElement), attrName, debugValue) :
(element as RElement).setAttribute(attrName, debugValue);
} else if (value !== undefined) {
const value = `bindings=${JSON.stringify({[attrName]: debugValue}, null, 2)}`;
if (isProceduralRenderer(renderer)) {
renderer.setValue((element as RComment), value);
} else {
(element as RComment).textContent = value;
}
}
}
}
function validateAgainstUnknownProperties(
hostView: LView, element: RElement | RComment, propName: string, tNode: TNode) {
// If the tag matches any of the schemas we shouldn't throw.
if (matchingSchemas(hostView, tNode.tagName)) {
return;
}
// If prop is not a known property of the HTML element...
if (!(propName in element) &&
// and we are in a browser context... (web worker nodes should be skipped)
typeof Node === 'function' && element instanceof Node &&
// and isn't a synthetic animation property...
propName[0] !== ANIMATION_PROP_PREFIX) {
// ... it is probably a user error and we should throw.
throw createUnknownPropertyError(propName, tNode);
}
}
function matchingSchemas(hostView: LView, tagName: string | null): boolean {
const schemas = hostView[TVIEW].schemas;
if (schemas !== null) {
for (let i = 0; i < schemas.length; i++) {
const schema = schemas[i];
if (schema === NO_ERRORS_SCHEMA ||
schema === CUSTOM_ELEMENTS_SCHEMA && tagName && tagName.indexOf('-') > -1) {
return true;
}
}
}
return false;
}
/**
* Stores debugging data for this property binding on first template pass.
* This enables features like DebugElement.properties.
*/
function savePropertyDebugData(
tNode: TNode, lView: LView, propName: string, tData: TData,
nativeOnly: boolean | undefined): void {
const lastBindingIndex = lView[BINDING_INDEX] - 1;
// Bind/interpolation functions save binding metadata in the last binding index,
// but leave the property name blank. If the interpolation delimiter is at the 0
// index, we know that this is our first pass and the property name still needs to
// be set.
const bindingMetadata = tData[lastBindingIndex] as string;
if (bindingMetadata[0] == INTERPOLATION_DELIMITER) {
tData[lastBindingIndex] = propName + bindingMetadata;
// We don't want to store indices for host bindings because they are stored in a
// different part of LView (the expando section).
if (!nativeOnly) {
if (tNode.propertyMetadataStartIndex == -1) {
tNode.propertyMetadataStartIndex = lastBindingIndex;
}
tNode.propertyMetadataEndIndex = lastBindingIndex + 1;
}
}
}
/**
* Creates an error that should be thrown when encountering an unknown property on an element.
* @param propName Name of the invalid property.
* @param tNode Node on which we encountered the error.
*/
function createUnknownPropertyError(propName: string, tNode: TNode): Error {
return new Error(
`Template error: Can't bind to '${propName}' since it isn't a known property of '${tNode.tagName}'.`);
}
/**
* Instantiate a root component.
@ -1515,7 +1672,7 @@ export function checkView<T>(hostView: LView, component: T) {
creationMode && executeViewQueryFn(RenderFlags.Create, hostTView, component);
// Reset the selected index so we can assert that `select` was called later
ngDevMode && setSelectedIndex(-1);
setSelectedIndex(-1);
templateFn(getRenderFlags(hostView), component);

View File

@ -7,6 +7,7 @@
*/
import {NgForOfContext} from '@angular/common';
import {ΔpropertyInterpolate, ΔpropertyInterpolate1, ΔpropertyInterpolate2, ΔpropertyInterpolate3, ΔpropertyInterpolate4, ΔpropertyInterpolate5, ΔpropertyInterpolate6, ΔpropertyInterpolate7, ΔpropertyInterpolate8, ΔpropertyInterpolateV} from '@angular/core/src/render3/instructions/all';
import {ΔdefineComponent} from '../../src/render3/definition';
import {RenderFlags, Δbind, Δelement, ΔelementAttribute, ΔelementEnd, ΔelementProperty, ΔelementStart, ΔelementStyleProp, ΔelementStyling, ΔelementStylingApply, ΔelementStylingMap, Δinterpolation1, Δproperty, Δselect, Δtemplate, Δtext, ΔtextBinding} from '../../src/render3/index';
@ -161,7 +162,7 @@ describe('instructions', () => {
});
});
describe('select', () => {
describe('Δselect', () => {
it('should error in DevMode if index is out of range', () => {
// Only one constant added, meaning only index `0` is valid.
const t = new TemplateFixture(createDiv, () => {}, 1, 0);
@ -173,7 +174,7 @@ describe('instructions', () => {
describe('property', () => {
// TODO(benlesh): Replace with TestBed tests once the instruction is being generated.
it('should set properties of the selected element', () => {
it('should set properties of the Δselected element', () => {
// <div [title]="title"></div>
const t = new TemplateFixture(createDiv, () => {}, 1, 1);
t.update(() => {
@ -241,7 +242,7 @@ describe('instructions', () => {
});
});
it('should error in dev mode if select was not called prior', () => {
it('should error in dev mode if Δselect was not called prior', () => {
const t = new TemplateFixture(createDiv, () => {}, 1, 1);
expect(() => { t.update(() => { Δproperty('title', 'test'); }); }).toThrow();
expect(() => {
@ -253,6 +254,724 @@ describe('instructions', () => {
});
});
/**
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
* TODO: REMOVE ALL OF THESE TemplateFixture TESTS FOR TestBed TESTS AFTER COMPILER IS UPDATED
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
*/
describe('ΔpropertyInterpolate instructions', () => {
describe('ΔpropertyInterpolate', () => {
it('should interpolate one value', () => {
// <div title="{{123}}"></div>
const t = new TemplateFixture(createDiv, () => {}, 1, 1);
t.update(() => {
Δselect(0);
ΔpropertyInterpolate('title', 123);
});
expect(t.html).toEqual('<div title="123"></div>');
t.update(() => {
Δselect(0);
ΔpropertyInterpolate('title', 'abc');
});
expect(t.html).toEqual('<div title="abc"></div>');
expect(ngDevMode).toHaveProperties({
firstTemplatePass: 1,
tNode: 2, // 1 for div, 1 for host element
tView: 2, // 1 for rootView + 1 for the template view
rendererCreateElement: 1,
rendererSetProperty: 2,
});
});
it('should chain', () => {
// <div title="{{123}}" accesskey="{{'A'}}"></div>
const t = new TemplateFixture(createDiv, () => {}, 1, 2);
t.update(() => {
Δselect(0);
ΔpropertyInterpolate('title', 123)('accessKey', 'A');
});
expect(t.html).toEqual('<div accesskey="A" title="123"></div>');
t.update(() => {
Δselect(0);
ΔpropertyInterpolate('title', 'abc')('accessKey', 'B');
});
expect(t.html).toEqual('<div accesskey="B" title="abc"></div>');
expect(ngDevMode).toHaveProperties({
firstTemplatePass: 1,
tNode: 2, // 1 for div, 1 for host element
tView: 2, // 1 for rootView + 1 for the template view
rendererCreateElement: 1,
rendererSetProperty: 4,
});
});
it('should error if called without Δselect called first', () => {
const t = new TemplateFixture(createDiv, () => {}, 1, 1);
expect(() => { t.update(() => { ΔpropertyInterpolate('title', 123); }); }).toThrow();
expect(() => {
Δselect(0);
t.update(() => { ΔpropertyInterpolate('title', 123); });
}).not.toThrow();
});
});
describe('ΔpropertyInterpolate1', () => {
it('should interpolate one value', () => {
// <div title="start{{123}}end"></div>
const t = new TemplateFixture(createDiv, () => {}, 1, 1);
t.update(() => {
Δselect(0);
ΔpropertyInterpolate1('title', 'start', 123, 'end');
});
expect(t.html).toEqual('<div title="start123end"></div>');
t.update(() => {
Δselect(0);
ΔpropertyInterpolate1('title', 'start', 'abc', 'end');
});
expect(t.html).toEqual('<div title="startabcend"></div>');
expect(ngDevMode).toHaveProperties({
firstTemplatePass: 1,
tNode: 2, // 1 for div, 1 for host element
tView: 2, // 1 for rootView + 1 for the template view
rendererCreateElement: 1,
rendererSetProperty: 2,
});
});
it('should chain', () => {
// <div title="start{{123}}end" data-teststartstart{{'A'}}end"></div>
const t = new TemplateFixture(createDiv, () => {}, 1, 2);
t.update(() => {
Δselect(0);
ΔpropertyInterpolate1('title', 'start', 123, 'end')('accessKey', 'start', 'A', 'end');
});
expect(t.html).toEqual('<div accesskey="startAend" title="start123end"></div>');
t.update(() => {
Δselect(0);
ΔpropertyInterpolate1('title', 'start', 'abc', 'end')('accessKey', 'start', 'B', 'end');
});
expect(t.html).toEqual('<div accesskey="startBend" title="startabcend"></div>');
expect(ngDevMode).toHaveProperties({
firstTemplatePass: 1,
tNode: 2, // 1 for div, 1 for host element
tView: 2, // 1 for rootView + 1 for the template view
rendererCreateElement: 1,
rendererSetProperty: 4,
});
});
it('should error if called without Δselect called first', () => {
const t = new TemplateFixture(createDiv, () => {}, 1, 1);
expect(() => {
t.update(() => { ΔpropertyInterpolate1('title', 'start', 'whatever', 'end'); });
}).toThrow();
expect(() => {
Δselect(0);
t.update(() => { ΔpropertyInterpolate1('title', 'start', 'whatever', 'end'); });
}).not.toThrow();
});
});
describe('ΔpropertyInterpolate2', () => {
it('should interpolate two values', () => {
// <div title="start: {{v0}}, 1: {{v1}}, end"></div>
const t = new TemplateFixture(createDiv, () => {}, 1, 2);
t.update(() => {
Δselect(0);
ΔpropertyInterpolate2('title', 'start: ', 0, ', 1: ', 1, ', end');
});
expect(t.html).toEqual('<div title="start: 0, 1: 1, end"></div>');
t.update(() => {
Δselect(0);
ΔpropertyInterpolate2('title', 'start: ', 'A', ', 1: ', 'B', ', end');
});
expect(t.html).toEqual('<div title="start: A, 1: B, end"></div>');
expect(ngDevMode).toHaveProperties({
firstTemplatePass: 1,
tNode: 2, // 1 for div, 1 for host element
tView: 2, // 1 for rootView + 1 for the template view
rendererCreateElement: 1,
rendererSetProperty: 2,
});
});
it('should chain', () => {
// <div title="start: {{v0}} 1: {{v1}}, end" accesskey="start: {{v0}}, 1: {{v1}},
// end"></div>
const t = new TemplateFixture(createDiv, () => {}, 1, 4);
t.update(() => {
Δselect(0);
ΔpropertyInterpolate2('title', 'start: ', 0, ', 1: ', 1, ', end')(
'accessKey', 'start: ', 0, ', 1: ', 1, ', end');
});
expect(t.html).toEqual(
'<div accesskey="start: 0, 1: 1, end" title="start: 0, 1: 1, end"></div>');
t.update(() => {
Δselect(0);
ΔpropertyInterpolate2('title', 'start: ', 'A', ', 1: ', 'B', ', end')(
'accessKey', 'start: ', 'A', ', 1: ', 'B', ', end');
});
expect(t.html).toEqual(
'<div accesskey="start: A, 1: B, end" title="start: A, 1: B, end"></div>');
expect(ngDevMode).toHaveProperties({
firstTemplatePass: 1,
tNode: 2, // 1 for div, 1 for host element
tView: 2, // 1 for rootView + 1 for the template view
rendererCreateElement: 1,
rendererSetProperty: 4,
});
});
it('should error if called without Δselect called first', () => {
const t = new TemplateFixture(createDiv, () => {}, 1, 2);
expect(() => {
t.update(() => { ΔpropertyInterpolate2('title', '', '', '', '', ''); });
}).toThrow();
expect(() => {
Δselect(0);
t.update(() => { ΔpropertyInterpolate2('title', '', '', '', '', ''); });
}).not.toThrow();
});
});
describe('ΔpropertyInterpolate3', () => {
it('should interpolate three values', () => {
// <div title="start: {{v0}}, 1: {{v1}}, 2: {{v2}}, end"></div>
const t = new TemplateFixture(createDiv, () => {}, 1, 3);
t.update(() => {
Δselect(0);
ΔpropertyInterpolate3('title', 'start: ', 0, ', 1: ', 1, ', 2: ', 2, ', end');
});
expect(t.html).toEqual('<div title="start: 0, 1: 1, 2: 2, end"></div>');
t.update(() => {
Δselect(0);
ΔpropertyInterpolate3('title', 'start: ', 'A', ', 1: ', 'B', ', 2: ', 'C', ', end');
});
expect(t.html).toEqual('<div title="start: A, 1: B, 2: C, end"></div>');
expect(ngDevMode).toHaveProperties({
firstTemplatePass: 1,
tNode: 2, // 1 for div, 1 for host element
tView: 2, // 1 for rootView + 1 for the template view
rendererCreateElement: 1,
rendererSetProperty: 2,
});
});
it('should chain', () => {
// <div title="start: {{v0}} 1: {{v1}} 2: {{v2}}, end" accesskey="start: {{v0}}, 1: {{v1}},
// 2: {{v2}}, end"></div>
const t = new TemplateFixture(createDiv, () => {}, 1, 6);
t.update(() => {
Δselect(0);
ΔpropertyInterpolate3('title', 'start: ', 0, ', 1: ', 1, ', 2: ', 2, ', end')(
'accessKey', 'start: ', 0, ', 1: ', 1, ', 2: ', 2, ', end');
});
expect(t.html).toEqual(
'<div accesskey="start: 0, 1: 1, 2: 2, end" title="start: 0, 1: 1, 2: 2, end"></div>');
t.update(() => {
Δselect(0);
ΔpropertyInterpolate3('title', 'start: ', 'A', ', 1: ', 'B', ', 2: ', 'C', ', end')(
'accessKey', 'start: ', 'A', ', 1: ', 'B', ', 2: ', 'C', ', end');
});
expect(t.html).toEqual(
'<div accesskey="start: A, 1: B, 2: C, end" title="start: A, 1: B, 2: C, end"></div>');
expect(ngDevMode).toHaveProperties({
firstTemplatePass: 1,
tNode: 2, // 1 for div, 1 for host element
tView: 2, // 1 for rootView + 1 for the template view
rendererCreateElement: 1,
rendererSetProperty: 4,
});
});
it('should error if called without Δselect called first', () => {
const t = new TemplateFixture(createDiv, () => {}, 1, 3);
expect(() => {
t.update(() => { ΔpropertyInterpolate3('title', '', '', '', '', '', '', ''); });
}).toThrow();
expect(() => {
Δselect(0);
t.update(() => { ΔpropertyInterpolate3('title', '', '', '', '', '', '', ''); });
}).not.toThrow();
});
});
describe('ΔpropertyInterpolate4', () => {
it('should interpolate four values', () => {
// <div title="start: {{v0}}, 1: {{v1}}, 2: {{v2}}, 3: {{v3}}, end"></div>
const t = new TemplateFixture(createDiv, () => {}, 1, 4);
t.update(() => {
Δselect(0);
ΔpropertyInterpolate4('title', 'start: ', 0, ', 1: ', 1, ', 2: ', 2, ', 3: ', 3, ', end');
});
expect(t.html).toEqual('<div title="start: 0, 1: 1, 2: 2, 3: 3, end"></div>');
t.update(() => {
Δselect(0);
ΔpropertyInterpolate4(
'title', 'start: ', 'A', ', 1: ', 'B', ', 2: ', 'C', ', 3: ', 'D', ', end');
});
expect(t.html).toEqual('<div title="start: A, 1: B, 2: C, 3: D, end"></div>');
expect(ngDevMode).toHaveProperties({
firstTemplatePass: 1,
tNode: 2, // 1 for div, 1 for host element
tView: 2, // 1 for rootView + 1 for the template view
rendererCreateElement: 1,
rendererSetProperty: 2,
});
});
it('should chain', () => {
// <div title="start: {{v0}} 1: {{v1}} 2: {{v2}} 3: {{v3}}, end" accesskey="start: {{v0}} 1:
// {{v1}} 2: {{v2}} 3: {{v3}}, end"></div>
const t = new TemplateFixture(createDiv, () => {}, 1, 8);
t.update(() => {
Δselect(0);
ΔpropertyInterpolate4('title', 'start: ', 0, ', 1: ', 1, ', 2: ', 2, ', 3: ', 3, ', end')(
'accessKey', 'start: ', 0, ', 1: ', 1, ', 2: ', 2, ', 3: ', 3, ', end');
});
expect(t.html).toEqual(
'<div accesskey="start: 0, 1: 1, 2: 2, 3: 3, end" title="start: 0, 1: 1, 2: 2, 3: 3, end"></div>');
t.update(() => {
Δselect(0);
ΔpropertyInterpolate4(
'title', 'start: ', 'A', ', 1: ', 'B', ', 2: ', 'C', ', 3: ', 'D', ', end')(
'accessKey', 'start: ', 'A', ', 1: ', 'B', ', 2: ', 'C', ', 3: ', 'D', ', end');
});
expect(t.html).toEqual(
'<div accesskey="start: A, 1: B, 2: C, 3: D, end" title="start: A, 1: B, 2: C, 3: D, end"></div>');
expect(ngDevMode).toHaveProperties({
firstTemplatePass: 1,
tNode: 2, // 1 for div, 1 for host element
tView: 2, // 1 for rootView + 1 for the template view
rendererCreateElement: 1,
rendererSetProperty: 4,
});
});
it('should error if called without Δselect called first', () => {
const t = new TemplateFixture(createDiv, () => {}, 1, 4);
expect(() => {
t.update(() => { ΔpropertyInterpolate4('title', '', '', '', '', '', '', '', '', ''); });
}).toThrow();
expect(() => {
Δselect(0);
t.update(() => { ΔpropertyInterpolate4('title', '', '', '', '', '', '', '', '', ''); });
}).not.toThrow();
});
});
describe('ΔpropertyInterpolate5', () => {
it('should interpolate five values', () => {
// <div title="start: {{v0}} 1: {{v1}} 2: {{v2}} 3: {{v3}} 4: {{v4}}, end"></div>
const t = new TemplateFixture(createDiv, () => {}, 1, 5);
t.update(() => {
Δselect(0);
ΔpropertyInterpolate5(
'title', 'start: ', 0, ', 1: ', 1, ', 2: ', 2, ', 3: ', 3, ', 4: ', 4, ', end');
});
expect(t.html).toEqual('<div title="start: 0, 1: 1, 2: 2, 3: 3, 4: 4, end"></div>');
t.update(() => {
Δselect(0);
ΔpropertyInterpolate5(
'title', 'start: ', 'A', ', 1: ', 'B', ', 2: ', 'C', ', 3: ', 'D', ', 4: ', 'E',
', end');
});
expect(t.html).toEqual('<div title="start: A, 1: B, 2: C, 3: D, 4: E, end"></div>');
expect(ngDevMode).toHaveProperties({
firstTemplatePass: 1,
tNode: 2, // 1 for div, 1 for host element
tView: 2, // 1 for rootView + 1 for the template view
rendererCreateElement: 1,
rendererSetProperty: 2,
});
});
it('should chain', () => {
// <div title="start: {{v0}} 1: {{v1}} 2: {{v2}} 3: {{v3}} 4: {{v4}}, end" accesskey="start:
// {{v0}} 1: {{v1}} 2: {{v2}} 3: {{v3}} 4: {{v4}}, end"></div>
const t = new TemplateFixture(createDiv, () => {}, 1, 10);
t.update(() => {
Δselect(0);
ΔpropertyInterpolate5(
'title', 'start: ', 0, ', 1: ', 1, ', 2: ', 2, ', 3: ', 3, ', 4: ', 4, ', end')(
'accessKey', 'start: ', 0, ', 1: ', 1, ', 2: ', 2, ', 3: ', 3, ', 4: ', 4, ', end');
});
expect(t.html).toEqual(
'<div accesskey="start: 0, 1: 1, 2: 2, 3: 3, 4: 4, end" title="start: 0, 1: 1, 2: 2, 3: 3, 4: 4, end"></div>');
t.update(() => {
Δselect(0);
ΔpropertyInterpolate5(
'title', 'start: ', 'A', ', 1: ', 'B', ', 2: ', 'C', ', 3: ', 'D', ', 4: ', 'E',
', end')(
'accessKey', 'start: ', 'A', ', 1: ', 'B', ', 2: ', 'C', ', 3: ', 'D', ', 4: ', 'E',
', end');
});
expect(t.html).toEqual(
'<div accesskey="start: A, 1: B, 2: C, 3: D, 4: E, end" title="start: A, 1: B, 2: C, 3: D, 4: E, end"></div>');
expect(ngDevMode).toHaveProperties({
firstTemplatePass: 1,
tNode: 2, // 1 for div, 1 for host element
tView: 2, // 1 for rootView + 1 for the template view
rendererCreateElement: 1,
rendererSetProperty: 4,
});
});
it('should error if called without Δselect called first', () => {
const t = new TemplateFixture(createDiv, () => {}, 1, 5);
expect(() => {
t.update(() => {
ΔpropertyInterpolate5('title', '', '', '', '', '', '', '', '', '', '', '');
});
}).toThrow();
expect(() => {
Δselect(0);
t.update(() => {
ΔpropertyInterpolate5('title', '', '', '', '', '', '', '', '', '', '', '');
});
}).not.toThrow();
});
});
describe('ΔpropertyInterpolate6', () => {
it('should interpolate six values', () => {
// <div title="start: {{v0}} 1: {{v1}} 2: {{v2}} 3: {{v3}} 4: {{v4}}, 5: {{v5}}, end"></div>
const t = new TemplateFixture(createDiv, () => {}, 1, 6);
t.update(() => {
Δselect(0);
ΔpropertyInterpolate6(
'title', 'start: ', 0, ', 1: ', 1, ', 2: ', 2, ', 3: ', 3, ', 4: ', 4, ', 5: ', 5,
', end');
});
expect(t.html).toEqual('<div title="start: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, end"></div>');
t.update(() => {
Δselect(0);
ΔpropertyInterpolate6(
'title', 'start: ', 'A', ', 1: ', 'B', ', 2: ', 'C', ', 3: ', 'D', ', 4: ', 'E',
', 5: ', 'F', ', end');
});
expect(t.html).toEqual('<div title="start: A, 1: B, 2: C, 3: D, 4: E, 5: F, end"></div>');
expect(ngDevMode).toHaveProperties({
firstTemplatePass: 1,
tNode: 2, // 1 for div, 1 for host element
tView: 2, // 1 for rootView + 1 for the template view
rendererCreateElement: 1,
rendererSetProperty: 2,
});
});
it('should chain', () => {
// <div title="start: {{v0}} 1: {{v1}} 2: {{v2}} 3: {{v3}} 4: {{v4}}, 5: {{v5}}, end"
// accesskey="start: {{v0}} 1: {{v1}} 2: {{v2}} 3: {{v3}} 4: {{v4}}, 5: {{v5}}, end"></div>
const t = new TemplateFixture(createDiv, () => {}, 1, 12);
t.update(() => {
Δselect(0);
ΔpropertyInterpolate6(
'title', 'start: ', 0, ', 1: ', 1, ', 2: ', 2, ', 3: ', 3, ', 4: ', 4, ', 5: ', 5,
', end')(
'accessKey', 'start: ', 0, ', 1: ', 1, ', 2: ', 2, ', 3: ', 3, ', 4: ', 4, ', 5: ', 5,
', end');
});
expect(t.html).toEqual(
'<div accesskey="start: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, end" title="start: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, end"></div>');
t.update(() => {
Δselect(0);
ΔpropertyInterpolate6(
'title', 'start: ', 'A', ', 1: ', 'B', ', 2: ', 'C', ', 3: ', 'D', ', 4: ', 'E',
', 5: ', 'F', ', end')(
'accessKey', 'start: ', 'A', ', 1: ', 'B', ', 2: ', 'C', ', 3: ', 'D', ', 4: ', 'E',
', 5: ', 'F', ', end');
});
expect(t.html).toEqual(
'<div accesskey="start: A, 1: B, 2: C, 3: D, 4: E, 5: F, end" title="start: A, 1: B, 2: C, 3: D, 4: E, 5: F, end"></div>');
expect(ngDevMode).toHaveProperties({
firstTemplatePass: 1,
tNode: 2, // 1 for div, 1 for host element
tView: 2, // 1 for rootView + 1 for the template view
rendererCreateElement: 1,
rendererSetProperty: 4,
});
});
it('should error if called without Δselect called first', () => {
const t = new TemplateFixture(createDiv, () => {}, 1, 6);
expect(() => {
t.update(() => {
ΔpropertyInterpolate6('title', '', '', '', '', '', '', '', '', '', '', '', '', '');
});
}).toThrow();
expect(() => {
Δselect(0);
t.update(() => {
ΔpropertyInterpolate6('title', '', '', '', '', '', '', '', '', '', '', '', '', '');
});
}).not.toThrow();
});
});
describe('ΔpropertyInterpolate7', () => {
it('should interpolate seven values', () => {
// <div title="start: {{v0}} 1: {{v1}} 2: {{v2}} 3: {{v3}} 4: {{v4}} 5: {{v5}}, 6: {{v6}},
// end"></div>
const t = new TemplateFixture(createDiv, () => {}, 1, 7);
t.update(() => {
Δselect(0);
ΔpropertyInterpolate7(
'title', 'start: ', 0, ', 1: ', 1, ', 2: ', 2, ', 3: ', 3, ', 4: ', 4, ', 5: ', 5,
', 6: ', 6, ', end');
});
expect(t.html).toEqual(
'<div title="start: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, end"></div>');
t.update(() => {
Δselect(0);
ΔpropertyInterpolate7(
'title', 'start: ', 'A', ', 1: ', 'B', ', 2: ', 'C', ', 3: ', 'D', ', 4: ', 'E',
', 5: ', 'F', ', 6: ', 'G', ', end');
});
expect(t.html).toEqual(
'<div title="start: A, 1: B, 2: C, 3: D, 4: E, 5: F, 6: G, end"></div>');
expect(ngDevMode).toHaveProperties({
firstTemplatePass: 1,
tNode: 2, // 1 for div, 1 for host element
tView: 2, // 1 for rootView + 1 for the template view
rendererCreateElement: 1,
rendererSetProperty: 2,
});
});
it('should chain', () => {
// <div title="start: {{v0}} 1: {{v1}} 2: {{v2}} 3: {{v3}} 4: {{v4}} 5: {{v5}}, 6: {{v6}},
// 7: {{v7}} end" accesskey="start: {{v0}} 1: {{v1}} 2: {{v2}} 3: {{v3}} 4: {{v4}} 5:
// {{v5}}, 6: {{v6}}, end"></div>
const t = new TemplateFixture(createDiv, () => {}, 1, 14);
t.update(() => {
Δselect(0);
ΔpropertyInterpolate7(
'title', 'start: ', 0, ', 1: ', 1, ', 2: ', 2, ', 3: ', 3, ', 4: ', 4, ', 5: ', 5,
', 6: ', 6, ', end')(
'accessKey', 'start: ', 0, ', 1: ', 1, ', 2: ', 2, ', 3: ', 3, ', 4: ', 4, ', 5: ', 5,
', 6: ', 6, ', end');
});
expect(t.html).toEqual(
'<div accesskey="start: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, end" title="start: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, end"></div>');
t.update(() => {
Δselect(0);
ΔpropertyInterpolate7(
'title', 'start: ', 'A', ', 1: ', 'B', ', 2: ', 'C', ', 3: ', 'D', ', 4: ', 'E',
', 5: ', 'F', ', 6: ', 'G', ', end')(
'accessKey', 'start: ', 'A', ', 1: ', 'B', ', 2: ', 'C', ', 3: ', 'D', ', 4: ', 'E',
', 5: ', 'F', ', 6: ', 'G', ', end');
});
expect(t.html).toEqual(
'<div accesskey="start: A, 1: B, 2: C, 3: D, 4: E, 5: F, 6: G, end" title="start: A, 1: B, 2: C, 3: D, 4: E, 5: F, 6: G, end"></div>');
expect(ngDevMode).toHaveProperties({
firstTemplatePass: 1,
tNode: 2, // 1 for div, 1 for host element
tView: 2, // 1 for rootView + 1 for the template view
rendererCreateElement: 1,
rendererSetProperty: 4,
});
});
it('should error if called without Δselect called first', () => {
const t = new TemplateFixture(createDiv, () => {}, 1, 7);
expect(() => {
t.update(() => {
ΔpropertyInterpolate7(
'title', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '');
});
}).toThrow();
expect(() => {
Δselect(0);
t.update(() => {
ΔpropertyInterpolate7(
'title', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '');
});
}).not.toThrow();
});
});
describe('ΔpropertyInterpolate8', () => {
it('should interpolate eight values', () => {
// <div title="start: {{v0}} 1: {{v1}} 2: {{v2}} 3: {{v3}} 4: {{v4}} 5: {{v5}}, 6: {{v6}},
// 7: {{v7}} end"></div>
const t = new TemplateFixture(createDiv, () => {}, 1, 8);
t.update(() => {
Δselect(0);
ΔpropertyInterpolate8(
'title', 'start: ', 0, ', 1: ', 1, ', 2: ', 2, ', 3: ', 3, ', 4: ', 4, ', 5: ', 5,
', 6: ', 6, ', 7: ', 7, ', end');
});
expect(t.html).toEqual(
'<div title="start: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 7, end"></div>');
t.update(() => {
Δselect(0);
ΔpropertyInterpolate8(
'title', 'start: ', 'A', ', 1: ', 'B', ', 2: ', 'C', ', 3: ', 'D', ', 4: ', 'E',
', 5: ', 'F', ', 6: ', 'G', ', 7: ', 'H', ', end');
});
expect(t.html).toEqual(
'<div title="start: A, 1: B, 2: C, 3: D, 4: E, 5: F, 6: G, 7: H, end"></div>');
expect(ngDevMode).toHaveProperties({
firstTemplatePass: 1,
tNode: 2, // 1 for div, 1 for host element
tView: 2, // 1 for rootView + 1 for the template view
rendererCreateElement: 1,
rendererSetProperty: 2,
});
});
it('should chain', () => {
// <div title="start: {{v0}} 1: {{v1}} 2: {{v2}} 3: {{v3}} 4: {{v4}} 5: {{v5}}, 6: {{v6}},
// 7: {{v7}} end" accesskey="start: {{v0}} 1: {{v1}} 2: {{v2}} 3: {{v3}} 4: {{v4}} 5:
// {{v5}}, 6: {{v6}}, 7: {{v7}} end"></div>
const t = new TemplateFixture(createDiv, () => {}, 1, 16);
t.update(() => {
Δselect(0);
ΔpropertyInterpolate8(
'title', 'start: ', 0, ', 1: ', 1, ', 2: ', 2, ', 3: ', 3, ', 4: ', 4, ', 5: ', 5,
', 6: ', 6, ', 7: ', 7, ', end')(
'accessKey', 'start: ', 0, ', 1: ', 1, ', 2: ', 2, ', 3: ', 3, ', 4: ', 4, ', 5: ', 5,
', 6: ', 6, ', 7: ', 7, ', end');
});
expect(t.html).toEqual(
'<div accesskey="start: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 7, end" title="start: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 7, end"></div>');
t.update(() => {
Δselect(0);
ΔpropertyInterpolate8(
'title', 'start: ', 'A', ', 1: ', 'B', ', 2: ', 'C', ', 3: ', 'D', ', 4: ', 'E',
', 5: ', 'F', ', 6: ', 'G', ', 7: ', 'H', ', end')(
'accessKey', 'start: ', 'A', ', 1: ', 'B', ', 2: ', 'C', ', 3: ', 'D', ', 4: ', 'E',
', 5: ', 'F', ', 6: ', 'G', ', 7: ', 'H', ', end');
});
expect(t.html).toEqual(
'<div accesskey="start: A, 1: B, 2: C, 3: D, 4: E, 5: F, 6: G, 7: H, end" title="start: A, 1: B, 2: C, 3: D, 4: E, 5: F, 6: G, 7: H, end"></div>');
expect(ngDevMode).toHaveProperties({
firstTemplatePass: 1,
tNode: 2, // 1 for div, 1 for host element
tView: 2, // 1 for rootView + 1 for the template view
rendererCreateElement: 1,
rendererSetProperty: 4,
});
});
it('should error if called without Δselect called first', () => {
const t = new TemplateFixture(createDiv, () => {}, 1, 8);
expect(() => {
t.update(() => {
ΔpropertyInterpolate8(
'title', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '');
});
}).toThrow();
expect(() => {
Δselect(0);
t.update(() => {
ΔpropertyInterpolate8(
'title', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '');
});
}).not.toThrow();
});
});
describe('ΔpropertyInterpolateV', () => {
it('should interpolate eight or more values', () => {
// <div title="start: {{v0}} 1: {{v1}} 2: {{v2}} 3: {{v3}} 4: {{v4}} 5: {{v5}}, 6: {{v6}},
// 7: {{v7}}, 8: {{v8}} end"></div>
const t = new TemplateFixture(createDiv, () => {}, 1, 9);
t.update(() => {
Δselect(0);
ΔpropertyInterpolateV('title', [
'start: ', 0, ', 1: ', 1, ', 2: ', 2, ', 3: ', 3, ', 4: ', 4, ', 5: ', 5, ', 6: ', 6,
', 7: ', 7, ', 8: ', 8, ', end'
]);
});
expect(t.html).toEqual(
'<div title="start: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 7, 8: 8, end"></div>');
t.update(() => {
Δselect(0);
ΔpropertyInterpolateV('title', [
'start: ', 'A', ', 1: ', 'B', ', 2: ', 'C', ', 3: ', 'D', ', 4: ', 'E', ', 5: ', 'F',
', 6: ', 'G', ', 7: ', 'H', ', 8: ', 'I', ', end'
]);
});
expect(t.html).toEqual(
'<div title="start: A, 1: B, 2: C, 3: D, 4: E, 5: F, 6: G, 7: H, 8: I, end"></div>');
expect(ngDevMode).toHaveProperties({
firstTemplatePass: 1,
tNode: 2, // 1 for div, 1 for host element
tView: 2, // 1 for rootView + 1 for the template view
rendererCreateElement: 1,
rendererSetProperty: 2,
});
});
it('should chain', () => {
// <div title="start: {{v0}} 1: {{v1}} 2: {{v2}} 3: {{v3}} 4: {{v4}} 5: {{v5}}, 6: {{v6}},
// 7: {{v7}} end" accesskey="start: {{v0}} 1: {{v1}} 2: {{v2}} 3: {{v3}} 4: {{v4}} 5:
// {{v5}}, 6: {{v6}}, 7: {{v7}}, 8: {{v8}} end"></div>
const t = new TemplateFixture(createDiv, () => {}, 1, 18);
t.update(() => {
Δselect(0);
ΔpropertyInterpolateV(
'title',
[
'start: ', 0, ', 1: ', 1, ', 2: ', 2, ', 3: ', 3, ', 4: ', 4, ', 5: ', 5, ', 6: ',
6, ', 7: ', 7, ', 8: ', 8, ', end'
])(
'accessKey', [
'start: ', 0, ', 1: ', 1, ', 2: ', 2, ', 3: ', 3, ', 4: ', 4, ', 5: ', 5, ', 6: ',
6, ', 7: ', 7, ', 8: ', 8, ', end'
]);
});
expect(t.html).toEqual(
'<div accesskey="start: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 7, 8: 8, end" title="start: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 7, 8: 8, end"></div>');
t.update(() => {
Δselect(0);
ΔpropertyInterpolateV(
'title',
[
'start: ', 'A', ', 1: ', 'B', ', 2: ', 'C', ', 3: ', 'D', ', 4: ', 'E', ', 5: ',
'F', ', 6: ', 'G', ', 7: ', 'H', ', 8: ', 'I', ', end'
])(
'accessKey', [
'start: ', 'A', ', 1: ', 'B', ', 2: ', 'C', ', 3: ', 'D', ', 4: ', 'E', ', 5: ',
'F', ', 6: ', 'G', ', 7: ', 'H', ', 8: ', 'I', ', end'
]);
});
expect(t.html).toEqual(
'<div accesskey="start: A, 1: B, 2: C, 3: D, 4: E, 5: F, 6: G, 7: H, 8: I, end" title="start: A, 1: B, 2: C, 3: D, 4: E, 5: F, 6: G, 7: H, 8: I, end"></div>');
expect(ngDevMode).toHaveProperties({
firstTemplatePass: 1,
tNode: 2, // 1 for div, 1 for host element
tView: 2, // 1 for rootView + 1 for the template view
rendererCreateElement: 1,
rendererSetProperty: 4,
});
});
it('should error if called without Δselect called first', () => {
const t = new TemplateFixture(createDiv, () => {}, 1, 9);
expect(() => {
t.update(() => {
ΔpropertyInterpolateV(
'title',
['', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '']);
});
}).toThrow();
expect(() => {
Δselect(0);
t.update(() => {
ΔpropertyInterpolateV(
'title',
['', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '']);
});
}).not.toThrow();
});
});
});
describe('elementProperty', () => {
it('should use sanitizer function when available', () => {
const t = new TemplateFixture(createDiv, () => {}, 1);