Skip to content

Fix translations text not being found by getByText #967

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions src/__tests__/byText.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -473,3 +473,21 @@ test('getByText and queryByText work properly with multiple nested fragments', (
expect(getByText('Hello')).toBeTruthy();
expect(queryByText('Hello')).not.toBeNull();
});

const TranslationText = ({
translationKey,
}: {
translationKey: string;
}): React.ReactElement => {
return <>{translationKey}</>;
};

test('it should be abdle to find text nested in components that render as string', () => {
const { getByText } = render(
<Text>
<TranslationText translationKey="hello" />
</Text>
);

expect(getByText('hello')).toBeTruthy();
});
6 changes: 3 additions & 3 deletions src/helpers/byDisplayValue.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { ReactTestInstance } from 'react-test-renderer';
import type { ReactTestInstance, ReactTestRenderer } from 'react-test-renderer';
import { matches, TextMatch } from '../matches';
import { makeQueries } from './makeQueries';
import type { Queries } from './makeQueries';
Expand Down Expand Up @@ -28,13 +28,13 @@ const getTextInputNodeByDisplayValue = (
};

const queryAllByDisplayValue = (
instance: ReactTestInstance
renderer: ReactTestRenderer
): ((
displayValue: TextMatch,
queryOptions?: TextMatchOptions
) => Array<ReactTestInstance>) =>
function queryAllByDisplayValueFn(displayValue, queryOptions) {
return instance.findAll((node) =>
return renderer.root.findAll((node) =>
getTextInputNodeByDisplayValue(node, displayValue, queryOptions)
);
};
Expand Down
6 changes: 3 additions & 3 deletions src/helpers/byPlaceholderText.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { ReactTestInstance } from 'react-test-renderer';
import type { ReactTestInstance, ReactTestRenderer } from 'react-test-renderer';
import { matches, TextMatch } from '../matches';
import { makeQueries } from './makeQueries';
import type { Queries } from './makeQueries';
Expand All @@ -24,13 +24,13 @@ const getTextInputNodeByPlaceholderText = (
};

const queryAllByPlaceholderText = (
instance: ReactTestInstance
renderer: ReactTestRenderer
): ((
placeholder: TextMatch,
queryOptions?: TextMatchOptions
) => Array<ReactTestInstance>) =>
function queryAllByPlaceholderFn(placeholder, queryOptions) {
return instance.findAll((node) =>
return renderer.root.findAll((node) =>
getTextInputNodeByPlaceholderText(node, placeholder, queryOptions)
);
};
Expand Down
6 changes: 3 additions & 3 deletions src/helpers/byTestId.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { ReactTestInstance } from 'react-test-renderer';
import type { ReactTestInstance, ReactTestRenderer } from 'react-test-renderer';
import { matches, TextMatch } from '../matches';
import { makeQueries } from './makeQueries';
import type { Queries } from './makeQueries';
Expand All @@ -14,13 +14,13 @@ const getNodeByTestId = (
};

const queryAllByTestId = (
instance: ReactTestInstance
renderer: ReactTestRenderer
): ((
testId: TextMatch,
queryOptions?: TextMatchOptions
) => Array<ReactTestInstance>) =>
function queryAllByTestIdFn(testId, queryOptions) {
const results = instance
const results = renderer.root
.findAll((node) => getNodeByTestId(node, testId, queryOptions))
.filter((element) => typeof element.type === 'string');

Expand Down
133 changes: 89 additions & 44 deletions src/helpers/byText.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import type { ReactTestInstance } from 'react-test-renderer';
import type {
ReactTestInstance,
ReactTestRenderer,
ReactTestRendererTree,
} from 'react-test-renderer';
import * as React from 'react';
import { matches, TextMatch } from '../matches';
import type { NormalizerFn } from '../matches';
import { makeQueries } from './makeQueries';
import type { Queries } from './makeQueries';
import { filterNodeByType } from './filterNodeByType';
import { createLibraryNotSupportedError } from './errors';

export type TextMatchOptions = {
Expand All @@ -13,75 +16,117 @@ export type TextMatchOptions = {
};

const getChildrenAsText = (
children: React.ReactChild[],
children: ReactTestRendererTree | ReactTestRendererTree[] | null,
TextComponent: React.ComponentType
) => {
const textContent: string[] = [];
React.Children.forEach(children, (child) => {
if (typeof child === 'string') {
textContent.push(child);
return;
}
): string[] => {
if (!children) {
return [];
}

if (typeof child === 'number') {
textContent.push(child.toString());
return;
if (typeof children === 'string') {
return [children];
}

const textContent: string[] = [];
if (!Array.isArray(children)) {
// Bail on traversing text children down the tree if current node (child)
// has no text. In such situations, react-test-renderer will traverse down
// this tree in a separate call and run this query again. As a result, the
// query will match the deepest text node that matches requested text.
if (children.type === 'Text') {
return [];
}

if (child?.props?.children) {
// Bail on traversing text children down the tree if current node (child)
// has no text. In such situations, react-test-renderer will traverse down
// this tree in a separate call and run this query again. As a result, the
// query will match the deepest text node that matches requested text.
if (filterNodeByType(child, TextComponent)) {
return;
}
return getChildrenAsText(children.rendered, TextComponent);
}

if (filterNodeByType(child, React.Fragment)) {
textContent.push(
...getChildrenAsText(child.props.children, TextComponent)
);
}
}
});
children.forEach((child) =>
textContent.push(...getChildrenAsText(child, TextComponent))
);

return textContent;
};

const getNodeByText = (
node: ReactTestInstance,
const getInstancesByText = (
tree: ReactTestRendererTree,
text: TextMatch,
options: TextMatchOptions = {}
) => {
): Omit<ReactTestInstance, 'parent'>[] => {
const instances: Omit<ReactTestInstance, 'parent'>[] = [];

try {
if (!tree.rendered) {
return [];
}

if (!tree.instance) {
if (!Array.isArray(tree.rendered)) {
return [...getInstancesByText(tree.rendered, text, options)];
}

tree.rendered.forEach((rendered) => {
instances.push(...getInstancesByText(rendered, text, options));
});
return instances;
}

const textChildren: string[] = [];
const { Text } = require('react-native');
const isTextComponent = filterNodeByType(node, Text);
if (isTextComponent) {
const textChildren = getChildrenAsText(node.props.children, Text);
if (textChildren) {
const textToTest = textChildren.join('');
const { exact, normalizer } = options;
return matches(text, textToTest, normalizer, exact);
if (!Array.isArray(tree.rendered)) {
if (tree.rendered.type === 'Text') {
textChildren.push(...getChildrenAsText(tree.rendered.rendered, Text));
}
instances.push(...getInstancesByText(tree.rendered, text, options));
} else {
tree.rendered.forEach((child) => {
if (child.type === 'Text') {
textChildren.push(...getChildrenAsText(child, Text));
} else {
instances.push(...getInstancesByText(child, text, options));
}
});
}
return false;

if (textChildren.length) {
const textToTest = textChildren.join('');
const { exact, normalizer } = options;
if (matches(text, textToTest, normalizer, exact)) {
instances.push(tree.instance);
}
}

return instances;
} catch (error) {
throw createLibraryNotSupportedError(error);
}
};

const queryAllByText = (
instance: ReactTestInstance
renderer: ReactTestRenderer
): ((
text: TextMatch,
queryOptions?: TextMatchOptions
) => Array<ReactTestInstance>) =>
function queryAllByTextFn(text, queryOptions) {
const results = instance.findAll((node) =>
getNodeByText(node, text, queryOptions)
);
const tree = renderer.toTree();
const treeInstance = renderer.root;

if (!tree) {
return [];
}

const instancesFound = getInstancesByText(tree, text, queryOptions);

return results;
// Instances in the tree are not of type ReactTestInstance because they do not have parent
// This is problematic when firing events so we find in the root nodes the one that matches
return instancesFound.map((instanceFound) => {
return treeInstance.find((treeInstance) => {
return (
treeInstance.instance &&
treeInstance.instance._reactInternals.stateNode === instanceFound
);
});
});
};

const getMultipleError = (text: TextMatch) =>
Expand Down
20 changes: 10 additions & 10 deletions src/helpers/findByAPI.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { ReactTestInstance } from 'react-test-renderer';
import type { ReactTestInstance, ReactTestRenderer } from 'react-test-renderer';
import type { WaitForOptions } from '../waitFor';
import type { TextMatch } from '../matches';
import type { TextMatchOptions } from './byText';
Expand Down Expand Up @@ -56,15 +56,15 @@ export type FindByAPI = {
) => Promise<ReactTestInstance>;
};

export const findByAPI = (instance: ReactTestInstance): FindByAPI => ({
findByTestId: findByTestId(instance),
findByText: findByText(instance),
findByPlaceholderText: findByPlaceholderText(instance),
findByDisplayValue: findByDisplayValue(instance),
findAllByTestId: findAllByTestId(instance),
findAllByText: findAllByText(instance),
findAllByPlaceholderText: findAllByPlaceholderText(instance),
findAllByDisplayValue: findAllByDisplayValue(instance),
export const findByAPI = (renderer: ReactTestRenderer): FindByAPI => ({
findByTestId: findByTestId(renderer),
findByText: findByText(renderer),
findByPlaceholderText: findByPlaceholderText(renderer),
findByDisplayValue: findByDisplayValue(renderer),
findAllByTestId: findAllByTestId(renderer),
findAllByText: findAllByText(renderer),
findAllByPlaceholderText: findAllByPlaceholderText(renderer),
findAllByDisplayValue: findAllByDisplayValue(renderer),

// Renamed
findByPlaceholder: () =>
Expand Down
28 changes: 14 additions & 14 deletions src/helpers/getByAPI.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { ReactTestInstance } from 'react-test-renderer';
import type { ReactTestInstance, ReactTestRenderer } from 'react-test-renderer';
import * as React from 'react';
import prettyFormat from 'pretty-format';
import type { TextMatch } from '../matches';
Expand Down Expand Up @@ -104,19 +104,19 @@ export const UNSAFE_getAllByProps = (
return results;
};

export const getByAPI = (instance: ReactTestInstance): GetByAPI => ({
getByText: getByText(instance),
getByPlaceholderText: getByPlaceholderText(instance),
getByDisplayValue: getByDisplayValue(instance),
getByTestId: getByTestId(instance),
getAllByText: getAllByText(instance),
getAllByPlaceholderText: getAllByPlaceholderText(instance),
getAllByDisplayValue: getAllByDisplayValue(instance),
getAllByTestId: getAllByTestId(instance),
export const getByAPI = (renderer: ReactTestRenderer): GetByAPI => ({
getByText: getByText(renderer),
getByPlaceholderText: getByPlaceholderText(renderer),
getByDisplayValue: getByDisplayValue(renderer),
getByTestId: getByTestId(renderer),
getAllByText: getAllByText(renderer),
getAllByPlaceholderText: getAllByPlaceholderText(renderer),
getAllByDisplayValue: getAllByDisplayValue(renderer),
getAllByTestId: getAllByTestId(renderer),

// Unsafe
UNSAFE_getByType: UNSAFE_getByType(instance),
UNSAFE_getAllByType: UNSAFE_getAllByType(instance),
UNSAFE_getByProps: UNSAFE_getByProps(instance),
UNSAFE_getAllByProps: UNSAFE_getAllByProps(instance),
UNSAFE_getByType: UNSAFE_getByType(renderer.root),
UNSAFE_getAllByType: UNSAFE_getAllByType(renderer.root),
UNSAFE_getByProps: UNSAFE_getByProps(renderer.root),
UNSAFE_getAllByProps: UNSAFE_getAllByProps(renderer.root),
});
Loading