Skip to content

feat: add query API #13

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
Oct 7, 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
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,30 @@ test('fetch data', async () => {
});
```

## `query` APIs

Each of the get APIs listed in the render section above have a complimentary query API. The get APIs will throw errors if a proper node cannot be found. This is normally the desired effect. However, if you want to make an assertion that an element is not present in the hierarchy, then you can use the query API instead:

```jsx
import { render } from 'react-native-testing-library';

const { queryByText } = render(<Form />);
const submitButton = queryByText('submit');
expect(submitButton).toBeNull(); // it doesn't exist
```

## `queryAll` APIs

Each of the query APIs have a corresponding queryAll version that always returns an Array of matching nodes. getAll is the same but throws when the array has a length of 0.

```jsx
import { render } from 'react-native-testing-library';

const { queryAllByText } = render(<Forms />);
const submitButtons = queryAllByText('submit');
expect(submitButtons).toHaveLength(3); // expect 3 elements
```

<!-- badges -->

[build-badge]: https://img.shields.io/circleci/project/github/callstack/react-native-testing-library/master.svg?style=flat-square
Expand Down
54 changes: 37 additions & 17 deletions src/__tests__/render.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,16 +51,19 @@ class Banana extends React.Component {
}
}

test('getByTestId', () => {
const { getByTestId } = render(<Banana />);
test('getByTestId, queryByTestId', () => {
const { getByTestId, queryByTestId } = render(<Banana />);
const component = getByTestId('bananaFresh');

expect(component.props.children).toBe('not fresh');
expect(() => getByTestId('InExistent')).toThrow();

expect(getByTestId('bananaFresh')).toBe(component);
expect(queryByTestId('InExistent')).toBeNull();
});

test('getByName', () => {
const { getByTestId, getByName } = render(<Banana />);
test('getByName, queryByName', () => {
const { getByTestId, getByName, queryByName } = render(<Banana />);
const bananaFresh = getByTestId('bananaFresh');
const button = getByName('Button');

Expand All @@ -72,22 +75,27 @@ test('getByName', () => {
sameButton.props.onPress();

expect(bananaFresh.props.children).toBe('not fresh');

expect(() => getByName('InExistent')).toThrow();

expect(queryByName('Button')).toBe(button);
expect(queryByName('InExistent')).toBeNull();
});

test('getAllByName', () => {
const { getAllByName } = render(<Banana />);
test('getAllByName, queryAllByName', () => {
const { getAllByName, queryAllByName } = render(<Banana />);
const [text, status, button] = getAllByName('Text');

expect(text.props.children).toBe('Is the banana fresh?');
expect(status.props.children).toBe('not fresh');
expect(button.props.children).toBe('Change freshness!');
expect(() => getAllByName('InExistent')).toThrow();

expect(queryAllByName('Text')[1]).toBe(status);
expect(queryAllByName('InExistent')).toHaveLength(0);
});

test('getByText', () => {
const { getByText } = render(<Banana />);
test('getByText, queryByText', () => {
const { getByText, queryByText } = render(<Banana />);
const button = getByText(/change/i);

expect(button.props.children).toBe('Change freshness!');
Expand All @@ -96,30 +104,42 @@ test('getByText', () => {

expect(sameButton.props.children).toBe('not fresh');
expect(() => getByText('InExistent')).toThrow();

expect(queryByText(/change/i)).toBe(button);
expect(queryByText('InExistent')).toBeNull();
});

test('getAllByText', () => {
const { getAllByText } = render(<Banana />);
const button = getAllByText(/fresh/i);
test('getAllByText, queryAllByText', () => {
const { getAllByText, queryAllByText } = render(<Banana />);
const buttons = getAllByText(/fresh/i);

expect(button).toHaveLength(3);
expect(buttons).toHaveLength(3);
expect(() => getAllByText('InExistent')).toThrow();

expect(queryAllByText(/fresh/i)).toEqual(buttons);
expect(queryAllByText('InExistent')).toHaveLength(0);
});

test('getByProps', () => {
const { getByProps } = render(<Banana />);
test('getByProps, queryByProps', () => {
const { getByProps, queryByProps } = render(<Banana />);
const primaryType = getByProps({ type: 'primary' });

expect(primaryType.props.children).toBe('Change freshness!');
expect(() => getByProps({ type: 'inexistent' })).toThrow();

expect(queryByProps({ type: 'primary' })).toBe(primaryType);
expect(queryByProps({ type: 'inexistent' })).toBeNull();
});

test('getAllByProps', () => {
const { getAllByProps } = render(<Banana />);
test('getAllByProp, queryAllByProps', () => {
const { getAllByProps, queryAllByProps } = render(<Banana />);
const primaryTypes = getAllByProps({ type: 'primary' });

expect(primaryTypes).toHaveLength(1);
expect(() => getAllByProps({ type: 'inexistent' })).toThrow();

expect(queryAllByProps({ type: 'primary' })).toEqual(primaryTypes);
expect(queryAllByProps({ type: 'inexistent' })).toHaveLength(0);
});

test('update', () => {
Expand Down
10 changes: 10 additions & 0 deletions src/helpers/getBy.js → src/helpers/getByAPI.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,13 @@ export const getAllByProps = (instance: ReactTestInstance) => (props: {
}
return results;
};

export const getByAPI = (instance: ReactTestInstance) => ({
getByTestId: getByTestId(instance),
getByName: getByName(instance),
getByText: getByText(instance),
getByProps: getByProps(instance),
getAllByName: getAllByName(instance),
getAllByText: getAllByText(instance),
getAllByProps: getAllByProps(instance),
});
91 changes: 91 additions & 0 deletions src/helpers/queryByAPI.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// @flow
import * as React from 'react';
import {
getByTestId,
getByName,
getByText,
getByProps,
getAllByName,
getAllByText,
getAllByProps,
} from './getByAPI';

export const queryByName = (instance: ReactTestInstance) => (
name: string | React.Element<*>
) => {
try {
return getByName(instance)(name);
} catch (error) {
return null;
}
};

export const queryByText = (instance: ReactTestInstance) => (
text: string | RegExp
) => {
try {
return getByText(instance)(text);
} catch (error) {
return null;
}
};

export const queryByProps = (instance: ReactTestInstance) => (props: {
[propName: string]: any,
}) => {
try {
return getByProps(instance)(props);
} catch (error) {
return null;
}
};

export const queryByTestId = (instance: ReactTestInstance) => (
testID: string
) => {
try {
return getByTestId(instance)(testID);
} catch (error) {
return null;
}
};

export const queryAllByName = (instance: ReactTestInstance) => (
name: string | React.Element<*>
) => {
try {
return getAllByName(instance)(name);
} catch (error) {
return [];
}
};

export const queryAllByText = (instance: ReactTestInstance) => (
text: string | RegExp
) => {
try {
return getAllByText(instance)(text);
} catch (error) {
return [];
}
};

export const queryAllByProps = (instance: ReactTestInstance) => (props: {
[propName: string]: any,
}) => {
try {
return getAllByProps(instance)(props);
} catch (error) {
return [];
}
};

export const queryByAPI = (instance: ReactTestInstance) => ({
queryByTestId: queryByTestId(instance),
queryByName: queryByName(instance),
queryByText: queryByText(instance),
queryByProps: queryByProps(instance),
queryAllByName: queryAllByName(instance),
queryAllByText: queryAllByText(instance),
queryAllByProps: queryAllByProps(instance),
});
20 changes: 4 additions & 16 deletions src/render.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,8 @@
// @flow
import * as React from 'react';
import TestRenderer from 'react-test-renderer'; // eslint-disable-line import/no-extraneous-dependencies
import {
getByTestId,
getByName,
getByText,
getByProps,
getAllByName,
getAllByText,
getAllByProps,
} from './helpers/getBy';
import { getByAPI } from './helpers/getByAPI';
import { queryByAPI } from './helpers/queryByAPI';

/**
* Renders test component deeply using react-test-renderer and exposes helpers
Expand All @@ -23,13 +16,8 @@ export default function render(
const instance = renderer.root;

return {
getByTestId: getByTestId(instance),
getByName: getByName(instance),
getByText: getByText(instance),
getByProps: getByProps(instance),
getAllByName: getAllByName(instance),
getAllByText: getAllByText(instance),
getAllByProps: getAllByProps(instance),
...getByAPI(instance),
...queryByAPI(instance),
update: renderer.update,
unmount: renderer.unmount,
};
Expand Down