Skip to content

Feat(breaking): unify wait for element signature with testing library #327

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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ The [public API](https://callstack.github.io/react-native-testing-library/docs/a

- [`render`](https://callstack.github.io/react-native-testing-library/docs/api#render) – deeply renders given React element and returns helpers to query the output components.
- [`fireEvent`](https://callstack.github.io/react-native-testing-library/docs/api#fireevent) - invokes named event handler on the element.
- [`waitForElement`](https://callstack.github.io/react-native-testing-library/docs/api#waitforelement) - waits for non-deterministic periods of time until your element appears or times out.
- [`waitFor`](https://callstack.github.io/react-native-testing-library/docs/api#waitfor) - waits for non-deterministic periods of time until your element appears or times out.
- [`within`](https://callstack.github.io/react-native-testing-library/docs/api#within) - creates a queries object scoped for given element.
- [`flushMicrotasksQueue`](https://callstack.github.io/react-native-testing-library/docs/api#flushmicrotasksqueue) - waits for microtasks queue to flush.

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// @flow
import React from 'react';
import { View, Text, TouchableOpacity } from 'react-native';
import { render, fireEvent, waitForElement } from '..';
import { render, fireEvent, waitFor } from '..';

class Banana extends React.Component<any> {
changeFresh = () => {
Expand Down Expand Up @@ -42,7 +42,7 @@ test('waits for element until it stops throwing', async () => {

expect(queryByText('Fresh')).toBeNull();

const freshBananaText = await waitForElement(() => getByText('Fresh'));
const freshBananaText = await waitFor(() => getByText('Fresh'));

expect(freshBananaText.props.children).toBe('Fresh');
});
Expand All @@ -52,7 +52,9 @@ test('waits for element until timeout is met', async () => {

fireEvent.press(getByName('TouchableOpacity'));

await expect(waitForElement(() => getByText('Fresh'), 100)).rejects.toThrow();
await expect(
waitFor(() => getByText('Fresh'), { timeout: 100 })
).rejects.toThrow();
});

test('waits for element with custom interval', async () => {
Expand All @@ -61,7 +63,7 @@ test('waits for element with custom interval', async () => {
});

try {
await waitForElement(() => mockFn(), 400, 200);
await waitFor(() => mockFn(), { timeout: 400, interval: 200 });
} catch (e) {
// suppress
}
Expand All @@ -77,7 +79,7 @@ test('works with fake timers', async () => {
});

try {
waitForElement(() => mockFn(), 400, 200);
waitFor(() => mockFn(), { timeout: 400, interval: 200 });
} catch (e) {
// suppress
}
Expand Down
2 changes: 1 addition & 1 deletion src/helpers/a11yAPI.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// @flow
import type { A11yRole, A11yStates, A11yState, A11yValue } from '../types.flow';
import type { WaitForOptions } from './findByAPI';
import type { WaitForOptions } from '../waitFor';
import makeQuery from './makeQuery';

type GetReturn = ReactTestInstance;
Expand Down
15 changes: 3 additions & 12 deletions src/helpers/findByAPI.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// @flow
import waitForElement from '../waitForElement';
import waitFor from '../waitFor';
import type { WaitForOptions } from '../waitFor';
import {
getByTestId,
getAllByTestId,
Expand All @@ -11,22 +12,12 @@ import {
getAllByDisplayValue,
} from './getByAPI';

export type WaitForOptions = {
timeout?: number,
interval?: number,
};

const makeFindQuery = <Text, Result>(
instance: ReactTestInstance,
getQuery: (instance: ReactTestInstance) => (text: Text) => Result,
text: Text,
waitForOptions: WaitForOptions
): Promise<Result> =>
waitForElement(
() => getQuery(instance)(text),
waitForOptions.timeout,
waitForOptions.interval
);
): Promise<Result> => waitFor(() => getQuery(instance)(text), waitForOptions);

export const findByTestId = (instance: ReactTestInstance) => (
testId: string,
Expand Down
16 changes: 4 additions & 12 deletions src/helpers/makeQuery.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
// @flow
import waitForElement from '../waitForElement';
import waitFor from '../waitFor';
import type { WaitForOptions } from '../waitFor';
import {
ErrorWithStack,
prepareErrorMessage,
createQueryByError,
} from './errors';
import type { WaitForOptions } from './findByAPI';

function isNodeValid(node: ReactTestInstance) {
return typeof node.type === 'string';
Expand Down Expand Up @@ -70,19 +70,11 @@ const makeQuery = <P: mixed, M: mixed>(
};

const findBy = (matcher: M, waitForOptions?: WaitForOptions) => {
return waitForElement(
() => getBy(matcher),
waitForOptions?.timeout,
waitForOptions?.interval
);
return waitFor(() => getBy(matcher), waitForOptions);
};

const findAllBy = (matcher: M, waitForOptions?: WaitForOptions) => {
return waitForElement(
() => getAllBy(matcher),
waitForOptions?.timeout,
waitForOptions?.interval
);
return waitFor(() => getAllBy(matcher), waitForOptions);
};

return {
Expand Down
4 changes: 2 additions & 2 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import fireEvent from './fireEvent';
import flushMicrotasksQueue from './flushMicrotasksQueue';
import render from './render';
import shallow from './shallow';
import waitForElement from './waitForElement';
import waitFor from './waitFor';
import within from './within';

export { act };
Expand All @@ -14,5 +14,5 @@ export { fireEvent };
export { flushMicrotasksQueue };
export { render };
export { shallow };
export { waitForElement };
export { waitFor };
export { within };
17 changes: 14 additions & 3 deletions src/waitForElement.js → src/waitFor.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,21 @@
// @flow
export default function waitForElement<T>(

const DEFAULT_TIMEOUT = 4500;
const DEFAULT_INTERVAL = 50;

export type WaitForOptions = {
timeout?: number,
interval?: number,
};

export default function waitFor<T>(
expectation: () => T,
timeout: number = 4500,
interval: number = 50
options?: WaitForOptions
): Promise<T> {
const timeout = options?.timeout ?? DEFAULT_TIMEOUT;
const interval = options?.interval ?? DEFAULT_INTERVAL;
const startTime = Date.now();

return new Promise((resolve, reject) => {
const rejectOrRerun = (error) => {
if (Date.now() - startTime >= timeout) {
Expand Down
13 changes: 7 additions & 6 deletions typings/__tests__/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
fireEvent,
shallow,
flushMicrotasksQueue,
waitForElement,
waitFor,
act,
within,
} from '../..';
Expand Down Expand Up @@ -250,12 +250,13 @@ const shallowTree: { output: React.ReactElement<any> } = shallow(

const waitForFlush: Promise<any> = flushMicrotasksQueue();

const waitBy: Promise<ReactTestInstance> = waitForElement<ReactTestInstance>(
() => tree.getByName('View')
const waitBy: Promise<ReactTestInstance> = waitFor<ReactTestInstance>(() =>
tree.getByName('View')
);
const waitByAll: Promise<ReactTestInstance[]> = waitFor<ReactTestInstance[]>(
() => tree.getAllByName('View'),
{ timeout: 1000, interval: 50 }
);
const waitByAll: Promise<Array<ReactTestInstance>> = waitForElement<
Array<ReactTestInstance>
>(() => tree.getAllByName('View'), 1000, 50);

act(() => {
render(<TestComponent />);
Expand Down
12 changes: 8 additions & 4 deletions typings/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -224,10 +224,14 @@ export type FireEventAPI = FireEventFunction & {
scroll: (element: ReactTestInstance, ...data: Array<any>) => any;
};

export type WaitForElementFunction = <T = any>(
type WaitForOptions = {
timeout?: number;
interval?: number;
};

export type WaitForFunction = <T = any>(
expectation: () => T,
timeout?: number,
interval?: number
options?: WaitForOptions
) => Promise<T>;

export declare const render: (
Expand All @@ -240,6 +244,6 @@ export declare const shallow: <P = {}>(
export declare const flushMicrotasksQueue: () => Promise<any>;
export declare const cleanup: () => void;
export declare const fireEvent: FireEventAPI;
export declare const waitForElement: WaitForElementFunction;
export declare const waitFor: WaitForFunction;
export declare const act: (callback: () => void) => Thenable;
export declare const within: (instance: ReactTestInstance) => Queries;
15 changes: 7 additions & 8 deletions website/docs/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -313,29 +313,28 @@ fireEvent.scroll(getByType(ScrollView), eventData);
expect(onEndReached).toHaveBeenCalled();
```

## `waitForElement`
## `waitFor`

- [`Example code`](https://github.com/callstack/react-native-testing-library/blob/master/src/__tests__/waitForElement.test.js)
- [`Example code`](https://github.com/callstack/react-native-testing-library/blob/master/src/__tests__/waitFor.test.js)

Defined as:

```jsx
function waitForElement<T>(
function waitFor<T>(
expectation: () => T,
timeout: number = 4500,
interval: number = 50
{ timeout: number = 4500, interval: number = 50 }
): Promise<T> {}
```

Waits for non-deterministic periods of time until your element appears or times out. `waitForElement` periodically calls `expectation` every `interval` milliseconds to determine whether the element appeared or not.
Waits for non-deterministic periods of time until your element appears or times out. `waitFor` periodically calls `expectation` every `interval` milliseconds to determine whether the element appeared or not.

```jsx
import { render, waitForElement } from 'react-testing-library';
import { render, waitFor } from 'react-testing-library';

test('waiting for an Banana to be ready', async () => {
const { getByText } = render(<Banana />);

await waitForElement(() => getByText('Banana ready'));
await waitFor(() => getByText('Banana ready'));
});
```

Expand Down
29 changes: 29 additions & 0 deletions website/docs/Migration20.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,35 @@ title: Migration to 2.0

This guides describes major steps involved in migrating your testing code from using React Native Testing Library version `1.x` to version `2.0`.

## WaitFor API changes

`waitForElement` function has been renamed to `waitFor` for consistency with React Testing Library. Additionally the signature has slightly changed from:

```jsx
export default function waitForElement<T>(
expectation: () => T,
timeout?: number,
interval?: number
: Promise<T> {
```

to:

```jsx
export default function waitFor<T>(
expectation: () => T,
{
timeout?: number,
interval?: number
}
): Promise<T> {
```

Both changes should improve code readibility.

:::note
Please note that in many cases `waitFor` call can be replaced by proper use of `findBy` asynchonous queries resulting in more streamlined test code.
:::

## Removed global `debug` function

Expand Down
2 changes: 1 addition & 1 deletion website/docs/Queries.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ title: Queries
`findAllBy` queries return a promise which resolves to an array when any matching elements are found. The promise is rejected if no elements match after a default timeout of 4500ms.

:::info
`findBy` and `findAllBy` queries accept optional `waitForOptions` object argument which can contain `timeout` and `interval` properies which have the same meaning as respective arguments to [`waitForElement`](https://callstack.github.io/react-native-testing-library/docs/api#waitforelement) function.
`findBy` and `findAllBy` queries accept optional `waitForOptions` object argument which can contain `timeout` and `interval` properies which have the same meaning as respective options for [`waitFor`](https://callstack.github.io/react-native-testing-library/docs/api#waitfor) function.
:::

## Queries
Expand Down