diff --git a/README.md b/README.md
index f52cc32..03e8351 100644
--- a/README.md
+++ b/README.md
@@ -27,6 +27,7 @@
+
## Table of Contents
- [The problem](#the-problem)
@@ -40,7 +41,7 @@
- [`toContainElement(element)`](#tocontainelementelement)
- [`toHaveProp(prop, value)`](#tohavepropprop-value)
- [`toHaveTextContent(text)`](#tohavetextcontenttext)
-- [Todo list](#todo-list)
+ - [`toHaveStyle(styles)`](#tohavestylestyles)
- [Inspiration](#inspiration)
- [Other solutions](#other-solutions)
@@ -244,9 +245,33 @@ expect(queryByTestId('count-value')).toHaveTextContent(/2/);
expect(queryByTestId('count-value')).not.toHaveTextContent('21');
```
-## Todo list
+### `toHaveStyle(styles)`
+
+```javascript
+toHaveStyle(styles);
+```
-- [ ] toHaveStyle(any) {?}
+Check if an element has the supplied styles.
+
+You can pass either an object of React Native style properties, or an array of objects with style
+properties. You cannot pass properties from a React Native stylesheet..
+
+#### Examples
+
+```javascript
+const styles = StyleSheet.create({ text: { fontSize: 16 } });
+
+const { queryByText } = render(
+ 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([{ color: 'black' }, { fontWeight: '600' }]);
+expect(queryByText('Hello World')).not.toHaveStyle({ color: 'white' });
+```
## Inspiration
diff --git a/src/__tests__/to-have-style.js b/src/__tests__/to-have-style.js
new file mode 100644
index 0000000..c18c963
--- /dev/null
+++ b/src/__tests__/to-have-style.js
@@ -0,0 +1,38 @@
+import React from 'react';
+import { StyleSheet, View, Text } from 'react-native';
+import { render } from 'native-testing-library';
+
+describe('.toHaveStyle', () => {
+ test('handles positive test cases', () => {
+ const styles = StyleSheet.create({ container: { color: 'white' } });
+ const { getByTestId } = render(
+
+ Hello World
+ ,
+ );
+
+ const container = getByTestId('container');
+
+ expect(container).toHaveStyle({ backgroundColor: 'blue', height: '100%' });
+ expect(container).toHaveStyle([{ backgroundColor: 'blue' }, { height: '100%' }]);
+ expect(container).toHaveStyle({ backgroundColor: 'blue' });
+ expect(container).toHaveStyle({ height: '100%' });
+ expect(container).toHaveStyle({ color: 'white' });
+ });
+
+ test('handles negative test cases', () => {
+ const { getByTestId } = render(
+
+ Hello World
+ ,
+ );
+
+ const container = getByTestId('container');
+
+ expect(() => expect(container).toHaveStyle({ fontWeight: 'bold' })).toThrowError();
+ expect(() => expect(container).not.toHaveStyle({ color: 'black' })).toThrowError();
+ });
+});
diff --git a/src/index.js b/src/index.js
index 37a4393..e66f7d9 100644
--- a/src/index.js
+++ b/src/index.js
@@ -3,5 +3,14 @@ import { toBeEmpty } from './to-be-empty';
import { toHaveProp } from './to-have-prop';
import { toHaveTextContent } from './to-have-text-content';
import { toContainElement } from './to-contain-element';
+import { toHaveStyle } from './to-have-style';
-export { toBeDisabled, toContainElement, toBeEmpty, toHaveProp, toHaveTextContent, toBeEnabled };
+export {
+ toBeDisabled,
+ toContainElement,
+ toBeEmpty,
+ toHaveProp,
+ toHaveTextContent,
+ toBeEnabled,
+ toHaveStyle,
+};
diff --git a/src/to-have-style.js b/src/to-have-style.js
new file mode 100644
index 0000000..fdba11a
--- /dev/null
+++ b/src/to-have-style.js
@@ -0,0 +1,49 @@
+import { matcherHint } from 'jest-matcher-utils';
+import jestDiff from 'jest-diff';
+import chalk from 'chalk';
+import { all, compose, mergeAll, toPairs } from 'ramda';
+
+import { checkReactElement } from './utils';
+
+function isSubset(expected, received) {
+ return compose(
+ all(([prop, value]) => received[prop] === value),
+ toPairs,
+ )(expected);
+}
+
+function printoutStyles(styles) {
+ return Object.keys(styles)
+ .sort()
+ .map(prop => `${prop}: ${styles[prop]};`)
+ .join('\n');
+}
+
+// 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 diffOutput = jestDiff(printoutStyles(expected), printoutStyles(received));
+ // Remove the "+ Received" annotation because this is a one-way diff
+ return diffOutput.replace(`${chalk.red('+ Received')}\n`, '');
+}
+
+export function toHaveStyle(element, style) {
+ checkReactElement(element, toHaveStyle, this);
+
+ const elementStyle = element.props.style;
+
+ const expected = Array.isArray(style) ? mergeAll(style) : style;
+ const received = Array.isArray(elementStyle) ? mergeAll(elementStyle) : elementStyle;
+
+ return {
+ pass: isSubset(expected, received),
+ message: () => {
+ const matcher = `${this.isNot ? '.not' : ''}.toHaveStyle`;
+ return [matcherHint(matcher, 'element', ''), expectedDiff(expected, received)].join('\n\n');
+ },
+ };
+}
diff --git a/src/utils.js b/src/utils.js
index d45f2a0..38870ef 100644
--- a/src/utils.js
+++ b/src/utils.js
@@ -36,6 +36,7 @@ class ReactElementTypeError extends Error {
try {
withType = printWithType('Received', received, printReceived);
} catch (e) {}
+ /* istanbul ignore next */
this.message = [
matcherHint(`${context.isNot ? '.not' : ''}.${matcherFn.name}`, 'received', ''),
'',