fix(animations): allow computeStyle to work on elements created in Node (#35810)

This patch is a follow-up patch to 35c9f0dc2f3665d4f9d9ece328cee4559bbec9c6.
It changes the `computeStyle` function to handle situations where
non string based values are returned from `window.getComputedStyle`.
This situation usually ocurrs in Node-based test environments where
the element or `window.getComputedStyle` is mocked out.

PR Close #35810
This commit is contained in:
Matias Niemelä 2020-03-02 14:47:46 -08:00 committed by atscott
parent c7d0567d37
commit 17cf04ebea
2 changed files with 33 additions and 7 deletions

View File

@ -248,12 +248,12 @@ export function hypenatePropsObject(object: {[key: string]: any}): {[key: string
* for that. * for that.
*/ */
export function computeStyle(element: HTMLElement, prop: string): string { export function computeStyle(element: HTMLElement, prop: string): string {
const gcs = window.getComputedStyle(element); const styles = window.getComputedStyle(element);
// this is casted to any because the `CSSStyleDeclaration` type is a fixed // this is casted to any because the `CSSStyleDeclaration` type is a fixed
// set of properties and `prop` is a dynamic reference to a property within // set of properties and `prop` is a dynamic reference to a property within
// the `CSSStyleDeclaration` list. // the `CSSStyleDeclaration` list.
let value = gcs[prop as any]; let value = getComputedValue(styles, prop as keyof CSSStyleDeclaration);
// Firefox returns empty string values for `margin` and `padding` properties // Firefox returns empty string values for `margin` and `padding` properties
// when extracted using getComputedStyle (see similar issue here: // when extracted using getComputedStyle (see similar issue here:
@ -261,13 +261,30 @@ export function computeStyle(element: HTMLElement, prop: string): string {
// we want to emulate the value that is returned by creating the top, // we want to emulate the value that is returned by creating the top,
// right, bottom and left properties as individual style lookups. // right, bottom and left properties as individual style lookups.
if (value.length === 0 && (prop === 'margin' || prop === 'padding')) { if (value.length === 0 && (prop === 'margin' || prop === 'padding')) {
const t = getComputedValue(styles, (prop + 'Top') as 'marginTop' | 'paddingTop');
const r = getComputedValue(styles, (prop + 'Right') as 'marginRight' | 'paddingRight');
const b = getComputedValue(styles, (prop + 'Bottom') as 'marginBottom' | 'paddingBottom');
const l = getComputedValue(styles, (prop + 'Left') as 'marginLeft' | 'paddingLeft');
// reconstruct the padding/margin value as `top right bottom left` // reconstruct the padding/margin value as `top right bottom left`
const propTop = (prop + 'Top') as 'marginTop' | 'paddingTop'; // we `trim()` the value because if all of the values above are
const propRight = (prop + 'Right') as 'marginRight' | 'paddingRight'; // empty string values then we would like the return value to
const propBottom = (prop + 'Bottom') as 'marginBottom' | 'paddingBottom'; // also be an empty string.
const propLeft = (prop + 'Left') as 'marginLeft' | 'paddingLeft'; value = `${t} ${r} ${b} ${l}`.trim();
value = `${gcs[propTop]} ${gcs[propRight]} ${gcs[propBottom]} ${gcs[propLeft]}`;
} }
return value; return value;
} }
/**
* Reads and returns the provided property style from the provided styles collection.
*
* This function is useful because it will return an empty string in the
* event that the value obtained from the styles collection is a non-string
* value (which is usually the case if the `styles` object is mocked out).
*/
function getComputedValue<K extends keyof CSSStyleDeclaration>(
styles: CSSStyleDeclaration, prop: K): string {
const value = styles[prop];
return typeof value === 'string' ? value : '';
}

View File

@ -11,6 +11,15 @@ describe('shared animations code', () => {
if (isNode) return; if (isNode) return;
describe('computeStyle', () => { describe('computeStyle', () => {
it('should return an empty string if the inner computed style value is not a string', () => {
const gcsSpy = spyOn(window, 'getComputedStyle').and.returnValue(() => null);
const elementLike = buildActualElement();
expect(computeStyle(elementLike, 'width')).toEqual('');
expect(computeStyle(elementLike, 'padding')).toEqual('');
expect(computeStyle(elementLike, 'margin')).toEqual('');
gcsSpy.and.callThrough();
});
it('should compute the margin style into the form top,right,bottom,left', () => { it('should compute the margin style into the form top,right,bottom,left', () => {
const div = buildActualElement(); const div = buildActualElement();