diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 22d0c9bc4..cee442654 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -72,7 +72,7 @@ jobs:
- name: Setup Node.js and deps
uses: ./.github/actions/setup-deps
- - name: Test in concurrent mode
+ - name: Test in legacy mode
run: CONCURRENT_MODE=0 yarn test:ci
test-website:
diff --git a/src/helpers/__tests__/accessiblity.test.tsx b/src/helpers/__tests__/accessiblity.test.tsx
index cbfd021c4..b206ccdc7 100644
--- a/src/helpers/__tests__/accessiblity.test.tsx
+++ b/src/helpers/__tests__/accessiblity.test.tsx
@@ -1,7 +1,7 @@
import React from 'react';
import { View, Text, TextInput, Pressable, Switch, TouchableOpacity } from 'react-native';
import { render, isHiddenFromAccessibility, isInaccessible, screen } from '../..';
-import { isAccessibilityElement } from '../accessibility';
+import { computeAriaLabel, isAccessibilityElement } from '../accessibility';
describe('isHiddenFromAccessibility', () => {
test('returns false for accessible elements', () => {
@@ -371,3 +371,39 @@ describe('isAccessibilityElement', () => {
expect(isAccessibilityElement(null)).toEqual(false);
});
});
+
+describe('computeAriaLabel', () => {
+ test('supports basic usage', () => {
+ render(
+
+
+
+
+ External Text
+
+
+
+ Text Content
+
+ ,
+ );
+
+ expect(computeAriaLabel(screen.getByTestId('label'))).toEqual('Internal Label');
+ expect(computeAriaLabel(screen.getByTestId('label-by-id'))).toEqual('External Text');
+ expect(computeAriaLabel(screen.getByTestId('no-label'))).toBeUndefined();
+ expect(computeAriaLabel(screen.getByTestId('text-content'))).toBeUndefined();
+ });
+
+ test('label priority', () => {
+ render(
+
+
+
+ External Label
+
+ ,
+ );
+
+ expect(computeAriaLabel(screen.getByTestId('subject'))).toEqual('External Label');
+ });
+});
diff --git a/src/helpers/__tests__/component-tree.test.tsx b/src/helpers/__tests__/component-tree.test.tsx
index 0746d58ff..fe7e3838f 100644
--- a/src/helpers/__tests__/component-tree.test.tsx
+++ b/src/helpers/__tests__/component-tree.test.tsx
@@ -228,8 +228,4 @@ describe('getUnsafeRootElement()', () => {
const view = screen.getByTestId('view');
expect(getUnsafeRootElement(view)).toEqual(screen.UNSAFE_root);
});
-
- it('returns null for null', () => {
- expect(getUnsafeRootElement(null)).toEqual(null);
- });
});
diff --git a/src/helpers/accessibility.ts b/src/helpers/accessibility.ts
index 062ea8fb2..40f7008ce 100644
--- a/src/helpers/accessibility.ts
+++ b/src/helpers/accessibility.ts
@@ -5,8 +5,9 @@ import {
Role,
StyleSheet,
} from 'react-native';
-import { ReactTestInstance } from 'react-test-renderer';
-import { getHostSiblings, getUnsafeRootElement } from './component-tree';
+import type { ReactTestInstance } from 'react-test-renderer';
+import { getHostSiblings, getUnsafeRootElement, isHostElement } from './component-tree';
+import { findAll } from './find-all';
import { isHostImage, isHostSwitch, isHostText, isHostTextInput } from './host-component-names';
import { getTextContent } from './text-content';
import { isTextInputEditable } from './text-input';
@@ -158,6 +159,19 @@ export function computeAriaModal(element: ReactTestInstance): boolean | undefine
}
export function computeAriaLabel(element: ReactTestInstance): string | undefined {
+ const labelElementId = element.props['aria-labelledby'] ?? element.props.accessibilityLabelledBy;
+ if (labelElementId) {
+ const rootElement = getUnsafeRootElement(element);
+ const labelElement = findAll(
+ rootElement,
+ (node) => isHostElement(node) && node.props.nativeID === labelElementId,
+ { includeHiddenElements: true },
+ );
+ if (labelElement.length > 0) {
+ return getTextContent(labelElement[0]);
+ }
+ }
+
const explicitLabel = element.props['aria-label'] ?? element.props.accessibilityLabel;
if (explicitLabel) {
return explicitLabel;
@@ -171,10 +185,6 @@ export function computeAriaLabel(element: ReactTestInstance): string | undefined
return undefined;
}
-export function computeAriaLabelledBy(element: ReactTestInstance): string | undefined {
- return element.props['aria-labelledby'] ?? element.props.accessibilityLabelledBy;
-}
-
// 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;
@@ -234,21 +244,7 @@ export function computeAriaValue(element: ReactTestInstance): AccessibilityValue
}
export function computeAccessibleName(element: ReactTestInstance): string | undefined {
- const label = computeAriaLabel(element);
- if (label) {
- return label;
- }
-
- const labelElementId = computeAriaLabelledBy(element);
- if (labelElementId) {
- const rootElement = getUnsafeRootElement(element);
- const labelElement = rootElement?.findByProps({ nativeID: labelElementId });
- if (labelElement) {
- return getTextContent(labelElement);
- }
- }
-
- return getTextContent(element);
+ return computeAriaLabel(element) ?? getTextContent(element);
}
type RoleSupportMap = Partial>;
diff --git a/src/helpers/component-tree.ts b/src/helpers/component-tree.ts
index 4a4a00897..bcb2a9f08 100644
--- a/src/helpers/component-tree.ts
+++ b/src/helpers/component-tree.ts
@@ -13,7 +13,7 @@ export function isHostElement(element?: ReactTestInstance | null): element is Ho
return typeof element?.type === 'string';
}
-export function isElementMounted(element: ReactTestInstance | null) {
+export function isElementMounted(element: ReactTestInstance) {
return getUnsafeRootElement(element) === screen.UNSAFE_root;
}
@@ -91,11 +91,7 @@ export function getHostSiblings(element: ReactTestInstance | null): HostTestInst
* @param element The element start traversing from.
* @returns The root element of the tree (host or composite).
*/
-export function getUnsafeRootElement(element: ReactTestInstance | null) {
- if (element == null) {
- return null;
- }
-
+export function getUnsafeRootElement(element: ReactTestInstance) {
let current = element;
while (current.parent) {
current = current.parent;
diff --git a/src/helpers/matchers/match-label-text.ts b/src/helpers/matchers/match-label-text.ts
index 1da29d867..f1ceaaac9 100644
--- a/src/helpers/matchers/match-label-text.ts
+++ b/src/helpers/matchers/match-label-text.ts
@@ -1,43 +1,11 @@
import { ReactTestInstance } from 'react-test-renderer';
import { matches, TextMatch, TextMatchOptions } from '../../matches';
-import { computeAriaLabel, computeAriaLabelledBy } from '../accessibility';
-import { findAll } from '../find-all';
-import { matchTextContent } from './match-text-content';
+import { computeAriaLabel } from '../accessibility';
-export function matchLabelText(
- root: ReactTestInstance,
- element: ReactTestInstance,
- expectedText: TextMatch,
- options: TextMatchOptions = {},
-) {
- return (
- matchAccessibilityLabel(element, expectedText, options) ||
- matchAccessibilityLabelledBy(root, computeAriaLabelledBy(element), expectedText, options)
- );
-}
-
-function matchAccessibilityLabel(
+export function matchAccessibilityLabel(
element: ReactTestInstance,
expectedLabel: TextMatch,
- options: TextMatchOptions,
+ options?: TextMatchOptions,
) {
- return matches(expectedLabel, computeAriaLabel(element), options.normalizer, options.exact);
-}
-
-function matchAccessibilityLabelledBy(
- root: ReactTestInstance,
- nativeId: string | undefined,
- text: TextMatch,
- options: TextMatchOptions,
-) {
- if (!nativeId) {
- return false;
- }
-
- return (
- findAll(
- root,
- (node) => node.props.nativeID === nativeId && matchTextContent(node, text, options),
- ).length > 0
- );
+ return matches(expectedLabel, computeAriaLabel(element), options?.normalizer, options?.exact);
}
diff --git a/src/matchers/to-be-busy.tsx b/src/matchers/to-be-busy.ts
similarity index 100%
rename from src/matchers/to-be-busy.tsx
rename to src/matchers/to-be-busy.ts
diff --git a/src/matchers/to-be-checked.tsx b/src/matchers/to-be-checked.ts
similarity index 100%
rename from src/matchers/to-be-checked.tsx
rename to src/matchers/to-be-checked.ts
diff --git a/src/matchers/to-be-disabled.tsx b/src/matchers/to-be-disabled.ts
similarity index 100%
rename from src/matchers/to-be-disabled.tsx
rename to src/matchers/to-be-disabled.ts
diff --git a/src/matchers/to-be-empty-element.tsx b/src/matchers/to-be-empty-element.ts
similarity index 100%
rename from src/matchers/to-be-empty-element.tsx
rename to src/matchers/to-be-empty-element.ts
diff --git a/src/matchers/to-be-expanded.tsx b/src/matchers/to-be-expanded.ts
similarity index 100%
rename from src/matchers/to-be-expanded.tsx
rename to src/matchers/to-be-expanded.ts
diff --git a/src/matchers/to-be-on-the-screen.tsx b/src/matchers/to-be-on-the-screen.ts
similarity index 100%
rename from src/matchers/to-be-on-the-screen.tsx
rename to src/matchers/to-be-on-the-screen.ts
diff --git a/src/matchers/to-be-partially-checked.tsx b/src/matchers/to-be-partially-checked.ts
similarity index 100%
rename from src/matchers/to-be-partially-checked.tsx
rename to src/matchers/to-be-partially-checked.ts
diff --git a/src/matchers/to-be-visible.tsx b/src/matchers/to-be-visible.ts
similarity index 100%
rename from src/matchers/to-be-visible.tsx
rename to src/matchers/to-be-visible.ts
diff --git a/src/matchers/to-contain-element.tsx b/src/matchers/to-contain-element.ts
similarity index 100%
rename from src/matchers/to-contain-element.tsx
rename to src/matchers/to-contain-element.ts
diff --git a/src/matchers/to-have-accessibility-value.tsx b/src/matchers/to-have-accessibility-value.ts
similarity index 100%
rename from src/matchers/to-have-accessibility-value.tsx
rename to src/matchers/to-have-accessibility-value.ts
diff --git a/src/matchers/to-have-accessible-name.tsx b/src/matchers/to-have-accessible-name.ts
similarity index 100%
rename from src/matchers/to-have-accessible-name.tsx
rename to src/matchers/to-have-accessible-name.ts
diff --git a/src/matchers/to-have-display-value.tsx b/src/matchers/to-have-display-value.ts
similarity index 100%
rename from src/matchers/to-have-display-value.tsx
rename to src/matchers/to-have-display-value.ts
diff --git a/src/matchers/to-have-style.tsx b/src/matchers/to-have-style.ts
similarity index 100%
rename from src/matchers/to-have-style.tsx
rename to src/matchers/to-have-style.ts
diff --git a/src/matchers/to-have-text-content.tsx b/src/matchers/to-have-text-content.ts
similarity index 100%
rename from src/matchers/to-have-text-content.tsx
rename to src/matchers/to-have-text-content.ts
diff --git a/src/matchers/utils.tsx b/src/matchers/utils.ts
similarity index 100%
rename from src/matchers/utils.tsx
rename to src/matchers/utils.ts
diff --git a/src/queries/label-text.ts b/src/queries/label-text.ts
index c9dd6dc06..2e018a6a0 100644
--- a/src/queries/label-text.ts
+++ b/src/queries/label-text.ts
@@ -1,7 +1,7 @@
import type { ReactTestInstance } from 'react-test-renderer';
import { findAll } from '../helpers/find-all';
import { TextMatch, TextMatchOptions } from '../matches';
-import { matchLabelText } from '../helpers/matchers/match-label-text';
+import { matchAccessibilityLabel } from '../helpers/matchers/match-label-text';
import { makeQueries } from './make-queries';
import type {
FindAllByQuery,
@@ -19,7 +19,7 @@ function queryAllByLabelText(instance: ReactTestInstance) {
return (text: TextMatch, queryOptions?: ByLabelTextOptions) => {
return findAll(
instance,
- (node) => matchLabelText(instance, node, text, queryOptions),
+ (node) => matchAccessibilityLabel(node, text, queryOptions),
queryOptions,
);
};