diff --git a/src/__tests__/waitFor.test.tsx b/src/__tests__/waitFor.test.tsx
index 15daf8ac7..6e73fb3a5 100644
--- a/src/__tests__/waitFor.test.tsx
+++ b/src/__tests__/waitFor.test.tsx
@@ -263,3 +263,56 @@ test.each([false, true])(
expect(mockFn).toHaveBeenCalledTimes(3);
}
);
+
+test.each([
+ [false, false],
+ [true, false],
+ [true, true],
+])(
+ 'flushes scheduled updates before returning (fakeTimers = %s, legacyFakeTimers = %s)',
+ async (fakeTimers, legacyFakeTimers) => {
+ if (fakeTimers) {
+ jest.useFakeTimers({ legacyFakeTimers });
+ }
+
+ function Apple({ onPress }: { onPress: (color: string) => void }) {
+ const [color, setColor] = React.useState('green');
+ const [syncedColor, setSyncedColor] = React.useState(color);
+
+ // On mount, set the color to "red" in a promise microtask
+ React.useEffect(() => {
+ // eslint-disable-next-line promise/prefer-await-to-then, promise/catch-or-return
+ Promise.resolve('red').then((c) => setColor(c));
+ }, []);
+
+ // Sync the `color` state to `syncedColor` state, but with a delay caused by the effect
+ React.useEffect(() => {
+ setSyncedColor(color);
+ }, [color]);
+
+ return (
+
+ {color}
+ onPress(syncedColor)}>
+ Trigger
+
+
+ );
+ }
+
+ const onPress = jest.fn();
+ const view = render();
+
+ // Required: this `waitFor` will succeed on first check, because the "root" view is there
+ // since the initial mount.
+ await waitFor(() => view.getByTestId('root'));
+
+ // This `waitFor` will also succeed on first check, because the promise that sets the
+ // `color` state to "red" resolves right after the previous `await waitFor` statement.
+ await waitFor(() => view.getByText('red'));
+
+ // Check that the `onPress` callback is called with the already-updated value of `syncedColor`.
+ fireEvent.press(view.getByText('Trigger'));
+ expect(onPress).toHaveBeenCalledWith('red');
+ }
+);
diff --git a/src/flushMicroTasks.ts b/src/flushMicroTasks.ts
index 011f01d6e..7b964a9d6 100644
--- a/src/flushMicroTasks.ts
+++ b/src/flushMicroTasks.ts
@@ -2,7 +2,7 @@ import { setImmediate } from './helpers/timers';
type Thenable = { then: (callback: () => T) => unknown };
-export function flushMicroTasks(): Thenable {
+export function flushMicroTasks(): Thenable {
return {
// using "thenable" instead of a Promise, because otherwise it breaks when
// using "modern" fake timers
diff --git a/src/waitFor.ts b/src/waitFor.ts
index 0077ee45a..58feb224a 100644
--- a/src/waitFor.ts
+++ b/src/waitFor.ts
@@ -1,6 +1,7 @@
/* globals jest */
import act, { setReactActEnvironment, getIsReactActEnvironment } from './act';
import { getConfig } from './config';
+import { flushMicroTasks } from './flushMicroTasks';
import { ErrorWithStack, copyStackTrace } from './helpers/errors';
import {
setTimeout,
@@ -196,7 +197,10 @@ export default async function waitFor(
setReactActEnvironment(false);
try {
- return await waitForInternal(expectation, optionsWithStackTrace);
+ const result = await waitForInternal(expectation, optionsWithStackTrace);
+ // Flush the microtask queue before restoring the `act` environment
+ await flushMicroTasks();
+ return result;
} finally {
setReactActEnvironment(previousActEnvironment);
}