From e598542add9bd7d0885f9201a75004a237a5c3e0 Mon Sep 17 00:00:00 2001 From: Philip Bulley Date: Mon, 7 Sep 2020 15:42:25 +0100 Subject: [PATCH] .toHaveStyle() to support transform styles (#26) * feat(toHaveStyle): Add ability to assert on transform styles * fix(docs): Change doc block lang to typescript to prevent prettier adding parens --- README.md | 15 +++++-- .../__snapshots__/to-have-style.js.snap | 28 ++++++++++++ src/__tests__/to-have-style.js | 45 +++++++++++++++++-- src/to-have-style.js | 34 +++++++++++--- 4 files changed, 111 insertions(+), 11 deletions(-) create mode 100644 src/__tests__/__snapshots__/to-have-style.js.snap diff --git a/README.md b/README.md index 216d0a5..615e3c3 100644 --- a/README.md +++ b/README.md @@ -264,8 +264,8 @@ expect(queryByTestId('count-value')).not.toHaveTextContent('21'); ### `toHaveStyle` -```javascript -toHaveStyle((style: object[] | object)); +```typescript +toHaveStyle(style: object[] | object); ``` Check if an element has the supplied styles. @@ -279,13 +279,22 @@ properties. You cannot pass properties from a React Native stylesheet. const styles = StyleSheet.create({ text: { fontSize: 16 } }); const { queryByText } = render( - Hello World, + + Hello World + , ); expect(queryByText('Hello World')).toHaveStyle({ color: 'black', fontWeight: '600', fontSize: 16 }); expect(queryByText('Hello World')).toHaveStyle({ color: 'black' }); expect(queryByText('Hello World')).toHaveStyle({ fontWeight: '600' }); expect(queryByText('Hello World')).toHaveStyle({ fontSize: 16 }); +expect(queryByText('Hello World')).toHaveStyle({ transform: [{ scale: 2 }, { rotate: '45deg' }] }); +expect(queryByText('Hello World')).toHaveStyle({ transform: [{ rotate: '45deg' }] }); expect(queryByText('Hello World')).toHaveStyle([{ color: 'black' }, { fontWeight: '600' }]); expect(queryByText('Hello World')).not.toHaveStyle({ color: 'white' }); ``` diff --git a/src/__tests__/__snapshots__/to-have-style.js.snap b/src/__tests__/__snapshots__/to-have-style.js.snap new file mode 100644 index 0000000..da86843 --- /dev/null +++ b/src/__tests__/__snapshots__/to-have-style.js.snap @@ -0,0 +1,28 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`.toHaveStyle handles negative test cases 1`] = ` +"expect(element).toHaveStyle() + +- Expected + + backgroundColor: blue; + transform: [ + { +- \\"scale\\": 1 ++ \\"scale\\": 2 + } + ];" +`; + +exports[`.toHaveStyle handles transform when transform undefined 1`] = ` +"expect(element).toHaveStyle() + +- Expected + +- transform: [ +- { +- \\"scale\\": 1 +- } +- ]; ++ transform: undefined;" +`; diff --git a/src/__tests__/to-have-style.js b/src/__tests__/to-have-style.js index a179f94..054a1f4 100644 --- a/src/__tests__/to-have-style.js +++ b/src/__tests__/to-have-style.js @@ -8,7 +8,15 @@ describe('.toHaveStyle', () => { const { getByTestId } = render( Hello World , @@ -22,17 +30,29 @@ describe('.toHaveStyle', () => { expect(container).toHaveStyle({ height: '100%' }); expect(container).toHaveStyle({ color: 'white' }); expect(container).toHaveStyle({ width: '50%' }); + expect(container).toHaveStyle({ transform: [{ scale: 2 }, { rotate: '45deg' }] }); + expect(container).toHaveStyle({ transform: [{ rotate: '45deg' }] }); }); test('handles negative test cases', () => { const { getByTestId } = render( - + Hello World , ); const container = getByTestId('container'); - + expect(() => + expect(container).toHaveStyle({ backgroundColor: 'blue', transform: [{ scale: 1 }] }), + ).toThrowErrorMatchingSnapshot(); expect(() => expect(container).toHaveStyle({ fontWeight: 'bold' })).toThrowError(); expect(() => expect(container).not.toHaveStyle({ color: 'black' })).toThrowError(); }); @@ -48,4 +68,23 @@ describe('.toHaveStyle', () => { expect(container).not.toHaveStyle({ fontWeight: 'bold' }); }); + + test('handles transform when transform undefined', () => { + const { getByTestId } = render( + + Hello World + , + ); + + const container = getByTestId('container'); + expect(() => + expect(container).toHaveStyle({ transform: [{ scale: 1 }] }), + ).toThrowErrorMatchingSnapshot(); + }); }); diff --git a/src/to-have-style.js b/src/to-have-style.js index 4cd3e5b..e639c92 100644 --- a/src/to-have-style.js +++ b/src/to-have-style.js @@ -7,7 +7,11 @@ import { checkReactElement } from './utils'; function isSubset(expected, received) { return compose( - all(([prop, value]) => received[prop] === value), + all(([prop, value]) => + Array.isArray(value) + ? isSubset(mergeAll(value), mergeAll(received[prop])) + : received[prop] === value, + ), toPairs, )(expected); } @@ -19,16 +23,36 @@ function mergeAllStyles(styles) { function printoutStyles(styles) { return Object.keys(styles) .sort() - .map(prop => `${prop}: ${styles[prop]};`) + .map(prop => + Array.isArray(styles[prop]) + ? `${prop}: ${JSON.stringify(styles[prop], null, 2)};` + : `${prop}: ${styles[prop]};`, + ) .join('\n'); } +/** + * Recursively narrows down the properties in received to those with counterparts in expected + */ +function narrow(expected, received) { + return Object.keys(received) + .filter(prop => expected[prop]) + .reduce( + (obj, prop) => + Object.assign(obj, { + [prop]: + Array.isArray(expected[prop]) && Array.isArray(received[prop]) + ? expected[prop].map((_, i) => narrow(expected[prop][i], received[prop][i])) + : received[prop], + }), + {}, + ); +} + // Highlights only style rules that were expected but were not found in the // received computed styles function expectedDiff(expected, elementStyles) { - const received = Object.keys(elementStyles) - .filter(prop => expected[prop]) - .reduce((obj, prop) => Object.assign(obj, { [prop]: elementStyles[prop] }), {}); + const received = narrow(expected, elementStyles); const diffOutput = jestDiff(printoutStyles(expected), printoutStyles(received)); // Remove the "+ Received" annotation because this is a one-way diff