diff --git a/src/__tests__/render.js b/src/__tests__/render.js index f00410b4..6f5b5b39 100644 --- a/src/__tests__/render.js +++ b/src/__tests__/render.js @@ -262,4 +262,38 @@ describe('render API', () => { `\`legacyRoot: true\` is not supported in this version of React. If your app runs React 19 or later, you should remove this flag. If your app runs React 18 or earlier, visit https://react.dev/blog/2022/03/08/react-18-upgrade-guide for upgrade instructions.`, ) }) + + test('reactStrictMode in renderOptions has precedence over config when rendering', () => { + const wrapperComponentMountEffect = jest.fn() + function WrapperComponent({children}) { + React.useEffect(() => { + wrapperComponentMountEffect() + }) + + return children + } + const ui =
+ configure({reactStrictMode: false}) + + render(ui, {wrapper: WrapperComponent, reactStrictMode: true}) + + expect(wrapperComponentMountEffect).toHaveBeenCalledTimes(2) + }) + + test('reactStrictMode in config is used when renderOptions does not specify reactStrictMode', () => { + const wrapperComponentMountEffect = jest.fn() + function WrapperComponent({children}) { + React.useEffect(() => { + wrapperComponentMountEffect() + }) + + return children + } + const ui =
+ configure({reactStrictMode: true}) + + render(ui, {wrapper: WrapperComponent}) + + expect(wrapperComponentMountEffect).toHaveBeenCalledTimes(2) + }) }) diff --git a/src/__tests__/renderHook.js b/src/__tests__/renderHook.js index fe7551a2..f331e90e 100644 --- a/src/__tests__/renderHook.js +++ b/src/__tests__/renderHook.js @@ -1,5 +1,5 @@ -import React from 'react' -import {renderHook} from '../pure' +import React, {useEffect} from 'react' +import {configure, renderHook} from '../pure' const isReact18 = React.version.startsWith('18.') const isReact19 = React.version.startsWith('19.') @@ -111,3 +111,31 @@ testGateReact19('legacyRoot throws', () => { `\`legacyRoot: true\` is not supported in this version of React. If your app runs React 19 or later, you should remove this flag. If your app runs React 18 or earlier, visit https://react.dev/blog/2022/03/08/react-18-upgrade-guide for upgrade instructions.`, ) }) + +describe('reactStrictMode', () => { + let originalConfig + beforeEach(() => { + // Grab the existing configuration so we can restore + // it at the end of the test + configure(existingConfig => { + originalConfig = existingConfig + // Don't change the existing config + return {} + }) + }) + + afterEach(() => { + configure(originalConfig) + }) + + test('reactStrictMode in renderOptions has precedence over config when rendering', () => { + const hookMountEffect = jest.fn() + configure({reactStrictMode: false}) + + renderHook(() => useEffect(() => hookMountEffect()), { + reactStrictMode: true, + }) + + expect(hookMountEffect).toHaveBeenCalledTimes(2) + }) +}) diff --git a/src/pure.js b/src/pure.js index fe95024a..0f9c487d 100644 --- a/src/pure.js +++ b/src/pure.js @@ -77,8 +77,8 @@ const mountedContainers = new Set() */ const mountedRootEntries = [] -function strictModeIfNeeded(innerElement) { - return getConfig().reactStrictMode +function strictModeIfNeeded(innerElement, reactStrictMode) { + return reactStrictMode ?? getConfig().reactStrictMode ? React.createElement(React.StrictMode, null, innerElement) : innerElement } @@ -91,14 +91,24 @@ function wrapUiIfNeeded(innerElement, wrapperComponent) { function createConcurrentRoot( container, - {hydrate, onCaughtError, onRecoverableError, ui, wrapper: WrapperComponent}, + { + hydrate, + onCaughtError, + onRecoverableError, + ui, + wrapper: WrapperComponent, + reactStrictMode, + }, ) { let root if (hydrate) { act(() => { root = ReactDOMClient.hydrateRoot( container, - strictModeIfNeeded(wrapUiIfNeeded(ui, WrapperComponent)), + strictModeIfNeeded( + wrapUiIfNeeded(ui, WrapperComponent), + reactStrictMode, + ), {onCaughtError, onRecoverableError}, ) }) @@ -144,17 +154,31 @@ function createLegacyRoot(container) { function renderRoot( ui, - {baseElement, container, hydrate, queries, root, wrapper: WrapperComponent}, + { + baseElement, + container, + hydrate, + queries, + root, + wrapper: WrapperComponent, + reactStrictMode, + }, ) { act(() => { if (hydrate) { root.hydrate( - strictModeIfNeeded(wrapUiIfNeeded(ui, WrapperComponent)), + strictModeIfNeeded( + wrapUiIfNeeded(ui, WrapperComponent), + reactStrictMode, + ), container, ) } else { root.render( - strictModeIfNeeded(wrapUiIfNeeded(ui, WrapperComponent)), + strictModeIfNeeded( + wrapUiIfNeeded(ui, WrapperComponent), + reactStrictMode, + ), container, ) } @@ -180,6 +204,7 @@ function renderRoot( baseElement, root, wrapper: WrapperComponent, + reactStrictMode, }) // Intentionally do not return anything to avoid unnecessarily complicating the API. // folks can use all the same utilities we return in the first place that are bound to the container @@ -212,6 +237,7 @@ function render( queries, hydrate = false, wrapper, + reactStrictMode, } = {}, ) { if (onUncaughtError !== undefined) { @@ -248,6 +274,7 @@ function render( onRecoverableError, ui, wrapper, + reactStrictMode, }) mountedRootEntries.push({container, root}) @@ -273,6 +300,7 @@ function render( hydrate, wrapper, root, + reactStrictMode, }) } diff --git a/types/index.d.ts b/types/index.d.ts index 2f814a6d..bdd60567 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -156,6 +156,11 @@ export interface RenderOptions< * @see https://testing-library.com/docs/react-testing-library/api/#wrapper */ wrapper?: React.JSXElementConstructor<{children: React.ReactNode}> | undefined + /** + * When enabled, is rendered around the inner element. + * If defined, overrides the value of `reactStrictMode` set in `configure`. + */ + reactStrictMode?: boolean } type Omit = Pick>