Skip to content

Commit

Permalink
feat: accessibility state (#1478)
Browse files Browse the repository at this point in the history
chore: add tests, update docs
  • Loading branch information
mdjastrzebski committed Aug 31, 2023
1 parent da0a888 commit 5cbe69a
Show file tree
Hide file tree
Showing 7 changed files with 300 additions and 13 deletions.
5 changes: 5 additions & 0 deletions src/helpers/__tests__/format-default.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
31 changes: 31 additions & 0 deletions src/helpers/accessiblity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
}
5 changes: 5 additions & 0 deletions src/helpers/format-default.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
8 changes: 4 additions & 4 deletions src/helpers/matchers/accessibilityState.ts
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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 (
Expand Down
122 changes: 122 additions & 0 deletions src/queries/__tests__/a11yState.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -437,3 +437,125 @@ test('error message renders the element tree, preserving only helpful props', as
</Text>"
`);
});

describe('aria-disabled prop', () => {
test('supports aria-disabled={true} prop', () => {
const screen = render(<View accessible aria-disabled={true} />);
expect(screen.getByAccessibilityState({ disabled: true })).toBeTruthy();
expect(screen.queryByAccessibilityState({ disabled: false })).toBeNull();
});

test('supports aria-disabled={false} prop', () => {
const screen = render(<View accessible aria-disabled={false} />);
expect(screen.getByAccessibilityState({ disabled: false })).toBeTruthy();
expect(screen.queryByAccessibilityState({ disabled: true })).toBeNull();
});

test('supports default aria-disabled prop', () => {
const screen = render(<View accessible />);
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(<View accessible aria-selected={true} />);
expect(screen.getByAccessibilityState({ selected: true })).toBeTruthy();
expect(screen.queryByAccessibilityState({ selected: false })).toBeNull();
});

test('supports aria-selected={false} prop', () => {
const screen = render(<View accessible aria-selected={false} />);
expect(screen.getByAccessibilityState({ selected: false })).toBeTruthy();
expect(screen.queryByAccessibilityState({ selected: true })).toBeNull();
});

test('supports default aria-selected prop', () => {
const screen = render(<View accessible />);
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(
<View accessible accessibilityRole="button" aria-checked={true} />
);
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(
<View accessible accessibilityRole="button" aria-checked={false} />
);
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(
<View accessible accessibilityRole="button" aria-checked="mixed" />
);
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(<View accessible accessibilityRole="button" />);
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(<View accessible aria-busy={true} />);
expect(screen.getByAccessibilityState({ busy: true })).toBeTruthy();
expect(screen.queryByAccessibilityState({ busy: false })).toBeNull();
});

test('supports aria-busy={false} prop', () => {
const screen = render(<View accessible aria-busy={false} />);
expect(screen.getByAccessibilityState({ busy: false })).toBeTruthy();
expect(screen.queryByAccessibilityState({ busy: true })).toBeNull();
});

test('supports default aria-busy prop', () => {
const screen = render(<View accessible />);
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(
<View accessible accessibilityRole="button" aria-expanded={true} />
);
expect(screen.getByAccessibilityState({ expanded: true })).toBeTruthy();
expect(screen.queryByAccessibilityState({ expanded: false })).toBeNull();
});

test('supports aria-expanded={false} prop', () => {
const screen = render(
<View accessible accessibilityRole="button" aria-expanded={false} />
);
expect(screen.getByAccessibilityState({ expanded: false })).toBeTruthy();
expect(screen.queryByAccessibilityState({ expanded: true })).toBeNull();
});

test('supports default aria-expanded prop', () => {
const screen = render(<View accessible accessibilityRole="button" />);
expect(screen.getByAccessibilityState({})).toBeTruthy();
expect(screen.queryByAccessibilityState({ expanded: true })).toBeNull();
expect(screen.queryByAccessibilityState({ expanded: false })).toBeNull();
});
});
124 changes: 124 additions & 0 deletions src/queries/__tests__/role.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,28 @@ describe('supports accessibility states', () => {
getByRole('button', { name: 'RNButton', disabled: true })
).toBeTruthy();
});

test('supports aria-disabled={true} prop', () => {
const screen = render(
<View accessible accessibilityRole="button" aria-disabled={true} />
);
expect(screen.getByRole('button', { disabled: true })).toBeTruthy();
expect(screen.queryByRole('button', { disabled: false })).toBeNull();
});

test('supports aria-disabled={false} prop', () => {
const screen = render(
<View accessible accessibilityRole="button" aria-disabled={false} />
);
expect(screen.getByRole('button', { disabled: false })).toBeTruthy();
expect(screen.queryByRole('button', { disabled: true })).toBeNull();
});

test('supports default aria-disabled prop', () => {
const screen = render(<View accessible accessibilityRole="button" />);
expect(screen.getByRole('button', { disabled: false })).toBeTruthy();
expect(screen.queryByRole('button', { disabled: true })).toBeNull();
});
});

describe('selected', () => {
Expand Down Expand Up @@ -406,6 +428,28 @@ describe('supports accessibility states', () => {

expect(queryByRole('tab', { selected: false })).toBe(null);
});

test('supports aria-selected={true} prop', () => {
const screen = render(
<View accessible accessibilityRole="button" aria-selected={true} />
);
expect(screen.getByRole('button', { selected: true })).toBeTruthy();
expect(screen.queryByRole('button', { selected: false })).toBeNull();
});

test('supports aria-selected={false} prop', () => {
const screen = render(
<View accessible accessibilityRole="button" aria-selected={false} />
);
expect(screen.getByRole('button', { selected: false })).toBeTruthy();
expect(screen.queryByRole('button', { selected: true })).toBeNull();
});

test('supports default aria-selected prop', () => {
const screen = render(<View accessible accessibilityRole="button" />);
expect(screen.getByRole('button', { selected: false })).toBeTruthy();
expect(screen.queryByRole('button', { selected: true })).toBeNull();
});
});

describe('checked', () => {
Expand Down Expand Up @@ -508,6 +552,41 @@ describe('supports accessibility states', () => {

expect(queryByRole('checkbox', { checked: false })).toBe(null);
});

test('supports aria-checked={true} prop', () => {
const screen = render(
<View accessible accessibilityRole="button" aria-checked={true} />
);
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(
<View accessible accessibilityRole="button" aria-checked={false} />
);
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(
<View accessible accessibilityRole="button" aria-checked="mixed" />
);
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(<View accessible accessibilityRole="button" />);
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', () => {
Expand Down Expand Up @@ -575,6 +654,28 @@ describe('supports accessibility states', () => {

expect(queryByRole('button', { selected: false })).toBe(null);
});

test('supports aria-busy={true} prop', () => {
const screen = render(
<View accessible accessibilityRole="button" aria-busy={true} />
);
expect(screen.getByRole('button', { busy: true })).toBeTruthy();
expect(screen.queryByRole('button', { busy: false })).toBeNull();
});

test('supports aria-busy={false} prop', () => {
const screen = render(
<View accessible accessibilityRole="button" aria-busy={false} />
);
expect(screen.getByRole('button', { busy: false })).toBeTruthy();
expect(screen.queryByRole('button', { busy: true })).toBeNull();
});

test('supports default aria-busy prop', () => {
const screen = render(<View accessible accessibilityRole="button" />);
expect(screen.getByRole('button', { busy: false })).toBeTruthy();
expect(screen.queryByRole('button', { busy: true })).toBeNull();
});
});

describe('expanded', () => {
Expand Down Expand Up @@ -641,6 +742,29 @@ describe('supports accessibility states', () => {

expect(queryByRole('button', { expanded: false })).toBe(null);
});

test('supports aria-expanded={true} prop', () => {
const screen = render(
<View accessible accessibilityRole="button" aria-expanded={true} />
);
expect(screen.getByRole('button', { expanded: true })).toBeTruthy();
expect(screen.queryByRole('button', { expanded: false })).toBeNull();
});

test('supports aria-expanded={false} prop', () => {
const screen = render(
<View accessible accessibilityRole="button" aria-expanded={false} />
);
expect(screen.getByRole('button', { expanded: false })).toBeTruthy();
expect(screen.queryByRole('button', { expanded: true })).toBeNull();
});

test('supports default aria-expanded prop', () => {
const screen = render(<View accessible accessibilityRole="button" />);
expect(screen.getByRole('button')).toBeTruthy();
expect(screen.queryByRole('button', { expanded: true })).toBeNull();
expect(screen.queryByRole('button', { expanded: false })).toBeNull();
});
});

test('ignores non queried accessibilityState', () => {
Expand Down

0 comments on commit 5cbe69a

Please sign in to comment.