Skip to content

feat: wrap waitFor in (async) act to support async queries without explicit act call #344

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 12 commits into from
May 21, 2020
18 changes: 17 additions & 1 deletion src/waitFor.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// @flow

import act from './act';
import { throwRemovedFunctionError } from './helpers/errors';

const DEFAULT_TIMEOUT = 4500;
Expand All @@ -10,7 +11,7 @@ export type WaitForOptions = {
interval?: number,
};

export default function waitFor<T>(
function waitForInternal<T>(
expectation: () => T,
options?: WaitForOptions
): Promise<T> {
Expand Down Expand Up @@ -38,6 +39,21 @@ export default function waitFor<T>(
});
}

export default async function waitFor<T>(
expectation: () => T,
options?: WaitForOptions
): Promise<T> {
let result: T;

//$FlowFixMe: this is just too complicated for flow
await act(async () => {
result = await waitForInternal(expectation, options);
});

//$FlowFixMe: either we have result or `waitFor` there error
return result;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about:

Suggested change
let result: T;
//$FlowFixMe: this is just too complicated for flow
await act(async () => {
result = await waitForInternal(expectation, options);
});
//$FlowFixMe: either we have result or `waitFor` there error
return result;
return act(() => waitForInternal(expectation, options));

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately does not work, as act is not forwarding the return value from the callback.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to capture it to intermediate variable so that it can be returned by the waitFor async wrapper

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I see it now. The error suppressed by "//$FlowFixMe: this is just too complicated for flow" comes from lacking lib defs of react-test-renderer, not Flow being dumb. Worth updating the comment

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated comment

}

export function waitForElement<T>(
expectation: () => T,
_timeout: number = 4500,
Expand Down
6 changes: 5 additions & 1 deletion website/docs/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,10 @@ function waitFor<T>(

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.

:::info
In order to properly use `waitFor` you need at least React >=16.9.0 (featuing async `act`) or React Native >=0.60 (which comes with React >=16.9.0).
:::

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

Expand Down Expand Up @@ -403,4 +407,4 @@ expect(submitButtons).toHaveLength(3); // expect 3 elements

## `act`

Useful function to help testing components that use hooks API. By default any `render`, `update`, and `fireEvent` calls are wrapped by this function, so there is no need to wrap it manually. This method is re-exported from [`react-test-renderer`](https://github.com/facebook/react/blob/master/packages/react-test-renderer/src/ReactTestRenderer.js#L567]).
Useful function to help testing components that use hooks API. By default any `render`, `update`, `fireEvent`, and `waitFor` calls are wrapped by this function, so there is no need to wrap it manually. This method is re-exported from [`react-test-renderer`](https://github.com/facebook/react/blob/master/packages/react-test-renderer/src/ReactTestRenderer.js#L567]).
4 changes: 4 additions & 0 deletions website/docs/GettingStarted.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ This library has a peerDependencies listing for `react-test-renderer` and, of co

As you may have noticed, it's not tied to React Native at all – you can safely use it in your React components if you feel like not interacting directly with DOM.

:::info
In order to properly use helpers for async tests (`findBy` queries and `waitFor`) you need at least React >=16.9.0 (featuing async `act`) or React Native >=0.60 (which comes with React >=16.9.0).
:::

### Additional Jest matchers

In order to use addtional React Native-specific jest matchers from [@testing-library/jest-native](https://github.com/testing-library/jest-native) package add it to your project:
Expand Down
4 changes: 4 additions & 0 deletions website/docs/Queries.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ 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
In order to properly use `findBy` and `findAllBy` queries you need at least React >=16.9.0 (featuing async `act`) or React Native >=0.60 (which comes with React >=16.9.0).
:::

:::info
`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.
:::
Expand Down