-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix: Prevent "missing act" warning for in-flight promises
- Loading branch information
Showing
2 changed files
with
167 additions
and
63 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,73 +1,150 @@ | ||
import * as React from 'react' | ||
import {render, waitForElementToBeRemoved, screen, waitFor} from '../' | ||
|
||
const fetchAMessage = () => | ||
new Promise(resolve => { | ||
// we are using random timeout here to simulate a real-time example | ||
// of an async operation calling a callback at a non-deterministic time | ||
const randomTimeout = Math.floor(Math.random() * 100) | ||
setTimeout(() => { | ||
resolve({returnedMessage: 'Hello World'}) | ||
}, randomTimeout) | ||
}) | ||
|
||
function ComponentWithLoader() { | ||
const [state, setState] = React.useState({data: undefined, loading: true}) | ||
React.useEffect(() => { | ||
let cancelled = false | ||
fetchAMessage().then(data => { | ||
if (!cancelled) { | ||
setState({data, loading: false}) | ||
} | ||
describe.each([ | ||
['real timers', () => jest.useRealTimers()], | ||
['fake legacy timers', () => jest.useFakeTimers('legacy')], | ||
['fake modern timers', () => jest.useFakeTimers('modern')], | ||
])( | ||
'it waits for the data to be loaded in a macrotask using %s', | ||
(label, useTimers) => { | ||
beforeEach(() => { | ||
useTimers() | ||
}) | ||
|
||
return () => { | ||
cancelled = true | ||
afterEach(() => { | ||
jest.useRealTimers() | ||
}) | ||
|
||
const fetchAMessageInAMacrotask = () => | ||
new Promise(resolve => { | ||
// we are using random timeout here to simulate a real-time example | ||
// of an async operation calling a callback at a non-deterministic time | ||
const randomTimeout = Math.floor(Math.random() * 100) | ||
setTimeout(() => { | ||
resolve({returnedMessage: 'Hello World'}) | ||
}, randomTimeout) | ||
}) | ||
|
||
function ComponentWithMacrotaskLoader() { | ||
const [state, setState] = React.useState({data: undefined, loading: true}) | ||
React.useEffect(() => { | ||
let cancelled = false | ||
fetchAMessageInAMacrotask().then(data => { | ||
if (!cancelled) { | ||
setState({data, loading: false}) | ||
} | ||
}) | ||
|
||
return () => { | ||
cancelled = true | ||
} | ||
}, []) | ||
|
||
if (state.loading) { | ||
return <div>Loading...</div> | ||
} | ||
|
||
return ( | ||
<div data-testid="message"> | ||
Loaded this message: {state.data.returnedMessage}! | ||
</div> | ||
) | ||
} | ||
}, []) | ||
|
||
if (state.loading) { | ||
return <div>Loading...</div> | ||
} | ||
test('waitForElementToBeRemoved', async () => { | ||
render(<ComponentWithMacrotaskLoader />) | ||
const loading = () => screen.getByText('Loading...') | ||
await waitForElementToBeRemoved(loading) | ||
expect(screen.getByTestId('message')).toHaveTextContent(/Hello World/) | ||
}) | ||
|
||
test('waitFor', async () => { | ||
render(<ComponentWithMacrotaskLoader />) | ||
// eslint-disable-next-line testing-library/prefer-find-by -- Sir, this is a test. | ||
await waitFor(() => screen.getByText(/Loading../)) | ||
// eslint-disable-next-line testing-library/prefer-find-by -- Sir, this is a test. | ||
await waitFor(() => screen.getByText(/Loaded this message:/)) | ||
expect(screen.getByTestId('message')).toHaveTextContent(/Hello World/) | ||
}) | ||
|
||
return ( | ||
<div data-testid="message"> | ||
Loaded this message: {state.data.returnedMessage}! | ||
</div> | ||
) | ||
} | ||
test('findBy', async () => { | ||
render(<ComponentWithMacrotaskLoader />) | ||
await expect(screen.findByTestId('message')).resolves.toHaveTextContent( | ||
/Hello World/, | ||
) | ||
}) | ||
}, | ||
) | ||
|
||
describe.each([ | ||
['real timers', () => jest.useRealTimers()], | ||
['fake legacy timers', () => jest.useFakeTimers('legacy')], | ||
// ['real timers', () => jest.useRealTimers()], | ||
// ['fake legacy timers', () => jest.useFakeTimers('legacy')], | ||
['fake modern timers', () => jest.useFakeTimers('modern')], | ||
])('it waits for the data to be loaded using %s', (label, useTimers) => { | ||
beforeEach(() => { | ||
useTimers() | ||
}) | ||
|
||
afterEach(() => { | ||
jest.useRealTimers() | ||
}) | ||
|
||
test('waitForElementToBeRemoved', async () => { | ||
render(<ComponentWithLoader />) | ||
const loading = () => screen.getByText('Loading...') | ||
await waitForElementToBeRemoved(loading) | ||
expect(screen.getByTestId('message')).toHaveTextContent(/Hello World/) | ||
}) | ||
|
||
test('waitFor', async () => { | ||
render(<ComponentWithLoader />) | ||
const message = () => screen.getByText(/Loaded this message:/) | ||
await waitFor(message) | ||
expect(screen.getByTestId('message')).toHaveTextContent(/Hello World/) | ||
}) | ||
|
||
test('findBy', async () => { | ||
render(<ComponentWithLoader />) | ||
await expect(screen.findByTestId('message')).resolves.toHaveTextContent( | ||
/Hello World/, | ||
) | ||
}) | ||
}) | ||
])( | ||
'it waits for the data to be loaded in a microtask using %s', | ||
(label, useTimers) => { | ||
beforeEach(() => { | ||
useTimers() | ||
}) | ||
|
||
afterEach(() => { | ||
jest.useRealTimers() | ||
}) | ||
|
||
const fetchAMessageInAMicrotask = () => | ||
Promise.resolve({ | ||
status: 200, | ||
json: () => Promise.resolve({title: 'Hello World'}), | ||
}) | ||
|
||
function ComponentWithMicrotaskLoader() { | ||
const [fetchState, setFetchState] = React.useState({fetching: true}) | ||
|
||
React.useEffect(() => { | ||
if (fetchState.fetching) { | ||
fetchAMessageInAMicrotask().then(res => { | ||
return res.json().then(data => { | ||
setFetchState({todo: data.title, fetching: false}) | ||
}) | ||
}) | ||
} | ||
}, [fetchState]) | ||
|
||
if (fetchState.fetching) { | ||
return <p>Loading..</p> | ||
} | ||
|
||
return ( | ||
<div data-testid="message">Loaded this message: {fetchState.todo}</div> | ||
) | ||
} | ||
|
||
test('waitForElementToBeRemoved', async () => { | ||
render(<ComponentWithMicrotaskLoader />) | ||
const loading = () => screen.getByText('Loading..') | ||
await waitForElementToBeRemoved(loading) | ||
expect(screen.getByTestId('message')).toHaveTextContent(/Hello World/) | ||
}) | ||
|
||
test('waitFor', async () => { | ||
render(<ComponentWithMicrotaskLoader />) | ||
await waitFor(() => { | ||
// eslint-disable-next-line testing-library/prefer-explicit-assert -- Sir, this is a test. | ||
screen.getByText('Loading..') | ||
}) | ||
await waitFor(() => { | ||
// eslint-disable-next-line testing-library/prefer-explicit-assert -- Sir, this is a test. | ||
screen.getByText(/Loaded this message:/) | ||
}) | ||
expect(screen.getByTestId('message')).toHaveTextContent(/Hello World/) | ||
}) | ||
|
||
test('findBy', async () => { | ||
render(<ComponentWithMicrotaskLoader />) | ||
await expect(screen.findByTestId('message')).resolves.toHaveTextContent( | ||
/Hello World/, | ||
) | ||
}) | ||
}, | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters