- Loaded this message: {this.state.data.returnedMessage}!
-
- )
+ }, [])
+
+ if (state.loading) {
+ return
Loading...
}
+
+ return (
+
+ Loaded this message: {state.data.returnedMessage}!
+
+ )
}
-test('it waits for the data to be loaded', async () => {
- render()
- const loading = () => screen.getByText('Loading...')
- await waitForElementToBeRemoved(loading)
- expect(screen.getByTestId('message')).toHaveTextContent(/Hello World/)
+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 using %s', (label, useTimers) => {
+ beforeEach(() => {
+ useTimers()
+ })
+
+ afterEach(() => {
+ jest.useRealTimers()
+ })
+
+ test('waitForElementToBeRemoved', async () => {
+ render()
+ const loading = () => screen.getByText('Loading...')
+ await waitForElementToBeRemoved(loading)
+ expect(screen.getByTestId('message')).toHaveTextContent(/Hello World/)
+ })
+
+ test('waitFor', async () => {
+ render()
+ const message = () => screen.getByText(/Loaded this message:/)
+ await waitFor(message)
+ expect(screen.getByTestId('message')).toHaveTextContent(/Hello World/)
+ })
+
+ test('findBy', async () => {
+ render()
+ await expect(screen.findByTestId('message')).resolves.toHaveTextContent(
+ /Hello World/,
+ )
+ })
})
diff --git a/src/__tests__/new-act.js b/src/__tests__/new-act.js
index 56ce4970..af81e29c 100644
--- a/src/__tests__/new-act.js
+++ b/src/__tests__/new-act.js
@@ -1,4 +1,4 @@
-let asyncAct
+let asyncAct, consoleErrorMock
jest.mock('react-dom/test-utils', () => ({
act: cb => {
@@ -9,11 +9,11 @@ jest.mock('react-dom/test-utils', () => ({
beforeEach(() => {
jest.resetModules()
asyncAct = require('../act-compat').asyncAct
- jest.spyOn(console, 'error').mockImplementation(() => {})
+ consoleErrorMock = jest.spyOn(console, 'error').mockImplementation(() => {})
})
afterEach(() => {
- console.error.mockRestore()
+ consoleErrorMock.mockRestore()
})
test('async act works when it does not exist (older versions of react)', async () => {
@@ -49,7 +49,7 @@ test('async act recovers from errors', async () => {
expect(console.error.mock.calls).toMatchInlineSnapshot(`
Array [
Array [
- "call console.error",
+ call console.error,
],
]
`)
@@ -67,7 +67,7 @@ test('async act recovers from sync errors', async () => {
expect(console.error.mock.calls).toMatchInlineSnapshot(`
Array [
Array [
- "call console.error",
+ call console.error,
],
]
`)
diff --git a/src/__tests__/no-act.js b/src/__tests__/no-act.js
index 039a79ae..d739e763 100644
--- a/src/__tests__/no-act.js
+++ b/src/__tests__/no-act.js
@@ -1,14 +1,15 @@
-let act, asyncAct
+let act, asyncAct, React, consoleErrorMock
beforeEach(() => {
jest.resetModules()
- act = require('..').act
+ act = require('../pure').act
asyncAct = require('../act-compat').asyncAct
- jest.spyOn(console, 'error').mockImplementation(() => {})
+ React = require('react')
+ consoleErrorMock = jest.spyOn(console, 'error').mockImplementation(() => {})
})
afterEach(() => {
- console.error.mockRestore()
+ consoleErrorMock.mockRestore()
})
jest.mock('react-dom/test-utils', () => ({}))
@@ -17,7 +18,10 @@ test('act works even when there is no act from test utils', () => {
const callback = jest.fn()
act(callback)
expect(callback).toHaveBeenCalledTimes(1)
- expect(console.error).toHaveBeenCalledTimes(0)
+ expect(console.error).toHaveBeenCalledTimes(
+ // ReactDOM.render is deprecated in React 18
+ React.version.startsWith('18') ? 1 : 0,
+ )
})
test('async act works when it does not exist (older versions of react)', async () => {
@@ -26,7 +30,10 @@ test('async act works when it does not exist (older versions of react)', async (
await Promise.resolve()
await callback()
})
- expect(console.error).toHaveBeenCalledTimes(0)
+ expect(console.error).toHaveBeenCalledTimes(
+ // ReactDOM.render is deprecated in React 18
+ React.version.startsWith('18') ? 2 : 0,
+ )
expect(callback).toHaveBeenCalledTimes(1)
callback.mockClear()
@@ -36,7 +43,10 @@ test('async act works when it does not exist (older versions of react)', async (
await Promise.resolve()
await callback()
})
- expect(console.error).toHaveBeenCalledTimes(0)
+ expect(console.error).toHaveBeenCalledTimes(
+ // ReactDOM.render is deprecated in React 18
+ React.version.startsWith('18') ? 2 : 0,
+ )
expect(callback).toHaveBeenCalledTimes(1)
})
@@ -49,14 +59,16 @@ test('async act recovers from errors', async () => {
} catch (err) {
console.error('call console.error')
}
- expect(console.error).toHaveBeenCalledTimes(1)
- expect(console.error.mock.calls).toMatchInlineSnapshot(`
- Array [
- Array [
- "call console.error",
- ],
- ]
- `)
+ expect(console.error).toHaveBeenCalledTimes(
+ // ReactDOM.render is deprecated in React 18
+ React.version.startsWith('18') ? 2 : 1,
+ )
+ expect(
+ console.error.mock.calls[
+ // ReactDOM.render is deprecated in React 18
+ React.version.startsWith('18') ? 1 : 0
+ ][0],
+ ).toMatch('call console.error')
})
test('async act recovers from sync errors', async () => {
@@ -71,7 +83,7 @@ test('async act recovers from sync errors', async () => {
expect(console.error.mock.calls).toMatchInlineSnapshot(`
Array [
Array [
- "call console.error",
+ call console.error,
],
]
`)
diff --git a/src/__tests__/old-act.js b/src/__tests__/old-act.js
index b3de9377..6081fef8 100644
--- a/src/__tests__/old-act.js
+++ b/src/__tests__/old-act.js
@@ -1,13 +1,13 @@
-let asyncAct
+let asyncAct, consoleErrorMock
beforeEach(() => {
jest.resetModules()
asyncAct = require('../act-compat').asyncAct
- jest.spyOn(console, 'error').mockImplementation(() => {})
+ consoleErrorMock = jest.spyOn(console, 'error').mockImplementation(() => {})
})
afterEach(() => {
- console.error.mockRestore()
+ consoleErrorMock.mockRestore()
})
jest.mock('react-dom/test-utils', () => ({
@@ -32,18 +32,18 @@ test('async act works even when the act is an old one', async () => {
console.error('sigil')
})
expect(console.error.mock.calls).toMatchInlineSnapshot(`
- Array [
- Array [
- "sigil",
- ],
- Array [
- "It looks like you're using a version of react-dom that supports the \\"act\\" function, but not an awaitable version of \\"act\\" which you will need. Please upgrade to at least react-dom@16.9.0 to remove this warning.",
- ],
- Array [
- "sigil",
- ],
- ]
- `)
+ Array [
+ Array [
+ sigil,
+ ],
+ Array [
+ It looks like you're using a version of react-dom that supports the "act" function, but not an awaitable version of "act" which you will need. Please upgrade to at least react-dom@16.9.0 to remove this warning.,
+ ],
+ Array [
+ sigil,
+ ],
+ ]
+ `)
expect(callback).toHaveBeenCalledTimes(1)
// and it doesn't warn you twice
@@ -71,10 +71,10 @@ test('async act recovers from async errors', async () => {
expect(console.error.mock.calls).toMatchInlineSnapshot(`
Array [
Array [
- "It looks like you're using a version of react-dom that supports the \\"act\\" function, but not an awaitable version of \\"act\\" which you will need. Please upgrade to at least react-dom@16.9.0 to remove this warning.",
+ It looks like you're using a version of react-dom that supports the "act" function, but not an awaitable version of "act" which you will need. Please upgrade to at least react-dom@16.9.0 to remove this warning.,
],
Array [
- "call console.error",
+ call console.error,
],
]
`)
@@ -92,7 +92,7 @@ test('async act recovers from sync errors', async () => {
expect(console.error.mock.calls).toMatchInlineSnapshot(`
Array [
Array [
- "call console.error",
+ call console.error,
],
]
`)
@@ -109,11 +109,11 @@ test('async act can handle any sort of console.error', async () => {
Array [
Array [
Object {
- "error": "some error",
+ error: some error,
},
],
Array [
- "It looks like you're using a version of react-dom that supports the \\"act\\" function, but not an awaitable version of \\"act\\" which you will need. Please upgrade to at least react-dom@16.9.0 to remove this warning.",
+ It looks like you're using a version of react-dom that supports the "act" function, but not an awaitable version of "act" which you will need. Please upgrade to at least react-dom@16.9.0 to remove this warning.,
],
]
`)
diff --git a/src/__tests__/render.js b/src/__tests__/render.js
index fdc1ff4c..fea1a649 100644
--- a/src/__tests__/render.js
+++ b/src/__tests__/render.js
@@ -78,14 +78,14 @@ test('renders options.wrapper around node', () => {
expect(screen.getByTestId('wrapper')).toBeInTheDocument()
expect(container.firstChild).toMatchInlineSnapshot(`
-
-
-
-`)
+
+
+
+ `)
})
test('flushes useEffect cleanup functions sync on unmount()', () => {
diff --git a/src/__tests__/stopwatch.js b/src/__tests__/stopwatch.js
index eeaf395c..400fce10 100644
--- a/src/__tests__/stopwatch.js
+++ b/src/__tests__/stopwatch.js
@@ -53,5 +53,8 @@ test('unmounts a component', async () => {
// and get an error.
await sleep(5)
// eslint-disable-next-line no-console
- expect(console.error).not.toHaveBeenCalled()
+ expect(console.error).toHaveBeenCalledTimes(
+ // ReactDOM.render is deprecated in React 18
+ React.version.startsWith('18') ? 1 : 0,
+ )
})
diff --git a/src/index.js b/src/index.js
index c89dc1a2..96fbe155 100644
--- a/src/index.js
+++ b/src/index.js
@@ -5,7 +5,7 @@ import {cleanup} from './pure'
// this ensures that tests run in isolation from each other
// if you don't like this then either import the `pure` module
// or set the RTL_SKIP_AUTO_CLEANUP env variable to 'true'.
-if (typeof process === "undefined" || !process.env?.RTL_SKIP_AUTO_CLEANUP) {
+if (typeof process === 'undefined' || !process.env?.RTL_SKIP_AUTO_CLEANUP) {
// ignore teardown() in code coverage because Jest does not support it
/* istanbul ignore else */
if (typeof afterEach === 'function') {
diff --git a/tests/setup-env.js b/tests/setup-env.js
index 264828a9..6c0b953b 100644
--- a/tests/setup-env.js
+++ b/tests/setup-env.js
@@ -1 +1,20 @@
import '@testing-library/jest-dom/extend-expect'
+
+let consoleErrorMock
+
+beforeEach(() => {
+ const originalConsoleError = console.error
+ consoleErrorMock = jest
+ .spyOn(console, 'error')
+ .mockImplementation((message, ...optionalParams) => {
+ // Ignore ReactDOM.render/ReactDOM.hydrate deprecation warning
+ if (message.indexOf('Use createRoot instead.') !== -1) {
+ return
+ }
+ originalConsoleError(message, ...optionalParams)
+ })
+})
+
+afterEach(() => {
+ consoleErrorMock.mockRestore()
+})
diff --git a/types/index.d.ts b/types/index.d.ts
index 6d0c67ee..604b3966 100644
--- a/types/index.d.ts
+++ b/types/index.d.ts
@@ -13,10 +13,11 @@ export * from '@testing-library/dom'
export type RenderResult<
Q extends Queries = typeof queries,
- Container extends Element | DocumentFragment = HTMLElement
+ Container extends Element | DocumentFragment = HTMLElement,
+ BaseElement extends Element | DocumentFragment = Container,
> = {
container: Container
- baseElement: Element
+ baseElement: BaseElement
debug: (
baseElement?:
| Element
@@ -32,13 +33,46 @@ export type RenderResult<
export interface RenderOptions<
Q extends Queries = typeof queries,
- Container extends Element | DocumentFragment = HTMLElement
+ Container extends Element | DocumentFragment = HTMLElement,
+ BaseElement extends Element | DocumentFragment = Container,
> {
+ /**
+ * By default, React Testing Library will create a div and append that div to the document.body. Your React component will be rendered in the created div. If you provide your own HTMLElement container via this option,
+ * it will not be appended to the document.body automatically.
+ *
+ * For example: If you are unit testing a `` element, it cannot be a child of a div. In this case, you can
+ * specify a table as the render container.
+ *
+ * @see https://testing-library.com/docs/react-testing-library/api/#container
+ */
container?: Container
- baseElement?: Element
+ /**
+ * Defaults to the container if the container is specified. Otherwise `document.body` is used for the default. This is used as
+ * the base element for the queries as well as what is printed when you use `debug()`.
+ *
+ * @see https://testing-library.com/docs/react-testing-library/api/#baseelement
+ */
+ baseElement?: BaseElement
+ /**
+ * If `hydrate` is set to `true`, then it will render with `ReactDOM.hydrate`. This may be useful if you are using server-side
+ * rendering and use ReactDOM.hydrate to mount your components.
+ *
+ * @see https://testing-library.com/docs/react-testing-library/api/#hydrate)
+ */
hydrate?: boolean
+ /**
+ * Queries to bind. Overrides the default set from DOM Testing Library unless merged.
+ *
+ * @see https://testing-library.com/docs/react-testing-library/api/#queries
+ */
queries?: Q
- wrapper?: React.ComponentType
+ /**
+ * Pass a React Component as the wrapper option to have it rendered around the inner element. This is most useful for creating
+ * reusable custom render functions for common data providers. See setup for examples.
+ *
+ * @see https://testing-library.com/docs/react-testing-library/api/#wrapper
+ */
+ wrapper?: React.JSXElementConstructor<{children: React.ReactElement}>
}
type Omit = Pick>
@@ -48,11 +82,12 @@ type Omit = Pick>
*/
export function render<
Q extends Queries = typeof queries,
- Container extends Element | DocumentFragment = HTMLElement
+ Container extends Element | DocumentFragment = HTMLElement,
+ BaseElement extends Element | DocumentFragment = Container,
>(
ui: React.ReactElement,
- options: RenderOptions,
-): RenderResult
+ options: RenderOptions,
+): RenderResult
export function render(
ui: React.ReactElement,
options?: Omit,
diff --git a/types/test.tsx b/types/test.tsx
index 5f9575ac..eae6e81f 100644
--- a/types/test.tsx
+++ b/types/test.tsx
@@ -3,39 +3,39 @@ import {render, fireEvent, screen, waitFor} from '.'
import * as pure from './pure'
export async function testRender() {
- const page = render()
+ const view = render()
// single queries
- page.getByText('foo')
- page.queryByText('foo')
- await page.findByText('foo')
+ view.getByText('foo')
+ view.queryByText('foo')
+ await view.findByText('foo')
// multiple queries
- page.getAllByText('bar')
- page.queryAllByText('bar')
- await page.findAllByText('bar')
+ view.getAllByText('bar')
+ view.queryAllByText('bar')
+ await view.findAllByText('bar')
// helpers
- const {container, rerender, debug} = page
+ const {container, rerender, debug} = view
expectType(container)
return {container, rerender, debug}
}
export async function testPureRender() {
- const page = pure.render()
+ const view = pure.render()
// single queries
- page.getByText('foo')
- page.queryByText('foo')
- await page.findByText('foo')
+ view.getByText('foo')
+ view.queryByText('foo')
+ await view.findByText('foo')
// multiple queries
- page.getAllByText('bar')
- page.queryAllByText('bar')
- await page.findAllByText('bar')
+ view.getAllByText('bar')
+ view.queryAllByText('bar')
+ await view.findAllByText('bar')
// helpers
- const {container, rerender, debug} = page
+ const {container, rerender, debug} = view
expectType(container)
return {container, rerender, debug}
}
@@ -100,6 +100,66 @@ export function testQueries() {
)
}
+export function wrappedRender(
+ ui: React.ReactElement,
+ options?: pure.RenderOptions,
+) {
+ const Wrapper = ({children}: {children: React.ReactElement}): JSX.Element => {
+ return
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.