mirror of
https://github.com/callstack/react-native-testing-library.git
synced 2026-09-18 23:09:04 +08:00
feat: add getByPlaceholder query functionality (#101)
### Summary There is a helper that exists in `react-testing-library` to query inputs by their `placeholder`. This PR adds that support. You **could** achieve this by querying for a `placeholder` prop, or querying for react native `TextInput`s manually and then checking their props, but it felt cleaner to have this contained in the testing library itself. It will also allow you to bypass any other instances that may have a `placeholder` prop that aren't `TextInput`s. If we don't want to expand the surface of the library further, I completely understand that and am fine with this being closed. Just seems like it may be useful and save some time for several scenarios =) ### Test plan + Render a `TextInput` with a `placeholder` prop + Query the test instance via `getByPlaceholder` for the input + Verify the test instance is found
This commit is contained in:
committed by
Michał Pierzchała
parent
5145a708db
commit
ee888f1dcd
@@ -10,6 +10,18 @@ exports[`debug 1`] = `
|
||||
>
|
||||
not fresh
|
||||
</Text>
|
||||
<TextInput
|
||||
allowFontScaling={true}
|
||||
placeholder=\\"Add custom freshness\\"
|
||||
testID=\\"bananaCustomFreshness\\"
|
||||
underlineColorAndroid=\\"transparent\\"
|
||||
/>
|
||||
<TextInput
|
||||
allowFontScaling={true}
|
||||
placeholder=\\"Who inspected freshness?\\"
|
||||
testID=\\"bananaChef\\"
|
||||
underlineColorAndroid=\\"transparent\\"
|
||||
/>
|
||||
<View
|
||||
accessible={true}
|
||||
isTVSelectable={true}
|
||||
@@ -42,6 +54,18 @@ exports[`debug changing component: bananaFresh button message should now be "fre
|
||||
>
|
||||
fresh
|
||||
</Text>
|
||||
<TextInput
|
||||
allowFontScaling={true}
|
||||
placeholder=\\"Add custom freshness\\"
|
||||
testID=\\"bananaCustomFreshness\\"
|
||||
underlineColorAndroid=\\"transparent\\"
|
||||
/>
|
||||
<TextInput
|
||||
allowFontScaling={true}
|
||||
placeholder=\\"Who inspected freshness?\\"
|
||||
testID=\\"bananaChef\\"
|
||||
underlineColorAndroid=\\"transparent\\"
|
||||
/>
|
||||
<View
|
||||
accessible={true}
|
||||
isTVSelectable={true}
|
||||
@@ -74,6 +98,18 @@ exports[`debug: shallow 1`] = `
|
||||
>
|
||||
not fresh
|
||||
</Text>
|
||||
<TextInput
|
||||
allowFontScaling={true}
|
||||
placeholder=\\"Add custom freshness\\"
|
||||
testID=\\"bananaCustomFreshness\\"
|
||||
underlineColorAndroid=\\"transparent\\"
|
||||
/>
|
||||
<TextInput
|
||||
allowFontScaling={true}
|
||||
placeholder=\\"Who inspected freshness?\\"
|
||||
testID=\\"bananaChef\\"
|
||||
underlineColorAndroid=\\"transparent\\"
|
||||
/>
|
||||
<Button
|
||||
onPress={[Function anonymous]}
|
||||
type=\\"primary\\"
|
||||
@@ -95,6 +131,18 @@ exports[`debug: shallow with message 1`] = `
|
||||
>
|
||||
not fresh
|
||||
</Text>
|
||||
<TextInput
|
||||
allowFontScaling={true}
|
||||
placeholder=\\"Add custom freshness\\"
|
||||
testID=\\"bananaCustomFreshness\\"
|
||||
underlineColorAndroid=\\"transparent\\"
|
||||
/>
|
||||
<TextInput
|
||||
allowFontScaling={true}
|
||||
placeholder=\\"Who inspected freshness?\\"
|
||||
testID=\\"bananaChef\\"
|
||||
underlineColorAndroid=\\"transparent\\"
|
||||
/>
|
||||
<Button
|
||||
onPress={[Function anonymous]}
|
||||
type=\\"primary\\"
|
||||
@@ -116,6 +164,18 @@ exports[`debug: with message 1`] = `
|
||||
>
|
||||
not fresh
|
||||
</Text>
|
||||
<TextInput
|
||||
allowFontScaling={true}
|
||||
placeholder=\\"Add custom freshness\\"
|
||||
testID=\\"bananaCustomFreshness\\"
|
||||
underlineColorAndroid=\\"transparent\\"
|
||||
/>
|
||||
<TextInput
|
||||
allowFontScaling={true}
|
||||
placeholder=\\"Who inspected freshness?\\"
|
||||
testID=\\"bananaChef\\"
|
||||
underlineColorAndroid=\\"transparent\\"
|
||||
/>
|
||||
<View
|
||||
accessible={true}
|
||||
isTVSelectable={true}
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
// @flow
|
||||
/* eslint-disable react/no-multi-comp */
|
||||
import React from 'react';
|
||||
import { View, Text, TouchableOpacity } from 'react-native';
|
||||
import { View, Text, TextInput, TouchableOpacity } from 'react-native';
|
||||
import stripAnsi from 'strip-ansi';
|
||||
import { render, fireEvent } from '..';
|
||||
|
||||
const PLACEHOLDER_FRESHNESS = 'Add custom freshness';
|
||||
const PLACEHOLDER_CHEF = 'Who inspected freshness?';
|
||||
|
||||
class Button extends React.Component<*> {
|
||||
render() {
|
||||
return (
|
||||
@@ -45,6 +48,11 @@ class Banana extends React.Component<*, *> {
|
||||
<Text testID="bananaFresh">
|
||||
{this.state.fresh ? 'fresh' : 'not fresh'}
|
||||
</Text>
|
||||
<TextInput
|
||||
testID="bananaCustomFreshness"
|
||||
placeholder={PLACEHOLDER_FRESHNESS}
|
||||
/>
|
||||
<TextInput testID="bananaChef" placeholder={PLACEHOLDER_CHEF} />
|
||||
<Button onPress={this.changeFresh} type="primary">
|
||||
Change freshness!
|
||||
</Button>
|
||||
@@ -138,6 +146,37 @@ test('getAllByText, queryAllByText', () => {
|
||||
expect(queryAllByText('InExistent')).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('getByPlaceholder, queryByPlaceholder', () => {
|
||||
const { getByPlaceholder, queryByPlaceholder } = render(<Banana />);
|
||||
const input = getByPlaceholder(/custom/i);
|
||||
|
||||
expect(input.props.placeholder).toBe(PLACEHOLDER_FRESHNESS);
|
||||
|
||||
const sameInput = getByPlaceholder(PLACEHOLDER_FRESHNESS);
|
||||
|
||||
expect(sameInput.props.placeholder).toBe(PLACEHOLDER_FRESHNESS);
|
||||
expect(() => getByPlaceholder('no placeholder')).toThrow(
|
||||
'No instances found'
|
||||
);
|
||||
|
||||
expect(queryByPlaceholder(/add/i)).toBe(input);
|
||||
expect(queryByPlaceholder('no placeholder')).toBeNull();
|
||||
expect(() => queryByPlaceholder(/fresh/)).toThrow('Expected 1 but found 2');
|
||||
});
|
||||
|
||||
test('getAllByPlaceholder, queryAllByPlaceholder', () => {
|
||||
const { getAllByPlaceholder, queryAllByPlaceholder } = render(<Banana />);
|
||||
const inputs = getAllByPlaceholder(/fresh/i);
|
||||
|
||||
expect(inputs).toHaveLength(2);
|
||||
expect(() => getAllByPlaceholder('no placeholder')).toThrow(
|
||||
'No instances found'
|
||||
);
|
||||
|
||||
expect(queryAllByPlaceholder(/fresh/i)).toEqual(inputs);
|
||||
expect(queryAllByPlaceholder('no placeholder')).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('getByProps, queryByProps', () => {
|
||||
const { getByProps, queryByProps } = render(<Banana />);
|
||||
const primaryType = getByProps({ type: 'primary' });
|
||||
|
||||
@@ -28,6 +28,21 @@ const getNodeByText = (node, text) => {
|
||||
}
|
||||
};
|
||||
|
||||
const getTextInputNodeByPlaceholder = (node, placeholder) => {
|
||||
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))
|
||||
);
|
||||
} catch (error) {
|
||||
throw createLibraryNotSupportedError(error);
|
||||
}
|
||||
};
|
||||
|
||||
const prepareErrorMessage = error =>
|
||||
// Strip info about custom predicate
|
||||
error.message.replace(/ matching custom predicate[^]*/gm, '');
|
||||
@@ -62,6 +77,17 @@ export const getByText = (instance: ReactTestInstance) =>
|
||||
}
|
||||
};
|
||||
|
||||
export const getByPlaceholder = (instance: ReactTestInstance) =>
|
||||
function getByPlaceholderFn(placeholder: string | RegExp) {
|
||||
try {
|
||||
return instance.find(node =>
|
||||
getTextInputNodeByPlaceholder(node, placeholder)
|
||||
);
|
||||
} catch (error) {
|
||||
throw new ErrorWithStack(prepareErrorMessage(error), getByPlaceholderFn);
|
||||
}
|
||||
};
|
||||
|
||||
export const getByProps = (instance: ReactTestInstance) =>
|
||||
function getByPropsFn(props: { [propName: string]: any }) {
|
||||
try {
|
||||
@@ -114,6 +140,20 @@ export const getAllByText = (instance: ReactTestInstance) =>
|
||||
return results;
|
||||
};
|
||||
|
||||
export const getAllByPlaceholder = (instance: ReactTestInstance) =>
|
||||
function getAllByPlaceholderFn(placeholder: string | RegExp) {
|
||||
const results = instance.findAll(node =>
|
||||
getTextInputNodeByPlaceholder(node, placeholder)
|
||||
);
|
||||
if (results.length === 0) {
|
||||
throw new ErrorWithStack(
|
||||
`No instances found with placeholder: ${String(placeholder)}`,
|
||||
getAllByPlaceholderFn
|
||||
);
|
||||
}
|
||||
return results;
|
||||
};
|
||||
|
||||
export const getAllByProps = (instance: ReactTestInstance) =>
|
||||
function getAllByPropsFn(props: { [propName: string]: any }) {
|
||||
const results = instance.findAllByProps(props);
|
||||
@@ -131,9 +171,11 @@ export const getByAPI = (instance: ReactTestInstance) => ({
|
||||
getByName: getByName(instance),
|
||||
getByType: getByType(instance),
|
||||
getByText: getByText(instance),
|
||||
getByPlaceholder: getByPlaceholder(instance),
|
||||
getByProps: getByProps(instance),
|
||||
getAllByName: getAllByName(instance),
|
||||
getAllByType: getAllByType(instance),
|
||||
getAllByText: getAllByText(instance),
|
||||
getAllByPlaceholder: getAllByPlaceholder(instance),
|
||||
getAllByProps: getAllByProps(instance),
|
||||
});
|
||||
|
||||
@@ -5,10 +5,12 @@ import {
|
||||
getByName,
|
||||
getByType,
|
||||
getByText,
|
||||
getByPlaceholder,
|
||||
getByProps,
|
||||
getAllByName,
|
||||
getAllByType,
|
||||
getAllByText,
|
||||
getAllByPlaceholder,
|
||||
getAllByProps,
|
||||
} from './getByAPI';
|
||||
import { ErrorWithStack, logDeprecationWarning } from './errors';
|
||||
@@ -48,6 +50,15 @@ export const queryByText = (instance: ReactTestInstance) =>
|
||||
}
|
||||
};
|
||||
|
||||
export const queryByPlaceholder = (instance: ReactTestInstance) =>
|
||||
function queryByPlaceholderFn(placeholder: string | RegExp) {
|
||||
try {
|
||||
return getByPlaceholder(instance)(placeholder);
|
||||
} catch (error) {
|
||||
return createQueryByError(error, queryByPlaceholder);
|
||||
}
|
||||
};
|
||||
|
||||
export const queryByProps = (instance: ReactTestInstance) =>
|
||||
function queryByPropsFn(props: { [propName: string]: any }) {
|
||||
try {
|
||||
@@ -97,6 +108,16 @@ export const queryAllByText = (instance: ReactTestInstance) => (
|
||||
}
|
||||
};
|
||||
|
||||
export const queryAllByPlaceholder = (instance: ReactTestInstance) => (
|
||||
placeholder: string | RegExp
|
||||
) => {
|
||||
try {
|
||||
return getAllByPlaceholder(instance)(placeholder);
|
||||
} catch (error) {
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
export const queryAllByProps = (instance: ReactTestInstance) => (props: {
|
||||
[propName: string]: any,
|
||||
}) => {
|
||||
@@ -112,9 +133,11 @@ export const queryByAPI = (instance: ReactTestInstance) => ({
|
||||
queryByName: queryByName(instance),
|
||||
queryByType: queryByType(instance),
|
||||
queryByText: queryByText(instance),
|
||||
queryByPlaceholder: queryByPlaceholder(instance),
|
||||
queryByProps: queryByProps(instance),
|
||||
queryAllByName: queryAllByName(instance),
|
||||
queryAllByType: queryAllByType(instance),
|
||||
queryAllByText: queryAllByText(instance),
|
||||
queryAllByPlaceholder: queryAllByPlaceholder(instance),
|
||||
queryAllByProps: queryAllByProps(instance),
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user