mirror of
https://github.com/callstack/react-native-testing-library.git
synced 2026-09-18 23:09:04 +08:00
refactor: accessibility handling (#1658)
* refactor: improve code structure * refactor: more tweaks
This commit is contained in:
committed by
GitHub
parent
4112abcf0a
commit
22ec13d0e5
Vendored
+1
-1
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"cSpell.words": ["Pressable", "RNTL", "Uncapitalize"]
|
||||
"cSpell.words": ["labelledby", "Pressable", "RNTL", "Uncapitalize", "valuenow", "valuetext"]
|
||||
}
|
||||
|
||||
@@ -1,8 +1,15 @@
|
||||
import { AccessibilityState, AccessibilityValue, StyleSheet } from 'react-native';
|
||||
import {
|
||||
AccessibilityRole,
|
||||
AccessibilityState,
|
||||
AccessibilityValue,
|
||||
Role,
|
||||
StyleSheet,
|
||||
} from 'react-native';
|
||||
import { ReactTestInstance } from 'react-test-renderer';
|
||||
import { getHostSiblings, getUnsafeRootElement } from './component-tree';
|
||||
import { getHostComponentNames, isHostText } from './host-component-names';
|
||||
import { getHostComponentNames, isHostText, isHostTextInput } from './host-component-names';
|
||||
import { getTextContent } from './text-content';
|
||||
import { isTextInputEditable } from './text-input';
|
||||
|
||||
type IsInaccessibleOptions = {
|
||||
cache?: WeakMap<ReactTestInstance, boolean>;
|
||||
@@ -45,7 +52,7 @@ export function isHiddenFromAccessibility(
|
||||
return false;
|
||||
}
|
||||
|
||||
/** RTL-compatitibility alias for `isHiddenFromAccessibility` */
|
||||
/** RTL-compatibility alias for `isHiddenFromAccessibility` */
|
||||
export const isInaccessible = isHiddenFromAccessibility;
|
||||
|
||||
function isSubtreeInaccessible(element: ReactTestInstance): boolean {
|
||||
@@ -78,7 +85,7 @@ function isSubtreeInaccessible(element: ReactTestInstance): boolean {
|
||||
// iOS: accessibilityViewIsModal or aria-modal
|
||||
// See: https://reactnative.dev/docs/accessibility#accessibilityviewismodal-ios
|
||||
const hostSiblings = getHostSiblings(element);
|
||||
if (hostSiblings.some((sibling) => getAccessibilityViewIsModal(sibling))) {
|
||||
if (hostSiblings.some((sibling) => computeAriaModal(sibling))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -115,7 +122,7 @@ export function isAccessibilityElement(element: ReactTestInstance | null): boole
|
||||
* @param element
|
||||
* @returns
|
||||
*/
|
||||
export function getAccessibilityRole(element: ReactTestInstance) {
|
||||
export function getRole(element: ReactTestInstance): Role | AccessibilityRole {
|
||||
const explicitRole = element.props.role ?? element.props.accessibilityRole;
|
||||
if (explicitRole) {
|
||||
return explicitRole;
|
||||
@@ -128,57 +135,55 @@ export function getAccessibilityRole(element: ReactTestInstance) {
|
||||
return 'none';
|
||||
}
|
||||
|
||||
export function getAccessibilityViewIsModal(element: ReactTestInstance) {
|
||||
export function computeAriaModal(element: ReactTestInstance): boolean | undefined {
|
||||
return element.props['aria-modal'] ?? element.props.accessibilityViewIsModal;
|
||||
}
|
||||
|
||||
export function getAccessibilityLabel(element: ReactTestInstance): string | undefined {
|
||||
export function computeAriaLabel(element: ReactTestInstance): string | undefined {
|
||||
return element.props['aria-label'] ?? element.props.accessibilityLabel;
|
||||
}
|
||||
|
||||
export function getAccessibilityLabelledBy(element: ReactTestInstance): string | undefined {
|
||||
export function computeAriaLabelledBy(element: ReactTestInstance): string | undefined {
|
||||
return element.props['aria-labelledby'] ?? element.props.accessibilityLabelledBy;
|
||||
}
|
||||
|
||||
export function getAccessibilityState(element: ReactTestInstance): AccessibilityState | undefined {
|
||||
const {
|
||||
accessibilityState,
|
||||
'aria-busy': ariaBusy,
|
||||
'aria-checked': ariaChecked,
|
||||
'aria-disabled': ariaDisabled,
|
||||
'aria-expanded': ariaExpanded,
|
||||
'aria-selected': ariaSelected,
|
||||
} = element.props;
|
||||
// See: https://github.com/callstack/react-native-testing-library/wiki/Accessibility:-State#busy-state
|
||||
export function computeAriaBusy({ props }: ReactTestInstance): boolean {
|
||||
return props['aria-busy'] ?? props.accessibilityState?.busy ?? false;
|
||||
}
|
||||
|
||||
const hasAnyAccessibilityStateProps =
|
||||
accessibilityState != null ||
|
||||
ariaBusy != null ||
|
||||
ariaChecked != null ||
|
||||
ariaDisabled != null ||
|
||||
ariaExpanded != null ||
|
||||
ariaSelected != null;
|
||||
|
||||
if (!hasAnyAccessibilityStateProps) {
|
||||
// See: https://github.com/callstack/react-native-testing-library/wiki/Accessibility:-State#checked-state
|
||||
export function computeAriaChecked(element: ReactTestInstance): AccessibilityState['checked'] {
|
||||
const role = getRole(element);
|
||||
if (role !== 'checkbox' && role !== 'radio') {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
busy: ariaBusy ?? accessibilityState?.busy,
|
||||
checked: ariaChecked ?? accessibilityState?.checked,
|
||||
disabled: ariaDisabled ?? accessibilityState?.disabled,
|
||||
expanded: ariaExpanded ?? accessibilityState?.expanded,
|
||||
selected: ariaSelected ?? accessibilityState?.selected,
|
||||
};
|
||||
const props = element.props;
|
||||
return props['aria-checked'] ?? props.accessibilityState?.checked;
|
||||
}
|
||||
|
||||
export function getAccessibilityCheckedState(
|
||||
element: ReactTestInstance,
|
||||
): AccessibilityState['checked'] {
|
||||
const { accessibilityState, 'aria-checked': ariaChecked } = element.props;
|
||||
return ariaChecked ?? accessibilityState?.checked;
|
||||
// See: https://github.com/callstack/react-native-testing-library/wiki/Accessibility:-State#disabled-state
|
||||
export function computeAriaDisabled(element: ReactTestInstance): boolean {
|
||||
if (isHostTextInput(element) && !isTextInputEditable(element)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const { props } = element;
|
||||
return props['aria-disabled'] ?? props.accessibilityState?.disabled ?? false;
|
||||
}
|
||||
|
||||
export function getAccessibilityValue(element: ReactTestInstance): AccessibilityValue | undefined {
|
||||
// See: https://github.com/callstack/react-native-testing-library/wiki/Accessibility:-State#expanded-state
|
||||
export function computeAriaExpanded({ props }: ReactTestInstance): boolean | undefined {
|
||||
return props['aria-expanded'] ?? props.accessibilityState?.expanded;
|
||||
}
|
||||
|
||||
// See: https://github.com/callstack/react-native-testing-library/wiki/Accessibility:-State#selected-state
|
||||
export function computeAriaSelected({ props }: ReactTestInstance): boolean {
|
||||
return props['aria-selected'] ?? props.accessibilityState?.selected ?? false;
|
||||
}
|
||||
|
||||
export function computeAriaValue(element: ReactTestInstance): AccessibilityValue {
|
||||
const {
|
||||
accessibilityValue,
|
||||
'aria-valuemax': ariaValueMax,
|
||||
@@ -187,17 +192,6 @@ export function getAccessibilityValue(element: ReactTestInstance): Accessibility
|
||||
'aria-valuetext': ariaValueText,
|
||||
} = element.props;
|
||||
|
||||
const hasAnyAccessibilityValueProps =
|
||||
accessibilityValue != null ||
|
||||
ariaValueMax != null ||
|
||||
ariaValueMin != null ||
|
||||
ariaValueNow != null ||
|
||||
ariaValueText != null;
|
||||
|
||||
if (!hasAnyAccessibilityValueProps) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
max: ariaValueMax ?? accessibilityValue?.max,
|
||||
min: ariaValueMin ?? accessibilityValue?.min,
|
||||
@@ -206,39 +200,13 @@ export function getAccessibilityValue(element: ReactTestInstance): Accessibility
|
||||
};
|
||||
}
|
||||
|
||||
export function isElementBusy(element: ReactTestInstance): NonNullable<AccessibilityState['busy']> {
|
||||
const { accessibilityState, 'aria-busy': ariaBusy } = element.props;
|
||||
return ariaBusy ?? accessibilityState?.busy ?? false;
|
||||
}
|
||||
|
||||
export function isElementCollapsed(
|
||||
element: ReactTestInstance,
|
||||
): NonNullable<AccessibilityState['expanded']> {
|
||||
const { accessibilityState, 'aria-expanded': ariaExpanded } = element.props;
|
||||
return (ariaExpanded ?? accessibilityState?.expanded) === false;
|
||||
}
|
||||
|
||||
export function isElementExpanded(
|
||||
element: ReactTestInstance,
|
||||
): NonNullable<AccessibilityState['expanded']> {
|
||||
const { accessibilityState, 'aria-expanded': ariaExpanded } = element.props;
|
||||
return ariaExpanded ?? accessibilityState?.expanded ?? false;
|
||||
}
|
||||
|
||||
export function isElementSelected(
|
||||
element: ReactTestInstance,
|
||||
): NonNullable<AccessibilityState['selected']> {
|
||||
const { accessibilityState, 'aria-selected': ariaSelected } = element.props;
|
||||
return ariaSelected ?? accessibilityState?.selected ?? false;
|
||||
}
|
||||
|
||||
export function getAccessibleName(element: ReactTestInstance): string | undefined {
|
||||
const label = getAccessibilityLabel(element);
|
||||
export function computeAccessibleName(element: ReactTestInstance): string | undefined {
|
||||
const label = computeAriaLabel(element);
|
||||
if (label) {
|
||||
return label;
|
||||
}
|
||||
|
||||
const labelElementId = getAccessibilityLabelledBy(element);
|
||||
const labelElementId = computeAriaLabelledBy(element);
|
||||
if (labelElementId) {
|
||||
const rootElement = getUnsafeRootElement(element);
|
||||
const labelElement = rootElement?.findByProps({ nativeID: labelElementId });
|
||||
|
||||
@@ -70,7 +70,7 @@ function getByTestId(instance: ReactTestInstance, testID: string) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given element is a host Text.
|
||||
* Checks if the given element is a host Text element.
|
||||
* @param element The element to check.
|
||||
*/
|
||||
export function isHostText(element?: ReactTestInstance | null): element is HostTestInstance {
|
||||
@@ -78,7 +78,7 @@ export function isHostText(element?: ReactTestInstance | null): element is HostT
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given element is a host TextInput.
|
||||
* Checks if the given element is a host TextInput element.
|
||||
* @param element The element to check.
|
||||
*/
|
||||
export function isHostTextInput(element?: ReactTestInstance | null): element is HostTestInstance {
|
||||
@@ -86,7 +86,15 @@ export function isHostTextInput(element?: ReactTestInstance | null): element is
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given element is a host ScrollView.
|
||||
* Checks if the given element is a host Switch element.
|
||||
* @param element The element to check.
|
||||
*/
|
||||
export function isHostSwitch(element?: ReactTestInstance | null): element is HostTestInstance {
|
||||
return element?.type === getHostComponentNames().switch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given element is a host ScrollView element.
|
||||
* @param element The element to check.
|
||||
*/
|
||||
export function isHostScrollView(element?: ReactTestInstance | null): element is HostTestInstance {
|
||||
@@ -94,7 +102,7 @@ export function isHostScrollView(element?: ReactTestInstance | null): element is
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given element is a host Modal.
|
||||
* Checks if the given element is a host Modal element.
|
||||
* @param element The element to check.
|
||||
*/
|
||||
export function isHostModal(element?: ReactTestInstance | null): element is HostTestInstance {
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import { AccessibilityState } from 'react-native';
|
||||
import { ReactTestInstance } from 'react-test-renderer';
|
||||
import { accessibilityStateKeys, getAccessibilityState } from '../accessibility';
|
||||
import {
|
||||
computeAriaBusy,
|
||||
computeAriaChecked,
|
||||
computeAriaDisabled,
|
||||
computeAriaExpanded,
|
||||
computeAriaSelected,
|
||||
} from '../accessibility';
|
||||
|
||||
// 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
|
||||
@@ -14,32 +19,25 @@ export interface AccessibilityStateMatcher {
|
||||
expanded?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default accessibility state values based on experiments using accessibility
|
||||
* inspector/screen reader on iOS and Android.
|
||||
*
|
||||
* @see https://github.com/callstack/react-native-testing-library/wiki/Accessibility:-State
|
||||
*/
|
||||
const defaultState: AccessibilityState = {
|
||||
disabled: false,
|
||||
selected: false,
|
||||
checked: undefined,
|
||||
busy: false,
|
||||
expanded: undefined,
|
||||
};
|
||||
|
||||
export function matchAccessibilityState(
|
||||
node: ReactTestInstance,
|
||||
matcher: AccessibilityStateMatcher,
|
||||
) {
|
||||
const state = getAccessibilityState(node);
|
||||
return accessibilityStateKeys.every((key) => matchState(matcher, state, key));
|
||||
}
|
||||
if (matcher.busy !== undefined && matcher.busy !== computeAriaBusy(node)) {
|
||||
return false;
|
||||
}
|
||||
if (matcher.checked !== undefined && matcher.checked !== computeAriaChecked(node)) {
|
||||
return false;
|
||||
}
|
||||
if (matcher.disabled !== undefined && matcher.disabled !== computeAriaDisabled(node)) {
|
||||
return false;
|
||||
}
|
||||
if (matcher.expanded !== undefined && matcher.expanded !== computeAriaExpanded(node)) {
|
||||
return false;
|
||||
}
|
||||
if (matcher.selected !== undefined && matcher.selected !== computeAriaSelected(node)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
function matchState(
|
||||
matcher: AccessibilityStateMatcher,
|
||||
state: AccessibilityState | undefined,
|
||||
key: keyof AccessibilityState,
|
||||
) {
|
||||
return matcher[key] === undefined || matcher[key] === (state?.[key] ?? defaultState[key]);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ReactTestInstance } from 'react-test-renderer';
|
||||
import { getAccessibilityValue } from '../accessibility';
|
||||
import { computeAriaValue } from '../accessibility';
|
||||
import { TextMatch } from '../../matches';
|
||||
import { matchStringProp } from './match-string-prop';
|
||||
|
||||
@@ -14,7 +14,7 @@ export function matchAccessibilityValue(
|
||||
node: ReactTestInstance,
|
||||
matcher: AccessibilityValueMatcher,
|
||||
): boolean {
|
||||
const value = getAccessibilityValue(node);
|
||||
const value = computeAriaValue(node);
|
||||
return (
|
||||
(matcher.min === undefined || matcher.min === value?.min) &&
|
||||
(matcher.max === undefined || matcher.max === value?.max) &&
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ReactTestInstance } from 'react-test-renderer';
|
||||
import { matches, TextMatch, TextMatchOptions } from '../../matches';
|
||||
import { getAccessibilityLabel, getAccessibilityLabelledBy } from '../accessibility';
|
||||
import { computeAriaLabel, computeAriaLabelledBy } from '../accessibility';
|
||||
import { findAll } from '../find-all';
|
||||
import { matchTextContent } from './match-text-content';
|
||||
|
||||
@@ -12,7 +12,7 @@ export function matchLabelText(
|
||||
) {
|
||||
return (
|
||||
matchAccessibilityLabel(element, expectedText, options) ||
|
||||
matchAccessibilityLabelledBy(root, getAccessibilityLabelledBy(element), expectedText, options)
|
||||
matchAccessibilityLabelledBy(root, computeAriaLabelledBy(element), expectedText, options)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ function matchAccessibilityLabel(
|
||||
extpectedLabel: TextMatch,
|
||||
options: TextMatchOptions,
|
||||
) {
|
||||
return matches(extpectedLabel, getAccessibilityLabel(element), options.normalizer, options.exact);
|
||||
return matches(extpectedLabel, computeAriaLabel(element), options.normalizer, options.exact);
|
||||
}
|
||||
|
||||
function matchAccessibilityLabelledBy(
|
||||
|
||||
@@ -5,7 +5,7 @@ import { screen } from '../../screen';
|
||||
import '../extend-expect';
|
||||
|
||||
function renderViewsWithRole(role: AccessibilityRole) {
|
||||
return render(
|
||||
render(
|
||||
<>
|
||||
<View
|
||||
testID={`${role}-checked`}
|
||||
|
||||
@@ -1,96 +0,0 @@
|
||||
import * as React from 'react';
|
||||
import { View } from 'react-native';
|
||||
import { render, screen } from '../..';
|
||||
import '../extend-expect';
|
||||
|
||||
test('toBeCollapsed() basic case', () => {
|
||||
render(
|
||||
<>
|
||||
<View testID="expanded" accessibilityState={{ expanded: true }} />
|
||||
<View testID="expanded-aria" aria-expanded />
|
||||
<View testID="not-expanded" accessibilityState={{ expanded: false }} />
|
||||
<View testID="not-expanded-aria" aria-expanded={false} />
|
||||
<View testID="default" />
|
||||
</>,
|
||||
);
|
||||
|
||||
expect(screen.getByTestId('expanded')).not.toBeCollapsed();
|
||||
expect(screen.getByTestId('expanded-aria')).not.toBeCollapsed();
|
||||
expect(screen.getByTestId('not-expanded')).toBeCollapsed();
|
||||
expect(screen.getByTestId('not-expanded-aria')).toBeCollapsed();
|
||||
expect(screen.getByTestId('default')).not.toBeCollapsed();
|
||||
});
|
||||
|
||||
test('toBeCollapsed() error messages', () => {
|
||||
render(
|
||||
<>
|
||||
<View testID="expanded" accessibilityState={{ expanded: true }} />
|
||||
<View testID="expanded-aria" aria-expanded />
|
||||
<View testID="not-expanded" accessibilityState={{ expanded: false }} />
|
||||
<View testID="not-expanded-aria" aria-expanded={false} />
|
||||
<View testID="default" />
|
||||
</>,
|
||||
);
|
||||
|
||||
expect(() => expect(screen.getByTestId('expanded')).toBeCollapsed())
|
||||
.toThrowErrorMatchingInlineSnapshot(`
|
||||
"expect(element).toBeCollapsed()
|
||||
|
||||
Received element is not collapsed:
|
||||
<View
|
||||
accessibilityState={
|
||||
{
|
||||
"expanded": true,
|
||||
}
|
||||
}
|
||||
testID="expanded"
|
||||
/>"
|
||||
`);
|
||||
|
||||
expect(() => expect(screen.getByTestId('expanded-aria')).toBeCollapsed())
|
||||
.toThrowErrorMatchingInlineSnapshot(`
|
||||
"expect(element).toBeCollapsed()
|
||||
|
||||
Received element is not collapsed:
|
||||
<View
|
||||
aria-expanded={true}
|
||||
testID="expanded-aria"
|
||||
/>"
|
||||
`);
|
||||
|
||||
expect(() => expect(screen.getByTestId('not-expanded')).not.toBeCollapsed())
|
||||
.toThrowErrorMatchingInlineSnapshot(`
|
||||
"expect(element).not.toBeCollapsed()
|
||||
|
||||
Received element is collapsed:
|
||||
<View
|
||||
accessibilityState={
|
||||
{
|
||||
"expanded": false,
|
||||
}
|
||||
}
|
||||
testID="not-expanded"
|
||||
/>"
|
||||
`);
|
||||
|
||||
expect(() => expect(screen.getByTestId('not-expanded-aria')).not.toBeCollapsed())
|
||||
.toThrowErrorMatchingInlineSnapshot(`
|
||||
"expect(element).not.toBeCollapsed()
|
||||
|
||||
Received element is collapsed:
|
||||
<View
|
||||
aria-expanded={false}
|
||||
testID="not-expanded-aria"
|
||||
/>"
|
||||
`);
|
||||
|
||||
expect(() => expect(screen.getByTestId('default')).toBeCollapsed())
|
||||
.toThrowErrorMatchingInlineSnapshot(`
|
||||
"expect(element).toBeCollapsed()
|
||||
|
||||
Received element is not collapsed:
|
||||
<View
|
||||
testID="default"
|
||||
/>"
|
||||
`);
|
||||
});
|
||||
@@ -94,3 +94,95 @@ test('toBeExpanded() error messages', () => {
|
||||
/>"
|
||||
`);
|
||||
});
|
||||
|
||||
test('toBeCollapsed() basic case', () => {
|
||||
render(
|
||||
<>
|
||||
<View testID="expanded" accessibilityState={{ expanded: true }} />
|
||||
<View testID="expanded-aria" aria-expanded />
|
||||
<View testID="not-expanded" accessibilityState={{ expanded: false }} />
|
||||
<View testID="not-expanded-aria" aria-expanded={false} />
|
||||
<View testID="default" />
|
||||
</>,
|
||||
);
|
||||
|
||||
expect(screen.getByTestId('expanded')).not.toBeCollapsed();
|
||||
expect(screen.getByTestId('expanded-aria')).not.toBeCollapsed();
|
||||
expect(screen.getByTestId('not-expanded')).toBeCollapsed();
|
||||
expect(screen.getByTestId('not-expanded-aria')).toBeCollapsed();
|
||||
expect(screen.getByTestId('default')).not.toBeCollapsed();
|
||||
});
|
||||
|
||||
test('toBeCollapsed() error messages', () => {
|
||||
render(
|
||||
<>
|
||||
<View testID="expanded" accessibilityState={{ expanded: true }} />
|
||||
<View testID="expanded-aria" aria-expanded />
|
||||
<View testID="not-expanded" accessibilityState={{ expanded: false }} />
|
||||
<View testID="not-expanded-aria" aria-expanded={false} />
|
||||
<View testID="default" />
|
||||
</>,
|
||||
);
|
||||
|
||||
expect(() => expect(screen.getByTestId('expanded')).toBeCollapsed())
|
||||
.toThrowErrorMatchingInlineSnapshot(`
|
||||
"expect(element).toBeCollapsed()
|
||||
|
||||
Received element is not collapsed:
|
||||
<View
|
||||
accessibilityState={
|
||||
{
|
||||
"expanded": true,
|
||||
}
|
||||
}
|
||||
testID="expanded"
|
||||
/>"
|
||||
`);
|
||||
|
||||
expect(() => expect(screen.getByTestId('expanded-aria')).toBeCollapsed())
|
||||
.toThrowErrorMatchingInlineSnapshot(`
|
||||
"expect(element).toBeCollapsed()
|
||||
|
||||
Received element is not collapsed:
|
||||
<View
|
||||
aria-expanded={true}
|
||||
testID="expanded-aria"
|
||||
/>"
|
||||
`);
|
||||
|
||||
expect(() => expect(screen.getByTestId('not-expanded')).not.toBeCollapsed())
|
||||
.toThrowErrorMatchingInlineSnapshot(`
|
||||
"expect(element).not.toBeCollapsed()
|
||||
|
||||
Received element is collapsed:
|
||||
<View
|
||||
accessibilityState={
|
||||
{
|
||||
"expanded": false,
|
||||
}
|
||||
}
|
||||
testID="not-expanded"
|
||||
/>"
|
||||
`);
|
||||
|
||||
expect(() => expect(screen.getByTestId('not-expanded-aria')).not.toBeCollapsed())
|
||||
.toThrowErrorMatchingInlineSnapshot(`
|
||||
"expect(element).not.toBeCollapsed()
|
||||
|
||||
Received element is collapsed:
|
||||
<View
|
||||
aria-expanded={false}
|
||||
testID="not-expanded-aria"
|
||||
/>"
|
||||
`);
|
||||
|
||||
expect(() => expect(screen.getByTestId('default')).toBeCollapsed())
|
||||
.toThrowErrorMatchingInlineSnapshot(`
|
||||
"expect(element).toBeCollapsed()
|
||||
|
||||
Received element is not collapsed:
|
||||
<View
|
||||
testID="default"
|
||||
/>"
|
||||
`);
|
||||
});
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { toBeOnTheScreen } from './to-be-on-the-screen';
|
||||
import { toBeChecked } from './to-be-checked';
|
||||
import { toBeCollapsed } from './to-be-collapsed';
|
||||
import { toBeDisabled, toBeEnabled } from './to-be-disabled';
|
||||
import { toBeBusy } from './to-be-busy';
|
||||
import { toBeEmptyElement } from './to-be-empty-element';
|
||||
import { toBeExpanded } from './to-be-expanded';
|
||||
import { toBeExpanded, toBeCollapsed } from './to-be-expanded';
|
||||
import { toBePartiallyChecked } from './to-be-partially-checked';
|
||||
import { toBeSelected } from './to-be-selected';
|
||||
import { toBeVisible } from './to-be-visible';
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
export { toBeBusy } from './to-be-busy';
|
||||
export { toBeChecked } from './to-be-checked';
|
||||
export { toBeCollapsed } from './to-be-collapsed';
|
||||
export { toBeDisabled, toBeEnabled } from './to-be-disabled';
|
||||
export { toBeEmptyElement } from './to-be-empty-element';
|
||||
export { toBeExpanded } from './to-be-expanded';
|
||||
export { toBeExpanded, toBeCollapsed } from './to-be-expanded';
|
||||
export { toBeOnTheScreen } from './to-be-on-the-screen';
|
||||
export { toBePartiallyChecked } from './to-be-partially-checked';
|
||||
export { toBeSelected } from './to-be-selected';
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { ReactTestInstance } from 'react-test-renderer';
|
||||
import { matcherHint } from 'jest-matcher-utils';
|
||||
import { isElementBusy } from '../helpers/accessibility';
|
||||
import { computeAriaBusy } from '../helpers/accessibility';
|
||||
import { checkHostElement, formatElement } from './utils';
|
||||
|
||||
export function toBeBusy(this: jest.MatcherContext, element: ReactTestInstance) {
|
||||
checkHostElement(element, toBeBusy, this);
|
||||
|
||||
return {
|
||||
pass: isElementBusy(element),
|
||||
pass: computeAriaBusy(element),
|
||||
message: () => {
|
||||
const matcher = matcherHint(`${this.isNot ? '.not' : ''}.toBeBusy`, 'element', '');
|
||||
return [
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
import type { ReactTestInstance } from 'react-test-renderer';
|
||||
import { matcherHint } from 'jest-matcher-utils';
|
||||
import {
|
||||
getAccessibilityCheckedState,
|
||||
getAccessibilityRole,
|
||||
isAccessibilityElement,
|
||||
} from '../helpers/accessibility';
|
||||
import { computeAriaChecked, getRole, isAccessibilityElement } from '../helpers/accessibility';
|
||||
import { ErrorWithStack } from '../helpers/errors';
|
||||
import { checkHostElement, formatElement } from './utils';
|
||||
|
||||
@@ -19,7 +15,7 @@ export function toBeChecked(this: jest.MatcherContext, element: ReactTestInstanc
|
||||
}
|
||||
|
||||
return {
|
||||
pass: getAccessibilityCheckedState(element) === true,
|
||||
pass: computeAriaChecked(element) === true,
|
||||
message: () => {
|
||||
const is = this.isNot ? 'is' : 'is not';
|
||||
return [
|
||||
@@ -37,6 +33,6 @@ function hasValidAccessibilityRole(element: ReactTestInstance) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const role = getAccessibilityRole(element);
|
||||
const role = getRole(element);
|
||||
return role === 'checkbox' || role === 'radio';
|
||||
}
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
import { ReactTestInstance } from 'react-test-renderer';
|
||||
import { matcherHint } from 'jest-matcher-utils';
|
||||
import { isElementCollapsed } from '../helpers/accessibility';
|
||||
import { checkHostElement, formatElement } from './utils';
|
||||
|
||||
export function toBeCollapsed(this: jest.MatcherContext, element: ReactTestInstance) {
|
||||
checkHostElement(element, toBeCollapsed, this);
|
||||
|
||||
return {
|
||||
pass: isElementCollapsed(element),
|
||||
message: () => {
|
||||
const matcher = matcherHint(`${this.isNot ? '.not' : ''}.toBeCollapsed`, 'element', '');
|
||||
return [
|
||||
matcher,
|
||||
'',
|
||||
`Received element is ${this.isNot ? '' : 'not '}collapsed:`,
|
||||
formatElement(element),
|
||||
].join('\n');
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -1,14 +1,13 @@
|
||||
import type { ReactTestInstance } from 'react-test-renderer';
|
||||
import { matcherHint } from 'jest-matcher-utils';
|
||||
import { isHostTextInput } from '../helpers/host-component-names';
|
||||
import { isTextInputEditable } from '../helpers/text-input';
|
||||
import { computeAriaDisabled } from '../helpers/accessibility';
|
||||
import { getHostParent } from '../helpers/component-tree';
|
||||
import { checkHostElement, formatElement } from './utils';
|
||||
|
||||
export function toBeDisabled(this: jest.MatcherContext, element: ReactTestInstance) {
|
||||
checkHostElement(element, toBeDisabled, this);
|
||||
|
||||
const isDisabled = isElementDisabled(element) || isAncestorDisabled(element);
|
||||
const isDisabled = computeAriaDisabled(element) || isAncestorDisabled(element);
|
||||
|
||||
return {
|
||||
pass: isDisabled,
|
||||
@@ -27,7 +26,7 @@ export function toBeDisabled(this: jest.MatcherContext, element: ReactTestInstan
|
||||
export function toBeEnabled(this: jest.MatcherContext, element: ReactTestInstance) {
|
||||
checkHostElement(element, toBeEnabled, this);
|
||||
|
||||
const isEnabled = !isElementDisabled(element) && !isAncestorDisabled(element);
|
||||
const isEnabled = !computeAriaDisabled(element) && !isAncestorDisabled(element);
|
||||
|
||||
return {
|
||||
pass: isEnabled,
|
||||
@@ -43,20 +42,11 @@ export function toBeEnabled(this: jest.MatcherContext, element: ReactTestInstanc
|
||||
};
|
||||
}
|
||||
|
||||
function isElementDisabled(element: ReactTestInstance) {
|
||||
if (isHostTextInput(element) && !isTextInputEditable(element)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const { accessibilityState, 'aria-disabled': ariaDisabled } = element.props;
|
||||
return ariaDisabled ?? accessibilityState?.disabled ?? false;
|
||||
}
|
||||
|
||||
function isAncestorDisabled(element: ReactTestInstance): boolean {
|
||||
const parent = getHostParent(element);
|
||||
if (parent == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return isElementDisabled(parent) || isAncestorDisabled(parent);
|
||||
return computeAriaDisabled(parent) || isAncestorDisabled(parent);
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { ReactTestInstance } from 'react-test-renderer';
|
||||
import { matcherHint } from 'jest-matcher-utils';
|
||||
import { isElementExpanded } from '../helpers/accessibility';
|
||||
import { computeAriaExpanded } from '../helpers/accessibility';
|
||||
import { checkHostElement, formatElement } from './utils';
|
||||
|
||||
export function toBeExpanded(this: jest.MatcherContext, element: ReactTestInstance) {
|
||||
checkHostElement(element, toBeExpanded, this);
|
||||
|
||||
return {
|
||||
pass: isElementExpanded(element),
|
||||
pass: computeAriaExpanded(element) === true,
|
||||
message: () => {
|
||||
const matcher = matcherHint(`${this.isNot ? '.not' : ''}.toBeExpanded`, 'element', '');
|
||||
return [
|
||||
@@ -19,3 +19,20 @@ export function toBeExpanded(this: jest.MatcherContext, element: ReactTestInstan
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function toBeCollapsed(this: jest.MatcherContext, element: ReactTestInstance) {
|
||||
checkHostElement(element, toBeCollapsed, this);
|
||||
|
||||
return {
|
||||
pass: computeAriaExpanded(element) === false,
|
||||
message: () => {
|
||||
const matcher = matcherHint(`${this.isNot ? '.not' : ''}.toBeCollapsed`, 'element', '');
|
||||
return [
|
||||
matcher,
|
||||
'',
|
||||
`Received element is ${this.isNot ? '' : 'not '}collapsed:`,
|
||||
formatElement(element),
|
||||
].join('\n');
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
import type { ReactTestInstance } from 'react-test-renderer';
|
||||
import { matcherHint } from 'jest-matcher-utils';
|
||||
import {
|
||||
getAccessibilityCheckedState,
|
||||
getAccessibilityRole,
|
||||
isAccessibilityElement,
|
||||
} from '../helpers/accessibility';
|
||||
import { computeAriaChecked, getRole, isAccessibilityElement } from '../helpers/accessibility';
|
||||
import { ErrorWithStack } from '../helpers/errors';
|
||||
import { checkHostElement, formatElement } from './utils';
|
||||
|
||||
@@ -19,7 +15,7 @@ export function toBePartiallyChecked(this: jest.MatcherContext, element: ReactTe
|
||||
}
|
||||
|
||||
return {
|
||||
pass: getAccessibilityCheckedState(element) === 'mixed',
|
||||
pass: computeAriaChecked(element) === 'mixed',
|
||||
message: () => {
|
||||
const is = this.isNot ? 'is' : 'is not';
|
||||
return [
|
||||
@@ -33,6 +29,6 @@ export function toBePartiallyChecked(this: jest.MatcherContext, element: ReactTe
|
||||
}
|
||||
|
||||
function hasValidAccessibilityRole(element: ReactTestInstance) {
|
||||
const role = getAccessibilityRole(element);
|
||||
const role = getRole(element);
|
||||
return isAccessibilityElement(element) && role === 'checkbox';
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { ReactTestInstance } from 'react-test-renderer';
|
||||
import { matcherHint } from 'jest-matcher-utils';
|
||||
import { isElementSelected } from '../helpers/accessibility';
|
||||
import { computeAriaSelected } from '../helpers/accessibility';
|
||||
import { checkHostElement, formatElement } from './utils';
|
||||
|
||||
export function toBeSelected(this: jest.MatcherContext, element: ReactTestInstance) {
|
||||
checkHostElement(element, toBeSelected, this);
|
||||
|
||||
return {
|
||||
pass: isElementSelected(element),
|
||||
pass: computeAriaSelected(element),
|
||||
message: () => {
|
||||
const is = this.isNot ? 'is' : 'is not';
|
||||
return [
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { ReactTestInstance } from 'react-test-renderer';
|
||||
import { matcherHint, stringify } from 'jest-matcher-utils';
|
||||
import { getAccessibilityValue } from '../helpers/accessibility';
|
||||
import { computeAriaValue } from '../helpers/accessibility';
|
||||
import {
|
||||
AccessibilityValueMatcher,
|
||||
matchAccessibilityValue,
|
||||
@@ -15,7 +15,7 @@ export function toHaveAccessibilityValue(
|
||||
) {
|
||||
checkHostElement(element, toHaveAccessibilityValue, this);
|
||||
|
||||
const receivedValue = getAccessibilityValue(element);
|
||||
const receivedValue = computeAriaValue(element);
|
||||
|
||||
return {
|
||||
pass: matchAccessibilityValue(element, expectedValue),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { ReactTestInstance } from 'react-test-renderer';
|
||||
import { matcherHint } from 'jest-matcher-utils';
|
||||
import { getAccessibleName } from '../helpers/accessibility';
|
||||
import { computeAccessibleName } from '../helpers/accessibility';
|
||||
import { TextMatch, TextMatchOptions, matches } from '../matches';
|
||||
import { checkHostElement, formatMessage } from './utils';
|
||||
|
||||
@@ -12,7 +12,7 @@ export function toHaveAccessibleName(
|
||||
) {
|
||||
checkHostElement(element, toHaveAccessibleName, this);
|
||||
|
||||
const receivedName = getAccessibleName(element);
|
||||
const receivedName = computeAccessibleName(element);
|
||||
const missingExpectedValue = arguments.length === 1;
|
||||
|
||||
let pass = false;
|
||||
|
||||
@@ -88,7 +88,7 @@ test('getAllByA11yState, queryAllByA11yState, findAllByA11yState', async () => {
|
||||
|
||||
describe('checked state matching', () => {
|
||||
it('handles true', () => {
|
||||
render(<View accessibilityState={{ checked: true }} />);
|
||||
render(<View role="checkbox" accessibilityState={{ checked: true }} />);
|
||||
|
||||
expect(screen.getByA11yState({ checked: true })).toBeTruthy();
|
||||
expect(screen.queryByA11yState({ checked: 'mixed' })).toBeFalsy();
|
||||
@@ -96,7 +96,7 @@ describe('checked state matching', () => {
|
||||
});
|
||||
|
||||
it('handles mixed', () => {
|
||||
render(<View accessibilityState={{ checked: 'mixed' }} />);
|
||||
render(<View role="checkbox" accessibilityState={{ checked: 'mixed' }} />);
|
||||
|
||||
expect(screen.getByA11yState({ checked: 'mixed' })).toBeTruthy();
|
||||
expect(screen.queryByA11yState({ checked: true })).toBeFalsy();
|
||||
@@ -104,15 +104,15 @@ describe('checked state matching', () => {
|
||||
});
|
||||
|
||||
it('handles false', () => {
|
||||
render(<View accessibilityState={{ checked: false }} />);
|
||||
render(<View role="checkbox" accessibilityState={{ checked: false }} />);
|
||||
|
||||
expect(screen.getByA11yState({ checked: false })).toBeTruthy();
|
||||
expect(screen.queryByA11yState({ checked: true })).toBeFalsy();
|
||||
expect(screen.queryByA11yState({ checked: 'mixed' })).toBeFalsy();
|
||||
});
|
||||
|
||||
it('handles default', () => {
|
||||
render(<View accessibilityState={{}} />);
|
||||
it('handles default', () => {
|
||||
render(<View role="checkbox" accessibilityState={{}} />);
|
||||
|
||||
expect(screen.queryByA11yState({ checked: false })).toBeFalsy();
|
||||
expect(screen.queryByA11yState({ checked: true })).toBeFalsy();
|
||||
@@ -135,7 +135,7 @@ describe('expanded state matching', () => {
|
||||
expect(screen.queryByA11yState({ expanded: true })).toBeFalsy();
|
||||
});
|
||||
|
||||
it('handles default', () => {
|
||||
it('handles default', () => {
|
||||
render(<View accessibilityState={{}} />);
|
||||
|
||||
expect(screen.queryByA11yState({ expanded: false })).toBeFalsy();
|
||||
@@ -158,7 +158,7 @@ describe('disabled state matching', () => {
|
||||
expect(screen.queryByA11yState({ disabled: true })).toBeFalsy();
|
||||
});
|
||||
|
||||
it('handles default', () => {
|
||||
it('handles default', () => {
|
||||
render(<View accessibilityState={{}} />);
|
||||
|
||||
expect(screen.getByA11yState({ disabled: false })).toBeTruthy();
|
||||
@@ -181,7 +181,7 @@ describe('busy state matching', () => {
|
||||
expect(screen.queryByA11yState({ busy: true })).toBeFalsy();
|
||||
});
|
||||
|
||||
it('handles default', () => {
|
||||
it('handles default', () => {
|
||||
render(<View accessibilityState={{}} />);
|
||||
|
||||
expect(screen.getByA11yState({ busy: false })).toBeTruthy();
|
||||
@@ -204,7 +204,7 @@ describe('selected state matching', () => {
|
||||
expect(screen.queryByA11yState({ selected: true })).toBeFalsy();
|
||||
});
|
||||
|
||||
it('handles default', () => {
|
||||
it('handles default', () => {
|
||||
render(<View accessibilityState={{}} />);
|
||||
|
||||
expect(screen.getByA11yState({ selected: false })).toBeTruthy();
|
||||
@@ -463,28 +463,28 @@ describe('aria-selected prop', () => {
|
||||
|
||||
describe('aria-checked prop', () => {
|
||||
test('supports aria-checked={true} prop', () => {
|
||||
render(<View accessible accessibilityRole="button" aria-checked={true} />);
|
||||
render(<View accessible role="checkbox" 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', () => {
|
||||
render(<View accessible accessibilityRole="button" aria-checked={false} />);
|
||||
render(<View accessible role="checkbox" 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', () => {
|
||||
render(<View accessible accessibilityRole="button" aria-checked="mixed" />);
|
||||
test('supports aria-checked="mixed" prop', () => {
|
||||
render(<View accessible accessibilityRole="checkbox" 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', () => {
|
||||
render(<View accessible accessibilityRole="button" />);
|
||||
test('supports default aria-checked prop', () => {
|
||||
render(<View accessible accessibilityRole="checkbox" />);
|
||||
expect(screen.getByAccessibilityState({})).toBeTruthy();
|
||||
expect(screen.queryByAccessibilityState({ checked: true })).toBeNull();
|
||||
expect(screen.queryByAccessibilityState({ checked: false })).toBeNull();
|
||||
|
||||
@@ -509,32 +509,32 @@ describe('supports accessibility states', () => {
|
||||
});
|
||||
|
||||
test('supports aria-checked={true} prop', () => {
|
||||
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();
|
||||
render(<View accessible role="checkbox" aria-checked={true} />);
|
||||
expect(screen.getByRole('checkbox', { checked: true })).toBeTruthy();
|
||||
expect(screen.queryByRole('checkbox', { checked: false })).toBeNull();
|
||||
expect(screen.queryByRole('checkbox', { checked: 'mixed' })).toBeNull();
|
||||
});
|
||||
|
||||
test('supports aria-checked={false} prop', () => {
|
||||
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();
|
||||
render(<View accessible role="checkbox" aria-checked={false} />);
|
||||
expect(screen.getByRole('checkbox', { checked: false })).toBeTruthy();
|
||||
expect(screen.queryByRole('checkbox', { checked: true })).toBeNull();
|
||||
expect(screen.queryByRole('checkbox', { checked: 'mixed' })).toBeNull();
|
||||
});
|
||||
|
||||
test('supports aria-checked="mixed prop', () => {
|
||||
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 aria-checked="mixed" prop', () => {
|
||||
render(<View accessible role="checkbox" aria-checked="mixed" />);
|
||||
expect(screen.getByRole('checkbox', { checked: 'mixed' })).toBeTruthy();
|
||||
expect(screen.queryByRole('checkbox', { checked: true })).toBeNull();
|
||||
expect(screen.queryByRole('checkbox', { checked: false })).toBeNull();
|
||||
});
|
||||
|
||||
test('supports default aria-selected prop', () => {
|
||||
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();
|
||||
render(<View accessible role="checkbox" />);
|
||||
expect(screen.getByRole('checkbox')).toBeTruthy();
|
||||
expect(screen.queryByRole('checkbox', { checked: true })).toBeNull();
|
||||
expect(screen.queryByRole('checkbox', { checked: false })).toBeNull();
|
||||
expect(screen.queryByRole('checkbox', { checked: 'mixed' })).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -728,7 +728,7 @@ describe('supports accessibility states', () => {
|
||||
test('matches an element combining all the options', () => {
|
||||
render(
|
||||
<TouchableOpacity
|
||||
accessibilityRole="button"
|
||||
accessibilityRole="checkbox"
|
||||
accessibilityState={{
|
||||
disabled: true,
|
||||
selected: true,
|
||||
@@ -742,7 +742,7 @@ describe('supports accessibility states', () => {
|
||||
);
|
||||
|
||||
expect(
|
||||
screen.getByRole('button', {
|
||||
screen.getByRole('checkbox', {
|
||||
name: 'Save',
|
||||
disabled: true,
|
||||
selected: true,
|
||||
|
||||
+2
-2
@@ -3,7 +3,7 @@ import type { AccessibilityRole, Role } from 'react-native';
|
||||
import {
|
||||
accessibilityStateKeys,
|
||||
accessibilityValueKeys,
|
||||
getAccessibilityRole,
|
||||
getRole,
|
||||
isAccessibilityElement,
|
||||
} from '../helpers/accessibility';
|
||||
import { findAll } from '../helpers/find-all';
|
||||
@@ -65,7 +65,7 @@ const queryAllByRole = (
|
||||
(node) =>
|
||||
// run the cheapest checks first, and early exit to avoid unneeded computations
|
||||
isAccessibilityElement(node) &&
|
||||
matchStringProp(getAccessibilityRole(node), role) &&
|
||||
matchStringProp(getRole(node), role) &&
|
||||
matchAccessibleStateIfNeeded(node, options) &&
|
||||
matchAccessibilityValueIfNeeded(node, options?.value) &&
|
||||
matchAccessibleNameIfNeeded(node, options?.name),
|
||||
|
||||
Reference in New Issue
Block a user