diff --git a/src/helpers/accessiblity.ts b/src/helpers/accessiblity.ts
index b29088ad9..a2b1cca88 100644
--- a/src/helpers/accessiblity.ts
+++ b/src/helpers/accessiblity.ts
@@ -160,3 +160,10 @@ export function getAccessibilityState(element: ReactTestInstance) {
selected: ariaSelected ?? accessibilityState?.selected,
};
}
+
+export function getAccessibilityCheckedState(
+ element: ReactTestInstance
+): AccessibilityState['checked'] {
+ const { accessibilityState, 'aria-checked': ariaChecked } = element.props;
+ return ariaChecked ?? accessibilityState?.checked;
+}
diff --git a/src/matchers/__tests__/to-be-checked.test.tsx b/src/matchers/__tests__/to-be-checked.test.tsx
new file mode 100644
index 000000000..83f75c3b2
--- /dev/null
+++ b/src/matchers/__tests__/to-be-checked.test.tsx
@@ -0,0 +1,178 @@
+import React from 'react';
+import { type AccessibilityRole, View } from 'react-native';
+import render from '../../render';
+import { screen } from '../../screen';
+import '../extend-expect';
+
+function renderViewsWithRole(role: AccessibilityRole) {
+ return render(
+ <>
+
+
+
+
+ >
+ );
+}
+
+test('toBeCheck() with checkbox role', () => {
+ renderViewsWithRole('checkbox');
+
+ const checked = screen.getByTestId('checkbox-checked');
+ const unchecked = screen.getByTestId('checkbox-unchecked');
+ const mixed = screen.getByTestId('checkbox-mixed');
+ const defaultView = screen.getByTestId('checkbox-default');
+
+ expect(checked).toBeChecked();
+ expect(unchecked).not.toBeChecked();
+ expect(mixed).not.toBeChecked();
+ expect(defaultView).not.toBeChecked();
+
+ expect(() => expect(checked).not.toBeChecked())
+ .toThrowErrorMatchingInlineSnapshot(`
+ "expect(element).not.toBeChecked()
+
+ Received element is checked:
+ "
+ `);
+ expect(() => expect(unchecked).toBeChecked())
+ .toThrowErrorMatchingInlineSnapshot(`
+ "expect(element).toBeChecked()
+
+ Received element is not checked:
+ "
+ `);
+ expect(() => expect(mixed).toBeChecked()).toThrowErrorMatchingInlineSnapshot(`
+ "expect(element).toBeChecked()
+
+ Received element is not checked:
+ "
+ `);
+ expect(() => expect(defaultView).toBeChecked())
+ .toThrowErrorMatchingInlineSnapshot(`
+ "expect(element).toBeChecked()
+
+ Received element is not checked:
+ "
+ `);
+});
+
+test('toBeCheck() with radio role', () => {
+ renderViewsWithRole('radio');
+
+ const checked = screen.getByTestId('radio-checked');
+ const unchecked = screen.getByTestId('radio-unchecked');
+ const defaultView = screen.getByTestId('radio-default');
+
+ expect(checked).toBeChecked();
+ expect(unchecked).not.toBeChecked();
+ expect(defaultView).not.toBeChecked();
+
+ expect(() => expect(checked).not.toBeChecked())
+ .toThrowErrorMatchingInlineSnapshot(`
+ "expect(element).not.toBeChecked()
+
+ Received element is checked:
+ "
+ `);
+ expect(() => expect(unchecked).toBeChecked())
+ .toThrowErrorMatchingInlineSnapshot(`
+ "expect(element).toBeChecked()
+
+ Received element is not checked:
+ "
+ `);
+ expect(() => expect(defaultView).toBeChecked())
+ .toThrowErrorMatchingInlineSnapshot(`
+ "expect(element).toBeChecked()
+
+ Received element is not checked:
+ "
+ `);
+});
+
+test('throws error for invalid role', () => {
+ renderViewsWithRole('adjustable');
+
+ const checked = screen.getByTestId('adjustable-checked');
+ const unchecked = screen.getByTestId('adjustable-unchecked');
+
+ expect(() =>
+ expect(checked).toBeChecked()
+ ).toThrowErrorMatchingInlineSnapshot(
+ `"toBeChecked() works only on accessibility elements with "checkbox" or "radio" role."`
+ );
+ expect(() =>
+ expect(unchecked).not.toBeChecked()
+ ).toThrowErrorMatchingInlineSnapshot(
+ `"toBeChecked() works only on accessibility elements with "checkbox" or "radio" role."`
+ );
+});
diff --git a/src/matchers/__tests__/to-be-partially-checked.test.tsx b/src/matchers/__tests__/to-be-partially-checked.test.tsx
new file mode 100644
index 000000000..b4f781801
--- /dev/null
+++ b/src/matchers/__tests__/to-be-partially-checked.test.tsx
@@ -0,0 +1,109 @@
+import React from 'react';
+import { type AccessibilityRole, View } from 'react-native';
+import render from '../../render';
+import { screen } from '../../screen';
+import '../extend-expect';
+
+function renderViewsWithRole(role: AccessibilityRole) {
+ return render(
+ <>
+
+
+
+
+ >
+ );
+}
+
+test('toBePartiallyCheck() with checkbox role', () => {
+ renderViewsWithRole('checkbox');
+
+ const checked = screen.getByTestId('checkbox-checked');
+ const unchecked = screen.getByTestId('checkbox-unchecked');
+ const mixed = screen.getByTestId('checkbox-mixed');
+ const defaultView = screen.getByTestId('checkbox-default');
+
+ expect(mixed).toBePartiallyChecked();
+
+ expect(checked).not.toBePartiallyChecked();
+ expect(unchecked).not.toBePartiallyChecked();
+ expect(defaultView).not.toBePartiallyChecked();
+
+ expect(() => expect(mixed).not.toBePartiallyChecked())
+ .toThrowErrorMatchingInlineSnapshot(`
+ "expect(element).not.toBePartiallyChecked()
+
+ Received element is partially checked:
+ "
+ `);
+
+ expect(() => expect(checked).toBePartiallyChecked())
+ .toThrowErrorMatchingInlineSnapshot(`
+ "expect(element).toBePartiallyChecked()
+
+ Received element is not partially checked:
+ "
+ `);
+ expect(() => expect(defaultView).toBePartiallyChecked())
+ .toThrowErrorMatchingInlineSnapshot(`
+ "expect(element).toBePartiallyChecked()
+
+ Received element is not partially checked:
+ "
+ `);
+});
+
+test('toBeCheck() with radio role', () => {
+ renderViewsWithRole('radio');
+
+ const checked = screen.getByTestId('radio-checked');
+ const mixed = screen.getByTestId('radio-mixed');
+
+ expect(() =>
+ expect(checked).toBePartiallyChecked()
+ ).toThrowErrorMatchingInlineSnapshot(
+ `"toBePartiallyChecked() works only on accessibility elements with "checkbox" role."`
+ );
+ expect(() =>
+ expect(mixed).toBePartiallyChecked()
+ ).toThrowErrorMatchingInlineSnapshot(
+ `"toBePartiallyChecked() works only on accessibility elements with "checkbox" role."`
+ );
+});
diff --git a/src/matchers/extend-expect.d.ts b/src/matchers/extend-expect.d.ts
index 7123d9d0b..781db2d38 100644
--- a/src/matchers/extend-expect.d.ts
+++ b/src/matchers/extend-expect.d.ts
@@ -2,9 +2,11 @@ import type { TextMatch, TextMatchOptions } from '../matches';
export interface JestNativeMatchers {
toBeOnTheScreen(): R;
+ toBeChecked(): R;
toBeDisabled(): R;
toBeEmptyElement(): R;
toBeEnabled(): R;
+ toBePartiallyChecked(): R;
toBeVisible(): R;
toHaveDisplayValue(expectedValue: TextMatch, options?: TextMatchOptions): R;
toHaveProp(name: string, expectedValue?: unknown): R;
diff --git a/src/matchers/extend-expect.ts b/src/matchers/extend-expect.ts
index 601d158f9..25b5d3c16 100644
--- a/src/matchers/extend-expect.ts
+++ b/src/matchers/extend-expect.ts
@@ -1,8 +1,10 @@
///
import { toBeOnTheScreen } from './to-be-on-the-screen';
+import { toBeChecked } from './to-be-checked';
import { toBeDisabled, toBeEnabled } from './to-be-disabled';
import { toBeEmptyElement } from './to-be-empty-element';
+import { toBePartiallyChecked } from './to-be-partially-checked';
import { toBeVisible } from './to-be-visible';
import { toHaveDisplayValue } from './to-have-display-value';
import { toHaveProp } from './to-have-prop';
@@ -10,9 +12,11 @@ import { toHaveTextContent } from './to-have-text-content';
expect.extend({
toBeOnTheScreen,
+ toBeChecked,
toBeDisabled,
toBeEmptyElement,
toBeEnabled,
+ toBePartiallyChecked,
toBeVisible,
toHaveDisplayValue,
toHaveProp,
diff --git a/src/matchers/index.tsx b/src/matchers/index.tsx
index ffe850f05..72718a184 100644
--- a/src/matchers/index.tsx
+++ b/src/matchers/index.tsx
@@ -1,3 +1,9 @@
export { toBeOnTheScreen } from './to-be-on-the-screen';
+export { toBeChecked } from './to-be-checked';
+export { toBeDisabled, toBeEnabled } from './to-be-disabled';
export { toBeEmptyElement } from './to-be-empty-element';
+export { toBePartiallyChecked } from './to-be-partially-checked';
export { toBeVisible } from './to-be-visible';
+export { toHaveDisplayValue } from './to-have-display-value';
+export { toHaveProp } from './to-have-prop';
+export { toHaveTextContent } from './to-have-text-content';
diff --git a/src/matchers/to-be-checked.tsx b/src/matchers/to-be-checked.tsx
new file mode 100644
index 000000000..7a9a67e0d
--- /dev/null
+++ b/src/matchers/to-be-checked.tsx
@@ -0,0 +1,43 @@
+import type { ReactTestInstance } from 'react-test-renderer';
+import { matcherHint } from 'jest-matcher-utils';
+import {
+ getAccessibilityCheckedState,
+ getAccessibilityRole,
+ isAccessibilityElement,
+} from '../helpers/accessiblity';
+import { ErrorWithStack } from '../helpers/errors';
+import { checkHostElement, formatElement } from './utils';
+
+export function toBeChecked(
+ this: jest.MatcherContext,
+ element: ReactTestInstance
+) {
+ checkHostElement(element, toBeChecked, this);
+
+ if (!hasValidAccessibilityRole(element)) {
+ throw new ErrorWithStack(
+ `toBeChecked() works only on accessibility elements with "checkbox" or "radio" role.`,
+ toBeChecked
+ );
+ }
+
+ return {
+ pass: getAccessibilityCheckedState(element) === true,
+ message: () => {
+ const is = this.isNot ? 'is' : 'is not';
+ return [
+ matcherHint(`${this.isNot ? '.not' : ''}.toBeChecked`, 'element', ''),
+ '',
+ `Received element ${is} checked:`,
+ formatElement(element),
+ ].join('\n');
+ },
+ };
+}
+
+const VALID_ROLES = new Set(['checkbox', 'radio']);
+
+function hasValidAccessibilityRole(element: ReactTestInstance) {
+ const role = getAccessibilityRole(element);
+ return isAccessibilityElement(element) && VALID_ROLES.has(role);
+}
diff --git a/src/matchers/to-be-disabled.tsx b/src/matchers/to-be-disabled.tsx
index 18be1bcc2..16bb96aa3 100644
--- a/src/matchers/to-be-disabled.tsx
+++ b/src/matchers/to-be-disabled.tsx
@@ -16,7 +16,7 @@ export function toBeDisabled(
return {
pass: isDisabled,
message: () => {
- const is = isDisabled ? 'is' : 'is not';
+ const is = this.isNot ? 'is' : 'is not';
return [
matcherHint(`${this.isNot ? '.not' : ''}.toBeDisabled`, 'element', ''),
'',
@@ -38,7 +38,7 @@ export function toBeEnabled(
return {
pass: isEnabled,
message: () => {
- const is = isEnabled ? 'is' : 'is not';
+ const is = this.isNot ? 'is' : 'is not';
return [
matcherHint(`${this.isNot ? '.not' : ''}.toBeEnabled`, 'element', ''),
'',
diff --git a/src/matchers/to-be-partially-checked.tsx b/src/matchers/to-be-partially-checked.tsx
new file mode 100644
index 000000000..079d80269
--- /dev/null
+++ b/src/matchers/to-be-partially-checked.tsx
@@ -0,0 +1,45 @@
+import type { ReactTestInstance } from 'react-test-renderer';
+import { matcherHint } from 'jest-matcher-utils';
+import {
+ getAccessibilityCheckedState,
+ getAccessibilityRole,
+ isAccessibilityElement,
+} from '../helpers/accessiblity';
+import { ErrorWithStack } from '../helpers/errors';
+import { checkHostElement, formatElement } from './utils';
+
+export function toBePartiallyChecked(
+ this: jest.MatcherContext,
+ element: ReactTestInstance
+) {
+ checkHostElement(element, toBePartiallyChecked, this);
+
+ if (!hasValidAccessibilityRole(element)) {
+ throw new ErrorWithStack(
+ 'toBePartiallyChecked() works only on accessibility elements with "checkbox" role.',
+ toBePartiallyChecked
+ );
+ }
+
+ return {
+ pass: getAccessibilityCheckedState(element) === 'mixed',
+ message: () => {
+ const is = this.isNot ? 'is' : 'is not';
+ return [
+ matcherHint(
+ `${this.isNot ? '.not' : ''}.toBePartiallyChecked`,
+ 'element',
+ ''
+ ),
+ '',
+ `Received element ${is} partially checked:`,
+ formatElement(element),
+ ].join('\n');
+ },
+ };
+}
+
+function hasValidAccessibilityRole(element: ReactTestInstance) {
+ const role = getAccessibilityRole(element);
+ return isAccessibilityElement(element) && role === 'checkbox';
+}