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
This commit is contained in:
Matias Niemelä 2019-03-01 14:15:11 -08:00 committed by Andrew Kushnir
parent 48214e2a05
commit 78adcfe0ee
13 changed files with 721 additions and 529 deletions

View File

@ -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);
}
}
}
/**

View File

@ -323,9 +323,9 @@ export interface StylingContext extends
*
* See [InitialStylingValuesIndex] for a breakdown of how all this works.
*/
export interface InitialStylingValues extends Array<string|boolean|null> {
export interface InitialStylingValues extends Array<string|boolean|number|null> {
[InitialStylingValuesIndex.DefaultNullValuePosition]: null;
[InitialStylingValuesIndex.InitialClassesStringPosition]: string|null;
[InitialStylingValuesIndex.CachedStringValuePosition]: string|null;
}
/**
@ -432,12 +432,48 @@ export interface InitialStylingValues extends Array<string|boolean|null> {
* ```
*/
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
}
/**

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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();
});
});

View File

@ -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();
});
});

View File

@ -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"
},

View File

@ -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"
},

View File

@ -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();
}

View File

@ -317,7 +317,7 @@ describe('css selector matching', () => {
expect(isMatching('div', tNode, selector)).toBeTruthy();
// <div class="abc"> (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();
});

View File

@ -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-msg><p>Pizza</p><p>Chocolate</p></pizza-msg>' 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,78 +315,46 @@ 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"
}
};