Skip to content

refactor: type all queries to return host components #1426

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

Merged
merged 9 commits into from
Aug 11, 2023
Merged
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
2 changes: 1 addition & 1 deletion src/fireEvent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ import {
ScrollViewProps,
} from 'react-native';
import act from './act';
import { isPointerEventEnabled } from './helpers/pointer-events';
import { isHostElement } from './helpers/component-tree';
import { isHostTextInput } from './helpers/host-component-names';
import { isPointerEventEnabled } from './helpers/pointer-events';

type EventHandler = (...args: unknown[]) => unknown;

Expand Down
23 changes: 14 additions & 9 deletions src/helpers/component-tree.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
import { ReactTestInstance } from 'react-test-renderer';

/**
* ReactTestInstance referring to host element.
*/
export type HostTestInstance = ReactTestInstance & { type: string };

/**
* Checks if the given element is a host element.
* @param element The element to check.
*/
export function isHostElement(element?: ReactTestInstance | null) {
export function isHostElement(
element?: ReactTestInstance | null
): element is HostTestInstance {
return typeof element?.type === 'string';
}

Expand All @@ -14,7 +21,7 @@ export function isHostElement(element?: ReactTestInstance | null) {
*/
export function getHostParent(
element: ReactTestInstance | null
): ReactTestInstance | null {
): HostTestInstance | null {
if (element == null) {
return null;
}
Expand All @@ -37,12 +44,12 @@ export function getHostParent(
*/
export function getHostChildren(
element: ReactTestInstance | null
): ReactTestInstance[] {
): HostTestInstance[] {
if (element == null) {
return [];
}

const hostChildren: ReactTestInstance[] = [];
const hostChildren: HostTestInstance[] = [];

element.children.forEach((child) => {
if (typeof child !== 'object') {
Expand All @@ -68,10 +75,8 @@ export function getHostChildren(
*/
export function getHostSelves(
element: ReactTestInstance | null
): ReactTestInstance[] {
return typeof element?.type === 'string'
? [element]
: getHostChildren(element);
): HostTestInstance[] {
return isHostElement(element) ? [element] : getHostChildren(element);
}

/**
Expand All @@ -80,7 +85,7 @@ export function getHostSelves(
*/
export function getHostSiblings(
element: ReactTestInstance | null
): ReactTestInstance[] {
): HostTestInstance[] {
const hostParent = getHostParent(element);
const hostSelves = getHostSelves(element);
return getHostChildren(hostParent).filter(
Expand Down
7 changes: 0 additions & 7 deletions src/helpers/filterNodeByType.ts

This file was deleted.

10 changes: 6 additions & 4 deletions src/helpers/findAll.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ReactTestInstance } from 'react-test-renderer';
import { getConfig } from '../config';
import { isHiddenFromAccessibility } from './accessiblity';
import { HostTestInstance, isHostElement } from './component-tree';

interface FindAllOptions {
/** Match elements hidden from accessibility */
Expand All @@ -17,7 +18,7 @@ export function findAll(
root: ReactTestInstance,
predicate: (element: ReactTestInstance) => boolean,
options?: FindAllOptions
) {
): HostTestInstance[] {
const results = findAllInternal(root, predicate, options);

const includeHiddenElements =
Expand All @@ -41,11 +42,11 @@ function findAllInternal(
root: ReactTestInstance,
predicate: (element: ReactTestInstance) => boolean,
options?: FindAllOptions
): Array<ReactTestInstance> {
const results: ReactTestInstance[] = [];
): HostTestInstance[] {
const results: HostTestInstance[] = [];

// Match descendants first but do not add them to results yet.
const matchingDescendants: ReactTestInstance[] = [];
const matchingDescendants: HostTestInstance[] = [];
root.children.forEach((child) => {
if (typeof child === 'string') {
return;
Expand All @@ -56,6 +57,7 @@ function findAllInternal(
if (
// When matchDeepestOnly = true: add current element only if no descendants match
(!options?.matchDeepestOnly || matchingDescendants.length === 0) &&
isHostElement(root) &&
predicate(root)
) {
results.push(root);
Expand Down
17 changes: 15 additions & 2 deletions src/helpers/host-component-names.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { ReactTestInstance } from 'react-test-renderer';
import { Switch, Text, TextInput, View } from 'react-native';
import { configureInternal, getConfig, HostComponentNames } from '../config';
import { renderWithAct } from '../render-act';
import { HostTestInstance } from './component-tree';

const userConfigErrorMessage = `There seems to be an issue with your configuration that prevents React Native Testing Library from working correctly.
Please check if you are using compatible versions of React Native and React Native Testing Library.`;
Expand Down Expand Up @@ -66,10 +67,22 @@ function getByTestId(instance: ReactTestInstance, testID: string) {
return nodes[0];
}

export function isHostText(element?: ReactTestInstance) {
/**
* Checks if the given element is a host Text.
* @param element The element to check.
*/
export function isHostText(
element?: ReactTestInstance | null
): element is HostTestInstance {
return element?.type === getHostComponentNames().text;
}

export function isHostTextInput(element?: ReactTestInstance) {
/**
* Checks if the given element is a host TextInput.
* @param element The element to check.
*/
export function isHostTextInput(
element?: ReactTestInstance | null
): element is HostTestInstance {
return element?.type === getHostComponentNames().textInput;
}
1 change: 0 additions & 1 deletion src/helpers/matchers/matchLabelText.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ function matchAccessibilityLabelledBy(
findAll(
root,
(node) =>
typeof node.type === 'string' &&
node.props.nativeID === nativeId &&
matchTextContent(node, text, options)
).length > 0
Expand Down
8 changes: 2 additions & 6 deletions src/queries/a11yState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,11 @@ import { CommonQueryOptions } from './options';

const queryAllByA11yState = (
instance: ReactTestInstance
): ((
matcher: AccessibilityStateMatcher,
queryOptions?: CommonQueryOptions
) => Array<ReactTestInstance>) =>
): QueryAllByQuery<AccessibilityStateMatcher, CommonQueryOptions> =>
function queryAllByA11yStateFn(matcher, queryOptions) {
return findAll(
instance,
(node) =>
typeof node.type === 'string' && matchAccessibilityState(node, matcher),
(node) => matchAccessibilityState(node, matcher),
queryOptions
);
};
Expand Down
8 changes: 2 additions & 6 deletions src/queries/a11yValue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,11 @@ import { CommonQueryOptions } from './options';

const queryAllByA11yValue = (
instance: ReactTestInstance
): ((
value: AccessibilityValueMatcher,
queryOptions?: CommonQueryOptions
) => Array<ReactTestInstance>) =>
): QueryAllByQuery<AccessibilityValueMatcher, CommonQueryOptions> =>
function queryAllByA11yValueFn(value, queryOptions) {
return findAll(
instance,
(node) =>
typeof node.type === 'string' && matchAccessibilityValue(node, value),
(node) => matchAccessibilityValue(node, value),
queryOptions
);
};
Expand Down
21 changes: 7 additions & 14 deletions src/queries/displayValue.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import type { ReactTestInstance } from 'react-test-renderer';
import { filterNodeByType } from '../helpers/filterNodeByType';
import { findAll } from '../helpers/findAll';
import { isHostTextInput } from '../helpers/host-component-names';
import { matches, TextMatch, TextMatchOptions } from '../matches';
import { getHostComponentNames } from '../helpers/host-component-names';
import { makeQueries } from './makeQueries';
import type {
FindAllByQuery,
Expand All @@ -16,32 +15,26 @@ import type { CommonQueryOptions } from './options';

type ByDisplayValueOptions = CommonQueryOptions & TextMatchOptions;

const getTextInputNodeByDisplayValue = (
const matchDisplayValue = (
node: ReactTestInstance,
value: TextMatch,
options: TextMatchOptions = {}
) => {
const { exact, normalizer } = options;
const nodeValue =
node.props.value !== undefined ? node.props.value : node.props.defaultValue;
const nodeValue = node.props.value ?? node.props.defaultValue;

return (
filterNodeByType(node, getHostComponentNames().textInput) &&
matches(value, nodeValue, normalizer, exact)
);
return matches(value, nodeValue, normalizer, exact);
};

const queryAllByDisplayValue = (
instance: ReactTestInstance
): ((
displayValue: TextMatch,
queryOptions?: ByDisplayValueOptions
) => Array<ReactTestInstance>) =>
): QueryAllByQuery<TextMatch, ByDisplayValueOptions> =>
function queryAllByDisplayValueFn(displayValue, queryOptions) {
return findAll(
instance,
(node) =>
getTextInputNodeByDisplayValue(node, displayValue, queryOptions),
isHostTextInput(node) &&
matchDisplayValue(node, displayValue, queryOptions),
queryOptions
);
};
Expand Down
9 changes: 2 additions & 7 deletions src/queries/hintText.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,11 @@ const getNodeByHintText = (

const queryAllByHintText = (
instance: ReactTestInstance
): ((
hint: TextMatch,
queryOptions?: ByHintTextOptions
) => Array<ReactTestInstance>) =>
): QueryAllByQuery<TextMatch, ByHintTextOptions> =>
function queryAllByA11yHintFn(hint, queryOptions) {
return findAll(
instance,
(node) =>
typeof node.type === 'string' &&
getNodeByHintText(node, hint, queryOptions),
(node) => getNodeByHintText(node, hint, queryOptions),
queryOptions
);
};
Expand Down
4 changes: 1 addition & 3 deletions src/queries/labelText.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,7 @@ function queryAllByLabelText(instance: ReactTestInstance) {
return (text: TextMatch, queryOptions?: ByLabelTextOptions) => {
return findAll(
instance,
(node) =>
typeof node.type === 'string' &&
matchLabelText(instance, node, text, queryOptions),
(node) => matchLabelText(instance, node, text, queryOptions),
queryOptions
);
};
Expand Down
19 changes: 6 additions & 13 deletions src/queries/placeholderText.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import type { ReactTestInstance } from 'react-test-renderer';
import { findAll } from '../helpers/findAll';
import { matches, TextMatch, TextMatchOptions } from '../matches';
import { filterNodeByType } from '../helpers/filterNodeByType';
import { getHostComponentNames } from '../helpers/host-component-names';
import { isHostTextInput } from '../helpers/host-component-names';
import { makeQueries } from './makeQueries';
import type {
FindAllByQuery,
Expand All @@ -16,30 +15,24 @@ import type { CommonQueryOptions } from './options';

type ByPlaceholderTextOptions = CommonQueryOptions & TextMatchOptions;

const getTextInputNodeByPlaceholderText = (
const matchPlaceholderText = (
node: ReactTestInstance,
placeholder: TextMatch,
options: TextMatchOptions = {}
) => {
const { exact, normalizer } = options;

return (
filterNodeByType(node, getHostComponentNames().textInput) &&
matches(placeholder, node.props.placeholder, normalizer, exact)
);
return matches(placeholder, node.props.placeholder, normalizer, exact);
};

const queryAllByPlaceholderText = (
instance: ReactTestInstance
): ((
placeholder: TextMatch,
queryOptions?: ByPlaceholderTextOptions
) => Array<ReactTestInstance>) =>
): QueryAllByQuery<TextMatch, ByPlaceholderTextOptions> =>
function queryAllByPlaceholderFn(placeholder, queryOptions) {
return findAll(
instance,
(node) =>
getTextInputNodeByPlaceholderText(node, placeholder, queryOptions),
isHostTextInput(node) &&
matchPlaceholderText(node, placeholder, queryOptions),
queryOptions
);
};
Expand Down
3 changes: 1 addition & 2 deletions src/queries/role.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,13 +61,12 @@ const matchAccessibilityValueIfNeeded = (

const queryAllByRole = (
instance: ReactTestInstance
): ((role: TextMatch, options?: ByRoleOptions) => Array<ReactTestInstance>) =>
): QueryAllByQuery<TextMatch, ByRoleOptions> =>
function queryAllByRoleFn(role, options) {
return findAll(
instance,
(node) =>
// run the cheapest checks first, and early exit to avoid unneeded computations
typeof node.type === 'string' &&
isAccessibilityElement(node) &&
matchStringProp(node.props.accessibilityRole, role) &&
matchAccessibleStateIfNeeded(node, options) &&
Expand Down
15 changes: 5 additions & 10 deletions src/queries/testId.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,27 +14,22 @@ import type { CommonQueryOptions } from './options';

type ByTestIdOptions = CommonQueryOptions & TextMatchOptions;

const getNodeByTestId = (
const matchTestId = (
node: ReactTestInstance,
testID: TextMatch,
testId: TextMatch,
options: TextMatchOptions = {}
) => {
const { exact, normalizer } = options;
return matches(testID, node.props.testID, normalizer, exact);
return matches(testId, node.props.testID, normalizer, exact);
};

const queryAllByTestId = (
instance: ReactTestInstance
): ((
testId: TextMatch,
queryOptions?: ByTestIdOptions
) => Array<ReactTestInstance>) =>
): QueryAllByQuery<TextMatch, ByTestIdOptions> =>
function queryAllByTestIdFn(testId, queryOptions) {
return findAll(
instance,
(node) =>
typeof node.type === 'string' &&
getNodeByTestId(node, testId, queryOptions),
(node) => matchTestId(node, testId, queryOptions),
queryOptions
);
};
Expand Down
9 changes: 3 additions & 6 deletions src/queries/text.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import type { ReactTestInstance } from 'react-test-renderer';
import { filterNodeByType } from '../helpers/filterNodeByType';
import { findAll } from '../helpers/findAll';
import { getHostComponentNames } from '../helpers/host-component-names';
import { isHostText } from '../helpers/host-component-names';
import { matchTextContent } from '../helpers/matchers/matchTextContent';
import { TextMatch, TextMatchOptions } from '../matches';
import { makeQueries } from './makeQueries';
Expand All @@ -19,13 +18,11 @@ type ByTextOptions = CommonQueryOptions & TextMatchOptions;

const queryAllByText = (
instance: ReactTestInstance
): ((text: TextMatch, options?: ByTextOptions) => Array<ReactTestInstance>) =>
): QueryAllByQuery<TextMatch, ByTextOptions> =>
function queryAllByTextFn(text, options = {}) {
return findAll(
instance,
(node) =>
filterNodeByType(node, getHostComponentNames().text) &&
matchTextContent(node, text, options),
(node) => isHostText(node) && matchTextContent(node, text, options),
{
...options,
matchDeepestOnly: true,
Expand Down
Loading