diff --git a/src/__tests__/getByApi.test.js b/src/__tests__/getByApi.test.js
index a2b2637d1..2756cff2d 100644
--- a/src/__tests__/getByApi.test.js
+++ b/src/__tests__/getByApi.test.js
@@ -50,3 +50,10 @@ test('supports a regex matcher', () => {
expect(getByTestId(/view/)).toBeTruthy();
expect(getAllByTestId(/text/)).toHaveLength(2);
});
+
+test('throws when no text matching is found', () => {
+ const { getByText } = render(Hello);
+ expect(() => getByText('SomethingElse')).toThrowError(
+ 'No instances found with text: SomethingElse'
+ );
+});
diff --git a/src/__tests__/queryByApi.test.js b/src/__tests__/queryByApi.test.js
index f7f39b991..b95f7488d 100644
--- a/src/__tests__/queryByApi.test.js
+++ b/src/__tests__/queryByApi.test.js
@@ -114,3 +114,32 @@ test('queryByText nested deep in ', () => {
).queryByText('Hello World!')
).toBeTruthy();
});
+
+test('queryByText subset of longer text', () => {
+ expect(
+ render(This is a long text).queryByText('long text')
+ ).toBeTruthy();
+});
+
+test('queryAllByText does not match several times the same text', () => {
+ const allMatched = render(
+
+ Start
+ This is a long text
+
+ ).queryAllByText('long text');
+ expect(allMatched.length).toBe(1);
+ expect(allMatched[0].props.nativeID).toBe('2');
+});
+
+test('queryAllByText matches all the matching nodes', () => {
+ const allMatched = render(
+
+ Start
+ This is a long text
+ This is another long text
+
+ ).queryAllByText('long text');
+ expect(allMatched.length).toBe(2);
+ expect(allMatched.map((node) => node.props.nativeID)).toEqual(['2', '3']);
+});
diff --git a/src/helpers/getByAPI.js b/src/helpers/getByAPI.js
index 844820125..d8bbac387 100644
--- a/src/helpers/getByAPI.js
+++ b/src/helpers/getByAPI.js
@@ -20,12 +20,29 @@ const getNodeByText = (node, text) => {
const textChildren = getChildrenAsText(node.props.children, Text);
if (textChildren) {
const textToTest = textChildren.join('');
- return typeof text === 'string'
- ? text === textToTest
- : text.test(textToTest);
+ if (typeof text === 'string') {
+ if (text === textToTest) {
+ return { exact: true };
+ } else {
+ if (textToTest.includes(text)) {
+ return {
+ exact: false,
+ text,
+ };
+ }
+
+ return null;
+ }
+ } else {
+ return text.test(textToTest)
+ ? {
+ exact: true,
+ }
+ : null;
+ }
}
}
- return false;
+ return null;
} catch (error) {
throw createLibraryNotSupportedError(error);
}
@@ -95,13 +112,46 @@ const getNodeByTestId = (node, testID) => {
: testID.test(node.props.testID);
};
+export const nonErroringGetByText = (instance: ReactTestInstance) =>
+ function nonErroringGetByTextFn(text: string | RegExp) {
+ const matchingInstances = instance.findAll((node) =>
+ getNodeByText(node, text)
+ );
+ if (matchingInstances.length === 0) {
+ return null;
+ }
+
+ const matches = matchingInstances.map((instance: ReactTestInstance) => ({
+ instance,
+ ...getNodeByText(instance, text),
+ }));
+
+ // Sort the matches from the best to the worst (exact matches should come first, then closest
+ // matching text)
+ matches.sort((firstMatch, secondMatch) => {
+ if (firstMatch.exact) {
+ return -1;
+ }
+ if (secondMatch.exact) {
+ return 1;
+ }
+ return secondMatch.text.length - firstMatch.text.length;
+ });
+
+ return matches[0].instance;
+ };
+
export const getByText = (instance: ReactTestInstance) =>
function getByTextFn(text: string | RegExp) {
- try {
- return instance.find((node) => getNodeByText(node, text));
- } catch (error) {
- throw new ErrorWithStack(prepareErrorMessage(error), getByTextFn);
+ const match = nonErroringGetByText(instance)(text);
+ if (match) {
+ return match;
}
+
+ throw new ErrorWithStack(
+ `No instances found with text: ${String(text)}`,
+ getByTextFn
+ );
};
export const getByPlaceholderText = (instance: ReactTestInstance) =>
@@ -148,9 +198,29 @@ export const getByTestId = (instance: ReactTestInstance) =>
}
};
-export const getAllByText = (instance: ReactTestInstance) =>
+export const getAllByText = (rootInstance: ReactTestInstance) =>
function getAllByTextFn(text: string | RegExp) {
- const results = instance.findAll((node) => getNodeByText(node, text));
+ const results = rootInstance.findAll((instance) => {
+ // We want to match only if there's no better match down the tree.
+ const match = getNodeByText(instance, text);
+ if (match && match.exact === false) {
+ const matchingInstances = instance.findAll((node) =>
+ getNodeByText(node, text)
+ );
+ if (matchingInstances.length === 0) {
+ return false;
+ }
+
+ if (matchingInstances.length === 1) {
+ return true;
+ }
+
+ // There's more than 1 match, that means that down the tree there will be a better matching node
+ return false;
+ }
+
+ return Boolean(match);
+ });
if (results.length === 0) {
throw new ErrorWithStack(
`No instances found with text: ${String(text)}`,
diff --git a/src/helpers/queryByAPI.js b/src/helpers/queryByAPI.js
index 3d5ce8488..2e3280f3f 100644
--- a/src/helpers/queryByAPI.js
+++ b/src/helpers/queryByAPI.js
@@ -2,7 +2,7 @@
import * as React from 'react';
import {
getByTestId,
- getByText,
+ nonErroringGetByText,
getByPlaceholderText,
getByDisplayValue,
getAllByTestId,
@@ -23,7 +23,7 @@ import {
export const queryByText = (instance: ReactTestInstance) =>
function queryByTextFn(text: string | RegExp) {
try {
- return getByText(instance)(text);
+ return nonErroringGetByText(instance)(text);
} catch (error) {
return createQueryByError(error, queryByTextFn);
}