Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit f9bb970

Browse files
authored
feat(pretty-format): support React 19 (#16123)
1 parent 4b9f547 commit f9bb970

7 files changed

Lines changed: 364 additions & 29 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
- `[jest-runtime]` Support `require()` of ES modules on Node v24.9+ ([#16074](https://github.com/jestjs/jest/pull/16074))
1313
- `[@jest/transform]` Add `canTransformSync(filename)` on `ScriptTransformer` so callers can pick the sync vs async transform path ([#16062](https://github.com/jestjs/jest/pull/16062))
1414
- `[jest-util]` Add `isError` helper ([#16076](https://github.com/jestjs/jest/pull/16076))
15+
- `[pretty-format]` Support React 19 ([#16123](https://github.com/jestjs/jest/pull/16123))
1516

1617
### Fixes
1718

packages/pretty-format/package.json

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,19 @@
2323
"dependencies": {
2424
"@jest/schemas": "workspace:*",
2525
"ansi-styles": "^5.2.0",
26-
"react-is": "^18.3.1"
26+
"react-is-18": "npm:react-is@^18.3.1",
27+
"react-is-19": "npm:react-is@^19.2.5"
2728
},
2829
"devDependencies": {
2930
"@types/react": "^18.3.23",
30-
"@types/react-is": "^18.3.1",
31+
"@types/react-is": "^19.0.0",
3132
"@types/react-test-renderer": "^18.3.1",
3233
"immutable": "^5.1.2",
3334
"jest-util": "workspace:*",
3435
"react": "18.3.1",
35-
"react-dom": "18.3.1",
36+
"react-17": "npm:react@^17.0.2",
37+
"react-18": "npm:react@^18.3.1",
38+
"react-19": "npm:react@^19.2.5",
3639
"react-test-renderer": "18.3.1"
3740
},
3841
"engines": {
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
import prettyFormat, {plugins} from '../';
9+
const {ReactElement} = plugins;
10+
11+
const formatElement = (element: unknown) =>
12+
prettyFormat(element, {plugins: [ReactElement]});
13+
14+
describe.each([
15+
['React 17', require('react-17') as typeof import('react')],
16+
['React 18', require('react-18') as typeof import('react')],
17+
['React 19', require('react-19') as typeof import('react')],
18+
])('%s', (_name, React) => {
19+
test('fragment', () => {
20+
expect(
21+
formatElement(
22+
React.createElement(
23+
React.Fragment,
24+
null,
25+
React.createElement('div', {className: 'foo'}, 'hello'),
26+
),
27+
),
28+
).toMatchSnapshot();
29+
});
30+
31+
test('host element', () => {
32+
expect(
33+
formatElement(
34+
React.createElement(
35+
'div',
36+
null,
37+
React.createElement('span', {className: 'bar'}, 'world'),
38+
),
39+
),
40+
).toMatchSnapshot();
41+
});
42+
43+
test('suspense', () => {
44+
expect(
45+
formatElement(
46+
React.createElement(
47+
React.Suspense,
48+
{fallback: React.createElement('span', null, 'loading')},
49+
React.createElement('div', null, 'content'),
50+
),
51+
),
52+
).toMatchSnapshot();
53+
});
54+
55+
test('forwardRef', () => {
56+
function Cat(props: Record<string, unknown>, _ref: unknown) {
57+
return React.createElement('div', props);
58+
}
59+
expect(
60+
formatElement(React.createElement(React.forwardRef(Cat), null, 'mouse')),
61+
).toMatchSnapshot();
62+
});
63+
64+
test('memo', () => {
65+
function Dog(props: Record<string, unknown>) {
66+
return React.createElement('div', props);
67+
}
68+
expect(
69+
formatElement(React.createElement(React.memo(Dog), null, 'cat')),
70+
).toMatchSnapshot();
71+
});
72+
73+
test('context provider', () => {
74+
const {Provider} = React.createContext('test');
75+
expect(
76+
formatElement(
77+
React.createElement(Provider, {value: 'test-value'}, 'child'),
78+
),
79+
).toMatchSnapshot();
80+
});
81+
82+
test('context consumer', () => {
83+
const {Consumer} = React.createContext('test');
84+
expect(
85+
formatElement(
86+
React.createElement(Consumer, {
87+
children: () => React.createElement('div', null, 'child'),
88+
}),
89+
),
90+
).toMatchSnapshot();
91+
});
92+
});
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
2+
3+
exports[`React 17 context consumer 1`] = `
4+
"<Context.Consumer>
5+
[Function children]
6+
</Context.Consumer>"
7+
`;
8+
9+
exports[`React 17 context provider 1`] = `
10+
"<Context.Provider
11+
value="test-value"
12+
>
13+
child
14+
</Context.Provider>"
15+
`;
16+
17+
exports[`React 17 forwardRef 1`] = `
18+
"<ForwardRef(Cat)>
19+
mouse
20+
</ForwardRef(Cat)>"
21+
`;
22+
23+
exports[`React 17 fragment 1`] = `
24+
"<React.Fragment>
25+
<div
26+
className="foo"
27+
>
28+
hello
29+
</div>
30+
</React.Fragment>"
31+
`;
32+
33+
exports[`React 17 host element 1`] = `
34+
"<div>
35+
<span
36+
className="bar"
37+
>
38+
world
39+
</span>
40+
</div>"
41+
`;
42+
43+
exports[`React 17 memo 1`] = `
44+
"<Memo(Dog)>
45+
cat
46+
</Memo(Dog)>"
47+
`;
48+
49+
exports[`React 17 suspense 1`] = `
50+
"<React.Suspense
51+
fallback={
52+
<span>
53+
loading
54+
</span>
55+
}
56+
>
57+
<div>
58+
content
59+
</div>
60+
</React.Suspense>"
61+
`;
62+
63+
exports[`React 18 context consumer 1`] = `
64+
"<Context.Consumer>
65+
[Function children]
66+
</Context.Consumer>"
67+
`;
68+
69+
exports[`React 18 context provider 1`] = `
70+
"<Context.Provider
71+
value="test-value"
72+
>
73+
child
74+
</Context.Provider>"
75+
`;
76+
77+
exports[`React 18 forwardRef 1`] = `
78+
"<ForwardRef(Cat)>
79+
mouse
80+
</ForwardRef(Cat)>"
81+
`;
82+
83+
exports[`React 18 fragment 1`] = `
84+
"<React.Fragment>
85+
<div
86+
className="foo"
87+
>
88+
hello
89+
</div>
90+
</React.Fragment>"
91+
`;
92+
93+
exports[`React 18 host element 1`] = `
94+
"<div>
95+
<span
96+
className="bar"
97+
>
98+
world
99+
</span>
100+
</div>"
101+
`;
102+
103+
exports[`React 18 memo 1`] = `
104+
"<Memo(Dog)>
105+
cat
106+
</Memo(Dog)>"
107+
`;
108+
109+
exports[`React 18 suspense 1`] = `
110+
"<React.Suspense
111+
fallback={
112+
<span>
113+
loading
114+
</span>
115+
}
116+
>
117+
<div>
118+
content
119+
</div>
120+
</React.Suspense>"
121+
`;
122+
123+
exports[`React 19 context consumer 1`] = `
124+
"<Context.Consumer>
125+
[Function children]
126+
</Context.Consumer>"
127+
`;
128+
129+
exports[`React 19 context provider 1`] = `
130+
"<Context.Provider
131+
value="test-value"
132+
>
133+
child
134+
</Context.Provider>"
135+
`;
136+
137+
exports[`React 19 forwardRef 1`] = `
138+
"<ForwardRef(Cat)>
139+
mouse
140+
</ForwardRef(Cat)>"
141+
`;
142+
143+
exports[`React 19 fragment 1`] = `
144+
"<React.Fragment>
145+
<div
146+
className="foo"
147+
>
148+
hello
149+
</div>
150+
</React.Fragment>"
151+
`;
152+
153+
exports[`React 19 host element 1`] = `
154+
"<div>
155+
<span
156+
className="bar"
157+
>
158+
world
159+
</span>
160+
</div>"
161+
`;
162+
163+
exports[`React 19 memo 1`] = `
164+
"<Memo(Dog)>
165+
cat
166+
</Memo(Dog)>"
167+
`;
168+
169+
exports[`React 19 suspense 1`] = `
170+
"<React.Suspense
171+
fallback={
172+
<span>
173+
loading
174+
</span>
175+
}
176+
>
177+
<div>
178+
content
179+
</div>
180+
</React.Suspense>"
181+
`;

packages/pretty-format/src/plugins/ReactElement.ts

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
* LICENSE file in the root directory of this source tree.
66
*/
77

8-
import * as ReactIs from 'react-is';
8+
import * as ReactIs18 from 'react-is-18';
9+
import * as ReactIs19 from 'react-is-19';
910
import type {Config, NewPlugin, Printer, Refs} from '../types';
1011
import {
1112
printChildren,
@@ -14,6 +15,20 @@ import {
1415
printProps,
1516
} from './lib/markup';
1617

18+
const isElement = (val: unknown) =>
19+
ReactIs18.isElement(val) || ReactIs19.isElement(val);
20+
const isFragment = (val: unknown) =>
21+
ReactIs18.isFragment(val) || ReactIs19.isFragment(val);
22+
const isSuspense = (val: unknown) =>
23+
ReactIs18.isSuspense(val) || ReactIs19.isSuspense(val);
24+
const isContextProvider = (val: unknown) =>
25+
ReactIs18.isContextProvider(val) || ReactIs19.isContextProvider(val);
26+
const isContextConsumer = (val: unknown) =>
27+
ReactIs18.isContextConsumer(val) || ReactIs19.isContextConsumer(val);
28+
const isForwardRef = (val: unknown) =>
29+
ReactIs18.isForwardRef(val) || ReactIs19.isForwardRef(val);
30+
const isMemo = (val: unknown) => ReactIs18.isMemo(val) || ReactIs19.isMemo(val);
31+
1732
// Given element.props.children, or subtree during recursive traversal,
1833
// return flattened array of children.
1934
const getChildren = (arg: unknown, children: Array<unknown> = []) => {
@@ -36,22 +51,22 @@ const getType = (element: any) => {
3651
return type.displayName || type.name || 'Unknown';
3752
}
3853

39-
if (ReactIs.isFragment(element)) {
54+
if (isFragment(element)) {
4055
return 'React.Fragment';
4156
}
42-
if (ReactIs.isSuspense(element)) {
57+
if (isSuspense(element)) {
4358
return 'React.Suspense';
4459
}
4560
if (typeof type === 'object' && type !== null) {
46-
if (ReactIs.isContextProvider(element)) {
61+
if (isContextProvider(element)) {
4762
return 'Context.Provider';
4863
}
4964

50-
if (ReactIs.isContextConsumer(element)) {
65+
if (isContextConsumer(element)) {
5166
return 'Context.Consumer';
5267
}
5368

54-
if (ReactIs.isForwardRef(element)) {
69+
if (isForwardRef(element)) {
5570
if (type.displayName) {
5671
return type.displayName;
5772
}
@@ -61,7 +76,7 @@ const getType = (element: any) => {
6176
return functionName === '' ? 'ForwardRef' : `ForwardRef(${functionName})`;
6277
}
6378

64-
if (ReactIs.isMemo(element)) {
79+
if (isMemo(element)) {
6580
const functionName =
6681
type.displayName || type.type.displayName || type.type.name || '';
6782

@@ -113,7 +128,7 @@ export const serialize: NewPlugin['serialize'] = (
113128
);
114129

115130
export const test: NewPlugin['test'] = (val: unknown) =>
116-
val != null && ReactIs.isElement(val);
131+
val != null && isElement(val);
117132

118133
const plugin: NewPlugin = {serialize, test};
119134

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
declare module 'react-is-18' {
9+
// eslint-disable-next-line import-x/no-extraneous-dependencies
10+
export * from 'react-is';
11+
}
12+
13+
declare module 'react-is-19' {
14+
// eslint-disable-next-line import-x/no-extraneous-dependencies
15+
export * from 'react-is';
16+
}

0 commit comments

Comments
 (0)