Skip to content

Commit

Permalink
.toHaveStyle() to support transform styles (#26)
Browse files Browse the repository at this point in the history
* feat(toHaveStyle): Add ability to assert on transform styles

* fix(docs): Change doc block lang to typescript to prevent prettier adding parens
  • Loading branch information
philipbulley authored Sep 7, 2020
1 parent 2649017 commit e598542
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 11 deletions.
15 changes: 12 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -279,13 +279,22 @@ properties. You cannot pass properties from a React Native stylesheet.
const styles = StyleSheet.create({ text: { fontSize: 16 } });

const { queryByText } = render(
<Text style={[{ color: 'black', fontWeight: '600' }, styles.text]}>Hello World</Text>,
<Text
style={[
{ color: 'black', fontWeight: '600', transform: [{ scale: 2 }, { rotate: '45deg' }] },
styles.text,
]}
>
Hello World
</Text>,
);

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' });
```
Expand Down
28 changes: 28 additions & 0 deletions src/__tests__/__snapshots__/to-have-style.js.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`.toHaveStyle handles negative test cases 1`] = `
"<dim>expect(</><red>element</><dim>).toHaveStyle()</>
<green>- Expected</>
<dim> backgroundColor: blue;</>
<dim> transform: [</>
<dim> {</>
<green>- \\"scale\\": 1</>
<red>+ \\"scale\\": 2</>
<dim> }</>
<dim> ];</>"
`;
exports[`.toHaveStyle handles transform when transform undefined 1`] = `
"<dim>expect(</><red>element</><dim>).toHaveStyle()</>
<green>- Expected</>
<green>- transform: [</>
<green>- {</>
<green>- \\"scale\\": 1</>
<green>- }</>
<green>- ];</>
<red>+ transform: undefined;</>"
`;
45 changes: 42 additions & 3 deletions src/__tests__/to-have-style.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,15 @@ describe('.toHaveStyle', () => {
const { getByTestId } = render(
<View
testID="container"
style={[{ backgroundColor: 'blue', height: '100%' }, [{ width: '50%' }], styles.container]}
style={[
{
backgroundColor: 'blue',
height: '100%',
transform: [{ scale: 2 }, { rotate: '45deg' }],
},
[{ width: '50%' }],
styles.container,
]}
>
<Text>Hello World</Text>
</View>,
Expand All @@ -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(
<View testID="container" style={{ backgroundColor: 'blue', color: 'black', height: '100%' }}>
<View
testID="container"
style={{
backgroundColor: 'blue',
color: 'black',
height: '100%',
transform: [{ scale: 2 }, { rotate: '45deg' }],
}}
>
<Text>Hello World</Text>
</View>,
);

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();
});
Expand All @@ -48,4 +68,23 @@ describe('.toHaveStyle', () => {

expect(container).not.toHaveStyle({ fontWeight: 'bold' });
});

test('handles transform when transform undefined', () => {
const { getByTestId } = render(
<View
testID="container"
style={{
backgroundColor: 'blue',
transform: undefined,
}}
>
<Text>Hello World</Text>
</View>,
);

const container = getByTestId('container');
expect(() =>
expect(container).toHaveStyle({ transform: [{ scale: 1 }] }),
).toThrowErrorMatchingSnapshot();
});
});
34 changes: 29 additions & 5 deletions src/to-have-style.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand All @@ -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
Expand Down

0 comments on commit e598542

Please sign in to comment.