main
js 274 lines 7.37 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 * @jest-environment ./scripts/jest/ReactDOMServerIntegrationEnvironment
9 */
10
11 'use strict';
12
13 const ReactDOMServerIntegrationUtils = require('./utils/ReactDOMServerIntegrationTestUtils');
14
15 let React;
16 let ReactDOMClient;
17 let ReactDOMServer;
18
19 function initModules() {
20 // Reset warning cache.
21 jest.resetModules();
22 React = require('react');
23 ReactDOMClient = require('react-dom/client');
24 ReactDOMServer = require('react-dom/server');
25
26 // Make them available to the helpers.
27 return {
28 ReactDOMClient,
29 ReactDOMServer,
30 };
31 }
32
33 const {resetModules, itRenders, itThrowsWhenRendering} =
34 ReactDOMServerIntegrationUtils(initModules);
35
36 describe('ReactDOMServerIntegrationSelect', () => {
37 let options;
38 beforeEach(() => {
39 resetModules();
40
41 options = [
42 <option key={1} value="foo" id="foo">
43 Foo
44 </option>,
45 <option key={2} value="bar" id="bar">
46 Bar
47 </option>,
48 <option key={3} value="baz" id="baz">
49 Baz
50 </option>,
51 ];
52 });
53
54 // a helper function to test the selected value of a <select> element.
55 // takes in a <select> DOM element (element) and a value or array of
56 // values that should be selected (selected).
57 const expectSelectValue = (element, selected) => {
58 if (!Array.isArray(selected)) {
59 selected = [selected];
60 }
61 // the select DOM element shouldn't ever have a value or defaultValue
62 // attribute; that is not how select values are expressed in the DOM.
63 expect(element.getAttribute('value')).toBe(null);
64 expect(element.getAttribute('defaultValue')).toBe(null);
65
66 ['foo', 'bar', 'baz'].forEach(value => {
67 const expectedValue = selected.indexOf(value) !== -1;
68 const option = element.querySelector(`#${value}`);
69 expect(option.selected).toBe(expectedValue);
70 });
71 };
72
73 itRenders('a select with a value and an onChange', async render => {
74 const e = await render(
75 <select value="bar" onChange={() => {}}>
76 {options}
77 </select>,
78 );
79 expectSelectValue(e, 'bar');
80 });
81
82 itRenders('a select with a value and readOnly', async render => {
83 const e = await render(
84 <select value="bar" readOnly={true}>
85 {options}
86 </select>,
87 );
88 expectSelectValue(e, 'bar');
89 });
90
91 itRenders('a select with a multiple values and an onChange', async render => {
92 const e = await render(
93 <select value={['bar', 'baz']} multiple={true} onChange={() => {}}>
94 {options}
95 </select>,
96 );
97 expectSelectValue(e, ['bar', 'baz']);
98 });
99
100 itRenders('a select with a multiple values and readOnly', async render => {
101 const e = await render(
102 <select value={['bar', 'baz']} multiple={true} readOnly={true}>
103 {options}
104 </select>,
105 );
106 expectSelectValue(e, ['bar', 'baz']);
107 });
108
109 itRenders('a select with a value and no onChange/readOnly', async render => {
110 // this configuration should raise a dev warning that value without
111 // onChange or readOnly is a mistake.
112 const e = await render(<select value="bar">{options}</select>, 1);
113 expectSelectValue(e, 'bar');
114 });
115
116 itRenders('a select with a defaultValue', async render => {
117 const e = await render(<select defaultValue="bar">{options}</select>);
118 expectSelectValue(e, 'bar');
119 });
120
121 itRenders('a select value overriding defaultValue', async render => {
122 const e = await render(
123 <select value="bar" defaultValue="baz" readOnly={true}>
124 {options}
125 </select>,
126 1,
127 );
128 expectSelectValue(e, 'bar');
129 });
130
131 itRenders(
132 'a select with options that use dangerouslySetInnerHTML',
133 async render => {
134 const e = await render(
135 <select defaultValue="baz" value="bar" readOnly={true}>
136 <option
137 id="foo"
138 value="foo"
139 dangerouslySetInnerHTML={{
140 __html: 'Foo',
141 }}>
142 {undefined}
143 </option>
144 <option
145 id="bar"
146 value="bar"
147 dangerouslySetInnerHTML={{
148 __html: 'Bar',
149 }}>
150 {null}
151 </option>
152 <option
153 id="baz"
154 dangerouslySetInnerHTML={{
155 __html: 'Baz', // This warns because no value prop is passed.
156 }}
157 />
158 </select>,
159 2,
160 );
161 expectSelectValue(e, 'bar');
162 },
163 );
164
165 itThrowsWhenRendering(
166 'a select with option that uses dangerouslySetInnerHTML and 0 as child',
167 async render => {
168 await render(
169 <select defaultValue="baz" value="foo" readOnly={true}>
170 <option
171 id="foo"
172 value="foo"
173 dangerouslySetInnerHTML={{
174 __html: 'Foo',
175 }}>
176 {0}
177 </option>
178 </select>,
179 1,
180 );
181 },
182 'Can only set one of `children` or `props.dangerouslySetInnerHTML`.',
183 );
184
185 itThrowsWhenRendering(
186 'a select with option that uses dangerouslySetInnerHTML and empty string as child',
187 async render => {
188 await render(
189 <select defaultValue="baz" value="foo" readOnly={true}>
190 <option
191 id="foo"
192 value="foo"
193 dangerouslySetInnerHTML={{
194 __html: 'Foo',
195 }}>
196 {''}
197 </option>
198 </select>,
199 1,
200 );
201 },
202 'Can only set one of `children` or `props.dangerouslySetInnerHTML`.',
203 );
204
205 itRenders(
206 'a select value overriding defaultValue no matter the prop order',
207 async render => {
208 const e = await render(
209 <select defaultValue="baz" value="bar" readOnly={true}>
210 {options}
211 </select>,
212 1,
213 );
214 expectSelectValue(e, 'bar');
215 },
216 );
217
218 itRenders('a select option with flattened children', async render => {
219 const e = await render(
220 <select value="bar" readOnly={true}>
221 <option value="bar">
222 A {'B'} {5n}
223 </option>
224 </select>,
225 );
226 const option = e.options[0];
227 expect(option.textContent).toBe('A B 5');
228 expect(option.value).toBe('bar');
229 expect(option.selected).toBe(true);
230 });
231
232 itRenders(
233 'a select option with flattened children no value',
234 async render => {
235 const e = await render(
236 <select value="A B" readOnly={true}>
237 <option>A {'B'}</option>
238 </select>,
239 );
240 const option = e.options[0];
241 expect(option.textContent).toBe('A B');
242 expect(option.value).toBe('A B');
243 expect(option.selected).toBe(true);
244 },
245 );
246
247 itRenders(
248 'a boolean true select value match the string "true"',
249 async render => {
250 const e = await render(
251 <select value={true} readOnly={true}>
252 <option value="first">First</option>
253 <option value="true">True</option>
254 </select>,
255 );
256 expect(e.firstChild.selected).toBe(false);
257 expect(e.lastChild.selected).toBe(true);
258 },
259 );
260
261 itRenders(
262 'a missing select value does not match the string "undefined"',
263 async render => {
264 const e = await render(
265 <select readOnly={true}>
266 <option value="first">First</option>
267 <option value="undefined">Undefined</option>
268 </select>,
269 );
270 expect(e.firstChild.selected).toBe(true);
271 expect(e.lastChild.selected).toBe(false);
272 },
273 );
274 });