main
js 258 lines 7.86 KB
Raw
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 * @emails react-core
8 */
9
10 'use strict';
11
12 let React;
13 let ReactDOMClient;
14 let act;
15 let assertConsoleErrorDev;
16
17 // TODO: Historically this module was used to confirm that the JSX transform
18 // produces the correct output. However, most users (and indeed our own test
19 // suite) use a tool like Babel or TypeScript to transform JSX; unlike the
20 // runtime, the transform is not part of React itself. So this is really just an
21 // integration suite for the Babel transform. We might consider deleting it. We
22 // should prefer to test the JSX runtime directly, in ReactCreateElement-test
23 // and ReactJsxRuntime-test. In the meantime, there's lots of overlap between
24 // those modules and this one.
25 describe('ReactJSXTransformIntegration', () => {
26 let Component;
27
28 beforeEach(() => {
29 jest.resetModules();
30
31 React = require('react');
32 ReactDOMClient = require('react-dom/client');
33 ({act, assertConsoleErrorDev} = require('internal-test-utils'));
34
35 Component = class extends React.Component {
36 render() {
37 return <div />;
38 }
39 };
40 });
41
42 it('sanity check: test environment is configured to compile JSX to the jsx() runtime', async () => {
43 function App() {
44 return <div />;
45 }
46 const source = App.toString();
47 if (__DEV__) {
48 expect(source).toContain('jsxDEV(');
49 } else {
50 expect(source).toContain('jsx(');
51 }
52 expect(source).not.toContain('React.createElement');
53 });
54
55 it('returns a complete element according to spec', () => {
56 const element = <Component />;
57 expect(element.type).toBe(Component);
58 expect(element.key).toBe(null);
59 expect(element.ref).toBe(null);
60 const expectation = {};
61 Object.freeze(expectation);
62 expect(element.props).toEqual(expectation);
63 });
64
65 it('allows a lower-case to be passed as the string type', () => {
66 const element = <div />;
67 expect(element.type).toBe('div');
68 expect(element.key).toBe(null);
69 expect(element.ref).toBe(null);
70 const expectation = {};
71 Object.freeze(expectation);
72 expect(element.props).toEqual(expectation);
73 });
74
75 it('allows a string to be passed as the type', () => {
76 const TagName = 'div';
77 const element = <TagName />;
78 expect(element.type).toBe('div');
79 expect(element.key).toBe(null);
80 expect(element.ref).toBe(null);
81 const expectation = {};
82 Object.freeze(expectation);
83 expect(element.props).toEqual(expectation);
84 });
85
86 it('returns an immutable element', () => {
87 const element = <Component />;
88 if (__DEV__) {
89 expect(() => (element.type = 'div')).toThrow();
90 } else {
91 expect(() => (element.type = 'div')).not.toThrow();
92 }
93 });
94
95 it('does not reuse the object that is spread into props', () => {
96 const config = {foo: 1};
97 const element = <Component {...config} />;
98 expect(element.props.foo).toBe(1);
99 config.foo = 2;
100 expect(element.props.foo).toBe(1);
101 });
102
103 it('extracts key from the rest of the props', () => {
104 const element = <Component key="12" foo="56" />;
105 expect(element.type).toBe(Component);
106 expect(element.key).toBe('12');
107 const expectation = {foo: '56'};
108 Object.freeze(expectation);
109 expect(element.props).toEqual(expectation);
110 });
111
112 it('does not extract ref from the rest of the props', () => {
113 const ref = React.createRef();
114 const element = <Component ref={ref} foo="56" />;
115 expect(element.type).toBe(Component);
116 expect(element.ref).toBe(ref);
117 assertConsoleErrorDev([
118 'Accessing element.ref was removed in React 19. ref is now a ' +
119 'regular prop. It will be removed from the JSX Element ' +
120 'type in a future release.',
121 ]);
122 const expectation = {foo: '56', ref};
123 Object.freeze(expectation);
124 expect(element.props).toEqual(expectation);
125 });
126
127 it('coerces the key to a string', () => {
128 const element = <Component key={12} foo="56" />;
129 expect(element.type).toBe(Component);
130 expect(element.key).toBe('12');
131 expect(element.ref).toBe(null);
132 const expectation = {foo: '56'};
133 Object.freeze(expectation);
134 expect(element.props).toEqual(expectation);
135 });
136
137 it('merges JSX children onto the children prop', () => {
138 const a = 1;
139 const element = <Component children="text">{a}</Component>;
140 expect(element.props.children).toBe(a);
141 });
142
143 it('does not override children if no JSX children are provided', () => {
144 const element = <Component children="text" />;
145 expect(element.props.children).toBe('text');
146 });
147
148 it('overrides children if null is provided as a JSX child', () => {
149 const element = <Component children="text">{null}</Component>;
150 expect(element.props.children).toBe(null);
151 });
152
153 it('overrides children if undefined is provided as an argument', () => {
154 const element = <Component children="text">{undefined}</Component>;
155 expect(element.props.children).toBe(undefined);
156
157 const element2 = React.cloneElement(
158 <Component children="text" />,
159 {},
160 undefined,
161 );
162 expect(element2.props.children).toBe(undefined);
163 });
164
165 it('merges JSX children onto the children prop in an array', () => {
166 const a = 1;
167 const b = 2;
168 const c = 3;
169 const element = (
170 <Component>
171 {a}
172 {b}
173 {c}
174 </Component>
175 );
176 expect(element.props.children).toEqual([1, 2, 3]);
177 });
178
179 it('allows static methods to be called using the type property', () => {
180 class StaticMethodComponent {
181 static someStaticMethod() {
182 return 'someReturnValue';
183 }
184 render() {
185 return <div />;
186 }
187 }
188
189 const element = <StaticMethodComponent />;
190 expect(element.type.someStaticMethod()).toBe('someReturnValue');
191 });
192
193 it('identifies valid elements', () => {
194 expect(React.isValidElement(<div />)).toEqual(true);
195 expect(React.isValidElement(<Component />)).toEqual(true);
196
197 expect(React.isValidElement(null)).toEqual(false);
198 expect(React.isValidElement(true)).toEqual(false);
199 expect(React.isValidElement({})).toEqual(false);
200 expect(React.isValidElement('string')).toEqual(false);
201 expect(React.isValidElement(Component)).toEqual(false);
202 expect(React.isValidElement({type: 'div', props: {}})).toEqual(false);
203 });
204
205 it('is indistinguishable from a plain object', () => {
206 const element = <div className="foo" />;
207 const object = {};
208 expect(element.constructor).toBe(object.constructor);
209 });
210
211 it('should use default prop value when removing a prop', async () => {
212 Component.defaultProps = {fruit: 'persimmon'};
213
214 const container = document.createElement('div');
215 const root = ReactDOMClient.createRoot(container);
216 let instance;
217 await act(() => {
218 root.render(<Component fruit="mango" ref={ref => (instance = ref)} />);
219 });
220 expect(instance.props.fruit).toBe('mango');
221
222 await act(() => {
223 root.render(<Component ref={ref => (instance = ref)} />);
224 });
225 expect(instance.props.fruit).toBe('persimmon');
226 });
227
228 it('should normalize props with default values', async () => {
229 class NormalizingComponent extends React.Component {
230 render() {
231 return <span>{this.props.prop}</span>;
232 }
233 }
234 NormalizingComponent.defaultProps = {prop: 'testKey'};
235
236 let container = document.createElement('div');
237 let root = ReactDOMClient.createRoot(container);
238 let instance;
239 await act(() => {
240 root.render(
241 <NormalizingComponent ref={current => (instance = current)} />,
242 );
243 });
244
245 expect(instance.props.prop).toBe('testKey');
246
247 container = document.createElement('div');
248 root = ReactDOMClient.createRoot(container);
249 let inst2;
250 await act(() => {
251 root.render(
252 <NormalizingComponent prop={null} ref={current => (inst2 = current)} />,
253 );
254 });
255
256 expect(inst2.props.prop).toBe(null);
257 });
258 });