From 78adcfe0ee07b161d1708fe20f5076bb953fdb25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matias=20Niemel=C3=A4?= Date: Fri, 1 Mar 2019 14:15:11 -0800 Subject: [PATCH] fix(ivy): ensure static styling is properly inherited into child components (#29015) Angular supports having a component extend off of a parent component. When this happens, all annotation-level data is inherited including styles and classes. Up until now, Ivy only paid attention to static styling values on the parent component and not the child component. This patch ensures that both the parent's component and child component's styling data is merged and rendered accordingly. Jira Issue: FW-1081 PR Close #29015 --- packages/core/src/render3/instructions.ts | 121 ++--- .../core/src/render3/interfaces/styling.ts | 44 +- .../styling/class_and_style_bindings.ts | 156 ++++--- packages/core/src/render3/styling/util.ts | 49 +- packages/core/src/render3/util/attrs_utils.ts | 107 +++++ .../core/test/acceptance/integration_spec.ts | 47 +- packages/core/test/acceptance/styling_spec.ts | 99 ++++ .../cyclic_import/bundle.golden_symbols.json | 21 +- .../bundling/todo/bundle.golden_symbols.json | 15 +- .../core/test/render3/integration_spec.ts | 8 +- .../render3/node_selector_matcher_spec.ts | 2 +- .../styling/class_and_style_bindings_spec.ts | 429 ++++++++++-------- .../angular_material_test_blocklist.js | 152 ++----- 13 files changed, 721 insertions(+), 529 deletions(-) create mode 100644 packages/core/src/render3/util/attrs_utils.ts create mode 100644 packages/core/test/acceptance/styling_spec.ts diff --git a/packages/core/src/render3/instructions.ts b/packages/core/src/render3/instructions.ts index 5e34be0df7..a09a3d4d58 100644 --- a/packages/core/src/render3/instructions.ts +++ b/packages/core/src/render3/instructions.ts @@ -30,9 +30,9 @@ import {ComponentDef, ComponentTemplate, DirectiveDef, DirectiveDefListOrFactory import {INJECTOR_BLOOM_PARENT_SIZE, NodeInjectorFactory} from './interfaces/injector'; import {AttributeMarker, InitialInputData, InitialInputs, LocalRefExtractor, PropertyAliasValue, PropertyAliases, TAttributes, TContainerNode, TElementContainerNode, TElementNode, TIcuContainerNode, TNode, TNodeFlags, TNodeProviderIndexes, TNodeType, TProjectionNode, TViewNode} from './interfaces/node'; import {PlayerFactory} from './interfaces/player'; -import {CssSelectorList, NG_PROJECT_AS_ATTR_NAME} from './interfaces/projection'; +import {CssSelectorList} from './interfaces/projection'; import {LQueries} from './interfaces/query'; -import {GlobalTargetResolver, ProceduralRenderer3, RComment, RElement, RText, Renderer3, RendererFactory3, isProceduralRenderer} from './interfaces/renderer'; +import {GlobalTargetResolver, 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, OpaqueViewState, PARENT, QUERIES, RENDERER, RENDERER_FACTORY, RootContext, RootContextFlags, SANITIZER, TData, TVIEW, TView, T_HOST} from './interfaces/view'; @@ -43,8 +43,9 @@ import {applyOnCreateInstructions} from './node_util'; import {decreaseElementDepthCount, enterView, getBindingsEnabled, getCheckNoChangesMode, getContextLView, getCurrentDirectiveDef, getElementDepthCount, getIsParent, getLView, getPreviousOrParentTNode, increaseElementDepthCount, isCreationMode, leaveView, nextContextImpl, resetComponentState, setBindingRoot, setCheckNoChangesMode, setCurrentDirectiveDef, setCurrentQueryIndex, setIsParent, setPreviousOrParentTNode} from './state'; import {getInitialClassNameValue, getInitialStyleStringValue, initializeStaticContext as initializeStaticStylingContext, patchContextWithStaticAttrs, renderInitialClasses, renderInitialStyles, renderStyling, updateClassProp as updateElementClassProp, updateContextWithBindings, updateStyleProp as updateElementStyleProp, updateStylingMap} from './styling/class_and_style_bindings'; import {BoundPlayerFactory} from './styling/player_factory'; -import {ANIMATION_PROP_PREFIX, allocateDirectiveIntoContext, createEmptyStylingContext, forceClassesAsString, forceStylesAsString, getStylingContext, hasClassInput, hasStyleInput, hasStyling, isAnimationProp} from './styling/util'; +import {ANIMATION_PROP_PREFIX, allocateDirectiveIntoContext, createEmptyStylingContext, forceClassesAsString, forceStylesAsString, getStylingContext, hasClassInput, hasStyleInput, isAnimationProp} from './styling/util'; import {NO_CHANGE} from './tokens'; +import {attrsStylingIndexOf, setUpAttributes} from './util/attrs_utils'; import {INTERPOLATION_DELIMITER, renderStringify} from './util/misc_utils'; import {findComponentView, getLViewParent, getRootContext, getRootView} from './util/view_traversal_utils'; import {getComponentViewByIndex, getNativeByIndex, getNativeByTNode, getTNode, isComponent, isComponentDef, isContentQueryHost, isRootView, loadInternal, readPatchedLView, unwrapRNode, viewAttachedToChangeDetector} from './util/view_utils'; @@ -620,15 +621,21 @@ export function elementStart( const tNode = createNodeAtIndex(index, TNodeType.Element, native !, name, attrs || null); if (attrs) { + const lastAttrIndex = setUpAttributes(native, attrs); + // it's important to only prepare styling-related datastructures once for a given // tNode and not each time an element is created. Also, the styling code is designed - // to be patched and constructed at various points, but only up until the first element - // is created. Then the styling context is locked and can only be instantiated for each - // successive element that is created. - if (tView.firstTemplatePass && !tNode.stylingTemplate && hasStyling(attrs)) { - tNode.stylingTemplate = initializeStaticStylingContext(attrs); + // to be patched and constructed at various points, but only up until the styling + // template is first allocated (which happens when the very first style/class binding + // value is evaluated). When the template is allocated (when it turns into a context) + // then the styling template is locked and cannot be further extended (it can only be + // instantiated into a context per element) + if (tView.firstTemplatePass && !tNode.stylingTemplate) { + const stylingAttrsStartIndex = attrsStylingIndexOf(attrs, lastAttrIndex); + if (stylingAttrsStartIndex >= 0) { + tNode.stylingTemplate = initializeStaticStylingContext(attrs, stylingAttrsStartIndex); + } } - setUpAttributes(native, attrs); } appendChild(native, tNode, lView); @@ -825,87 +832,6 @@ function createViewBlueprint(bindingStartIndex: number, initialViewLength: numbe return blueprint; } -/** - * Assigns all attribute values to the provided element via the inferred renderer. - * - * This function accepts two forms of attribute entries: - * - * default: (key, value): - * attrs = [key1, value1, key2, value2] - * - * namespaced: (NAMESPACE_MARKER, uri, name, value) - * attrs = [NAMESPACE_MARKER, uri, name, value, NAMESPACE_MARKER, uri, name, value] - * - * The `attrs` array can contain a mix of both the default and namespaced entries. - * The "default" values are set without a marker, but if the function comes across - * a marker value then it will attempt to set a namespaced value. If the marker is - * not of a namespaced value then the function will quit and return the index value - * where it stopped during the iteration of the attrs array. - * - * See [AttributeMarker] to understand what the namespace marker value is. - * - * Note that this instruction does not support assigning style and class values to - * an element. See `elementStart` and `elementHostAttrs` to learn how styling values - * are applied to an element. - * - * @param native The element that the attributes will be assigned to - * @param attrs The attribute array of values that will be assigned to the element - * @returns the index value that was last accessed in the attributes array - */ -function setUpAttributes(native: RElement, attrs: TAttributes): number { - const renderer = getLView()[RENDERER]; - const isProc = isProceduralRenderer(renderer); - - let i = 0; - while (i < attrs.length) { - const value = attrs[i]; - if (typeof value === 'number') { - // only namespaces are supported. Other value types (such as style/class - // entries) are not supported in this function. - if (value !== AttributeMarker.NamespaceURI) { - break; - } - - // we just landed on the marker value ... therefore - // we should skip to the next entry - i++; - - const namespaceURI = attrs[i++] as string; - const attrName = attrs[i++] as string; - const attrVal = attrs[i++] as string; - ngDevMode && ngDevMode.rendererSetAttribute++; - isProc ? - (renderer as ProceduralRenderer3).setAttribute(native, attrName, attrVal, namespaceURI) : - native.setAttributeNS(namespaceURI, attrName, attrVal); - } else { - /// attrName is string; - const attrName = value as string; - const attrVal = attrs[++i]; - if (attrName !== NG_PROJECT_AS_ATTR_NAME) { - // Standard attributes - ngDevMode && ngDevMode.rendererSetAttribute++; - if (isAnimationProp(attrName)) { - if (isProc) { - (renderer as ProceduralRenderer3).setProperty(native, attrName, attrVal); - } - } else { - isProc ? - (renderer as ProceduralRenderer3) - .setAttribute(native, attrName as string, attrVal as string) : - native.setAttribute(attrName as string, attrVal as string); - } - } - i++; - } - } - - // another piece of code may iterate over the same attributes array. Therefore - // it may be helpful to return the exact spot where the attributes array exited - // whether by running into an unsupported marker or if all the static values were - // iterated over. - return i; -} - export function createError(text: string, token: any) { return new Error(`Renderer: ${text} [${renderStringify(token)}]`); } @@ -1556,13 +1482,18 @@ function initElementStyling( */ export function elementHostAttrs(directive: any, attrs: TAttributes) { const tNode = getPreviousOrParentTNode(); - if (!tNode.stylingTemplate) { - tNode.stylingTemplate = initializeStaticStylingContext(attrs); - } const lView = getLView(); const native = getNativeByTNode(tNode, lView) as RElement; - const i = setUpAttributes(native, attrs); - patchContextWithStaticAttrs(tNode.stylingTemplate, attrs, i, directive); + const lastAttrIndex = setUpAttributes(native, attrs); + const stylingAttrsStartIndex = attrsStylingIndexOf(attrs, lastAttrIndex); + if (stylingAttrsStartIndex >= 0) { + if (tNode.stylingTemplate) { + patchContextWithStaticAttrs(tNode.stylingTemplate, attrs, stylingAttrsStartIndex, directive); + } else { + tNode.stylingTemplate = + initializeStaticStylingContext(attrs, stylingAttrsStartIndex, directive); + } + } } /** diff --git a/packages/core/src/render3/interfaces/styling.ts b/packages/core/src/render3/interfaces/styling.ts index 1ac69ee063..90ca68100f 100644 --- a/packages/core/src/render3/interfaces/styling.ts +++ b/packages/core/src/render3/interfaces/styling.ts @@ -323,9 +323,9 @@ export interface StylingContext extends * * See [InitialStylingValuesIndex] for a breakdown of how all this works. */ -export interface InitialStylingValues extends Array { +export interface InitialStylingValues extends Array { [InitialStylingValuesIndex.DefaultNullValuePosition]: null; - [InitialStylingValuesIndex.InitialClassesStringPosition]: string|null; + [InitialStylingValuesIndex.CachedStringValuePosition]: string|null; } /** @@ -432,12 +432,48 @@ export interface InitialStylingValues extends Array { * ``` */ export const enum InitialStylingValuesIndex { + /** + * The first value is always `null` so that `styles[0] == null` for unassigned values + */ DefaultNullValuePosition = 0, - InitialClassesStringPosition = 1, + + /** + * Used for non-styling code to examine what the style or className string is: + * styles: ['width', '100px', 0, 'opacity', null, 0, 'height', '200px', 0] + * => initialStyles[CachedStringValuePosition] = 'width:100px;height:200px'; + * classes: ['foo', true, 0, 'bar', false, 0, 'baz', true, 0] + * => initialClasses[CachedStringValuePosition] = 'foo bar'; + * + * Note that this value is `null` by default and it will only be populated + * once `getInitialStyleStringValue` or `getInitialClassNameValue` is executed. + */ + CachedStringValuePosition = 1, + + /** + * Where the style or class values start in the tuple + */ KeyValueStartPosition = 2, + + /** + * The offset value (index + offset) for the property value for each style/class entry + */ PropOffset = 0, + + /** + * The offset value (index + offset) for the style/class value for each style/class entry + */ ValueOffset = 1, - Size = 2 + + /** + * The offset value (index + offset) for the style/class directive owner for each style/class + entry + */ + DirectiveOwnerOffset = 2, + + /** + * The total size for each style/class entry (prop + value + directiveOwner) + */ + Size = 3 } /** diff --git a/packages/core/src/render3/styling/class_and_style_bindings.ts b/packages/core/src/render3/styling/class_and_style_bindings.ts index d8f43405d0..1fec18b5d5 100644 --- a/packages/core/src/render3/styling/class_and_style_bindings.ts +++ b/packages/core/src/render3/styling/class_and_style_bindings.ts @@ -41,29 +41,10 @@ import {addPlayerInternal, allocPlayerContext, allocateDirectiveIntoContext, cre /** * Creates a new StylingContext an fills it with the provided static styling attribute values. */ -export function initializeStaticContext(attrs: TAttributes): StylingContext { +export function initializeStaticContext( + attrs: TAttributes, stylingStartIndex: number, directiveRef?: any | null): StylingContext { const context = createEmptyStylingContext(); - const initialClasses: InitialStylingValues = context[StylingIndex.InitialClassValuesPosition] = - [null, null]; - const initialStyles: InitialStylingValues = context[StylingIndex.InitialStyleValuesPosition] = - [null, null]; - - // The attributes array has marker values (numbers) indicating what the subsequent - // values represent. When we encounter a number, we set the mode to that type of attribute. - let mode = -1; - for (let i = 0; i < attrs.length; i++) { - const attr = attrs[i]; - if (typeof attr == 'number') { - mode = attr; - } else if (mode === AttributeMarker.Styles) { - initialStyles.push(attr as string, attrs[++i] as string); - } else if (mode === AttributeMarker.Classes) { - initialClasses.push(attr as string, true); - } else if (mode === AttributeMarker.SelectOnly) { - break; - } - } - + patchContextWithStaticAttrs(context, attrs, stylingStartIndex, directiveRef); return context; } @@ -74,34 +55,41 @@ export function initializeStaticContext(attrs: TAttributes): StylingContext { * @param context the existing styling context * @param attrs an array of new static styling attributes that will be * assigned to the context + * @param attrsStylingStartIndex what index to start iterating within the + * provided `attrs` array to start reading style and class values * @param directiveRef the directive instance with which static data is associated with. */ export function patchContextWithStaticAttrs( - context: StylingContext, attrs: TAttributes, startingIndex: number, directiveRef: any): void { + context: StylingContext, attrs: TAttributes, attrsStylingStartIndex: number, + directiveRef?: any | null): void { + // this means the context has already been set and instantiated + if (context[StylingIndex.MasterFlagPosition] & StylingFlags.BindingAllocationLocked) return; + // If the styling context has already been patched with the given directive's bindings, // then there is no point in doing it again. The reason why this may happen (the directive // styling being patched twice) is because the `stylingBinding` function is called each time // an element is created (both within a template function and within directive host bindings). const directives = context[StylingIndex.DirectiveRegistryPosition]; - if (getDirectiveRegistryValuesIndexOf(directives, directiveRef) == -1) { + let detectedIndex = getDirectiveRegistryValuesIndexOf(directives, directiveRef || null); + if (detectedIndex === -1) { // this is a new directive which we have not seen yet. - allocateDirectiveIntoContext(context, directiveRef); + detectedIndex = allocateDirectiveIntoContext(context, directiveRef); + } + const directiveIndex = detectedIndex / DirectiveRegistryValuesIndex.Size; - let initialClasses: InitialStylingValues|null = null; - let initialStyles: InitialStylingValues|null = null; - - let mode = -1; - for (let i = startingIndex; i < attrs.length; i++) { - const attr = attrs[i]; - if (typeof attr == 'number') { - mode = attr; - } else if (mode == AttributeMarker.Classes) { - initialClasses = initialClasses || context[StylingIndex.InitialClassValuesPosition]; - patchInitialStylingValue(initialClasses, attr, true); - } else if (mode == AttributeMarker.Styles) { - initialStyles = initialStyles || context[StylingIndex.InitialStyleValuesPosition]; - patchInitialStylingValue(initialStyles, attr, attrs[++i]); - } + let initialClasses: InitialStylingValues|null = null; + let initialStyles: InitialStylingValues|null = null; + let mode = -1; + for (let i = attrsStylingStartIndex; i < attrs.length; i++) { + const attr = attrs[i]; + if (typeof attr == 'number') { + mode = attr; + } else if (mode == AttributeMarker.Classes) { + initialClasses = initialClasses || context[StylingIndex.InitialClassValuesPosition]; + patchInitialStylingValue(initialClasses, attr, true, directiveIndex); + } else if (mode == AttributeMarker.Styles) { + initialStyles = initialStyles || context[StylingIndex.InitialStyleValuesPosition]; + patchInitialStylingValue(initialStyles, attr, attrs[++i], directiveIndex); } } } @@ -110,29 +98,44 @@ export function patchContextWithStaticAttrs( * Designed to add a style or class value into the existing set of initial styles. * * The function will search and figure out if a style/class value is already present - * within the provided initial styling array. If and when a style/class value is not - * present (or if it's value is falsy) then it will be inserted/updated in the list - * of initial styling values. + * within the provided initial styling array. If and when a style/class value is + * present (allocated) then the code below will set the new value depending on the + * following cases: + * + * 1) if the existing value is falsy (this happens because a `[class.prop]` or + * `[style.prop]` binding was set, but there wasn't a matching static style + * or class present on the context) + * 2) if the value was set already by the template, component or directive, but the + * new value is set on a higher level (i.e. a sub component which extends a parent + * component sets its value after the parent has already set the same one) + * 3) if the same directive provides a new set of styling values to set + * + * @param initialStyling the initial styling array where the new styling entry will be added to + * @param prop the property value of the new entry (e.g. `width` (styles) or `foo` (classes)) + * @param value the styling value of the new entry (e.g. `absolute` (styles) or `true` (classes)) + * @param directiveOwnerIndex the directive owner index value of the styling source responsible + * for these styles (see `interfaces/styling.ts#directives` for more info) */ function patchInitialStylingValue( - initialStyling: InitialStylingValues, prop: string, value: any): void { - // Even values are keys; Odd numbers are values; Search keys only - for (let i = InitialStylingValuesIndex.KeyValueStartPosition; i < initialStyling.length;) { - const key = initialStyling[i]; + initialStyling: InitialStylingValues, prop: string, value: any, + directiveOwnerIndex: number): void { + for (let i = InitialStylingValuesIndex.KeyValueStartPosition; i < initialStyling.length; + i += InitialStylingValuesIndex.Size) { + const key = initialStyling[i + InitialStylingValuesIndex.PropOffset]; if (key === prop) { - const existingValue = initialStyling[i + InitialStylingValuesIndex.ValueOffset]; - - // If there is no previous style value (when `null`) or no previous class - // applied (when `false`) then we update the the newly given value. - if (existingValue == null || existingValue == false) { - initialStyling[i + InitialStylingValuesIndex.ValueOffset] = value; + const existingValue = + initialStyling[i + InitialStylingValuesIndex.ValueOffset] as string | null | boolean; + const existingOwner = + initialStyling[i + InitialStylingValuesIndex.DirectiveOwnerOffset] as number; + if (allowValueChange(existingValue, value, existingOwner, directiveOwnerIndex)) { + addOrUpdateStaticStyle(i, initialStyling, prop, value, directiveOwnerIndex); } return; } - i = i + InitialStylingValuesIndex.Size; } + // We did not find existing key, add a new one. - initialStyling.push(prop, value); + addOrUpdateStaticStyle(null, initialStyling, prop, value, directiveOwnerIndex); } /** @@ -377,8 +380,10 @@ export function updateContextWithBindings( let initialValuesToLookup = entryIsClassBased ? initialClasses : initialStyles; let indexForInitial = getInitialStylingValuesIndexOf(initialValuesToLookup, propName); if (indexForInitial === -1) { - indexForInitial = initialValuesToLookup.length + InitialStylingValuesIndex.ValueOffset; - initialValuesToLookup.push(propName, entryIsClassBased ? false : null); + indexForInitial = addOrUpdateStaticStyle( + null, initialValuesToLookup, propName, entryIsClassBased ? false : null, + directiveIndex) + + InitialStylingValuesIndex.ValueOffset; } else { indexForInitial += InitialStylingValuesIndex.ValueOffset; } @@ -1262,7 +1267,7 @@ function getInitialValue(context: StylingContext, flag: number): string|boolean| const entryIsClassBased = flag & StylingFlags.Class; const initialValues = entryIsClassBased ? context[StylingIndex.InitialClassValuesPosition] : context[StylingIndex.InitialStyleValuesPosition]; - return initialValues[index]; + return initialValues[index] as string | boolean | null; } function getInitialIndex(flag: number): number { @@ -1769,7 +1774,7 @@ function allowValueChange( */ export function getInitialClassNameValue(context: StylingContext): string { const initialClassValues = context[StylingIndex.InitialClassValuesPosition]; - let className = initialClassValues[InitialStylingValuesIndex.InitialClassesStringPosition]; + let className = initialClassValues[InitialStylingValuesIndex.CachedStringValuePosition]; if (className === null) { className = ''; for (let i = InitialStylingValuesIndex.KeyValueStartPosition; i < initialClassValues.length; @@ -1779,7 +1784,7 @@ export function getInitialClassNameValue(context: StylingContext): string { className += (className.length ? ' ' : '') + initialClassValues[i]; } } - initialClassValues[InitialStylingValuesIndex.InitialClassesStringPosition] = className; + initialClassValues[InitialStylingValuesIndex.CachedStringValuePosition] = className; } return className; } @@ -1797,7 +1802,7 @@ export function getInitialClassNameValue(context: StylingContext): string { */ export function getInitialStyleStringValue(context: StylingContext): string { const initialStyleValues = context[StylingIndex.InitialStyleValuesPosition]; - let styleString = initialStyleValues[InitialStylingValuesIndex.InitialClassesStringPosition]; + let styleString = initialStyleValues[InitialStylingValuesIndex.CachedStringValuePosition]; if (styleString === null) { styleString = ''; for (let i = InitialStylingValuesIndex.KeyValueStartPosition; i < initialStyleValues.length; @@ -1807,7 +1812,7 @@ export function getInitialStyleStringValue(context: StylingContext): string { styleString += (styleString.length ? ';' : '') + `${initialStyleValues[i]}:${value}`; } } - initialStyleValues[InitialStylingValuesIndex.InitialClassesStringPosition] = styleString; + initialStyleValues[InitialStylingValuesIndex.CachedStringValuePosition] = styleString; } return styleString; } @@ -1956,3 +1961,30 @@ function registerMultiMapEntry( } cachedValues.push(0, startPosition, null, count); } + +/** + * Inserts or updates an existing entry in the provided `staticStyles` collection. + * + * @param index the index representing an existing styling entry in the collection: + * if provided (numeric): then it will update the existing entry at the given position + * if null: then it will insert a new entry within the collection + * @param staticStyles a collection of style or class entries where the value will + * be inserted or patched + * @param prop the property value of the entry (e.g. `width` (styles) or `foo` (classes)) + * @param value the styling value of the entry (e.g. `absolute` (styles) or `true` (classes)) + * @param directiveOwnerIndex the directive owner index value of the styling source responsible + * for these styles (see `interfaces/styling.ts#directives` for more info) + * @returns the index of the updated or new entry within the collection + */ +function addOrUpdateStaticStyle( + index: number | null, staticStyles: InitialStylingValues, prop: string, + value: string | boolean | null, directiveOwnerIndex: number) { + if (index === null) { + index = staticStyles.length; + staticStyles.push(null, null, null); + staticStyles[index + InitialStylingValuesIndex.PropOffset] = prop; + } + staticStyles[index + InitialStylingValuesIndex.ValueOffset] = value; + staticStyles[index + InitialStylingValuesIndex.DirectiveOwnerOffset] = directiveOwnerIndex; + return index; +} diff --git a/packages/core/src/render3/styling/util.ts b/packages/core/src/render3/styling/util.ts index 2984841b72..0331d39397 100644 --- a/packages/core/src/render3/styling/util.ts +++ b/packages/core/src/render3/styling/util.ts @@ -14,7 +14,7 @@ import {LContext} from '../interfaces/context'; import {AttributeMarker, TAttributes, TNode, TNodeFlags} from '../interfaces/node'; import {PlayState, Player, PlayerContext, PlayerIndex} from '../interfaces/player'; import {RElement} from '../interfaces/renderer'; -import {InitialStylingValues, StylingContext, StylingFlags, StylingIndex} from '../interfaces/styling'; +import {DirectiveRegistryValuesIndex, InitialStylingValues, StylingContext, StylingFlags, StylingIndex} from '../interfaces/styling'; import {HEADER_OFFSET, HOST, LView, RootContext} from '../interfaces/view'; import {getTNode, isStylingContext} from '../util/view_utils'; @@ -37,13 +37,48 @@ export function createEmptyStylingContext( [0], // CachedMultiStyleValue null, // PlayerContext ]; + + // whenever a context is created there is always a `null` directive + // that is registered (which is a placeholder for the "template"). allocateDirectiveIntoContext(context, null); return context; } -export function allocateDirectiveIntoContext(context: StylingContext, directiveRef: any | null) { +/** + * Allocates (registers) a directive into the directive registry within the provided styling + * context. + * + * For each and every `[style]`, `[style.prop]`, `[class]`, `[class.name]` binding + * (as well as static style and class attributes) a directive, component or template + * is marked as the owner. When an owner is determined (this happens when the template + * is first passed over) the directive owner is allocated into the styling context. When + * this happens, each owner gets its own index value. This then ensures that once any + * style and/or class binding are assigned into the context then they are marked to + * that directive's index value. + * + * @param context the target StylingContext + * @param directiveRef the directive that will be allocated into the context + * @returns the index where the directive was inserted into + */ +export function allocateDirectiveIntoContext( + context: StylingContext, directiveRef: any | null): number { // this is a new directive which we have not seen yet. - context[StylingIndex.DirectiveRegistryPosition].push(directiveRef, -1, false, null); + const dirs = context[StylingIndex.DirectiveRegistryPosition]; + const i = dirs.length; + + // we preemptively make space into the directives array and then + // assign values slot-by-slot to ensure that if the directive ordering + // changes then it will still function + dirs.push(null, null, null, null); + dirs[i + DirectiveRegistryValuesIndex.DirectiveValueOffset] = directiveRef; + dirs[i + DirectiveRegistryValuesIndex.DirtyFlagOffset] = false; + dirs[i + DirectiveRegistryValuesIndex.StyleSanitizerOffset] = null; + + // -1 is used to signal that the directive has been allocated, but + // no actual style or class bindings have been registered yet... + dirs[i + DirectiveRegistryValuesIndex.SinglePropValuesIndexOffset] = -1; + + return i; } /** @@ -228,11 +263,3 @@ export function allocPlayerContext(data: StylingContext): PlayerContext { export function throwInvalidRefError() { throw new Error('Only elements that exist in an Angular application can be used for animations'); } - -export function hasStyling(attrs: TAttributes): boolean { - for (let i = 0; i < attrs.length; i++) { - const attr = attrs[i]; - if (attr == AttributeMarker.Classes || attr == AttributeMarker.Styles) return true; - } - return false; -} diff --git a/packages/core/src/render3/util/attrs_utils.ts b/packages/core/src/render3/util/attrs_utils.ts new file mode 100644 index 0000000000..f810cd6b0d --- /dev/null +++ b/packages/core/src/render3/util/attrs_utils.ts @@ -0,0 +1,107 @@ +/** + * @license + * Copyright Google Inc. 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 {AttributeMarker, TAttributes} from '../interfaces/node'; +import {NG_PROJECT_AS_ATTR_NAME} from '../interfaces/projection'; +import {ProceduralRenderer3, RElement, isProceduralRenderer} from '../interfaces/renderer'; +import {RENDERER} from '../interfaces/view'; +import {getLView} from '../state'; +import {isAnimationProp} from '../styling/util'; + + + +/** + * Assigns all attribute values to the provided element via the inferred renderer. + * + * This function accepts two forms of attribute entries: + * + * default: (key, value): + * attrs = [key1, value1, key2, value2] + * + * namespaced: (NAMESPACE_MARKER, uri, name, value) + * attrs = [NAMESPACE_MARKER, uri, name, value, NAMESPACE_MARKER, uri, name, value] + * + * The `attrs` array can contain a mix of both the default and namespaced entries. + * The "default" values are set without a marker, but if the function comes across + * a marker value then it will attempt to set a namespaced value. If the marker is + * not of a namespaced value then the function will quit and return the index value + * where it stopped during the iteration of the attrs array. + * + * See [AttributeMarker] to understand what the namespace marker value is. + * + * Note that this instruction does not support assigning style and class values to + * an element. See `elementStart` and `elementHostAttrs` to learn how styling values + * are applied to an element. + * + * @param native The element that the attributes will be assigned to + * @param attrs The attribute array of values that will be assigned to the element + * @returns the index value that was last accessed in the attributes array + */ +export function setUpAttributes(native: RElement, attrs: TAttributes): number { + const renderer = getLView()[RENDERER]; + const isProc = isProceduralRenderer(renderer); + + let i = 0; + while (i < attrs.length) { + const value = attrs[i]; + if (typeof value === 'number') { + // only namespaces are supported. Other value types (such as style/class + // entries) are not supported in this function. + if (value !== AttributeMarker.NamespaceURI) { + break; + } + + // we just landed on the marker value ... therefore + // we should skip to the next entry + i++; + + const namespaceURI = attrs[i++] as string; + const attrName = attrs[i++] as string; + const attrVal = attrs[i++] as string; + ngDevMode && ngDevMode.rendererSetAttribute++; + isProc ? + (renderer as ProceduralRenderer3).setAttribute(native, attrName, attrVal, namespaceURI) : + native.setAttributeNS(namespaceURI, attrName, attrVal); + } else { + /// attrName is string; + const attrName = value as string; + const attrVal = attrs[++i]; + if (attrName !== NG_PROJECT_AS_ATTR_NAME) { + // Standard attributes + ngDevMode && ngDevMode.rendererSetAttribute++; + if (isAnimationProp(attrName)) { + if (isProc) { + (renderer as ProceduralRenderer3).setProperty(native, attrName, attrVal); + } + } else { + isProc ? + (renderer as ProceduralRenderer3) + .setAttribute(native, attrName as string, attrVal as string) : + native.setAttribute(attrName as string, attrVal as string); + } + } + i++; + } + } + + // another piece of code may iterate over the same attributes array. Therefore + // it may be helpful to return the exact spot where the attributes array exited + // whether by running into an unsupported marker or if all the static values were + // iterated over. + return i; +} + + +export function attrsStylingIndexOf(attrs: TAttributes, startIndex: number): number { + for (let i = startIndex; i < attrs.length; i++) { + const val = attrs[i]; + if (val === AttributeMarker.Classes || val === AttributeMarker.Styles) { + return i; + } + } + return -1; +} diff --git a/packages/core/test/acceptance/integration_spec.ts b/packages/core/test/acceptance/integration_spec.ts index 0360ecb21d..7612e8b22e 100644 --- a/packages/core/test/acceptance/integration_spec.ts +++ b/packages/core/test/acceptance/integration_spec.ts @@ -9,39 +9,9 @@ import {Component, Directive, HostBinding, HostListener, Input, QueryList, ViewC import {TestBed} from '@angular/core/testing'; import {By} from '@angular/platform-browser'; import {expect} from '@angular/platform-browser/testing/src/matchers'; -import {onlyInIvy} from '@angular/private/testing'; +import {ivyEnabled, onlyInIvy} from '@angular/private/testing'; describe('acceptance integration tests', () => { - onlyInIvy('[style] and [class] bindings are a new feature') - .it('should render host bindings on the root component', () => { - @Component({template: '...'}) - class MyApp { - @HostBinding('style') public myStylesExp = {}; - @HostBinding('class') public myClassesExp = {}; - } - - TestBed.configureTestingModule({declarations: [MyApp]}); - const fixture = TestBed.createComponent(MyApp); - const element = fixture.nativeElement; - fixture.detectChanges(); - - const component = fixture.componentInstance; - component.myStylesExp = {width: '100px'}; - component.myClassesExp = 'foo'; - fixture.detectChanges(); - - expect(element.style['width']).toEqual('100px'); - expect(element.classList.contains('foo')).toBeTruthy(); - - component.myStylesExp = {width: '200px'}; - component.myClassesExp = 'bar'; - fixture.detectChanges(); - - expect(element.style['width']).toEqual('200px'); - expect(element.classList.contains('foo')).toBeFalsy(); - expect(element.classList.contains('bar')).toBeTruthy(); - }); - it('should only call inherited host listeners once', () => { let clicks = 0; @@ -97,20 +67,6 @@ describe('acceptance integration tests', () => { expect(subInstance.dirs.first).toBeAnInstanceOf(SomeDir); }); - it('should render host class and style on the root component', () => { - @Component({template: '...', host: {class: 'foo', style: 'color: red'}}) - class MyApp { - } - - TestBed.configureTestingModule({declarations: [MyApp]}); - const fixture = TestBed.createComponent(MyApp); - const element = fixture.nativeElement; - fixture.detectChanges(); - - expect(element.style['color']).toEqual('red'); - expect(element.classList.contains('foo')).toBeTruthy(); - }); - it('should not set inputs after destroy', () => { @Directive({ selector: '[no-assign-after-destroy]', @@ -146,5 +102,4 @@ describe('acceptance integration tests', () => { fixture.detectChanges(); }).not.toThrow(); }); - }); diff --git a/packages/core/test/acceptance/styling_spec.ts b/packages/core/test/acceptance/styling_spec.ts new file mode 100644 index 0000000000..4eb477fef4 --- /dev/null +++ b/packages/core/test/acceptance/styling_spec.ts @@ -0,0 +1,99 @@ +/** + * @license + * Copyright Google Inc. 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 {Component, HostBinding} from '@angular/core'; +import {TestBed} from '@angular/core/testing'; +import {expect} from '@angular/platform-browser/testing/src/matchers'; +import {ivyEnabled, onlyInIvy} from '@angular/private/testing'; + +describe('acceptance integration tests', () => { + onlyInIvy('[style] and [class] bindings are a new feature') + .it('should render host bindings on the root component', () => { + @Component({template: '...'}) + class MyApp { + @HostBinding('style') public myStylesExp = {}; + @HostBinding('class') public myClassesExp = {}; + } + + TestBed.configureTestingModule({declarations: [MyApp]}); + const fixture = TestBed.createComponent(MyApp); + const element = fixture.nativeElement; + fixture.detectChanges(); + + const component = fixture.componentInstance; + component.myStylesExp = {width: '100px'}; + component.myClassesExp = 'foo'; + fixture.detectChanges(); + + expect(element.style['width']).toEqual('100px'); + expect(element.classList.contains('foo')).toBeTruthy(); + + component.myStylesExp = {width: '200px'}; + component.myClassesExp = 'bar'; + fixture.detectChanges(); + + expect(element.style['width']).toEqual('200px'); + expect(element.classList.contains('foo')).toBeFalsy(); + expect(element.classList.contains('bar')).toBeTruthy(); + }); + + it('should render host class and style on the root component', () => { + @Component({template: '...', host: {class: 'foo', style: 'color: red'}}) + class MyApp { + } + + TestBed.configureTestingModule({declarations: [MyApp]}); + const fixture = TestBed.createComponent(MyApp); + const element = fixture.nativeElement; + fixture.detectChanges(); + + expect(element.style['color']).toEqual('red'); + expect(element.classList.contains('foo')).toBeTruthy(); + }); + + it('should combine the inherited static styles of a parent and child component', () => { + @Component({template: '...', host: {'style': 'width:100px; height:100px;'}}) + class ParentCmp { + } + + @Component({template: '...', host: {'style': 'width:200px; color:red'}}) + class ChildCmp extends ParentCmp { + } + + TestBed.configureTestingModule({declarations: [ChildCmp]}); + const fixture = TestBed.createComponent(ChildCmp); + fixture.detectChanges(); + + const element = fixture.nativeElement; + if (ivyEnabled) { + expect(element.style['height']).toEqual('100px'); + } + expect(element.style['width']).toEqual('200px'); + expect(element.style['color']).toEqual('red'); + }); + + it('should combine the inherited static classes of a parent and child component', () => { + @Component({template: '...', host: {'class': 'foo bar'}}) + class ParentCmp { + } + + @Component({template: '...', host: {'class': 'foo baz'}}) + class ChildCmp extends ParentCmp { + } + + TestBed.configureTestingModule({declarations: [ChildCmp]}); + const fixture = TestBed.createComponent(ChildCmp); + fixture.detectChanges(); + + const element = fixture.nativeElement; + if (ivyEnabled) { + expect(element.classList.contains('bar')).toBeTruthy(); + } + expect(element.classList.contains('foo')).toBeTruthy(); + expect(element.classList.contains('baz')).toBeTruthy(); + }); +}); diff --git a/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json b/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json index 10ee5080db..d0d82ba7e6 100644 --- a/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json +++ b/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json @@ -158,6 +158,9 @@ { "name": "addComponentLogic" }, + { + "name": "addOrUpdateStaticStyle" + }, { "name": "addToViewTree" }, @@ -167,6 +170,9 @@ { "name": "allocateDirectiveIntoContext" }, + { + "name": "allowValueChange" + }, { "name": "appendChild" }, @@ -176,6 +182,9 @@ { "name": "attachPatchData" }, + { + "name": "attrsStylingIndexOf" + }, { "name": "baseResolveDirective" }, @@ -323,6 +332,9 @@ { "name": "getDirectiveDef" }, + { + "name": "getDirectiveRegistryValuesIndexOf" + }, { "name": "getElementDepthCount" }, @@ -416,9 +428,6 @@ { "name": "hasStyleInput" }, - { - "name": "hasStyling" - }, { "name": "hasTagAndTypeMatch" }, @@ -524,6 +533,12 @@ { "name": "noSideEffects" }, + { + "name": "patchContextWithStaticAttrs" + }, + { + "name": "patchInitialStylingValue" + }, { "name": "postProcessBaseDirective" }, diff --git a/packages/core/test/bundling/todo/bundle.golden_symbols.json b/packages/core/test/bundling/todo/bundle.golden_symbols.json index c8b4beddd7..a279b0e311 100644 --- a/packages/core/test/bundling/todo/bundle.golden_symbols.json +++ b/packages/core/test/bundling/todo/bundle.golden_symbols.json @@ -362,6 +362,9 @@ { "name": "addComponentLogic" }, + { + "name": "addOrUpdateStaticStyle" + }, { "name": "addPlayerInternal" }, @@ -401,6 +404,9 @@ { "name": "attachPatchData" }, + { + "name": "attrsStylingIndexOf" + }, { "name": "baseResolveDirective" }, @@ -854,9 +860,6 @@ { "name": "hasStyleInput" }, - { - "name": "hasStyling" - }, { "name": "hasTagAndTypeMatch" }, @@ -1064,6 +1067,12 @@ { "name": "noSideEffects" }, + { + "name": "patchContextWithStaticAttrs" + }, + { + "name": "patchInitialStylingValue" + }, { "name": "pointers" }, diff --git a/packages/core/test/render3/integration_spec.ts b/packages/core/test/render3/integration_spec.ts index 21371db81e..5e396bee48 100644 --- a/packages/core/test/render3/integration_spec.ts +++ b/packages/core/test/render3/integration_spec.ts @@ -1678,7 +1678,7 @@ describe('render3 integration test', () => { if (rf & RenderFlags.Create) { elementStart( 0, 'div', - ['DirWithClass', AttributeMarker.Classes, 'apple', 'orange', 'banana']); + ['DirWithClass', '', AttributeMarker.Classes, 'apple', 'orange', 'banana']); elementStyling(); elementEnd(); } @@ -1698,9 +1698,9 @@ describe('render3 integration test', () => { */ const App = createComponent('app', function(rf: RenderFlags, ctx: any) { if (rf & RenderFlags.Create) { - elementStart( - 0, 'div', - ['DirWithStyle', AttributeMarker.Styles, 'width', '100px', 'height', '200px']); + elementStart(0, 'div', [ + 'DirWithStyle', '', AttributeMarker.Styles, 'width', '100px', 'height', '200px' + ]); elementStyling(); elementEnd(); } diff --git a/packages/core/test/render3/node_selector_matcher_spec.ts b/packages/core/test/render3/node_selector_matcher_spec.ts index 9a62268fd1..0b55dd306d 100644 --- a/packages/core/test/render3/node_selector_matcher_spec.ts +++ b/packages/core/test/render3/node_selector_matcher_spec.ts @@ -317,7 +317,7 @@ describe('css selector matching', () => { expect(isMatching('div', tNode, selector)).toBeTruthy(); //
(with styling context but without attrs) - tNode.stylingTemplate = initializeStaticContext([AttributeMarker.Classes, 'abc']); + tNode.stylingTemplate = initializeStaticContext([AttributeMarker.Classes, 'abc'], 0); tNode.attrs = null; expect(isMatching('div', tNode, selector)).toBeTruthy(); }); diff --git a/packages/core/test/render3/styling/class_and_style_bindings_spec.ts b/packages/core/test/render3/styling/class_and_style_bindings_spec.ts index 2ea84900b3..7eda24e508 100644 --- a/packages/core/test/render3/styling/class_and_style_bindings_spec.ts +++ b/packages/core/test/render3/styling/class_and_style_bindings_spec.ts @@ -39,7 +39,7 @@ describe('style and class based bindings', () => { return lView; } - function initContext( + function createStylingTemplate( initialStyles?: (number | string)[] | null, styleBindings?: string[] | null, initialClasses?: (string | number | boolean)[] | null, classBindings?: string[] | null, sanitizer?: StyleSanitizeFn | null): StylingContext { @@ -53,8 +53,17 @@ describe('style and class based bindings', () => { attrsWithStyling.push(...initialStyles as any); } - const tpl = initializeStaticContext(attrsWithStyling) !; + const tpl = initializeStaticContext(attrsWithStyling, 0) !; updateContextWithBindings(tpl, null, classBindings || null, styleBindings || null, sanitizer); + return tpl; + } + + function createStylingContext( + initialStyles?: (number | string)[] | null, styleBindings?: string[] | null, + initialClasses?: (string | number | boolean)[] | null, classBindings?: string[] | null, + sanitizer?: StyleSanitizeFn | null): StylingContext { + const tpl = createStylingTemplate( + initialStyles, styleBindings, initialClasses, classBindings, sanitizer); return allocStylingContext(element, tpl); } @@ -198,7 +207,7 @@ describe('style and class based bindings', () => { describe('styles', () => { describe('static styling properties within a context', () => { it('should initialize empty template', () => { - const template = initContext(); + const template = createStylingContext(); assertContext(template, [ element, masterConfig(9), @@ -213,13 +222,14 @@ describe('style and class based bindings', () => { }); it('should initialize static styles and classes', () => { - const template = initContext(['color', 'red', 'width', '10px'], null, ['foo', 'bar']); + const template = + createStylingContext(['color', 'red', 'width', '10px'], null, ['foo', 'bar']); assertContext(template, [ element, masterConfig(9), [null, 2, false, null], - [null, null, 'color', 'red', 'width', '10px'], - [null, null, 'foo', true, 'bar', true], + [null, null, 'color', 'red', 0, 'width', '10px', 0], + [null, null, 'foo', true, 0, 'bar', true, 0], [0, 0, 0, 0], [0, 0, 9, null, 0], [0, 0, 9, null, 0], @@ -227,91 +237,133 @@ describe('style and class based bindings', () => { ]); }); - it('should initialize and then patch static styling inline with existing static styling', + it('should initialize and then patch static styling inline with existing static styling and also replace values if the same directive runs twice', () => { - const template = initContext(['color', 'red'], null, ['foo']); + const template = createStylingTemplate(['color', 'red'], null, ['foo']); expect(template[StylingIndex.InitialStyleValuesPosition]).toEqual([ null, null, 'color', 'red', + 0, ]); expect(template[StylingIndex.InitialClassValuesPosition]).toEqual([ null, null, 'foo', true, + 0, ]); patchContext(template, ['color', 'black', 'height', '200px'], ['bar', 'foo'], '1'); expect(template[StylingIndex.InitialStyleValuesPosition]).toEqual([ - null, null, 'color', 'red', 'height', '200px' + null, + null, + 'color', + 'red', + 0, + 'height', + '200px', + 1, ]); expect(template[StylingIndex.InitialClassValuesPosition]).toEqual([ - null, null, 'foo', true, 'bar', true + null, + null, + 'foo', + true, + 0, + 'bar', + true, + 1, + ]); + + patchContext(template, ['color', 'orange'], []); + expect(template[StylingIndex.InitialStyleValuesPosition]).toEqual([ + null, + null, + 'color', + 'orange', + 0, + 'height', + '200px', + 1, + ]); + expect(template[StylingIndex.InitialClassValuesPosition]).toEqual([ + null, + null, + 'foo', + true, + 0, + 'bar', + true, + 1, ]); }); - it('should only populate static styles for a given directive once', () => { - const template = initContext(['color', 'red'], null, ['foo']); - expect(template[StylingIndex.InitialStyleValuesPosition]).toEqual([ - null, - null, - 'color', - 'red', - ]); - expect(template[StylingIndex.InitialClassValuesPosition]).toEqual([ - null, - null, - 'foo', - true, - ]); + it('should only populate static styles for a given directive once only when the context is allocated', + () => { + const template = createStylingTemplate(['color', 'red'], null, ['foo']); + expect(template[StylingIndex.InitialStyleValuesPosition]).toEqual([ + null, + null, + 'color', + 'red', + 0, + ]); + expect(template[StylingIndex.InitialClassValuesPosition]).toEqual([ + null, + null, + 'foo', + true, + 0, + ]); - patchContext(template, ['color', 'black', 'height', '200px'], ['bar', 'foo']); - expect(template[StylingIndex.InitialStyleValuesPosition]).toEqual([ - null, - null, - 'color', - 'red', - ]); - expect(template[StylingIndex.InitialClassValuesPosition]).toEqual([ - null, - null, - 'foo', - true, - ]); + patchContext(template, ['color', 'black', 'height', '200px'], ['bar', 'foo']); + expect(template[StylingIndex.InitialStyleValuesPosition]).toEqual([ + null, + null, + 'color', + 'black', + 0, + 'height', + '200px', + 0, + ]); + expect(template[StylingIndex.InitialClassValuesPosition]).toEqual([ + null, + null, + 'foo', + true, + 0, + 'bar', + true, + 0, + ]); - patchContext(template, ['color', 'black', 'height', '200px'], ['bar', 'foo'], '1'); - expect(template[StylingIndex.InitialStyleValuesPosition]).toEqual([ - null, - null, - 'color', - 'red', - 'height', - '200px', - ]); - expect(template[StylingIndex.InitialClassValuesPosition]).toEqual([ - null, null, 'foo', true, 'bar', true - ]); + allocStylingContext(element, template); - patchContext(template, ['color', 'black', 'height', '200px'], ['bar', 'foo'], '1'); - expect(template[StylingIndex.InitialStyleValuesPosition]).toEqual([ - null, - null, - 'color', - 'red', - 'height', - '200px', - ]); - expect(template[StylingIndex.InitialClassValuesPosition]).toEqual([ - null, - null, - 'foo', - true, - 'bar', - true, - ]); - }); + patchContext(template, ['color', 'black', 'height', '200px'], ['bar', 'foo'], '1'); + expect(template[StylingIndex.InitialStyleValuesPosition]).toEqual([ + null, + null, + 'color', + 'black', + 0, + 'height', + '200px', + 0, + ]); + expect(template[StylingIndex.InitialClassValuesPosition]).toEqual([ + null, + null, + 'foo', + true, + 0, + 'bar', + true, + 0, + ]); + }); }); describe('instructions', () => { @@ -440,8 +492,8 @@ describe('style and class based bindings', () => { null, masterConfig(17, false, false), // [null, 2, false, null], - [null, null, 'width', null], - [null, null, 'foo', false], + [null, null, 'width', null, 0], + [null, null, 'foo', false, 0], [1, 1, 1, 1, 9, 13], [1, 0, 21, null, 1], [1, 0, 17, null, 1], @@ -478,8 +530,8 @@ describe('style and class based bindings', () => { null, masterConfig(25, false, false), // [null, 2, false, null, 'SOME DIRECTIVE', 6, false, null], - [null, null, 'width', null, 'height', null], - [null, null, 'foo', false, 'bar', false], + [null, null, 'width', null, 0, 'height', null, 1], + [null, null, 'foo', false, 0, 'bar', false, 1], [2, 2, 1, 1, 9, 17, 2, 1, 9, 13, 21], [2, 0, 33, null, 1, 0, 37, null, 1], [2, 0, 25, null, 1, 0, 29, null, 1], @@ -492,7 +544,7 @@ describe('style and class based bindings', () => { 0, // #13 - cleanStyle(5, 29), + cleanStyle(6, 29), 'height', null, 1, @@ -504,7 +556,7 @@ describe('style and class based bindings', () => { 0, // #21 - cleanClass(5, 37), + cleanClass(6, 37), 'bar', null, 1, @@ -516,7 +568,7 @@ describe('style and class based bindings', () => { 0, // #29 - cleanStyle(5, 13), + cleanStyle(6, 13), 'height', null, 1, @@ -528,7 +580,7 @@ describe('style and class based bindings', () => { 0, // #37 - cleanClass(5, 21), + cleanClass(6, 21), 'bar', null, 1, @@ -544,8 +596,8 @@ describe('style and class based bindings', () => { null, 2, false, null, 'SOME DIRECTIVE', 6, false, null, 'SOME DIRECTIVE 2', 11, false, null ], - [null, null, 'width', null, 'height', null, 'opacity', null], - [null, null, 'foo', false, 'bar', false, 'baz', false], + [null, null, 'width', null, 0, 'height', null, 1, 'opacity', null, 2], + [null, null, 'foo', false, 0, 'bar', false, 1, 'baz', false, 2], [3, 3, 1, 1, 9, 21, 2, 1, 9, 13, 25, 3, 3, 17, 9, 13, 29, 25, 21], [3, 0, 45, null, 1, 0, 49, null, 1, 0, 53, null, 1], [3, 0, 33, null, 1, 0, 37, null, 1, 0, 41, null, 1], @@ -558,13 +610,13 @@ describe('style and class based bindings', () => { 0, // #13 - cleanStyle(5, 37), + cleanStyle(6, 37), 'height', null, 1, // #17 - cleanStyle(7, 41), + cleanStyle(9, 41), 'opacity', null, 2, @@ -576,13 +628,13 @@ describe('style and class based bindings', () => { 0, // #25 - cleanClass(5, 49), + cleanClass(6, 49), 'bar', null, 1, // #29 - cleanClass(7, 53), + cleanClass(9, 53), 'baz', null, 2, @@ -594,13 +646,13 @@ describe('style and class based bindings', () => { 0, // #37 - cleanStyle(5, 13), + cleanStyle(6, 13), 'height', null, 1, // #41 - cleanStyle(7, 17), + cleanStyle(9, 17), 'opacity', null, 2, @@ -612,13 +664,13 @@ describe('style and class based bindings', () => { 0, // #49 - cleanClass(5, 25), + cleanClass(6, 25), 'bar', null, 1, // #53 - cleanClass(7, 29), + cleanClass(9, 29), 'baz', null, 2, @@ -642,7 +694,7 @@ describe('style and class based bindings', () => { it('should build a list of multiple styling values', () => { const getStyles = trackStylesFactory(); - const stylingContext = initContext(); + const stylingContext = createStylingContext(); updateStyles(stylingContext, { width: '100px', height: '100px', @@ -652,7 +704,7 @@ describe('style and class based bindings', () => { }); it('should evaluate the delta between style changes when rendering occurs', () => { - const stylingContext = initContext(['width', '100px'], ['width', 'height']); + const stylingContext = createStylingContext(['width', '100px'], ['width', 'height']); updateStyles(stylingContext, { height: '200px', }); @@ -674,7 +726,7 @@ describe('style and class based bindings', () => { it('should update individual values on a set of styles', () => { const getStyles = trackStylesFactory(); - const stylingContext = initContext(null, ['width', 'height']); + const stylingContext = createStylingContext(null, ['width', 'height']); updateStyles(stylingContext, { width: '100px', height: '100px', @@ -684,7 +736,7 @@ describe('style and class based bindings', () => { }); it('should only mark itself as updated when one or more properties have been applied', () => { - const stylingContext = initContext(); + const stylingContext = createStylingContext(); expect(isContextDirty(stylingContext)).toBeFalsy(); updateStyles(stylingContext, { @@ -709,7 +761,7 @@ describe('style and class based bindings', () => { }); it('should only mark itself as updated when any single properties have been applied', () => { - const stylingContext = initContext(null, ['height']); + const stylingContext = createStylingContext(null, ['height']); updateStyles(stylingContext, { width: '100px', height: '100px', @@ -729,7 +781,7 @@ describe('style and class based bindings', () => { it('should prioritize multi and single styles over initial styles', () => { const getStyles = trackStylesFactory(); - const stylingContext = initContext( + const stylingContext = createStylingContext( ['width', '100px', 'height', '100px', 'opacity', '0'], ['width', 'height', 'opacity']); expect(getStyles(stylingContext)).toEqual({}); @@ -764,7 +816,7 @@ describe('style and class based bindings', () => { }); it('should cleanup removed styles from the context once the styles are built', () => { - const stylingContext = initContext(null, ['width', 'height']); + const stylingContext = createStylingContext(null, ['width', 'height']); const getStyles = trackStylesFactory(); updateStyles(stylingContext, {width: '100px', height: '100px'}); @@ -776,7 +828,7 @@ describe('style and class based bindings', () => { 0, // #13 - cleanStyle(5, 21), + cleanStyle(6, 21), 'height', null, 0, @@ -788,7 +840,7 @@ describe('style and class based bindings', () => { 0, // #21 - dirtyStyle(5, 13), + dirtyStyle(6, 13), 'height', '100px', 0, @@ -805,7 +857,7 @@ describe('style and class based bindings', () => { 0, // #13 - cleanStyle(5, 25), + cleanStyle(6, 25), 'height', null, 0, @@ -823,7 +875,7 @@ describe('style and class based bindings', () => { 0, // #25 - dirtyStyle(5, 13), + dirtyStyle(6, 13), 'height', null, 0, @@ -838,7 +890,7 @@ describe('style and class based bindings', () => { 0, // #13 - cleanStyle(5, 25), + cleanStyle(6, 25), 'height', null, 0, @@ -856,7 +908,7 @@ describe('style and class based bindings', () => { 0, // #23 - cleanStyle(5, 13), + cleanStyle(6, 13), 'height', null, 0, @@ -873,7 +925,7 @@ describe('style and class based bindings', () => { 0, // #13 - cleanStyle(5, 25), + cleanStyle(6, 25), 'height', null, 0, @@ -891,7 +943,7 @@ describe('style and class based bindings', () => { 0, // #23 - cleanStyle(5, 13), + cleanStyle(6, 13), 'height', null, 0, @@ -908,7 +960,7 @@ describe('style and class based bindings', () => { 0, // #13 - cleanStyle(5, 25), + cleanStyle(6, 25), 'height', null, 0, @@ -926,7 +978,7 @@ describe('style and class based bindings', () => { 0, // #23 - cleanStyle(5, 13), + cleanStyle(6, 13), 'height', null, 0, @@ -935,7 +987,7 @@ describe('style and class based bindings', () => { it('should find the next available space in the context when data is added after being removed before', () => { - const stylingContext = initContext(null, ['line-height']); + const stylingContext = createStylingContext(null, ['line-height']); const getStyles = trackStylesFactory(); updateStyles(stylingContext, {width: '100px', height: '100px', opacity: '0.5'}); @@ -1137,7 +1189,7 @@ describe('style and class based bindings', () => { it('should render all data as not being dirty after the styles are built', () => { const getStyles = trackStylesFactory(); - const stylingContext = initContext(null, ['height']); + const stylingContext = createStylingContext(null, ['height']); const cachedStyleValue = {width: '100px'}; @@ -1148,7 +1200,7 @@ describe('style and class based bindings', () => { element, masterConfig(13, true), // [null, 2, true, null], - [null, null, 'height', null], + [null, null, 'height', null, 0], [null, null], [1, 0, 1, 0, 9], [0, 0, 21, null, 0], @@ -1180,7 +1232,7 @@ describe('style and class based bindings', () => { element, masterConfig(13, false), // [null, 2, false, null], - [null, null, 'height', null], + [null, null, 'height', null, 0], [null, null], [1, 0, 1, 0, 9], [0, 0, 21, null, 0], @@ -1212,7 +1264,8 @@ describe('style and class based bindings', () => { const getStyles = trackStylesFactory(); const styleBindings = ['border-image', 'border-width']; const styleSanitizer = defaultStyleSanitizer; - const stylingContext = initContext(null, styleBindings, null, null, styleSanitizer); + const stylingContext = + createStylingContext(null, styleBindings, null, null, styleSanitizer); updateStyleProp(stylingContext, 0, 'url(foo.jpg)'); updateStyleProp(stylingContext, 1, '100px'); @@ -1225,7 +1278,7 @@ describe('style and class based bindings', () => { 0, // #13 - dirtyStyle(5, 21), + dirtyStyle(6, 21), 'border-width', '100px', 0, @@ -1237,7 +1290,7 @@ describe('style and class based bindings', () => { 0, // #21 - cleanStyle(5, 13), + cleanStyle(6, 13), 'border-width', null, 0, @@ -1253,7 +1306,7 @@ describe('style and class based bindings', () => { 0, // #13 - dirtyStyle(5, 25), + dirtyStyle(6, 25), 'border-width', '100px', 0, @@ -1271,7 +1324,7 @@ describe('style and class based bindings', () => { 0, // #23 - cleanStyle(5, 13), + cleanStyle(6, 13), 'border-width', null, 0, @@ -1287,7 +1340,7 @@ describe('style and class based bindings', () => { 0, // #13 - cleanStyle(5, 25), + cleanStyle(6, 25), 'border-width', '100px', 0, @@ -1305,7 +1358,7 @@ describe('style and class based bindings', () => { 0, // #23 - cleanStyle(5, 13), + cleanStyle(6, 13), 'border-width', null, 0, @@ -1427,7 +1480,7 @@ describe('style and class based bindings', () => { 1, // #13 - cleanStyle(5, 21), + cleanStyle(6, 21), 'height', null, 1, @@ -1439,7 +1492,7 @@ describe('style and class based bindings', () => { 1, // #21 - dirtyStyle(5, 13), + dirtyStyle(6, 13), 'height', '99px', 1, @@ -1463,7 +1516,7 @@ describe('style and class based bindings', () => { 1, // #13 - cleanStyle(5, 25), + cleanStyle(6, 25), 'height', null, 1, @@ -1481,7 +1534,7 @@ describe('style and class based bindings', () => { 2, // #25 - dirtyStyle(5, 13), + dirtyStyle(6, 13), 'height', '999px', 3, @@ -1499,7 +1552,7 @@ describe('style and class based bindings', () => { 1, // #13 - cleanStyle(5, 25), + cleanStyle(6, 25), 'height', null, 1, @@ -1517,7 +1570,7 @@ describe('style and class based bindings', () => { 2, // #25 - dirtyStyle(5, 13), + dirtyStyle(6, 13), 'height', '999px', 3, @@ -1560,7 +1613,7 @@ describe('style and class based bindings', () => { 1, // #13 - cleanClass(5, 33), + cleanClass(6, 33), 'green', null, 1, @@ -1590,7 +1643,7 @@ describe('style and class based bindings', () => { 3, // #33 - dirtyClass(5, 13), + dirtyClass(6, 13), 'green', true, 3, @@ -1608,7 +1661,7 @@ describe('style and class based bindings', () => { 1, // #13 - cleanClass(5, 29), + cleanClass(6, 29), 'green', null, 1, @@ -1632,7 +1685,7 @@ describe('style and class based bindings', () => { 2, // #29 - dirtyClass(5, 13), + dirtyClass(6, 13), 'green', true, 3, @@ -1656,13 +1709,13 @@ describe('style and class based bindings', () => { 1, // #13 - cleanClass(5, 17), + cleanClass(6, 17), 'green', null, 1, // #17 - dirtyClass(5, 13), + dirtyClass(6, 13), 'green', true, 1, @@ -1813,7 +1866,7 @@ describe('style and class based bindings', () => { }); it('should skip issuing style updates if there is nothing to update upon first render', () => { - const stylingContext = initContext(null, ['color']); + const stylingContext = createStylingContext(null, ['color']); const store = new MockStylingStore(element as HTMLElement, BindingType.Class); const getStyles = trackStylesFactory(store); const otherDirective = {}; @@ -1844,13 +1897,13 @@ describe('style and class based bindings', () => { describe('classes', () => { it('should initialize with the provided class bindings', () => { - const template = initContext(null, null, null, ['one', 'two']); + const template = createStylingContext(null, null, null, ['one', 'two']); assertContext(template, [ element, masterConfig(17, false), // [null, 2, false, null], [null, null], - [null, null, 'one', false, 'two', false], + [null, null, 'one', false, 0, 'two', false, 0], [0, 2, 0, 2, 9, 13], [2, 0, 17, null, 2], [0, 0, 17, null, 0], @@ -1863,7 +1916,7 @@ describe('style and class based bindings', () => { 0, // #13 - cleanClass(5, 21), + cleanClass(6, 21), 'two', null, 0, @@ -1875,7 +1928,7 @@ describe('style and class based bindings', () => { 0, // #21 - cleanClass(5, 13), + cleanClass(6, 13), 'two', null, 0, @@ -1884,7 +1937,7 @@ describe('style and class based bindings', () => { it('should update multi class properties against the static classes', () => { const getClasses = trackClassesFactory(); - const stylingContext = initContext(null, null, ['bar'], ['bar', 'foo']); + const stylingContext = createStylingContext(null, null, ['bar'], ['bar', 'foo']); expect(getClasses(stylingContext)).toEqual({}); updateClasses(stylingContext, {foo: true, bar: false}); expect(getClasses(stylingContext)).toEqual({'foo': true, 'bar': false}); @@ -1894,7 +1947,7 @@ describe('style and class based bindings', () => { it('should update single class properties despite static classes being present', () => { const getClasses = trackClassesFactory(); - const stylingContext = initContext(null, null, ['bar'], ['bar', 'foo']); + const stylingContext = createStylingContext(null, null, ['bar'], ['bar', 'foo']); expect(getClasses(stylingContext)).toEqual({}); updateClassProp(stylingContext, 0, true); @@ -1909,7 +1962,7 @@ describe('style and class based bindings', () => { it('should understand updating multi-classes using a string-based value while respecting single class-based props', () => { const getClasses = trackClassesFactory(); - const stylingContext = initContext(null, null, null, ['baz']); + const stylingContext = createStylingContext(null, null, null, ['baz']); expect(getClasses(stylingContext)).toEqual({}); updateStylingMap(stylingContext, 'foo bar baz'); @@ -1924,14 +1977,14 @@ describe('style and class based bindings', () => { it('should place styles within the context and work alongside style-based values in harmony', () => { const getStylesAndClasses = trackStylesAndClasses(); - const stylingContext = - initContext(['width', '100px'], ['width', 'height'], ['wide'], ['wide', 'tall']); + const stylingContext = createStylingContext( + ['width', '100px'], ['width', 'height'], ['wide'], ['wide', 'tall']); assertContext(stylingContext, [ element, masterConfig(25, false), // [null, 2, false, null], - [null, null, 'width', '100px', 'height', null], - [null, null, 'wide', true, 'tall', false], + [null, null, 'width', '100px', 0, 'height', null, 0], + [null, null, 'wide', true, 0, 'tall', false, 0], [2, 2, 2, 2, 9, 13, 17, 21], [2, 0, 33, null, 2], [2, 0, 25, null, 2], @@ -1944,7 +1997,7 @@ describe('style and class based bindings', () => { 0, // #13 - cleanStyle(5, 29), + cleanStyle(6, 29), 'height', null, 0, @@ -1956,7 +2009,7 @@ describe('style and class based bindings', () => { 0, // #21 - cleanClass(5, 37), + cleanClass(6, 37), 'tall', null, 0, @@ -1968,7 +2021,7 @@ describe('style and class based bindings', () => { 0, // #29 - cleanStyle(5, 13), + cleanStyle(6, 13), 'height', null, 0, @@ -1980,7 +2033,7 @@ describe('style and class based bindings', () => { 0, // #37 - cleanClass(5, 21), + cleanClass(6, 21), 'tall', null, 0, @@ -1994,8 +2047,8 @@ describe('style and class based bindings', () => { element, masterConfig(25, true), // [null, 2, true, null], - [null, null, 'width', '100px', 'height', null], - [null, null, 'wide', true, 'tall', false], + [null, null, 'width', '100px', 0, 'height', null, 0], + [null, null, 'wide', true, 0, 'tall', false, 0], [2, 2, 2, 2, 9, 13, 17, 21], [2, 0, 37, 'tall round', 2], [2, 0, 25, cachedStyleMap, 2], @@ -2008,7 +2061,7 @@ describe('style and class based bindings', () => { 0, // #13 - cleanStyle(5, 33), + cleanStyle(6, 33), 'height', null, 0, @@ -2020,7 +2073,7 @@ describe('style and class based bindings', () => { 0, // #21 - cleanClass(5, 37), + cleanClass(6, 37), 'tall', null, 0, @@ -2038,13 +2091,13 @@ describe('style and class based bindings', () => { 0, // #33 - cleanStyle(5, 13), + cleanStyle(6, 13), 'height', null, 0, // #37 - dirtyClass(5, 21), + dirtyClass(6, 21), 'tall', true, 0, @@ -2076,8 +2129,8 @@ describe('style and class based bindings', () => { element, masterConfig(25, true), // [null, 2, true, null], - [null, null, 'width', '100px', 'height', null], - [null, null, 'wide', true, 'tall', false], + [null, null, 'width', '100px', 0, 'height', null, 0], + [null, null, 'wide', true, 0, 'tall', false, 0], [2, 2, 2, 2, 9, 13, 17, 21], [2, 0, 37, cachedClassMap, 2], [1, 0, 25, cachedStyleMap, 1], @@ -2090,7 +2143,7 @@ describe('style and class based bindings', () => { 0, // #13 - cleanStyle(5, 33), + cleanStyle(6, 33), 'height', null, 0, @@ -2102,7 +2155,7 @@ describe('style and class based bindings', () => { 0, // #21 - cleanClass(5, 37), + cleanClass(6, 37), 'tall', null, 0, @@ -2120,13 +2173,13 @@ describe('style and class based bindings', () => { 0, // #33 - cleanStyle(5, 13), + cleanStyle(6, 13), 'height', null, 0, // #37 - cleanClass(5, 21), + cleanClass(6, 21), 'tall', true, 0, @@ -2158,7 +2211,7 @@ describe('style and class based bindings', () => { it('should skip updating multi classes and styles if the input identity has not changed', () => { - const stylingContext = initContext(); + const stylingContext = createStylingContext(); const getStylesAndClasses = trackStylesAndClasses(); const stylesMap = {width: '200px'}; @@ -2214,7 +2267,7 @@ describe('style and class based bindings', () => { }); it('should skip updating multi classes if the string-based identity has not changed', () => { - const stylingContext = initContext(); + const stylingContext = createStylingContext(); const getClasses = trackClassesFactory(); const classes = 'apple orange banana'; @@ -2262,7 +2315,7 @@ describe('style and class based bindings', () => { }); it('should skip issuing class updates if there is nothing to update upon first render', () => { - const stylingContext = initContext(null, null, ['blue'], ['blue']); + const stylingContext = createStylingContext(null, null, ['blue'], ['blue']); const store = new MockStylingStore(element as HTMLElement, BindingType.Class); const getClasses = trackClassesFactory(store); @@ -2292,7 +2345,7 @@ describe('style and class based bindings', () => { describe('players', () => { it('should build a player with the computed styles and classes', () => { - const context = initContext(); + const context = createStylingContext(); const styles = {width: '100px', height: '200px'}; const classes = 'foo bar'; @@ -2332,7 +2385,7 @@ describe('style and class based bindings', () => { }); it('should only build one player for a given style map', () => { - const context = initContext(null, []); + const context = createStylingContext(null, []); let count = 0; const buildFn = (element: HTMLElement, type: BindingType, value: any) => { @@ -2355,7 +2408,7 @@ describe('style and class based bindings', () => { }); it('should only build one player for a given class map', () => { - const context = initContext(null, []); + const context = createStylingContext(null, []); let count = 0; const buildFn = (element: HTMLElement, type: BindingType, value: any) => { @@ -2377,7 +2430,7 @@ describe('style and class based bindings', () => { }); it('should store active players in the player context and remove them once destroyed', () => { - const context = initContext(null, []); + const context = createStylingContext(null, []); const handler = new CorePlayerHandler(); const lView = createMockViewData(handler, context); @@ -2439,7 +2492,7 @@ describe('style and class based bindings', () => { it('should kick off single property change players alongside map-based ones and remove the players', () => { - const context = initContext(null, ['width', 'height'], null, ['foo', 'bar']); + const context = createStylingContext(null, ['width', 'height'], null, ['foo', 'bar']); const handler = new CorePlayerHandler(); const lView = createMockViewData(handler, context); @@ -2536,7 +2589,7 @@ describe('style and class based bindings', () => { it('should destroy an existing player that was queued before it is flushed once the binding updates', () => { - const context = initContext(null, ['width']); + const context = createStylingContext(null, ['width']); const handler = new CorePlayerHandler(); const lView = createMockViewData(handler, context); @@ -2571,7 +2624,7 @@ describe('style and class based bindings', () => { it('should nullify style map and style property factories if any follow up expressions not use them', () => { - const context = initContext(null, ['color'], null, ['foo']); + const context = createStylingContext(null, ['color'], null, ['foo']); const handler = new CorePlayerHandler(); const lView = createMockViewData(handler, context); @@ -2590,15 +2643,15 @@ describe('style and class based bindings', () => { }; assertContext(context, [ - element, // - masterConfig(17, false), // - [null, 2, false, null], // - [null, null, 'color', null], // - [null, null, 'foo', false], // - [1, 1, 1, 1, 9, 13], // - [1, 0, 21, null, 1], // - [1, 0, 17, null, 1], // - null, // + element, // + masterConfig(17, false), // + [null, 2, false, null], // + [null, null, 'color', null, 0], // + [null, null, 'foo', false, 0], // + [1, 1, 1, 1, 9, 13], // + [1, 0, 21, null, 1], // + [1, 0, 17, null, 1], // + null, // // #9 cleanStyle(3, 17), @@ -2656,8 +2709,8 @@ describe('style and class based bindings', () => { element, // masterConfig(17, false), // [null, 2, false, null], // - [null, null, 'color', null], // - [null, null, 'foo', false], // + [null, null, 'color', null, 0], // + [null, null, 'foo', false, 0], // [1, 1, 1, 1, 9, 13], // [1, 0, 25, classMapWithPlayerFactory, 1], // [1, 0, 17, styleMapWithPlayerFactory, 1], // @@ -2714,14 +2767,14 @@ describe('style and class based bindings', () => { ] as PlayerContext); assertContext(context, [ - element, // - masterConfig(17, false), // - [null, 2, false, null], // - [null, null, 'color', null], // - [null, null, 'foo', false], // - [1, 1, 1, 1, 9, 13], // - [1, 0, 25, cachedClassMap, 1], // - [1, 0, 17, cachedStyleMap, 1], // + element, // + masterConfig(17, false), // + [null, 2, false, null], // + [null, null, 'color', null, 0], // + [null, null, 'foo', false, 0], // + [1, 1, 1, 1, 9, 13], // + [1, 0, 25, cachedClassMap, 1], // + [1, 0, 17, cachedStyleMap, 1], // playerContext, // #9 @@ -2763,7 +2816,7 @@ describe('style and class based bindings', () => { }); it('should not call a factory if no style and/or class values have been updated', () => { - const context = initContext([]); + const context = createStylingContext([]); const handler = new CorePlayerHandler(); const lView = createMockViewData(handler, context); @@ -2813,7 +2866,7 @@ describe('style and class based bindings', () => { it('should invoke a single prop player over a multi style player when present and delegate back if not', () => { - const context = initContext(null, ['color']); + const context = createStylingContext(null, ['color']); const handler = new CorePlayerHandler(); const lView = createMockViewData(handler, context); @@ -2860,7 +2913,7 @@ describe('style and class based bindings', () => { }); it('should return the old player for styles when a follow-up player is instantiated', () => { - const context = initContext([]); + const context = createStylingContext([]); const handler = new CorePlayerHandler(); const lView = createMockViewData(handler, context); @@ -2890,7 +2943,7 @@ describe('style and class based bindings', () => { }); it('should return the old player for classes when a follow-up player is instantiated', () => { - const context = initContext(); + const context = createStylingContext(); const handler = new CorePlayerHandler(); const lView = createMockViewData(handler, context); @@ -2932,7 +2985,7 @@ describe('style and class based bindings', () => { } }) as StyleSanitizeFn; - const context = initContext(null, null, null, null, sanitizer); + const context = createStylingContext(null, null, null, null, sanitizer); const handler = new CorePlayerHandler(); const lView = createMockViewData(handler, context); @@ -2959,7 +3012,7 @@ describe('style and class based bindings', () => { it('should automatically destroy existing players when the follow-up binding is not apart of a factory', () => { - const context = initContext(null, ['width'], null, ['foo', 'bar']); + const context = createStylingContext(null, ['width'], null, ['foo', 'bar']); const handler = new CorePlayerHandler(); const lView = createMockViewData(handler, context); diff --git a/tools/material-ci/angular_material_test_blocklist.js b/tools/material-ci/angular_material_test_blocklist.js index d2fda4e4b3..5f1ce83d21 100644 --- a/tools/material-ci/angular_material_test_blocklist.js +++ b/tools/material-ci/angular_material_test_blocklist.js @@ -17,37 +17,33 @@ // tslint:disable window.testBlocklist = { - "Portals CdkPortalOutlet should not clear programmatically-attached portals on init": { - "error": "ObjectUnsubscribedError: object unsubscribed", - "notes": "Unknown" - }, "Portals DomPortalOutlet should attach and detach a component portal without a ViewContainerRef": { "error": "Error: Expected '

Pizza

Chocolate

' to be '', 'Expected the DomPortalOutlet to be empty after detach'.", "notes": "Unknown" }, "CdkDrag in a drop container should be able to customize the preview element": { "error": "Error: Expected cdk-drag cdk-drag-preview to contain 'custom-preview'.", - "notes": "FW-1134: Queries don't match structural directives with ng-template in selector" + "notes": "Unknown" }, "CdkDrag in a drop container should position custom previews next to the pointer": { "error": "Error: Expected 'translate3d(8px, 33px, 0px)' to be 'translate3d(50px, 50px, 0px)'.", - "notes": "FW-1134: Queries don't match structural directives with ng-template in selector" + "notes": "Unknown" }, "CdkDrag in a drop container should lock position inside a drop container along the x axis": { "error": "Error: Expected 'translate3d(58px, 33px, 0px)' to be 'translate3d(100px, 50px, 0px)'.", - "notes": "FW-1134: Queries don't match structural directives with ng-template in selector" + "notes": "Unknown" }, "CdkDrag in a drop container should lock position inside a drop container along the y axis": { "error": "Error: Expected 'translate3d(8px, 83px, 0px)' to be 'translate3d(50px, 100px, 0px)'.", - "notes": "FW-1134: Queries don't match structural directives with ng-template in selector" + "notes": "Unknown" }, "CdkDrag in a drop container should inherit the position locking from the drop container": { "error": "Error: Expected 'translate3d(58px, 33px, 0px)' to be 'translate3d(100px, 50px, 0px)'.", - "notes": "FW-1134: Queries don't match structural directives with ng-template in selector" + "notes": "Unknown" }, "CdkDrag in a drop container should be able to customize the placeholder": { "error": "Error: Expected cdk-drag cdk-drag-placeholder to contain 'custom-placeholder'.", - "notes": "FW-1134: Queries don't match structural directives with ng-template in selector" + "notes": "Unknown" }, "CdkTable should be able to render multiple header and footer rows": { "error": "Error: Missing definitions for header, footer, and row; cannot determine which columns should be rendered.", @@ -129,21 +125,13 @@ window.testBlocklist = { "error": "Error: Failed: Expected node descendant num to be 2 but was 0", "notes": "Unknown" }, - "MatInput without forms validates the type": { - "error": "Error: Input type \"file\" isn't supported by matInput.", - "notes": "Unknown" - }, - "MatInput with textarea autosize should work in a step": { - "error": "TypeError: Cannot read property 'getBoundingClientRect' of null", - "notes": "Unknown" - }, "MatChipList StandardChipList basic behaviors should toggle the chips disabled state based on whether it is disabled": { "error": "Error: Expected true to be false.", - "notes": "MatChipList does not find MatChip content children because descendants is not true anymore. TODO: Fix spec so that it does not have the wrapping div" + "notes": "Unknown" }, "MatChipList StandardChipList focus behaviors should focus the first chip on focus": { "error": "Error: Expected -1 to be 0.", - "notes": "MatChipList does not find MatChip content children because descendants is not true anymore. TODO: Fix spec so that it does not have the wrapping div" + "notes": "Unknown" }, "MatChipList StandardChipList focus behaviors should watch for chip focus": { "error": "TypeError: Cannot read property 'focus' of undefined", @@ -187,23 +175,23 @@ window.testBlocklist = { }, "MatChipList FormFieldChipList keyboard behavior should maintain focus if the active chip is deleted": { "error": "TypeError: Cannot read property 'nativeElement' of null", - "notes": "FW-1064: debugElement.query does not find directive when that directive precedes another" + "notes": "Unknown" }, "MatChipList FormFieldChipList keyboard behavior when the input has focus should not focus the last chip when press DELETE": { "error": "TypeError: Cannot read property 'nativeElement' of null", - "notes": "FW-1064: debugElement.query does not find directive when that directive precedes another" + "notes": "Unknown" }, "MatChipList FormFieldChipList keyboard behavior when the input has focus should focus the last chip when press BACKSPACE": { "error": "TypeError: Cannot read property 'nativeElement' of null", - "notes": "FW-1064: debugElement.query does not find directive when that directive precedes another" + "notes": "Unknown" }, "MatChipList FormFieldChipList should complete the stateChanges stream on destroy": { "error": "TypeError: Cannot read property 'nativeElement' of null", - "notes": "FW-1064: debugElement.query does not find directive when that directive precedes another" + "notes": "Unknown" }, "MatChipList FormFieldChipList should point the label id to the chip input": { "error": "TypeError: Cannot read property 'nativeElement' of null", - "notes": "FW-1064: debugElement.query does not find directive when that directive precedes another" + "notes": "Unknown" }, "MatChipList with chip remove should properly focus next item if chip is removed through click": { "error": "TypeError: Cannot read property 'focus' of undefined", @@ -229,30 +217,10 @@ window.testBlocklist = { "error": "TypeError: Cannot read property 'nativeElement' of undefined", "notes": "Unknown" }, - "MatStepper basic stepper should set the correct aria-posinset and aria-setsize": { - "error": "Error: Expected $.length = 0 to equal 3.", - "notes": "Unknown" - }, "MatStepper linear stepper should not move to next step if current step is pending": { "error": "TypeError: Cannot read property 'nativeElement' of undefined", "notes": "Unknown" }, - "MatStepper aria labelling should not set aria-label or aria-labelledby attributes if they are not passed in": { - "error": "TypeError: Cannot read property 'hasAttribute' of null", - "notes": "Unknown" - }, - "MatStepper aria labelling should set the aria-label attribute": { - "error": "TypeError: Cannot read property 'getAttribute' of null", - "notes": "Unknown" - }, - "MatStepper aria labelling should set the aria-labelledby attribute": { - "error": "TypeError: Cannot read property 'getAttribute' of null", - "notes": "Unknown" - }, - "MatStepper aria labelling should not be able to set both an aria-label and aria-labelledby": { - "error": "TypeError: Cannot read property 'getAttribute' of null", - "notes": "Unknown" - }, "MatStepper stepper with error state should show error state": { "error": "TypeError: Cannot read property 'nativeElement' of undefined", "notes": "Unknown" @@ -266,44 +234,36 @@ window.testBlocklist = { "notes": "Unknown" }, "MatSidenav should be fixed position when in fixed mode": { - "error": "Error: Expected ng-tns-c28435-0 ng-trigger ng-trigger-transform mat-drawer mat-drawer-over ng-star-inserted to contain 'mat-sidenav-fixed'.", - "notes": "FW-1132: Host class bindings don't work if super class has host class bindings" + "error": "Error: Expected ng-tns-c21962-0 ng-trigger ng-trigger-transform mat-drawer mat-sidenav mat-drawer-over ng-star-inserted to contain 'mat-sidenav-fixed'.", + "notes": "Unknown" }, "MatSidenav should set fixed bottom and top when in fixed mode": { "error": "Error: Expected '' to be '20px'.", - "notes": "FW-1132: Host class bindings don't work if super class has host class bindings" - }, - "MatTree flat tree should initialize with rendered dataNodes": { - "error": "TypeError: Cannot read property 'classList' of undefined", "notes": "FW-1081: Static host classes don't work if component has superclass with host classes" }, "MatTree flat tree with toggle should expand/collapse the node": { - "error": "TypeError: Cannot read property 'click' of undefined", - "notes": "FW-1081: Static host classes don't work if component has superclass with host classes" + "error": "Error: Expected 0 to be 1, 'Expect node expanded one level'.", + "notes": "Unknown" }, "MatTree flat tree with toggle should expand/collapse the node recursively": { - "error": "TypeError: Cannot read property 'click' of undefined", - "notes": "FW-1081: Static host classes don't work if component has superclass with host classes" - }, - "MatTree flat tree with undefined or null children should initialize with rendered dataNodes": { - "error": "TypeError: Cannot read property 'classList' of undefined", - "notes": "FW-1081: Static host classes don't work if component has superclass with host classes" - }, - "MatTree nested tree with undefined or null children should initialize with rendered dataNodes": { - "error": "TypeError: Cannot read property 'classList' of undefined", - "notes": "FW-1081: Static host classes don't work if component has superclass with host classes" - }, - "MatTree nested tree should initialize with rendered dataNodes": { - "error": "TypeError: Cannot read property 'classList' of undefined", - "notes": "FW-1081: Static host classes don't work if component has superclass with host classes" + "error": "Error: Expected 0 to be 3, 'Expect nodes expanded'.", + "notes": "Unknown" }, "MatTree nested tree with toggle should expand/collapse the node": { - "error": "TypeError: Cannot read property 'click' of undefined", - "notes": "FW-1081: Static host classes don't work if component has superclass with host classes" + "error": "Error: Expected 0 to be 1, 'Expect node expanded'.", + "notes": "Unknown" }, "MatTree nested tree with toggle should expand/collapse the node recursively": { - "error": "TypeError: Cannot read property 'click' of undefined", - "notes": "FW-1081: Static host classes don't work if component has superclass with host classes" + "error": "Error: Expected 0 to be 3, 'Expect node expanded'.", + "notes": "Unknown" + }, + "MatInput without forms validates the type": { + "error": "Error: Input type \"file\" isn't supported by matInput.", + "notes": "Unknown" + }, + "MatInput with textarea autosize should work in a step": { + "error": "TypeError: Cannot read property 'getBoundingClientRect' of null", + "notes": "Unknown" }, "Dialog should set the proper animation states": { "error": "TypeError: Cannot read property 'componentInstance' of null", @@ -355,79 +315,47 @@ window.testBlocklist = { }, "MatTooltip special cases should clear the `user-select` when a tooltip is set on a text field": { "error": "Error: Expected 'none' to be falsy.", - "notes": "FW-1133: Inline styles are not applied before constructor is run" + "notes": "Unknown" }, "MatTooltip special cases should clear the `-webkit-user-drag` on draggable elements": { "error": "Error: Expected 'none' to be falsy.", - "notes": "FW-1133: Inline styles are not applied before constructor is run" - }, - "MatTable with basic data source should be able to create a table with the right content and without when row": { - "error": "TypeError: Cannot read property 'querySelectorAll' of null", - "notes": "Unknown" - }, - "MatTable with basic data source should create a table with special when row": { - "error": "TypeError: Cannot read property 'querySelectorAll' of null", - "notes": "Unknown" - }, - "MatTable with basic data source should create a table with multiTemplateDataRows true": { - "error": "TypeError: Cannot read property 'querySelectorAll' of null", "notes": "Unknown" }, "MatTable should be able to render a table correctly with native elements": { "error": "Error: Missing definitions for header, footer, and row; cannot determine which columns should be rendered.", "notes": "Unknown" }, - "MatTable should render with MatTableDataSource and sort": { - "error": "TypeError: Cannot read property 'querySelectorAll' of null", - "notes": "Unknown" - }, - "MatTable should render with MatTableDataSource and pagination": { - "error": "TypeError: Cannot read property 'querySelectorAll' of null", - "notes": "Unknown" - }, "MatTable should apply custom sticky CSS class to sticky cells": { "error": "Error: Missing definitions for header, footer, and row; cannot determine which columns should be rendered.", "notes": "Unknown" }, - "MatTable with MatTableDataSource and sort/pagination/filter should create table and display data source contents": { - "error": "TypeError: Cannot read property 'querySelectorAll' of null", - "notes": "Unknown" - }, - "MatTable with MatTableDataSource and sort/pagination/filter changing data should update the table contents": { - "error": "TypeError: Cannot read property 'querySelectorAll' of null", - "notes": "Unknown" - }, "MatTable with MatTableDataSource and sort/pagination/filter should be able to filter the table contents": { - "error": "TypeError: Cannot read property 'querySelectorAll' of null", - "notes": "Unknown" - }, - "MatTable with MatTableDataSource and sort/pagination/filter should not match concatenated words": { - "error": "TypeError: Cannot read property 'querySelectorAll' of null", + "error": "TypeError: Cannot read property 'length' of undefined", "notes": "Unknown" }, "MatTable with MatTableDataSource and sort/pagination/filter should be able to sort the table contents": { - "error": "TypeError: Cannot read property 'querySelectorAll' of null", + "error": "Error: Failed: Expected cell contents to be a_3 but was a_1", "notes": "Unknown" }, "MatTable with MatTableDataSource and sort/pagination/filter should by default correctly sort an empty string": { - "error": "TypeError: Cannot read property 'querySelectorAll' of null", + "error": "Error: Failed: Expected cell contents to be but was a_1", "notes": "Unknown" }, "MatTable with MatTableDataSource and sort/pagination/filter should by default correctly sort undefined values": { - "error": "TypeError: Cannot read property 'querySelectorAll' of null", + "error": "Error: Failed: Expected cell contents to be but was a_1", "notes": "Unknown" }, "MatTable with MatTableDataSource and sort/pagination/filter should sort zero correctly": { - "error": "TypeError: Cannot read property 'querySelectorAll' of null", + "error": "Error: Failed: Expected cell contents to be -1 but was a_1", "notes": "Unknown" }, "MatTable with MatTableDataSource and sort/pagination/filter should be able to page the table contents": { - "error": "TypeError: Cannot read property 'querySelectorAll' of null", + "error": "Error: Failed: Expected 7 total rows but got 105", "notes": "Unknown" }, "MatTable with MatTableDataSource and sort/pagination/filter should sort strings with numbers larger than MAX_SAFE_INTEGER correctly": { - "error": "TypeError: Cannot read property 'querySelectorAll' of null", + "error": "Error: Failed: Expected cell contents to be 9563256840123535 but was a_1", "notes": "Unknown" } }; -// clang-format on +// clang-format on \ No newline at end of file