Skip to content

Add getByPlaceholder query functionality #101

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 5 commits into from
Dec 22, 2018
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
8 changes: 8 additions & 0 deletions docs/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,14 @@ A method returning a `ReactTestInstance` with matching text – may be a string

A method returning an array of `ReactTestInstance`s with matching text – may be a string or regular expression.

### `getByPlaceholder: (placeholder: string | RegExp)`

A method returning a `ReactTestInstance` for a `TextInput` with a matching placeholder – may be a string or regular expression. Throws when no matches.

### `getAllByPlaceholder: (placeholder: string | RegExp)`

A method returning an array of `ReactTestInstance`s for `TextInput`'s with a matching placeholder – may be a string or regular expression.

### `getByProps: (props: { [propName: string]: any })`

A method returning a `ReactTestInstance` with matching props object. Throws when no matches.
Expand Down
60 changes: 60 additions & 0 deletions src/__tests__/__snapshots__/render.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down Expand Up @@ -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}
Expand Down Expand Up @@ -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\\"
Expand All @@ -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\\"
Expand All @@ -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}
Expand Down
41 changes: 40 additions & 1 deletion src/__tests__/render.test.js
Original file line number Diff line number Diff line change
@@ -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 (
Expand Down Expand Up @@ -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>
Expand Down Expand Up @@ -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' });
Expand Down
42 changes: 42 additions & 0 deletions src/helpers/getByAPI.js
Original file line number Diff line number Diff line change
Expand Up @@ -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, '');
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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);
Expand All @@ -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),
});
23 changes: 23 additions & 0 deletions src/helpers/queryByAPI.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ import {
getByName,
getByType,
getByText,
getByPlaceholder,
getByProps,
getAllByName,
getAllByType,
getAllByText,
getAllByPlaceholder,
getAllByProps,
} from './getByAPI';
import { ErrorWithStack, logDeprecationWarning } from './errors';
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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,
}) => {
Expand All @@ -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),
});
8 changes: 8 additions & 0 deletions typings/__tests__/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,15 @@ interface HasRequiredProp {

const View = props => props.children;
const Text = props => props.children;
const TextInput = props => props.children;
const ElementWithRequiredProps = (props: HasRequiredProp) => (
<Text>{props.requiredProp}</Text>
);

const TestComponent = () => (
<View>
<Text>Test component</Text>
<TextInput placeholder="my placeholder" />
</View>
);

Expand All @@ -36,6 +38,8 @@ const getByTypeWithRequiredProps: ReactTestInstance = tree.getByType(
);
const getByTextString: ReactTestInstance = tree.getByText('<View />');
const getByTextRegExp: ReactTestInstance = tree.getByText(/View/g);
const getByPlaceholderString: ReactTestInstance = tree.getByPlaceholder('my placeholder');
const getByPlaceholderRegExp: ReactTestInstance = tree.getByPlaceholder(/placeholder/g);
const getByProps: ReactTestInstance = tree.getByProps({ value: 2 });
const getByTestId: ReactTestInstance = tree.getByTestId('test-id');
const getAllByNameString: Array<ReactTestInstance> = tree.getAllByName('View');
Expand Down Expand Up @@ -65,6 +69,10 @@ const queryByTextString: ReactTestInstance | null = tree.queryByText(
'<View />'
);
const queryByTextRegExp: ReactTestInstance | null = tree.queryByText(/View/g);
const queryByPlaceholderString: ReactTestInstance | null = tree.queryByText(
'my placeholder'
);
const queryByPlaceholderRegExp: ReactTestInstance | null = tree.queryByText(/placeholder/g);
const queryByProps: ReactTestInstance | null = tree.queryByProps({ value: 2 });
const queryByTestId: ReactTestInstance | null = tree.queryByTestId('test-id');
const queryAllByNameString: Array<ReactTestInstance> = tree.getAllByName(
Expand Down
4 changes: 4 additions & 0 deletions typings/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,29 @@ export interface GetByAPI {
getByName: (name: React.ReactType | string) => ReactTestInstance;
getByType: <P>(type: React.ComponentType<P>) => ReactTestInstance;
getByText: (text: string | RegExp) => ReactTestInstance;
getByPlaceholder: (placeholder: string | RegExp) => ReactTestInstance;
getByProps: (props: Record<string, any>) => ReactTestInstance;
getByTestId: (testID: string) => ReactTestInstance;
getAllByName: (name: React.ReactType | string) => Array<ReactTestInstance>;
getAllByType: <P>(type: React.ComponentType<P>) => Array<ReactTestInstance>;
getAllByText: (text: string | RegExp) => Array<ReactTestInstance>;
getAllByPlaceholder: (placeholder: string | RegExp) => Array<ReactTestInstance>;
getAllByProps: (props: Record<string, any>) => Array<ReactTestInstance>;
}

export interface QueryByAPI {
queryByName: (name: React.ReactType | string) => ReactTestInstance | null;
queryByType: <P>(type: React.ComponentType<P>) => ReactTestInstance | null;
queryByText: (name: string | RegExp) => ReactTestInstance | null;
queryByPlaceholder: (placeholder: string | RegExp) => ReactTestInstance | null;
queryByProps: (props: Record<string, any>) => ReactTestInstance | null;
queryByTestId: (testID: string) => ReactTestInstance | null;
queryAllByName: (name: React.ReactType | string) => Array<ReactTestInstance> | [];
queryAllByType: <P>(
type: React.ComponentType<P>
) => Array<ReactTestInstance> | [];
queryAllByText: (text: string | RegExp) => Array<ReactTestInstance> | [];
queryAllByPlaceholder: (placeholder: string | RegExp) => Array<ReactTestInstance> | [];
queryAllByProps: (
props: Record<string, any>
) => Array<ReactTestInstance> | [];
Expand Down