From 17cf04ebea6a7d2857881c13140af4ba0ffb6990 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matias=20Niemel=C3=A4?= Date: Mon, 2 Mar 2020 14:47:46 -0800 Subject: [PATCH] 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 --- .../animations/browser/src/render/shared.ts | 31 ++++++++++++++----- .../browser/test/render/shared_spec.ts | 9 ++++++ 2 files changed, 33 insertions(+), 7 deletions(-) diff --git a/packages/animations/browser/src/render/shared.ts b/packages/animations/browser/src/render/shared.ts index 6ae40fd625..f9fd26f3ea 100644 --- a/packages/animations/browser/src/render/shared.ts +++ b/packages/animations/browser/src/render/shared.ts @@ -248,12 +248,12 @@ export function hypenatePropsObject(object: {[key: string]: any}): {[key: string * for that. */ 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 // set of properties and `prop` is a dynamic reference to a property within // 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 // 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, // right, bottom and left properties as individual style lookups. 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` - const propTop = (prop + 'Top') as 'marginTop' | 'paddingTop'; - const propRight = (prop + 'Right') as 'marginRight' | 'paddingRight'; - const propBottom = (prop + 'Bottom') as 'marginBottom' | 'paddingBottom'; - const propLeft = (prop + 'Left') as 'marginLeft' | 'paddingLeft'; - value = `${gcs[propTop]} ${gcs[propRight]} ${gcs[propBottom]} ${gcs[propLeft]}`; + // we `trim()` the value because if all of the values above are + // empty string values then we would like the return value to + // also be an empty string. + value = `${t} ${r} ${b} ${l}`.trim(); } 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( + styles: CSSStyleDeclaration, prop: K): string { + const value = styles[prop]; + return typeof value === 'string' ? value : ''; +} diff --git a/packages/animations/browser/test/render/shared_spec.ts b/packages/animations/browser/test/render/shared_spec.ts index a181cd7a25..ffc5070826 100644 --- a/packages/animations/browser/test/render/shared_spec.ts +++ b/packages/animations/browser/test/render/shared_spec.ts @@ -11,6 +11,15 @@ describe('shared animations code', () => { if (isNode) return; 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', () => { const div = buildActualElement();