Skip to content

Commit 2982038

Browse files
committed
feat: Add support for React 19 Canary
1 parent 4e10ba3 commit 2982038

File tree

9 files changed

+97
-46
lines changed

9 files changed

+97
-46
lines changed

.github/workflows/validate.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ jobs:
3030
fail-fast: false
3131
matrix:
3232
node: [14, 16, 18]
33-
react: [latest, canary, experimental]
33+
react: ['18.x', latest, canary, experimental]
3434
runs-on: ubuntu-latest
3535
steps:
3636
- name: ⬇️ Checkout repo

jest.config.js

+9-6
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,17 @@ const {jest: jestConfig} = require('kcd-scripts/config')
33
module.exports = Object.assign(jestConfig, {
44
coverageThreshold: {
55
...jestConfig.coverageThreshold,
6-
// Full coverage across the build matrix (React versions) but not in a single job
6+
// Full coverage across the build matrix (React 18, 19) but not in a single job
77
// Ful coverage is checked via codecov
8-
'./src/pure.js': {
9-
// minimum coverage of jobs using different React versions
10-
branches: 97,
8+
'./src/act-compat': {
9+
branches: 90,
10+
},
11+
'./src/pure': {
12+
// minimum coverage of jobs using React 18 and 19
13+
branches: 95,
1114
functions: 88,
12-
lines: 94,
13-
statements: 94,
15+
lines: 92,
16+
statements: 92,
1417
},
1518
},
1619
})

src/__tests__/new-act.js

+8-5
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
let asyncAct
22

3-
jest.mock('react-dom/test-utils', () => ({
4-
act: cb => {
5-
return cb()
6-
},
7-
}))
3+
jest.mock('react', () => {
4+
return {
5+
...jest.requireActual('react'),
6+
act: cb => {
7+
return cb()
8+
},
9+
}
10+
})
811

912
beforeEach(() => {
1013
jest.resetModules()

src/__tests__/render.js

+29-19
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,11 @@ import ReactDOM from 'react-dom'
33
import ReactDOMServer from 'react-dom/server'
44
import {fireEvent, render, screen, configure} from '../'
55

6-
// Needs to be changed to 19.0.0 once alpha started.
7-
const isReactExperimental = React.version.startsWith('18.3.0-experimental')
8-
const isReactCanary = React.version.startsWith('18.3.0-canary')
6+
const isReact18 = React.version.startsWith('18.')
7+
const isReact19 = React.version.startsWith('19.')
98

10-
// Needs to be changed to isReactExperimental || isReactCanary once alpha started.
11-
const testGateReact18 = isReactExperimental ? test.skip : test
9+
const testGateReact18 = isReact18 ? test : test.skip
10+
const testGateReact19 = isReact19 ? test : test.skip
1211

1312
describe('render API', () => {
1413
let originalConfig
@@ -224,32 +223,43 @@ describe('render API', () => {
224223
expect(() => {
225224
render(<div />, {legacyRoot: true})
226225
}).toErrorDev(
227-
isReactCanary
228-
? [
229-
"Warning: ReactDOM.render is no longer supported in React 18. Use createRoot instead. Until you switch to the new API, your app will behave as if it's running React 17. Learn more: https://react.dev/link/switch-to-createroot",
230-
]
231-
: [
232-
"Warning: ReactDOM.render is no longer supported in React 18. Use createRoot instead. Until you switch to the new API, your app will behave as if it's running React 17. Learn more: https://reactjs.org/link/switch-to-createroot",
233-
],
226+
[
227+
"Warning: ReactDOM.render is no longer supported in React 18. Use createRoot instead. Until you switch to the new API, your app will behave as if it's running React 17. Learn more: https://reactjs.org/link/switch-to-createroot",
228+
],
234229
{withoutStack: true},
235230
)
236231
})
237232

233+
testGateReact19('legacyRoot throws', () => {
234+
expect(() => {
235+
render(<div />, {legacyRoot: true})
236+
}).toThrowErrorMatchingInlineSnapshot(
237+
`\`legacyRoot: true\` is not supported in this version of React. Please use React 18 instead.`,
238+
)
239+
})
240+
238241
testGateReact18('legacyRoot uses legacy ReactDOM.hydrate', () => {
239242
const ui = <div />
240243
const container = document.createElement('div')
241244
container.innerHTML = ReactDOMServer.renderToString(ui)
242245
expect(() => {
243246
render(ui, {container, hydrate: true, legacyRoot: true})
244247
}).toErrorDev(
245-
isReactCanary
246-
? [
247-
"Warning: ReactDOM.hydrate is no longer supported in React 18. Use hydrateRoot instead. Until you switch to the new API, your app will behave as if it's running React 17. Learn more: https://react.dev/link/switch-to-createroot",
248-
]
249-
: [
250-
"Warning: ReactDOM.hydrate is no longer supported in React 18. Use hydrateRoot instead. Until you switch to the new API, your app will behave as if it's running React 17. Learn more: https://reactjs.org/link/switch-to-createroot",
251-
],
248+
[
249+
"Warning: ReactDOM.hydrate is no longer supported in React 18. Use hydrateRoot instead. Until you switch to the new API, your app will behave as if it's running React 17. Learn more: https://reactjs.org/link/switch-to-createroot",
250+
],
252251
{withoutStack: true},
253252
)
254253
})
254+
255+
testGateReact19('legacyRoot throws even with hydrate', () => {
256+
const ui = <div />
257+
const container = document.createElement('div')
258+
container.innerHTML = ReactDOMServer.renderToString(ui)
259+
expect(() => {
260+
render(ui, {container, hydrate: true, legacyRoot: true})
261+
}).toThrowErrorMatchingInlineSnapshot(
262+
`\`legacyRoot: true\` is not supported in this version of React. Please use React 18 instead.`,
263+
)
264+
})
255265
})

src/__tests__/renderHook.js

+27-12
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
import React from 'react'
22
import {renderHook} from '../pure'
33

4-
// Needs to be changed to 19.0.0 once alpha started.
5-
const isReactExperimental = React.version.startsWith('18.3.0-experimental')
6-
const isReactCanary = React.version.startsWith('18.3.0')
4+
const isReact18 = React.version.startsWith('18.')
5+
const isReact19 = React.version.startsWith('19.')
76

8-
// Needs to be changed to isReactExperimental || isReactCanary once alpha started.
9-
const testGateReact18 = isReactExperimental ? test.skip : test
7+
const testGateReact18 = isReact18 ? test : test.skip
8+
const testGateReact19 = isReact19 ? test : test.skip
109

1110
test('gives committed result', () => {
1211
const {result} = renderHook(() => {
@@ -85,14 +84,30 @@ testGateReact18('legacyRoot uses legacy ReactDOM.render', () => {
8584
},
8685
).result
8786
}).toErrorDev(
88-
isReactCanary
89-
? [
90-
"Warning: ReactDOM.render is no longer supported in React 18. Use createRoot instead. Until you switch to the new API, your app will behave as if it's running React 17. Learn more: https://react.dev/link/switch-to-createroot",
91-
]
92-
: [
93-
"Warning: ReactDOM.render is no longer supported in React 18. Use createRoot instead. Until you switch to the new API, your app will behave as if it's running React 17. Learn more: https://reactjs.org/link/switch-to-createroot",
94-
],
87+
[
88+
"Warning: ReactDOM.render is no longer supported in React 18. Use createRoot instead. Until you switch to the new API, your app will behave as if it's running React 17. Learn more: https://reactjs.org/link/switch-to-createroot",
89+
],
9590
{withoutStack: true},
9691
)
9792
expect(result.current).toEqual('provided')
9893
})
94+
95+
testGateReact19('legacyRoot throws', () => {
96+
const Context = React.createContext('default')
97+
function Wrapper({children}) {
98+
return <Context.Provider value="provided">{children}</Context.Provider>
99+
}
100+
expect(() => {
101+
renderHook(
102+
() => {
103+
return React.useContext(Context)
104+
},
105+
{
106+
wrapper: Wrapper,
107+
legacyRoot: true,
108+
},
109+
).result
110+
}).toThrowErrorMatchingInlineSnapshot(
111+
`\`legacyRoot: true\` is not supported in this version of React. Please use React 18 instead.`,
112+
)
113+
})

src/act-compat.js

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
import * as testUtils from 'react-dom/test-utils'
1+
import * as React from 'react'
2+
import * as DeprecatedReactTestUtils from 'react-dom/test-utils'
23

3-
const domAct = testUtils.act
4+
const reactAct = React.act ?? DeprecatedReactTestUtils.act
45

56
function getGlobalThis() {
67
/* istanbul ignore else */
@@ -78,7 +79,7 @@ function withGlobalActEnvironment(actImplementation) {
7879
}
7980
}
8081

81-
const act = withGlobalActEnvironment(domAct)
82+
const act = withGlobalActEnvironment(reactAct)
8283

8384
export default act
8485
export {

src/pure.js

+17
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,14 @@ function render(
207207
wrapper,
208208
} = {},
209209
) {
210+
if (legacyRoot && typeof ReactDOM.render !== 'function') {
211+
const error = new Error(
212+
'`legacyRoot: true` is not supported in this version of React. Please use React 18 instead.',
213+
)
214+
Error.captureStackTrace(error, render)
215+
throw error
216+
}
217+
210218
if (!baseElement) {
211219
// default to document.body instead of documentElement to avoid output of potentially-large
212220
// head elements (such as JSS style blocks) in debug output
@@ -263,6 +271,15 @@ function cleanup() {
263271

264272
function renderHook(renderCallback, options = {}) {
265273
const {initialProps, ...renderOptions} = options
274+
275+
if (renderOptions.legacyRoot && typeof ReactDOM.render !== 'function') {
276+
const error = new Error(
277+
'`legacyRoot: true` is not supported in this version of React. Please use React 18 instead.',
278+
)
279+
Error.captureStackTrace(error, renderHook)
280+
throw error
281+
}
282+
266283
const result = React.createRef()
267284

268285
function TestComponent({renderCallbackProps}) {

tests/shouldIgnoreConsoleError.js

+1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ module.exports = function shouldIgnoreConsoleError(format) {
3636
// Ignore it too.
3737
return true
3838
}
39+
// TODO: Suppress deprecation warning from react-dom/test-utils
3940
}
4041
}
4142
// Looks legit

types/index.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ export interface RenderOptions<
7373
*/
7474
hydrate?: boolean
7575
/**
76+
* Only works if used with React 18.
7677
* Set to `true` if you want to force synchronous `ReactDOM.render`.
7778
* Otherwise `render` will default to concurrent React if available.
7879
*/

0 commit comments

Comments
 (0)