Skip to content

Commit 2a6bb3b

Browse files
committed
feat: accessibility state
chore: add tests, update docs
1 parent da0a888 commit 2a6bb3b

File tree

7 files changed

+300
-13
lines changed

7 files changed

+300
-13
lines changed

src/helpers/__tests__/format-default.test.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,13 @@ describe('mapPropsForQueryError', () => {
2020
accessibilityLabelledBy: 'LABELLED_BY',
2121
accessibilityRole: 'ROLE',
2222
accessibilityHint: 'HINT',
23+
'aria-busy': 'ARIA-BUSY',
24+
'aria-checked': 'ARIA-CHECKED',
25+
'aria-disabled': 'ARIA-DISABLED',
26+
'aria-expanded': 'ARIA-EXPANDED',
2327
'aria-label': 'ARIA_LABEL',
2428
'aria-labelledby': 'ARIA_LABELLED_BY',
29+
'aria-selected': 'ARIA-SELECTED',
2530
placeholder: 'PLACEHOLDER',
2631
value: 'VALUE',
2732
defaultValue: 'DEFAULT_VALUE',

src/helpers/accessiblity.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,3 +129,34 @@ export function getAccessibilityLabelledBy(
129129
element.props['aria-labelledby'] ?? element.props.accessibilityLabelledBy
130130
);
131131
}
132+
133+
export function getAccessibilityState(element: ReactTestInstance) {
134+
const {
135+
accessibilityState,
136+
'aria-busy': ariaBusy,
137+
'aria-checked': ariaChecked,
138+
'aria-disabled': ariaDisabled,
139+
'aria-expanded': ariaExpanded,
140+
'aria-selected': ariaSelected,
141+
} = element.props;
142+
143+
const hasAnyAccessibilityStateProps =
144+
accessibilityState != null ||
145+
ariaBusy != null ||
146+
ariaChecked != null ||
147+
ariaDisabled != null ||
148+
ariaExpanded != null ||
149+
ariaSelected != null;
150+
151+
if (!hasAnyAccessibilityStateProps) {
152+
return undefined;
153+
}
154+
155+
return {
156+
busy: ariaBusy ?? accessibilityState?.busy,
157+
checked: ariaChecked ?? accessibilityState?.checked,
158+
disabled: ariaDisabled ?? accessibilityState?.disabled,
159+
expanded: ariaExpanded ?? accessibilityState?.expanded,
160+
selected: ariaSelected ?? accessibilityState?.selected,
161+
};
162+
}

src/helpers/format-default.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,14 @@ const propsToDisplay = [
88
'accessibilityLabelledBy',
99
'accessibilityRole',
1010
'accessibilityViewIsModal',
11+
'aria-busy',
12+
'aria-checked',
13+
'aria-disabled',
14+
'aria-expanded',
1115
'aria-hidden',
1216
'aria-label',
1317
'aria-labelledby',
18+
'aria-selected',
1419
'defaultValue',
1520
'importantForAccessibility',
1621
'nativeID',

src/helpers/matchers/accessibilityState.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { AccessibilityState } from 'react-native';
22
import { ReactTestInstance } from 'react-test-renderer';
3-
import { accessibilityStateKeys } from '../accessiblity';
3+
import { accessibilityStateKeys, getAccessibilityState } from '../accessiblity';
44

55
// This type is the same as AccessibilityState from `react-native` package
66
// It is re-declared here due to issues with migration from `@types/react-native` to
@@ -32,13 +32,13 @@ export function matchAccessibilityState(
3232
node: ReactTestInstance,
3333
matcher: AccessibilityStateMatcher
3434
) {
35-
const state = node.props.accessibilityState;
36-
return accessibilityStateKeys.every((key) => matchState(state, matcher, key));
35+
const state = getAccessibilityState(node);
36+
return accessibilityStateKeys.every((key) => matchState(matcher, state, key));
3737
}
3838

3939
function matchState(
40-
state: AccessibilityState,
4140
matcher: AccessibilityStateMatcher,
41+
state: AccessibilityState | undefined,
4242
key: keyof AccessibilityState
4343
) {
4444
return (

src/queries/__tests__/a11yState.test.tsx

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -437,3 +437,125 @@ test('error message renders the element tree, preserving only helpful props', as
437437
</Text>"
438438
`);
439439
});
440+
441+
describe('aria-disabled prop', () => {
442+
test('supports aria-disabled={true} prop', () => {
443+
const screen = render(<View accessible aria-disabled={true} />);
444+
expect(screen.getByAccessibilityState({ disabled: true })).toBeTruthy();
445+
expect(screen.queryByAccessibilityState({ disabled: false })).toBeNull();
446+
});
447+
448+
test('supports aria-disabled={false} prop', () => {
449+
const screen = render(<View accessible aria-disabled={false} />);
450+
expect(screen.getByAccessibilityState({ disabled: false })).toBeTruthy();
451+
expect(screen.queryByAccessibilityState({ disabled: true })).toBeNull();
452+
});
453+
454+
test('supports default aria-disabled prop', () => {
455+
const screen = render(<View accessible />);
456+
expect(screen.getByAccessibilityState({ disabled: false })).toBeTruthy();
457+
expect(screen.queryByAccessibilityState({ disabled: true })).toBeNull();
458+
});
459+
});
460+
461+
describe('aria-selected prop', () => {
462+
test('supports aria-selected={true} prop', () => {
463+
const screen = render(<View accessible aria-selected={true} />);
464+
expect(screen.getByAccessibilityState({ selected: true })).toBeTruthy();
465+
expect(screen.queryByAccessibilityState({ selected: false })).toBeNull();
466+
});
467+
468+
test('supports aria-selected={false} prop', () => {
469+
const screen = render(<View accessible aria-selected={false} />);
470+
expect(screen.getByAccessibilityState({ selected: false })).toBeTruthy();
471+
expect(screen.queryByAccessibilityState({ selected: true })).toBeNull();
472+
});
473+
474+
test('supports default aria-selected prop', () => {
475+
const screen = render(<View accessible />);
476+
expect(screen.getByAccessibilityState({ selected: false })).toBeTruthy();
477+
expect(screen.queryByAccessibilityState({ selected: true })).toBeNull();
478+
});
479+
});
480+
481+
describe('aria-checked prop', () => {
482+
test('supports aria-checked={true} prop', () => {
483+
const screen = render(
484+
<View accessible accessibilityRole="button" aria-checked={true} />
485+
);
486+
expect(screen.getByAccessibilityState({ checked: true })).toBeTruthy();
487+
expect(screen.queryByAccessibilityState({ checked: false })).toBeNull();
488+
expect(screen.queryByAccessibilityState({ checked: 'mixed' })).toBeNull();
489+
});
490+
491+
test('supports aria-checked={false} prop', () => {
492+
const screen = render(
493+
<View accessible accessibilityRole="button" aria-checked={false} />
494+
);
495+
expect(screen.getByAccessibilityState({ checked: false })).toBeTruthy();
496+
expect(screen.queryByAccessibilityState({ checked: true })).toBeNull();
497+
expect(screen.queryByAccessibilityState({ checked: 'mixed' })).toBeNull();
498+
});
499+
500+
test('supports aria-checked="mixed prop', () => {
501+
const screen = render(
502+
<View accessible accessibilityRole="button" aria-checked="mixed" />
503+
);
504+
expect(screen.getByAccessibilityState({ checked: 'mixed' })).toBeTruthy();
505+
expect(screen.queryByAccessibilityState({ checked: true })).toBeNull();
506+
expect(screen.queryByAccessibilityState({ checked: false })).toBeNull();
507+
});
508+
509+
test('supports default aria-selected prop', () => {
510+
const screen = render(<View accessible accessibilityRole="button" />);
511+
expect(screen.getByAccessibilityState({})).toBeTruthy();
512+
expect(screen.queryByAccessibilityState({ checked: true })).toBeNull();
513+
expect(screen.queryByAccessibilityState({ checked: false })).toBeNull();
514+
expect(screen.queryByAccessibilityState({ checked: 'mixed' })).toBeNull();
515+
});
516+
});
517+
518+
describe('aria-busy prop', () => {
519+
test('supports aria-busy={true} prop', () => {
520+
const screen = render(<View accessible aria-busy={true} />);
521+
expect(screen.getByAccessibilityState({ busy: true })).toBeTruthy();
522+
expect(screen.queryByAccessibilityState({ busy: false })).toBeNull();
523+
});
524+
525+
test('supports aria-busy={false} prop', () => {
526+
const screen = render(<View accessible aria-busy={false} />);
527+
expect(screen.getByAccessibilityState({ busy: false })).toBeTruthy();
528+
expect(screen.queryByAccessibilityState({ busy: true })).toBeNull();
529+
});
530+
531+
test('supports default aria-busy prop', () => {
532+
const screen = render(<View accessible />);
533+
expect(screen.getByAccessibilityState({ busy: false })).toBeTruthy();
534+
expect(screen.queryByAccessibilityState({ busy: true })).toBeNull();
535+
});
536+
});
537+
538+
describe('aria-expanded prop', () => {
539+
test('supports aria-expanded={true} prop', () => {
540+
const screen = render(
541+
<View accessible accessibilityRole="button" aria-expanded={true} />
542+
);
543+
expect(screen.getByAccessibilityState({ expanded: true })).toBeTruthy();
544+
expect(screen.queryByAccessibilityState({ expanded: false })).toBeNull();
545+
});
546+
547+
test('supports aria-expanded={false} prop', () => {
548+
const screen = render(
549+
<View accessible accessibilityRole="button" aria-expanded={false} />
550+
);
551+
expect(screen.getByAccessibilityState({ expanded: false })).toBeTruthy();
552+
expect(screen.queryByAccessibilityState({ expanded: true })).toBeNull();
553+
});
554+
555+
test('supports default aria-expanded prop', () => {
556+
const screen = render(<View accessible accessibilityRole="button" />);
557+
expect(screen.getByAccessibilityState({})).toBeTruthy();
558+
expect(screen.queryByAccessibilityState({ expanded: true })).toBeNull();
559+
expect(screen.queryByAccessibilityState({ expanded: false })).toBeNull();
560+
});
561+
});

src/queries/__tests__/role.test.tsx

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,28 @@ describe('supports accessibility states', () => {
339339
getByRole('button', { name: 'RNButton', disabled: true })
340340
).toBeTruthy();
341341
});
342+
343+
test('supports aria-disabled={true} prop', () => {
344+
const screen = render(
345+
<View accessible accessibilityRole="button" aria-disabled={true} />
346+
);
347+
expect(screen.getByRole('button', { disabled: true })).toBeTruthy();
348+
expect(screen.queryByRole('button', { disabled: false })).toBeNull();
349+
});
350+
351+
test('supports aria-disabled={false} prop', () => {
352+
const screen = render(
353+
<View accessible accessibilityRole="button" aria-disabled={false} />
354+
);
355+
expect(screen.getByRole('button', { disabled: false })).toBeTruthy();
356+
expect(screen.queryByRole('button', { disabled: true })).toBeNull();
357+
});
358+
359+
test('supports default aria-disabled prop', () => {
360+
const screen = render(<View accessible accessibilityRole="button" />);
361+
expect(screen.getByRole('button', { disabled: false })).toBeTruthy();
362+
expect(screen.queryByRole('button', { disabled: true })).toBeNull();
363+
});
342364
});
343365

344366
describe('selected', () => {
@@ -406,6 +428,28 @@ describe('supports accessibility states', () => {
406428

407429
expect(queryByRole('tab', { selected: false })).toBe(null);
408430
});
431+
432+
test('supports aria-selected={true} prop', () => {
433+
const screen = render(
434+
<View accessible accessibilityRole="button" aria-selected={true} />
435+
);
436+
expect(screen.getByRole('button', { selected: true })).toBeTruthy();
437+
expect(screen.queryByRole('button', { selected: false })).toBeNull();
438+
});
439+
440+
test('supports aria-selected={false} prop', () => {
441+
const screen = render(
442+
<View accessible accessibilityRole="button" aria-selected={false} />
443+
);
444+
expect(screen.getByRole('button', { selected: false })).toBeTruthy();
445+
expect(screen.queryByRole('button', { selected: true })).toBeNull();
446+
});
447+
448+
test('supports default aria-selected prop', () => {
449+
const screen = render(<View accessible accessibilityRole="button" />);
450+
expect(screen.getByRole('button', { selected: false })).toBeTruthy();
451+
expect(screen.queryByRole('button', { selected: true })).toBeNull();
452+
});
409453
});
410454

411455
describe('checked', () => {
@@ -508,6 +552,41 @@ describe('supports accessibility states', () => {
508552

509553
expect(queryByRole('checkbox', { checked: false })).toBe(null);
510554
});
555+
556+
test('supports aria-checked={true} prop', () => {
557+
const screen = render(
558+
<View accessible accessibilityRole="button" aria-checked={true} />
559+
);
560+
expect(screen.getByRole('button', { checked: true })).toBeTruthy();
561+
expect(screen.queryByRole('button', { checked: false })).toBeNull();
562+
expect(screen.queryByRole('button', { checked: 'mixed' })).toBeNull();
563+
});
564+
565+
test('supports aria-checked={false} prop', () => {
566+
const screen = render(
567+
<View accessible accessibilityRole="button" aria-checked={false} />
568+
);
569+
expect(screen.getByRole('button', { checked: false })).toBeTruthy();
570+
expect(screen.queryByRole('button', { checked: true })).toBeNull();
571+
expect(screen.queryByRole('button', { checked: 'mixed' })).toBeNull();
572+
});
573+
574+
test('supports aria-checked="mixed prop', () => {
575+
const screen = render(
576+
<View accessible accessibilityRole="button" aria-checked="mixed" />
577+
);
578+
expect(screen.getByRole('button', { checked: 'mixed' })).toBeTruthy();
579+
expect(screen.queryByRole('button', { checked: true })).toBeNull();
580+
expect(screen.queryByRole('button', { checked: false })).toBeNull();
581+
});
582+
583+
test('supports default aria-selected prop', () => {
584+
const screen = render(<View accessible accessibilityRole="button" />);
585+
expect(screen.getByRole('button')).toBeTruthy();
586+
expect(screen.queryByRole('button', { checked: true })).toBeNull();
587+
expect(screen.queryByRole('button', { checked: false })).toBeNull();
588+
expect(screen.queryByRole('button', { checked: 'mixed' })).toBeNull();
589+
});
511590
});
512591

513592
describe('busy', () => {
@@ -575,6 +654,28 @@ describe('supports accessibility states', () => {
575654

576655
expect(queryByRole('button', { selected: false })).toBe(null);
577656
});
657+
658+
test('supports aria-busy={true} prop', () => {
659+
const screen = render(
660+
<View accessible accessibilityRole="button" aria-busy={true} />
661+
);
662+
expect(screen.getByRole('button', { busy: true })).toBeTruthy();
663+
expect(screen.queryByRole('button', { busy: false })).toBeNull();
664+
});
665+
666+
test('supports aria-busy={false} prop', () => {
667+
const screen = render(
668+
<View accessible accessibilityRole="button" aria-busy={false} />
669+
);
670+
expect(screen.getByRole('button', { busy: false })).toBeTruthy();
671+
expect(screen.queryByRole('button', { busy: true })).toBeNull();
672+
});
673+
674+
test('supports default aria-busy prop', () => {
675+
const screen = render(<View accessible accessibilityRole="button" />);
676+
expect(screen.getByRole('button', { busy: false })).toBeTruthy();
677+
expect(screen.queryByRole('button', { busy: true })).toBeNull();
678+
});
578679
});
579680

580681
describe('expanded', () => {
@@ -641,6 +742,29 @@ describe('supports accessibility states', () => {
641742

642743
expect(queryByRole('button', { expanded: false })).toBe(null);
643744
});
745+
746+
test('supports aria-expanded={true} prop', () => {
747+
const screen = render(
748+
<View accessible accessibilityRole="button" aria-expanded={true} />
749+
);
750+
expect(screen.getByRole('button', { expanded: true })).toBeTruthy();
751+
expect(screen.queryByRole('button', { expanded: false })).toBeNull();
752+
});
753+
754+
test('supports aria-expanded={false} prop', () => {
755+
const screen = render(
756+
<View accessible accessibilityRole="button" aria-expanded={false} />
757+
);
758+
expect(screen.getByRole('button', { expanded: false })).toBeTruthy();
759+
expect(screen.queryByRole('button', { expanded: true })).toBeNull();
760+
});
761+
762+
test('supports default aria-expanded prop', () => {
763+
const screen = render(<View accessible accessibilityRole="button" />);
764+
expect(screen.getByRole('button')).toBeTruthy();
765+
expect(screen.queryByRole('button', { expanded: true })).toBeNull();
766+
expect(screen.queryByRole('button', { expanded: false })).toBeNull();
767+
});
644768
});
645769

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

0 commit comments

Comments
 (0)