From 34ffda8570769c8678f98a66b61dbc9c46e07045 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Zakrzewski?= Date: Wed, 9 Sep 2020 15:27:02 +0200 Subject: [PATCH 01/28] Add TextMatch options to getByAPI --- src/helpers/getByAPI.js | 87 +++++++++++++++++++++++++++++------------ typings/index.d.ts | 50 ++++++++++++++--------- 2 files changed, 95 insertions(+), 42 deletions(-) diff --git a/src/helpers/getByAPI.js b/src/helpers/getByAPI.js index 844820125..a8a9c2d8c 100644 --- a/src/helpers/getByAPI.js +++ b/src/helpers/getByAPI.js @@ -9,9 +9,32 @@ import { throwRenamedFunctionError, } from './errors'; +export type TextMatchOptions = { + exact: boolean, +}; + +const DEFAULT_TEXT_MATCH_OPTIONS: TextMatchOptions = { + exact: true, +}; + const filterNodeByType = (node, type) => node.type === type; -const getNodeByText = (node, text) => { +const matchText = ( + match: string | RegExp, + textToTest: string, + options?: TextMatchOptions +): boolean => { + const exact = options?.exact ?? DEFAULT_TEXT_MATCH_OPTIONS.exact; + if (typeof match === 'string') { + return exact + ? match === textToTest + : textToTest.toLowerCase().includes(match.toLowerCase()); + } else { + return match.test(textToTest); + } +}; + +const getNodeByText = (node, text, options?: TextMatchOptions) => { try { // eslint-disable-next-line const { Text } = require('react-native'); @@ -20,9 +43,7 @@ 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); + return matchText(text, textToTest, options); } } return false; @@ -59,30 +80,34 @@ const getChildrenAsText = (children, TextComponent, textContent = []) => { return textContent; }; -const getTextInputNodeByPlaceholderText = (node, placeholder) => { +const getTextInputNodeByPlaceholderText = ( + node, + placeholder, + options?: TextMatchOptions +) => { try { // eslint-disable-next-line const { TextInput } = require('react-native'); return ( filterNodeByType(node, TextInput) && - (typeof placeholder === 'string' - ? placeholder === node.props.placeholder - : placeholder.test(node.props.placeholder)) + matchText(placeholder, node.props.placeholder, options) ); } catch (error) { throw createLibraryNotSupportedError(error); } }; -const getTextInputNodeByDisplayValue = (node, value) => { +const getTextInputNodeByDisplayValue = ( + node, + value, + options?: TextMatchOptions +) => { try { // eslint-disable-next-line const { TextInput } = require('react-native'); return ( filterNodeByType(node, TextInput) && - (typeof value === 'string' - ? value === node.props.value - : value.test(node.props.value)) + matchText(value, node.props.value, options) ); } catch (error) { throw createLibraryNotSupportedError(error); @@ -96,19 +121,22 @@ const getNodeByTestId = (node, testID) => { }; export const getByText = (instance: ReactTestInstance) => - function getByTextFn(text: string | RegExp) { + function getByTextFn(text: string | RegExp, options?: TextMatchOptions) { try { - return instance.find((node) => getNodeByText(node, text)); + return instance.find((node) => getNodeByText(node, text, options)); } catch (error) { throw new ErrorWithStack(prepareErrorMessage(error), getByTextFn); } }; export const getByPlaceholderText = (instance: ReactTestInstance) => - function getByPlaceholderTextFn(placeholder: string | RegExp) { + function getByPlaceholderTextFn( + placeholder: string | RegExp, + options?: TextMatchOptions + ) { try { return instance.find((node) => - getTextInputNodeByPlaceholderText(node, placeholder) + getTextInputNodeByPlaceholderText(node, placeholder, options) ); } catch (error) { throw new ErrorWithStack( @@ -119,10 +147,13 @@ export const getByPlaceholderText = (instance: ReactTestInstance) => }; export const getByDisplayValue = (instance: ReactTestInstance) => - function getByDisplayValueFn(placeholder: string | RegExp) { + function getByDisplayValueFn( + placeholder: string | RegExp, + options?: TextMatchOptions + ) { try { return instance.find((node) => - getTextInputNodeByDisplayValue(node, placeholder) + getTextInputNodeByDisplayValue(node, placeholder, options) ); } catch (error) { throw new ErrorWithStack(prepareErrorMessage(error), getByDisplayValueFn); @@ -149,8 +180,10 @@ export const getByTestId = (instance: ReactTestInstance) => }; export const getAllByText = (instance: ReactTestInstance) => - function getAllByTextFn(text: string | RegExp) { - const results = instance.findAll((node) => getNodeByText(node, text)); + function getAllByTextFn(text: string | RegExp, options?: TextMatchOptions) { + const results = instance.findAll((node) => + getNodeByText(node, text, options) + ); if (results.length === 0) { throw new ErrorWithStack( `No instances found with text: ${String(text)}`, @@ -161,9 +194,12 @@ export const getAllByText = (instance: ReactTestInstance) => }; export const getAllByPlaceholderText = (instance: ReactTestInstance) => - function getAllByPlaceholderTextFn(placeholder: string | RegExp) { + function getAllByPlaceholderTextFn( + placeholder: string | RegExp, + options?: TextMatchOptions + ) { const results = instance.findAll((node) => - getTextInputNodeByPlaceholderText(node, placeholder) + getTextInputNodeByPlaceholderText(node, placeholder, options) ); if (results.length === 0) { throw new ErrorWithStack( @@ -175,9 +211,12 @@ export const getAllByPlaceholderText = (instance: ReactTestInstance) => }; export const getAllByDisplayValue = (instance: ReactTestInstance) => - function getAllByDisplayValueFn(value: string | RegExp) { + function getAllByDisplayValueFn( + value: string | RegExp, + options?: TextMatchOptions + ) { const results = instance.findAll((node) => - getTextInputNodeByDisplayValue(node, value) + getTextInputNodeByDisplayValue(node, value, options) ); if (results.length === 0) { throw new ErrorWithStack( diff --git a/typings/index.d.ts b/typings/index.d.ts index 898e504da..ebaac87ae 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -15,16 +15,17 @@ type FindReturn = Promise; type FindAllReturn = Promise; interface GetByAPI { - getByText: (text: string | RegExp) => ReactTestInstance; - getByPlaceholderText: (placeholder: string | RegExp) => ReactTestInstance; - getByDisplayValue: (value: string | RegExp) => ReactTestInstance; + getByText: (text: string | RegExp, options?: TextMatchOptions) => ReactTestInstance; + getByPlaceholderText: (placeholder: string | RegExp, options?: TextMatchOptions) => ReactTestInstance; + getByDisplayValue: (value: string | RegExp, options?: TextMatchOptions) => ReactTestInstance; getByTestId: (testID: string | RegExp) => ReactTestInstance; getAllByTestId: (testID: string | RegExp) => Array; - getAllByText: (text: string | RegExp) => Array; + getAllByText: (text: string | RegExp, options?: TextMatchOptions) => Array; getAllByPlaceholderText: ( - placeholder: string | RegExp + placeholder: string | RegExp, + options?: TextMatchOptions, ) => Array; - getAllByDisplayValue: (value: string | RegExp) => Array; + getAllByDisplayValue: (value: string | RegExp, options?: TextMatchOptions) => Array; // Unsafe aliases UNSAFE_getByType:

(type: React.ComponentType

) => ReactTestInstance; @@ -64,19 +65,22 @@ interface GetByAPI { } interface QueryByAPI { - queryByText: (name: string | RegExp) => ReactTestInstance | null; + queryByText: (name: string | RegExp, options?: TextMatchOptions) => ReactTestInstance | null; queryByPlaceholderText: ( - placeholder: string | RegExp + placeholder: string | RegExp, + options?: TextMatchOptions, ) => ReactTestInstance | null; - queryByDisplayValue: (value: string | RegExp) => ReactTestInstance | null; + queryByDisplayValue: (value: string | RegExp, options?: TextMatchOptions) => ReactTestInstance | null; queryByTestId: (testID: string | RegExp) => ReactTestInstance | null; queryAllByTestId: (testID: string | RegExp) => Array | []; - queryAllByText: (text: string | RegExp) => Array | []; + queryAllByText: (text: string | RegExp, options?: TextMatchOptions) => Array | []; queryAllByPlaceholderText: ( - placeholder: string | RegExp + placeholder: string | RegExp, + options?: TextMatchOptions, ) => Array | []; queryAllByDisplayValue: ( - value: string | RegExp + value: string | RegExp, + options?: TextMatchOptions, ) => Array | []; // Unsafe aliases @@ -127,28 +131,34 @@ interface QueryByAPI { interface FindByAPI { findByText: ( text: string | RegExp, - waitForOptions?: WaitForOptions + waitForOptions?: WaitForOptions, + options?: TextMatchOptions, ) => FindReturn; findByPlaceholderText: ( placeholder: string | RegExp, - waitForOptions?: WaitForOptions + waitForOptions?: WaitForOptions, + options?: TextMatchOptions, ) => FindReturn; findByDisplayValue: ( value: string | RegExp, - waitForOptions?: WaitForOptions + waitForOptions?: WaitForOptions, + options?: TextMatchOptions, ) => FindReturn; findByTestId: (testID: string | RegExp, waitForOptions?: WaitForOptions) => FindReturn; findAllByText: ( text: string | RegExp, - waitForOptions?: WaitForOptions + waitForOptions?: WaitForOptions, + options?: TextMatchOptions, ) => FindAllReturn; findAllByPlaceholderText: ( placeholder: string | RegExp, - waitForOptions?: WaitForOptions + waitForOptions?: WaitForOptions, + options?: TextMatchOptions, ) => FindAllReturn; findAllByDisplayValue: ( value: string | RegExp, - waitForOptions?: WaitForOptions + waitForOptions?: WaitForOptions, + options?: TextMatchOptions, ) => FindAllReturn; findAllByTestId: ( testID: string | RegExp, @@ -325,6 +335,10 @@ export declare const render: ( export declare const cleanup: () => void; export declare const fireEvent: FireEventAPI; +type TextMatchOptions = { + exact: boolean, +}; + type WaitForOptions = { timeout?: number; interval?: number; From d95ff2fa0633819f4ed81d77ca54be87f316e0ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Zakrzewski?= Date: Wed, 9 Sep 2020 15:27:37 +0200 Subject: [PATCH 02/28] Add TextMatch options to queryByAPI --- src/helpers/queryByAPI.js | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/src/helpers/queryByAPI.js b/src/helpers/queryByAPI.js index 3d5ce8488..de063aa19 100644 --- a/src/helpers/queryByAPI.js +++ b/src/helpers/queryByAPI.js @@ -19,29 +19,36 @@ import { throwRemovedFunctionError, throwRenamedFunctionError, } from './errors'; +import type { TextMatchOptions } from './getByAPI'; export const queryByText = (instance: ReactTestInstance) => - function queryByTextFn(text: string | RegExp) { + function queryByTextFn(text: string | RegExp, options?: TextMatchOptions) { try { - return getByText(instance)(text); + return getByText(instance)(text, options); } catch (error) { return createQueryByError(error, queryByTextFn); } }; export const queryByPlaceholderText = (instance: ReactTestInstance) => - function queryByPlaceholderTextFn(placeholder: string | RegExp) { + function queryByPlaceholderTextFn( + placeholder: string | RegExp, + options?: TextMatchOptions + ) { try { - return getByPlaceholderText(instance)(placeholder); + return getByPlaceholderText(instance)(placeholder, options); } catch (error) { return createQueryByError(error, queryByPlaceholderTextFn); } }; export const queryByDisplayValue = (instance: ReactTestInstance) => - function queryByDisplayValueFn(value: string | RegExp) { + function queryByDisplayValueFn( + value: string | RegExp, + options?: TextMatchOptions + ) { try { - return getByDisplayValue(instance)(value); + return getByDisplayValue(instance)(value, options); } catch (error) { return createQueryByError(error, queryByDisplayValueFn); } @@ -57,30 +64,33 @@ export const queryByTestId = (instance: ReactTestInstance) => }; export const queryAllByText = (instance: ReactTestInstance) => ( - text: string | RegExp + text: string | RegExp, + options?: TextMatchOptions ) => { try { - return getAllByText(instance)(text); + return getAllByText(instance)(text, options); } catch (error) { return []; } }; export const queryAllByPlaceholderText = (instance: ReactTestInstance) => ( - placeholder: string | RegExp + placeholder: string | RegExp, + options?: TextMatchOptions ) => { try { - return getAllByPlaceholderText(instance)(placeholder); + return getAllByPlaceholderText(instance)(placeholder, options); } catch (error) { return []; } }; export const queryAllByDisplayValue = (instance: ReactTestInstance) => ( - value: string | RegExp + value: string | RegExp, + options?: TextMatchOptions ) => { try { - return getAllByDisplayValue(instance)(value); + return getAllByDisplayValue(instance)(value, options); } catch (error) { return []; } From b6163192ce239ba41c71f21a6dfdefcc4ec995f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Zakrzewski?= Date: Wed, 9 Sep 2020 15:28:31 +0200 Subject: [PATCH 03/28] Add TextMatch options to findByAPI --- src/helpers/findByAPI.js | 69 +++++++++++++++++++++++++++++++--------- 1 file changed, 54 insertions(+), 15 deletions(-) diff --git a/src/helpers/findByAPI.js b/src/helpers/findByAPI.js index 7cb8b4699..1084905f1 100644 --- a/src/helpers/findByAPI.js +++ b/src/helpers/findByAPI.js @@ -12,13 +12,18 @@ import { getAllByDisplayValue, } from './getByAPI'; import { throwRenamedFunctionError } from './errors'; +import type { TextMatchOptions } from './getByAPI'; const makeFindQuery = ( instance: ReactTestInstance, - getQuery: (instance: ReactTestInstance) => (text: Text) => Result, + getQuery: ( + instance: ReactTestInstance + ) => (text: Text, options?: TextMatchOptions) => Result, text: Text, - waitForOptions: WaitForOptions -): Promise => waitFor(() => getQuery(instance)(text), waitForOptions); + waitForOptions: WaitForOptions, + textMatchOptions?: TextMatchOptions +): Promise => + waitFor(() => getQuery(instance)(text, textMatchOptions), waitForOptions); export const findByTestId = (instance: ReactTestInstance) => ( testId: string | RegExp, @@ -32,34 +37,68 @@ export const findAllByTestId = (instance: ReactTestInstance) => ( export const findByText = (instance: ReactTestInstance) => ( text: string | RegExp, - waitForOptions: WaitForOptions = {} -) => makeFindQuery(instance, getByText, text, waitForOptions); + waitForOptions: WaitForOptions = {}, + textMatchOptions?: TextMatchOptions +) => makeFindQuery(instance, getByText, text, waitForOptions, textMatchOptions); export const findAllByText = (instance: ReactTestInstance) => ( text: string | RegExp, - waitForOptions: WaitForOptions = {} -) => makeFindQuery(instance, getAllByText, text, waitForOptions); + waitForOptions: WaitForOptions = {}, + textMatchOptions?: TextMatchOptions +) => + makeFindQuery(instance, getAllByText, text, waitForOptions, textMatchOptions); export const findByPlaceholderText = (instance: ReactTestInstance) => ( placeholder: string | RegExp, - waitForOptions: WaitForOptions = {} -) => makeFindQuery(instance, getByPlaceholderText, placeholder, waitForOptions); + waitForOptions: WaitForOptions = {}, + textMatchOptions?: TextMatchOptions +) => + makeFindQuery( + instance, + getByPlaceholderText, + placeholder, + waitForOptions, + textMatchOptions + ); export const findAllByPlaceholderText = (instance: ReactTestInstance) => ( placeholder: string | RegExp, - waitForOptions: WaitForOptions = {} + waitForOptions: WaitForOptions = {}, + textMatchOptions?: TextMatchOptions ) => - makeFindQuery(instance, getAllByPlaceholderText, placeholder, waitForOptions); + makeFindQuery( + instance, + getAllByPlaceholderText, + placeholder, + waitForOptions, + textMatchOptions + ); export const findByDisplayValue = (instance: ReactTestInstance) => ( value: string | RegExp, - waitForOptions: WaitForOptions = {} -) => makeFindQuery(instance, getByDisplayValue, value, waitForOptions); + waitForOptions: WaitForOptions = {}, + textMatchOptions?: TextMatchOptions +) => + makeFindQuery( + instance, + getByDisplayValue, + value, + waitForOptions, + textMatchOptions + ); export const findAllByDisplayValue = (instance: ReactTestInstance) => ( value: string | RegExp, - waitForOptions: WaitForOptions = {} -) => makeFindQuery(instance, getAllByDisplayValue, value, waitForOptions); + waitForOptions: WaitForOptions = {}, + textMatchOptions?: TextMatchOptions +) => + makeFindQuery( + instance, + getAllByDisplayValue, + value, + waitForOptions, + textMatchOptions + ); export const findByAPI = (instance: ReactTestInstance) => ({ findByTestId: findByTestId(instance), From 38578705b072a9dfb49bd1bda99d4d1dd316912f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Zakrzewski?= Date: Wed, 9 Sep 2020 15:30:41 +0200 Subject: [PATCH 04/28] Add tests covering new TextMatch options --- src/__tests__/getByApi.test.js | 58 ++++++++++++++++++++++++++++++++ src/__tests__/queryByApi.test.js | 15 +++++++++ 2 files changed, 73 insertions(+) diff --git a/src/__tests__/getByApi.test.js b/src/__tests__/getByApi.test.js index a2b2637d1..942b1681d 100644 --- a/src/__tests__/getByApi.test.js +++ b/src/__tests__/getByApi.test.js @@ -50,3 +50,61 @@ test('supports a regex matcher', () => { expect(getByTestId(/view/)).toBeTruthy(); expect(getAllByTestId(/text/)).toHaveLength(2); }); + +describe('Supports a TextMatch options', () => { + test('getByText, getAllByText', () => { + const { getByText, getAllByText } = render( + + Text and details +