diff --git a/package.json b/package.json
index 81e1aba96..71b5791f3 100644
--- a/package.json
+++ b/package.json
@@ -86,7 +86,9 @@
},
"jest": {
"preset": "./jest-preset",
- "setupFilesAfterEnv": ["./jest-setup.ts"],
+ "setupFilesAfterEnv": [
+ "./jest-setup.ts"
+ ],
"testPathIgnorePatterns": [
"timerUtils",
"examples/"
diff --git a/src/helpers/matchers/matchLabelText.ts b/src/helpers/matchers/matchLabelText.ts
new file mode 100644
index 000000000..8058f2904
--- /dev/null
+++ b/src/helpers/matchers/matchLabelText.ts
@@ -0,0 +1,51 @@
+import { ReactTestInstance } from 'react-test-renderer';
+import { matches, TextMatch, TextMatchOptions } from '../../matches';
+import { findAll } from '../findAll';
+import { matchTextContent } from './matchTextContent';
+
+export function matchLabelText(
+ root: ReactTestInstance,
+ element: ReactTestInstance,
+ text: TextMatch,
+ options: TextMatchOptions = {}
+) {
+ return (
+ matchAccessibilityLabel(element, text, options) ||
+ matchAccessibilityLabelledBy(
+ root,
+ element.props.accessibilityLabelledBy,
+ text,
+ options
+ )
+ );
+}
+
+function matchAccessibilityLabel(
+ element: ReactTestInstance,
+ text: TextMatch,
+ options: TextMatchOptions
+) {
+ const { exact, normalizer } = options;
+ return matches(text, element.props.accessibilityLabel, normalizer, exact);
+}
+
+function matchAccessibilityLabelledBy(
+ root: ReactTestInstance,
+ nativeId: string | undefined,
+ text: TextMatch,
+ options: TextMatchOptions
+) {
+ if (!nativeId) {
+ return false;
+ }
+
+ return (
+ findAll(
+ root,
+ (node) =>
+ typeof node.type === 'string' &&
+ node.props.nativeID === nativeId &&
+ matchTextContent(node, text, options)
+ ).length > 0
+ );
+}
diff --git a/src/helpers/matchers/matchTextContent.ts b/src/helpers/matchers/matchTextContent.ts
index cf7770e27..4c025e6ac 100644
--- a/src/helpers/matchers/matchTextContent.ts
+++ b/src/helpers/matchers/matchTextContent.ts
@@ -1,23 +1,19 @@
-import { Text } from 'react-native';
import type { ReactTestInstance } from 'react-test-renderer';
-import { getConfig } from '../../config';
import { matches, TextMatch, TextMatchOptions } from '../../matches';
-import { filterNodeByType } from '../filterNodeByType';
import { getTextContent } from '../getTextContent';
-import { getHostComponentNames } from '../host-component-names';
+/**
+ * Matches the given node's text content against string or regex matcher.
+ *
+ * @param node - Node which text content will be matched
+ * @param text - The string or regex to match.
+ * @returns - Whether the node's text content matches the given string or regex.
+ */
export function matchTextContent(
node: ReactTestInstance,
text: TextMatch,
options: TextMatchOptions = {}
) {
- const textType = getConfig().useBreakingChanges
- ? getHostComponentNames().text
- : Text;
- if (!filterNodeByType(node, textType)) {
- return false;
- }
-
const textContent = getTextContent(node);
const { exact, normalizer } = options;
return matches(text, textContent, normalizer, exact);
diff --git a/src/queries/__tests__/labelText.test.tsx b/src/queries/__tests__/labelText.test.tsx
index 9e1b5c0cd..07aa3fccc 100644
--- a/src/queries/__tests__/labelText.test.tsx
+++ b/src/queries/__tests__/labelText.test.tsx
@@ -1,5 +1,5 @@
import * as React from 'react';
-import { View, Text, TouchableOpacity } from 'react-native';
+import { View, Text, TextInput, TouchableOpacity } from 'react-native';
import { render } from '../..';
const BUTTON_LABEL = 'cool button';
@@ -165,3 +165,31 @@ test('byLabelText queries support hidden option', () => {
`"Unable to find an element with accessibilityLabel: hidden"`
);
});
+
+test('getByLabelText supports accessibilityLabelledBy', async () => {
+ const { getByLabelText, getByTestId } = render(
+ <>
+ Label for input
+ {/* @ts-expect-error: waiting for RN 0.71.2 to fix incorrectly omitted `accessibilityLabelledBy` typedef. */}
+
+ >
+ );
+
+ expect(getByLabelText('Label for input')).toBe(getByTestId('textInput'));
+ expect(getByLabelText(/input/)).toBe(getByTestId('textInput'));
+});
+
+test('getByLabelText supports nested accessibilityLabelledBy', async () => {
+ const { getByLabelText, getByTestId } = render(
+ <>
+
+ Label for input
+
+ {/* @ts-expect-error: waiting for RN 0.71.2 to fix incorrectly omitted `accessibilityLabelledBy` typedef. */}
+
+ >
+ );
+
+ expect(getByLabelText('Label for input')).toBe(getByTestId('textInput'));
+ expect(getByLabelText(/input/)).toBe(getByTestId('textInput'));
+});
diff --git a/src/queries/__tests__/text.breaking.test.tsx b/src/queries/__tests__/text.breaking.test.tsx
index 322588ad7..eb243edd1 100644
--- a/src/queries/__tests__/text.breaking.test.tsx
+++ b/src/queries/__tests__/text.breaking.test.tsx
@@ -510,6 +510,5 @@ test('byText support hidden option', () => {
test('byText should return host component', () => {
const { getByText } = render(hello);
-
expect(getByText('hello').type).toBe('Text');
});
diff --git a/src/queries/__tests__/text.test.tsx b/src/queries/__tests__/text.test.tsx
index a7d280d32..27f9eb82a 100644
--- a/src/queries/__tests__/text.test.tsx
+++ b/src/queries/__tests__/text.test.tsx
@@ -505,6 +505,5 @@ test('byText support hidden option', () => {
test('byText should return composite Text', () => {
const { getByText } = render(hello);
-
expect(getByText('hello').type).toBe(Text);
});
diff --git a/src/queries/labelText.ts b/src/queries/labelText.ts
index acfed4520..7b5fba156 100644
--- a/src/queries/labelText.ts
+++ b/src/queries/labelText.ts
@@ -1,6 +1,7 @@
import type { ReactTestInstance } from 'react-test-renderer';
import { findAll } from '../helpers/findAll';
-import { matches, TextMatch, TextMatchOptions } from '../matches';
+import { TextMatch, TextMatchOptions } from '../matches';
+import { matchLabelText } from '../helpers/matchers/matchLabelText';
import { makeQueries } from './makeQueries';
import type {
FindAllByQuery,
@@ -14,30 +15,17 @@ import { CommonQueryOptions } from './options';
type ByLabelTextOptions = CommonQueryOptions & TextMatchOptions;
-const getNodeByLabelText = (
- node: ReactTestInstance,
- text: TextMatch,
- options: TextMatchOptions = {}
-) => {
- const { exact, normalizer } = options;
- return matches(text, node.props.accessibilityLabel, normalizer, exact);
-};
-
-const queryAllByLabelText = (
- instance: ReactTestInstance
-): ((
- text: TextMatch,
- queryOptions?: ByLabelTextOptions
-) => Array) =>
- function queryAllByLabelTextFn(text, queryOptions) {
+function queryAllByLabelText(instance: ReactTestInstance) {
+ return (text: TextMatch, queryOptions?: ByLabelTextOptions) => {
return findAll(
instance,
(node) =>
typeof node.type === 'string' &&
- getNodeByLabelText(node, text, queryOptions),
+ matchLabelText(instance, node, text, queryOptions),
queryOptions
);
};
+}
const getMultipleError = (labelText: TextMatch) =>
`Found multiple elements with accessibilityLabel: ${String(labelText)} `;
diff --git a/src/queries/testId.ts b/src/queries/testId.ts
index 3e7977d9f..c33122b91 100644
--- a/src/queries/testId.ts
+++ b/src/queries/testId.ts
@@ -30,13 +30,13 @@ const queryAllByTestId = (
queryOptions?: ByTestIdOptions
) => Array) =>
function queryAllByTestIdFn(testId, queryOptions) {
- const results = findAll(
+ return findAll(
instance,
- (node) => getNodeByTestId(node, testId, queryOptions),
+ (node) =>
+ typeof node.type === 'string' &&
+ getNodeByTestId(node, testId, queryOptions),
queryOptions
);
-
- return results.filter((element) => typeof element.type === 'string');
};
const getMultipleError = (testId: TextMatch) =>
diff --git a/src/queries/text.ts b/src/queries/text.ts
index ef34333a9..744d88a04 100644
--- a/src/queries/text.ts
+++ b/src/queries/text.ts
@@ -1,13 +1,15 @@
import type { ReactTestInstance } from 'react-test-renderer';
import { Text } from 'react-native';
-import { findAll } from '../helpers/findAll';
-import { matchTextContent } from '../helpers/matchers/matchTextContent';
-import { TextMatch, TextMatchOptions } from '../matches';
+import { getConfig } from '../config';
import {
getCompositeParentOfType,
isHostElementForType,
} from '../helpers/component-tree';
-import { getConfig } from '../config';
+import { filterNodeByType } from '../helpers/filterNodeByType';
+import { findAll } from '../helpers/findAll';
+import { getHostComponentNames } from '../helpers/host-component-names';
+import { matchTextContent } from '../helpers/matchers/matchTextContent';
+import { TextMatch, TextMatchOptions } from '../matches';
import { makeQueries } from './makeQueries';
import type {
FindAllByQuery,
@@ -39,7 +41,8 @@ const queryAllByText = (
const results = findAll(
baseInstance,
- (node) => matchTextContent(node, text, options),
+ (node) =>
+ filterNodeByType(node, Text) && matchTextContent(node, text, options),
{ ...options, matchDeepestOnly: true }
);
@@ -47,10 +50,16 @@ const queryAllByText = (
}
// vNext version: returns host Text
- return findAll(instance, (node) => matchTextContent(node, text, options), {
- ...options,
- matchDeepestOnly: true,
- });
+ return findAll(
+ instance,
+ (node) =>
+ filterNodeByType(node, getHostComponentNames().text) &&
+ matchTextContent(node, text, options),
+ {
+ ...options,
+ matchDeepestOnly: true,
+ }
+ );
};
const getMultipleError = (text: TextMatch) =>
diff --git a/website/docs/Queries.md b/website/docs/Queries.md
index 979038f81..ddacabeac 100644
--- a/website/docs/Queries.md
+++ b/website/docs/Queries.md
@@ -212,7 +212,9 @@ getByLabelText(
): ReactTestInstance;
```
-Returns a `ReactTestInstance` with matching `accessibilityLabel` prop.
+Returns a `ReactTestInstance` with matching label:
+- either by matching [`accessibilityLabel`](https://reactnative.dev/docs/accessibility#accessibilitylabel) prop
+- or by matching text content of view referenced by [`accessibilityLabelledBy`](https://reactnative.dev/docs/accessibility#accessibilitylabelledby-android) prop
```jsx
import { render, screen } from '@testing-library/react-native';