diff --git a/src/__tests__/to-be-disabled.js b/src/__tests__/to-be-disabled.js
index 2a7a426..585b91b 100644
--- a/src/__tests__/to-be-disabled.js
+++ b/src/__tests__/to-be-disabled.js
@@ -88,3 +88,15 @@ test('.toBeEnabled', () => {
expect(() => expect(queryByTestId('without')).not.toBeEnabled()).toThrowError();
expect(() => expect(queryByText('without')).not.toBeEnabled()).toThrowError();
});
+
+test('matcher misses', () => {
+ const { queryByTestId, queryByText } = render(
+
+
+
+ ,
+ );
+
+ expect(() => expect(queryByTestId('enabled')).toBeDisabled()).toThrowError();
+ expect(() => expect(queryByText('disabled')).toBeEnabled()).toThrowError();
+});
diff --git a/src/__tests__/to-have-prop.js b/src/__tests__/to-have-prop.js
new file mode 100644
index 0000000..2cec068
--- /dev/null
+++ b/src/__tests__/to-have-prop.js
@@ -0,0 +1,34 @@
+import React from 'react';
+import { Button, Text, View } from 'react-native';
+import { render } from 'native-testing-library';
+
+test('.toHaveProp', () => {
+ const { debug, queryByTestId } = render(
+
+
+ text
+
+
+ ,
+ );
+
+ expect(queryByTestId('button')).toHaveProp('accessibilityStates', ['disabled']);
+ expect(queryByTestId('button')).toHaveProp('accessible');
+ expect(queryByTestId('button')).not.toHaveProp('disabled');
+ expect(queryByTestId('button')).not.toHaveProp('title', 'ok');
+
+ expect(queryByTestId('text')).toHaveProp('allowFontScaling', false);
+ expect(queryByTestId('text')).not.toHaveProp('style');
+
+ expect(() =>
+ expect(queryByTestId('button')).not.toHaveProp('accessibilityStates', ['disabled']),
+ ).toThrowError();
+ expect(() => expect(queryByTestId('button')).not.toHaveProp('accessible')).toThrowError();
+ expect(() => expect(queryByTestId('button')).toHaveProp('disabled')).toThrowError();
+ expect(() => expect(queryByTestId('button')).toHaveProp('title', 'ok')).toThrowError();
+
+ expect(() =>
+ expect(queryByTestId('text')).not.toHaveProp('allowFontScaling', false),
+ ).toThrowError();
+ expect(() => expect(queryByTestId('text')).toHaveProp('style')).toThrowError();
+});
diff --git a/src/index.js b/src/index.js
index 249971f..37a4393 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1,6 +1,7 @@
import { toBeDisabled, toBeEnabled } from './to-be-disabled';
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';
-export { toBeDisabled, toContainElement, toBeEmpty, toHaveTextContent, toBeEnabled };
+export { toBeDisabled, toContainElement, toBeEmpty, toHaveProp, toHaveTextContent, toBeEnabled };
diff --git a/src/to-have-prop.js b/src/to-have-prop.js
new file mode 100644
index 0000000..0212a64
--- /dev/null
+++ b/src/to-have-prop.js
@@ -0,0 +1,47 @@
+import { equals, isNil, not } from 'ramda';
+import { matcherHint, stringify, printExpected } from 'jest-matcher-utils';
+import { checkReactElement, getMessage, VALID_ELEMENTS } from './utils';
+
+function printAttribute(name, value) {
+ return value === undefined ? name : `${name}=${stringify(value)}`;
+}
+
+function getPropComment(name, value) {
+ return value === undefined
+ ? `element.hasProp(${stringify(name)})`
+ : `element.getAttribute(${stringify(name)}) === ${stringify(value)}`;
+}
+
+export function toHaveProp(element, name, expectedValue) {
+ checkReactElement(element, toHaveProp, this);
+
+ const prop = element.props[name];
+
+ const isDefined = expectedValue !== undefined;
+ const isAllowed = VALID_ELEMENTS.includes(element.type);
+ const hasProp = not(isNil(prop));
+
+ return {
+ pass: isDefined && isAllowed ? hasProp && equals(prop, expectedValue) : hasProp,
+ message: () => {
+ const to = this.isNot ? 'not to' : 'to';
+ const receivedProp = hasProp ? printAttribute(name, prop) : null;
+ const matcher = matcherHint(
+ `${this.isNot ? '.not' : ''}.toHaveProp`,
+ 'element',
+ printExpected(name),
+ {
+ secondArgument: isDefined ? printExpected(expectedValue) : undefined,
+ comment: getPropComment(name, expectedValue),
+ },
+ );
+ return getMessage(
+ matcher,
+ `Expected the element ${to} have prop`,
+ printAttribute(name, expectedValue),
+ 'Received',
+ receivedProp,
+ );
+ },
+ };
+}
diff --git a/src/utils.js b/src/utils.js
index 9c0ea23..f41db43 100644
--- a/src/utils.js
+++ b/src/utils.js
@@ -51,10 +51,6 @@ function checkReactElement(element, ...args) {
}
}
-function display(value) {
- return typeof value === 'string' ? value : stringify(value);
-}
-
function getType({ type }) {
return type.displayName || type.name || type;
}
@@ -70,6 +66,10 @@ function printElement({ props }) {
)}`;
}
+function display(value) {
+ return typeof value === 'string' ? value : stringify(value)
+}
+
function getMessage(matcher, expectedLabel, expectedValue, receivedLabel, receivedValue) {
return [
`${matcher}\n`,