diff --git a/src/helpers/__tests__/format-default.test.tsx b/src/helpers/__tests__/format-default.test.tsx
index 315db2e16..8f2e784b2 100644
--- a/src/helpers/__tests__/format-default.test.tsx
+++ b/src/helpers/__tests__/format-default.test.tsx
@@ -20,8 +20,13 @@ describe('mapPropsForQueryError', () => {
accessibilityLabelledBy: 'LABELLED_BY',
accessibilityRole: 'ROLE',
accessibilityHint: 'HINT',
+ 'aria-busy': 'ARIA-BUSY',
+ 'aria-checked': 'ARIA-CHECKED',
+ 'aria-disabled': 'ARIA-DISABLED',
+ 'aria-expanded': 'ARIA-EXPANDED',
'aria-label': 'ARIA_LABEL',
'aria-labelledby': 'ARIA_LABELLED_BY',
+ 'aria-selected': 'ARIA-SELECTED',
placeholder: 'PLACEHOLDER',
value: 'VALUE',
defaultValue: 'DEFAULT_VALUE',
diff --git a/src/helpers/accessiblity.ts b/src/helpers/accessiblity.ts
index 631c16500..b29088ad9 100644
--- a/src/helpers/accessiblity.ts
+++ b/src/helpers/accessiblity.ts
@@ -129,3 +129,34 @@ export function getAccessibilityLabelledBy(
element.props['aria-labelledby'] ?? element.props.accessibilityLabelledBy
);
}
+
+export function getAccessibilityState(element: ReactTestInstance) {
+ const {
+ accessibilityState,
+ 'aria-busy': ariaBusy,
+ 'aria-checked': ariaChecked,
+ 'aria-disabled': ariaDisabled,
+ 'aria-expanded': ariaExpanded,
+ 'aria-selected': ariaSelected,
+ } = element.props;
+
+ const hasAnyAccessibilityStateProps =
+ accessibilityState != null ||
+ ariaBusy != null ||
+ ariaChecked != null ||
+ ariaDisabled != null ||
+ ariaExpanded != null ||
+ ariaSelected != null;
+
+ if (!hasAnyAccessibilityStateProps) {
+ return undefined;
+ }
+
+ return {
+ busy: ariaBusy ?? accessibilityState?.busy,
+ checked: ariaChecked ?? accessibilityState?.checked,
+ disabled: ariaDisabled ?? accessibilityState?.disabled,
+ expanded: ariaExpanded ?? accessibilityState?.expanded,
+ selected: ariaSelected ?? accessibilityState?.selected,
+ };
+}
diff --git a/src/helpers/format-default.ts b/src/helpers/format-default.ts
index fb1fcb62f..1f2faab51 100644
--- a/src/helpers/format-default.ts
+++ b/src/helpers/format-default.ts
@@ -8,9 +8,14 @@ const propsToDisplay = [
'accessibilityLabelledBy',
'accessibilityRole',
'accessibilityViewIsModal',
+ 'aria-busy',
+ 'aria-checked',
+ 'aria-disabled',
+ 'aria-expanded',
'aria-hidden',
'aria-label',
'aria-labelledby',
+ 'aria-selected',
'defaultValue',
'importantForAccessibility',
'nativeID',
diff --git a/src/helpers/matchers/accessibilityState.ts b/src/helpers/matchers/accessibilityState.ts
index cd02a9679..4f46d9737 100644
--- a/src/helpers/matchers/accessibilityState.ts
+++ b/src/helpers/matchers/accessibilityState.ts
@@ -1,6 +1,6 @@
import { AccessibilityState } from 'react-native';
import { ReactTestInstance } from 'react-test-renderer';
-import { accessibilityStateKeys } from '../accessiblity';
+import { accessibilityStateKeys, getAccessibilityState } from '../accessiblity';
// This type is the same as AccessibilityState from `react-native` package
// It is re-declared here due to issues with migration from `@types/react-native` to
@@ -32,13 +32,13 @@ export function matchAccessibilityState(
node: ReactTestInstance,
matcher: AccessibilityStateMatcher
) {
- const state = node.props.accessibilityState;
- return accessibilityStateKeys.every((key) => matchState(state, matcher, key));
+ const state = getAccessibilityState(node);
+ return accessibilityStateKeys.every((key) => matchState(matcher, state, key));
}
function matchState(
- state: AccessibilityState,
matcher: AccessibilityStateMatcher,
+ state: AccessibilityState | undefined,
key: keyof AccessibilityState
) {
return (
diff --git a/src/queries/__tests__/a11yState.test.tsx b/src/queries/__tests__/a11yState.test.tsx
index ea3163b46..f3b01cf42 100644
--- a/src/queries/__tests__/a11yState.test.tsx
+++ b/src/queries/__tests__/a11yState.test.tsx
@@ -437,3 +437,125 @@ test('error message renders the element tree, preserving only helpful props', as
"
`);
});
+
+describe('aria-disabled prop', () => {
+ test('supports aria-disabled={true} prop', () => {
+ const screen = render();
+ expect(screen.getByAccessibilityState({ disabled: true })).toBeTruthy();
+ expect(screen.queryByAccessibilityState({ disabled: false })).toBeNull();
+ });
+
+ test('supports aria-disabled={false} prop', () => {
+ const screen = render();
+ expect(screen.getByAccessibilityState({ disabled: false })).toBeTruthy();
+ expect(screen.queryByAccessibilityState({ disabled: true })).toBeNull();
+ });
+
+ test('supports default aria-disabled prop', () => {
+ const screen = render();
+ expect(screen.getByAccessibilityState({ disabled: false })).toBeTruthy();
+ expect(screen.queryByAccessibilityState({ disabled: true })).toBeNull();
+ });
+});
+
+describe('aria-selected prop', () => {
+ test('supports aria-selected={true} prop', () => {
+ const screen = render();
+ expect(screen.getByAccessibilityState({ selected: true })).toBeTruthy();
+ expect(screen.queryByAccessibilityState({ selected: false })).toBeNull();
+ });
+
+ test('supports aria-selected={false} prop', () => {
+ const screen = render();
+ expect(screen.getByAccessibilityState({ selected: false })).toBeTruthy();
+ expect(screen.queryByAccessibilityState({ selected: true })).toBeNull();
+ });
+
+ test('supports default aria-selected prop', () => {
+ const screen = render();
+ expect(screen.getByAccessibilityState({ selected: false })).toBeTruthy();
+ expect(screen.queryByAccessibilityState({ selected: true })).toBeNull();
+ });
+});
+
+describe('aria-checked prop', () => {
+ test('supports aria-checked={true} prop', () => {
+ const screen = render(
+
+ );
+ expect(screen.getByAccessibilityState({ checked: true })).toBeTruthy();
+ expect(screen.queryByAccessibilityState({ checked: false })).toBeNull();
+ expect(screen.queryByAccessibilityState({ checked: 'mixed' })).toBeNull();
+ });
+
+ test('supports aria-checked={false} prop', () => {
+ const screen = render(
+
+ );
+ expect(screen.getByAccessibilityState({ checked: false })).toBeTruthy();
+ expect(screen.queryByAccessibilityState({ checked: true })).toBeNull();
+ expect(screen.queryByAccessibilityState({ checked: 'mixed' })).toBeNull();
+ });
+
+ test('supports aria-checked="mixed prop', () => {
+ const screen = render(
+
+ );
+ expect(screen.getByAccessibilityState({ checked: 'mixed' })).toBeTruthy();
+ expect(screen.queryByAccessibilityState({ checked: true })).toBeNull();
+ expect(screen.queryByAccessibilityState({ checked: false })).toBeNull();
+ });
+
+ test('supports default aria-selected prop', () => {
+ const screen = render();
+ expect(screen.getByAccessibilityState({})).toBeTruthy();
+ expect(screen.queryByAccessibilityState({ checked: true })).toBeNull();
+ expect(screen.queryByAccessibilityState({ checked: false })).toBeNull();
+ expect(screen.queryByAccessibilityState({ checked: 'mixed' })).toBeNull();
+ });
+});
+
+describe('aria-busy prop', () => {
+ test('supports aria-busy={true} prop', () => {
+ const screen = render();
+ expect(screen.getByAccessibilityState({ busy: true })).toBeTruthy();
+ expect(screen.queryByAccessibilityState({ busy: false })).toBeNull();
+ });
+
+ test('supports aria-busy={false} prop', () => {
+ const screen = render();
+ expect(screen.getByAccessibilityState({ busy: false })).toBeTruthy();
+ expect(screen.queryByAccessibilityState({ busy: true })).toBeNull();
+ });
+
+ test('supports default aria-busy prop', () => {
+ const screen = render();
+ expect(screen.getByAccessibilityState({ busy: false })).toBeTruthy();
+ expect(screen.queryByAccessibilityState({ busy: true })).toBeNull();
+ });
+});
+
+describe('aria-expanded prop', () => {
+ test('supports aria-expanded={true} prop', () => {
+ const screen = render(
+
+ );
+ expect(screen.getByAccessibilityState({ expanded: true })).toBeTruthy();
+ expect(screen.queryByAccessibilityState({ expanded: false })).toBeNull();
+ });
+
+ test('supports aria-expanded={false} prop', () => {
+ const screen = render(
+
+ );
+ expect(screen.getByAccessibilityState({ expanded: false })).toBeTruthy();
+ expect(screen.queryByAccessibilityState({ expanded: true })).toBeNull();
+ });
+
+ test('supports default aria-expanded prop', () => {
+ const screen = render();
+ expect(screen.getByAccessibilityState({})).toBeTruthy();
+ expect(screen.queryByAccessibilityState({ expanded: true })).toBeNull();
+ expect(screen.queryByAccessibilityState({ expanded: false })).toBeNull();
+ });
+});
diff --git a/src/queries/__tests__/role.test.tsx b/src/queries/__tests__/role.test.tsx
index c54fb9832..cb25c284b 100644
--- a/src/queries/__tests__/role.test.tsx
+++ b/src/queries/__tests__/role.test.tsx
@@ -339,6 +339,28 @@ describe('supports accessibility states', () => {
getByRole('button', { name: 'RNButton', disabled: true })
).toBeTruthy();
});
+
+ test('supports aria-disabled={true} prop', () => {
+ const screen = render(
+
+ );
+ expect(screen.getByRole('button', { disabled: true })).toBeTruthy();
+ expect(screen.queryByRole('button', { disabled: false })).toBeNull();
+ });
+
+ test('supports aria-disabled={false} prop', () => {
+ const screen = render(
+
+ );
+ expect(screen.getByRole('button', { disabled: false })).toBeTruthy();
+ expect(screen.queryByRole('button', { disabled: true })).toBeNull();
+ });
+
+ test('supports default aria-disabled prop', () => {
+ const screen = render();
+ expect(screen.getByRole('button', { disabled: false })).toBeTruthy();
+ expect(screen.queryByRole('button', { disabled: true })).toBeNull();
+ });
});
describe('selected', () => {
@@ -406,6 +428,28 @@ describe('supports accessibility states', () => {
expect(queryByRole('tab', { selected: false })).toBe(null);
});
+
+ test('supports aria-selected={true} prop', () => {
+ const screen = render(
+
+ );
+ expect(screen.getByRole('button', { selected: true })).toBeTruthy();
+ expect(screen.queryByRole('button', { selected: false })).toBeNull();
+ });
+
+ test('supports aria-selected={false} prop', () => {
+ const screen = render(
+
+ );
+ expect(screen.getByRole('button', { selected: false })).toBeTruthy();
+ expect(screen.queryByRole('button', { selected: true })).toBeNull();
+ });
+
+ test('supports default aria-selected prop', () => {
+ const screen = render();
+ expect(screen.getByRole('button', { selected: false })).toBeTruthy();
+ expect(screen.queryByRole('button', { selected: true })).toBeNull();
+ });
});
describe('checked', () => {
@@ -508,6 +552,41 @@ describe('supports accessibility states', () => {
expect(queryByRole('checkbox', { checked: false })).toBe(null);
});
+
+ test('supports aria-checked={true} prop', () => {
+ const screen = render(
+
+ );
+ expect(screen.getByRole('button', { checked: true })).toBeTruthy();
+ expect(screen.queryByRole('button', { checked: false })).toBeNull();
+ expect(screen.queryByRole('button', { checked: 'mixed' })).toBeNull();
+ });
+
+ test('supports aria-checked={false} prop', () => {
+ const screen = render(
+
+ );
+ expect(screen.getByRole('button', { checked: false })).toBeTruthy();
+ expect(screen.queryByRole('button', { checked: true })).toBeNull();
+ expect(screen.queryByRole('button', { checked: 'mixed' })).toBeNull();
+ });
+
+ test('supports aria-checked="mixed prop', () => {
+ const screen = render(
+
+ );
+ expect(screen.getByRole('button', { checked: 'mixed' })).toBeTruthy();
+ expect(screen.queryByRole('button', { checked: true })).toBeNull();
+ expect(screen.queryByRole('button', { checked: false })).toBeNull();
+ });
+
+ test('supports default aria-selected prop', () => {
+ const screen = render();
+ expect(screen.getByRole('button')).toBeTruthy();
+ expect(screen.queryByRole('button', { checked: true })).toBeNull();
+ expect(screen.queryByRole('button', { checked: false })).toBeNull();
+ expect(screen.queryByRole('button', { checked: 'mixed' })).toBeNull();
+ });
});
describe('busy', () => {
@@ -575,6 +654,28 @@ describe('supports accessibility states', () => {
expect(queryByRole('button', { selected: false })).toBe(null);
});
+
+ test('supports aria-busy={true} prop', () => {
+ const screen = render(
+
+ );
+ expect(screen.getByRole('button', { busy: true })).toBeTruthy();
+ expect(screen.queryByRole('button', { busy: false })).toBeNull();
+ });
+
+ test('supports aria-busy={false} prop', () => {
+ const screen = render(
+
+ );
+ expect(screen.getByRole('button', { busy: false })).toBeTruthy();
+ expect(screen.queryByRole('button', { busy: true })).toBeNull();
+ });
+
+ test('supports default aria-busy prop', () => {
+ const screen = render();
+ expect(screen.getByRole('button', { busy: false })).toBeTruthy();
+ expect(screen.queryByRole('button', { busy: true })).toBeNull();
+ });
});
describe('expanded', () => {
@@ -641,6 +742,29 @@ describe('supports accessibility states', () => {
expect(queryByRole('button', { expanded: false })).toBe(null);
});
+
+ test('supports aria-expanded={true} prop', () => {
+ const screen = render(
+
+ );
+ expect(screen.getByRole('button', { expanded: true })).toBeTruthy();
+ expect(screen.queryByRole('button', { expanded: false })).toBeNull();
+ });
+
+ test('supports aria-expanded={false} prop', () => {
+ const screen = render(
+
+ );
+ expect(screen.getByRole('button', { expanded: false })).toBeTruthy();
+ expect(screen.queryByRole('button', { expanded: true })).toBeNull();
+ });
+
+ test('supports default aria-expanded prop', () => {
+ const screen = render();
+ expect(screen.getByRole('button')).toBeTruthy();
+ expect(screen.queryByRole('button', { expanded: true })).toBeNull();
+ expect(screen.queryByRole('button', { expanded: false })).toBeNull();
+ });
});
test('ignores non queried accessibilityState', () => {
diff --git a/website/docs/Queries.md b/website/docs/Queries.md
index 2a6c37ab7..e99cc5b8b 100644
--- a/website/docs/Queries.md
+++ b/website/docs/Queries.md
@@ -30,8 +30,8 @@ title: Queries
- [Precision](#precision)
- [Normalization](#normalization)
- [Unit testing helpers](#unit-testing-helpers)
- - [`UNSAFE_ByType`](#unsafe_bytype)
- - [`UNSAFE_ByProps`](#unsafe_byprops)
+ - [`UNSAFE_ByType`](#unsafebytype)
+ - [`UNSAFE_ByProps`](#unsafebyprops)
## Variants
@@ -142,15 +142,15 @@ const element3 = screen.getByRole('button', { name: 'Hello', disabled: true });
`name`: Finds an element with given `role`/`accessibilityRole` and an accessible name (equivalent to `byText` or `byLabelText` query).
-`disabled`: You can filter elements by their disabled state. The possible values are `true` or `false`. Querying `disabled: false` will also match elements with `disabled: undefined` (see the [wiki](https://github.com/callstack/react-native-testing-library/wiki/Accessibility:-State) for more details). See [React Native's accessibilityState](https://reactnative.dev/docs/accessibility#accessibilitystate) docs to learn more about the `disabled` state.
+`disabled`: You can filter elements by their disabled state (coming either from `aria-disabled` prop or `accessbilityState.disabled` prop). The possible values are `true` or `false`. Querying `disabled: false` will also match elements with `disabled: undefined` (see the [wiki](https://github.com/callstack/react-native-testing-library/wiki/Accessibility:-State) for more details). See [React Native's accessibilityState](https://reactnative.dev/docs/accessibility#accessibilitystate) docs to learn more about the `disabled` state.
-`selected`: You can filter elements by their selected state. The possible values are `true` or `false`. Querying `selected: false` will also match elements with `selected: undefined` (see the [wiki](https://github.com/callstack/react-native-testing-library/wiki/Accessibility:-State) for more details). See [React Native's accessibilityState](https://reactnative.dev/docs/accessibility#accessibilitystate) docs to learn more about the `selected` state.
+`selected`: You can filter elements by their selected state (coming either from `aria-selected` prop or `accessbilityState.selected` prop). The possible values are `true` or `false`. Querying `selected: false` will also match elements with `selected: undefined` (see the [wiki](https://github.com/callstack/react-native-testing-library/wiki/Accessibility:-State) for more details). See [React Native's accessibilityState](https://reactnative.dev/docs/accessibility#accessibilitystate) docs to learn more about the `selected` state.
-`checked`: You can filter elements by their checked state. The possible values are `true`, `false`, or `"mixed"`. See [React Native's accessibilityState](https://reactnative.dev/docs/accessibility#accessibilitystate) docs to learn more about the `checked` state.
+`checked`: You can filter elements by their checked state (coming either from `aria-checked` prop or `accessbilityState.checked` prop). The possible values are `true`, `false`, or `"mixed"`. See [React Native's accessibilityState](https://reactnative.dev/docs/accessibility#accessibilitystate) docs to learn more about the `checked` state.
-`busy`: You can filter elements by their busy state. The possible values are `true` or `false`. Querying `busy: false` will also match elements with `busy: undefined` (see the [wiki](https://github.com/callstack/react-native-testing-library/wiki/Accessibility:-State) for more details). See [React Native's accessibilityState](https://reactnative.dev/docs/accessibility#accessibilitystate) docs to learn more about the `busy` state.
+`busy`: You can filter elements by their busy state (coming either from `aria-busy` prop or `accessbilityState.busy` prop). The possible values are `true` or `false`. Querying `busy: false` will also match elements with `busy: undefined` (see the [wiki](https://github.com/callstack/react-native-testing-library/wiki/Accessibility:-State) for more details). See [React Native's accessibilityState](https://reactnative.dev/docs/accessibility#accessibilitystate) docs to learn more about the `busy` state.
-`expanded`: You can filter elements by their expanded state. The possible values are `true` or `false`. See [React Native's accessibilityState](https://reactnative.dev/docs/accessibility#accessibilitystate) docs to learn more about the `expanded` state.
+`expanded`: You can filter elements by their expanded state (coming either from `aria-expanded` prop or `accessbilityState.expanded` prop). The possible values are `true` or `false`. See [React Native's accessibilityState](https://reactnative.dev/docs/accessibility#accessibilitystate) docs to learn more about the `expanded` state.
`value`: Filter elements by their accessibility, available value entries include numeric `min`, `max` & `now`, as well as string or regex `text` key. See React Native [accessibilityValue](https://reactnative.dev/docs/accessibility#accessibilityvalue) docs to learn more about this prop.
@@ -331,8 +331,8 @@ getByA11yState(
disabled?: boolean,
selected?: boolean,
checked?: boolean | 'mixed',
- expanded?: boolean,
busy?: boolean,
+ expanded?: boolean,
},
options?: {
includeHiddenElements?: boolean;
@@ -340,7 +340,7 @@ getByA11yState(
): ReactTestInstance;
```
-Returns a `ReactTestInstance` with matching `accessibilityState` prop.
+Returns a `ReactTestInstance` with matching `accessibilityState` prop or ARIA state props: `aria-disabled`, `aria-selected`, `aria-checked`, `aria-busy`, and `aria-expanded`.
```jsx
import { render, screen } from '@testing-library/react-native';