@samitouri / QOS-React / commits / 8d1038fc6d

Break up ReactDOMServerIntegrationForm-test (#13600)

In https://github.com/facebook/react/pull/13394, I encountered an issue where the ReactDOMServerIntegrationForm test suite consumed sufficient memory to crash CircleCI. Breaking up this test suite by form element type resolved the issue. This commit performs that change separate from the Symbol/Function stringification changes in #13394.

Nathan Hunzaker committed Sep 8, 2018 at 11:31 UTC 8d1038fc6dee1d0264b5ec9f8bb13305e7495b8e
7 files changed +944 -802
packages/react-dom/src/__tests__/ReactDOMServerIntegrationCheckbox-test.js new
+99
@@ -0,0 +1,99 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its 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 +const ReactDOMServerIntegrationUtils = require('./utils/ReactDOMServerIntegrationTestUtils');
13 +
14 +let React;
15 +let ReactDOM;
16 +let ReactDOMServer;
17 +
18 +function initModules() {
19 + // Reset warning cache.
20 + jest.resetModuleRegistry();
21 + React = require('react');
22 + ReactDOM = require('react-dom');
23 + ReactDOMServer = require('react-dom/server');
24 +
25 + // Make them available to the helpers.
26 + return {
27 + ReactDOM,
28 + ReactDOMServer,
29 + };
30 +}
31 +
32 +const {resetModules, itRenders} = ReactDOMServerIntegrationUtils(initModules);
33 +
34 +describe('ReactDOMServerIntegrationCheckbox', () => {
35 + beforeEach(() => {
36 + resetModules();
37 + });
38 +
39 + itRenders('a checkbox that is checked with an onChange', async render => {
40 + const e = await render(
41 + <input type="checkbox" checked={true} onChange={() => {}} />,
42 + );
43 + expect(e.checked).toBe(true);
44 + });
45 +
46 + itRenders('a checkbox that is checked with readOnly', async render => {
47 + const e = await render(
48 + <input type="checkbox" checked={true} readOnly={true} />,
49 + );
50 + expect(e.checked).toBe(true);
51 + });
52 +
53 + itRenders(
54 + 'a checkbox that is checked and no onChange/readOnly',
55 + async render => {
56 + // this configuration should raise a dev warning that checked without
57 + // onChange or readOnly is a mistake.
58 + const e = await render(<input type="checkbox" checked={true} />, 1);
59 + expect(e.checked).toBe(true);
60 + },
61 + );
62 +
63 + itRenders('a checkbox with defaultChecked', async render => {
64 + const e = await render(<input type="checkbox" defaultChecked={true} />);
65 + expect(e.checked).toBe(true);
66 + expect(e.getAttribute('defaultChecked')).toBe(null);
67 + });
68 +
69 + itRenders('a checkbox checked overriding defaultChecked', async render => {
70 + const e = await render(
71 + <input
72 + type="checkbox"
73 + checked={true}
74 + defaultChecked={false}
75 + readOnly={true}
76 + />,
77 + 1,
78 + );
79 + expect(e.checked).toBe(true);
80 + expect(e.getAttribute('defaultChecked')).toBe(null);
81 + });
82 +
83 + itRenders(
84 + 'a checkbox checked overriding defaultChecked no matter the prop order',
85 + async render => {
86 + const e = await render(
87 + <input
88 + type="checkbox"
89 + defaultChecked={false}
90 + checked={true}
91 + readOnly={true}
92 + />,
93 + 1,
94 + );
95 + expect(e.checked).toBe(true);
96 + expect(e.getAttribute('defaultChecked')).toBe(null);
97 + },
98 + );
99 +});
packages/react-dom/src/__tests__/ReactDOMServerIntegrationForms-test.js deleted
-802
@@ -1,802 +0,0 @@
1 -/**
2 - * Copyright (c) Facebook, Inc. and its 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 -const ReactDOMServerIntegrationUtils = require('./utils/ReactDOMServerIntegrationTestUtils');
13 -
14 -let React;
15 -let ReactDOM;
16 -let ReactDOMServer;
17 -
18 -function initModules() {
19 - // Reset warning cache.
20 - jest.resetModuleRegistry();
21 - React = require('react');
22 - ReactDOM = require('react-dom');
23 - ReactDOMServer = require('react-dom/server');
24 -
25 - // Make them available to the helpers.
26 - return {
27 - ReactDOM,
28 - ReactDOMServer,
29 - };
30 -}
31 -
32 -const {
33 - resetModules,
34 - itRenders,
35 - itClientRenders,
36 - itThrowsWhenRendering,
37 - renderIntoDom,
38 - serverRender,
39 -} = ReactDOMServerIntegrationUtils(initModules);
40 -
41 -describe('ReactDOMServerIntegration', () => {
42 - beforeEach(() => {
43 - resetModules();
44 - });
45 -
46 - describe('form controls', function() {
47 - describe('inputs', function() {
48 - itRenders('an input with a value and an onChange', async render => {
49 - const e = await render(<input value="foo" onChange={() => {}} />);
50 - expect(e.value).toBe('foo');
51 - });
52 -
53 - itRenders('an input with a value and readOnly', async render => {
54 - const e = await render(<input value="foo" readOnly={true} />);
55 - expect(e.value).toBe('foo');
56 - });
57 -
58 - itRenders(
59 - 'an input with a value and no onChange/readOnly',
60 - async render => {
61 - // this configuration should raise a dev warning that value without
62 - // onChange or readOnly is a mistake.
63 - const e = await render(<input value="foo" />, 1);
64 - expect(e.value).toBe('foo');
65 - expect(e.getAttribute('value')).toBe('foo');
66 - },
67 - );
68 -
69 - itRenders('an input with a defaultValue', async render => {
70 - const e = await render(<input defaultValue="foo" />);
71 - expect(e.value).toBe('foo');
72 - expect(e.getAttribute('value')).toBe('foo');
73 - expect(e.getAttribute('defaultValue')).toBe(null);
74 - });
75 -
76 - itRenders('an input value overriding defaultValue', async render => {
77 - const e = await render(
78 - <input value="foo" defaultValue="bar" readOnly={true} />,
79 - 1,
80 - );
81 - expect(e.value).toBe('foo');
82 - expect(e.getAttribute('value')).toBe('foo');
83 - expect(e.getAttribute('defaultValue')).toBe(null);
84 - });
85 -
86 - itRenders(
87 - 'an input value overriding defaultValue no matter the prop order',
88 - async render => {
89 - const e = await render(
90 - <input defaultValue="bar" value="foo" readOnly={true} />,
91 - 1,
92 - );
93 - expect(e.value).toBe('foo');
94 - expect(e.getAttribute('value')).toBe('foo');
95 - expect(e.getAttribute('defaultValue')).toBe(null);
96 - },
97 - );
98 - });
99 -
100 - describe('checkboxes', function() {
101 - itRenders('a checkbox that is checked with an onChange', async render => {
102 - const e = await render(
103 - <input type="checkbox" checked={true} onChange={() => {}} />,
104 - );
105 - expect(e.checked).toBe(true);
106 - });
107 -
108 - itRenders('a checkbox that is checked with readOnly', async render => {
109 - const e = await render(
110 - <input type="checkbox" checked={true} readOnly={true} />,
111 - );
112 - expect(e.checked).toBe(true);
113 - });
114 -
115 - itRenders(
116 - 'a checkbox that is checked and no onChange/readOnly',
117 - async render => {
118 - // this configuration should raise a dev warning that checked without
119 - // onChange or readOnly is a mistake.
120 - const e = await render(<input type="checkbox" checked={true} />, 1);
121 - expect(e.checked).toBe(true);
122 - },
123 - );
124 -
125 - itRenders('a checkbox with defaultChecked', async render => {
126 - const e = await render(<input type="checkbox" defaultChecked={true} />);
127 - expect(e.checked).toBe(true);
128 - expect(e.getAttribute('defaultChecked')).toBe(null);
129 - });
130 -
131 - itRenders(
132 - 'a checkbox checked overriding defaultChecked',
133 - async render => {
134 - const e = await render(
135 - <input
136 - type="checkbox"
137 - checked={true}
138 - defaultChecked={false}
139 - readOnly={true}
140 - />,
141 - 1,
142 - );
143 - expect(e.checked).toBe(true);
144 - expect(e.getAttribute('defaultChecked')).toBe(null);
145 - },
146 - );
147 -
148 - itRenders(
149 - 'a checkbox checked overriding defaultChecked no matter the prop order',
150 - async render => {
151 - const e = await render(
152 - <input
153 - type="checkbox"
154 - defaultChecked={false}
155 - checked={true}
156 - readOnly={true}
157 - />,
158 - 1,
159 - );
160 - expect(e.checked).toBe(true);
161 - expect(e.getAttribute('defaultChecked')).toBe(null);
162 - },
163 - );
164 - });
165 -
166 - describe('textareas', function() {
167 - // textareas
168 - // ---------
169 - itRenders('a textarea with a value and an onChange', async render => {
170 - const e = await render(<textarea value="foo" onChange={() => {}} />);
171 - // textarea DOM elements don't have a value **attribute**, the text is
172 - // a child of the element and accessible via the .value **property**.
173 - expect(e.getAttribute('value')).toBe(null);
174 - expect(e.value).toBe('foo');
175 - });
176 -
177 - itRenders('a textarea with a value and readOnly', async render => {
178 - const e = await render(<textarea value="foo" readOnly={true} />);
179 - // textarea DOM elements don't have a value **attribute**, the text is
180 - // a child of the element and accessible via the .value **property**.
181 - expect(e.getAttribute('value')).toBe(null);
182 - expect(e.value).toBe('foo');
183 - });
184 -
185 - itRenders(
186 - 'a textarea with a value and no onChange/readOnly',
187 - async render => {
188 - // this configuration should raise a dev warning that value without
189 - // onChange or readOnly is a mistake.
190 - const e = await render(<textarea value="foo" />, 1);
191 - expect(e.getAttribute('value')).toBe(null);
192 - expect(e.value).toBe('foo');
193 - },
194 - );
195 -
196 - itRenders('a textarea with a defaultValue', async render => {
197 - const e = await render(<textarea defaultValue="foo" />);
198 - expect(e.getAttribute('value')).toBe(null);
199 - expect(e.getAttribute('defaultValue')).toBe(null);
200 - expect(e.value).toBe('foo');
201 - });
202 -
203 - itRenders('a textarea value overriding defaultValue', async render => {
204 - const e = await render(
205 - <textarea value="foo" defaultValue="bar" readOnly={true} />,
206 - 1,
207 - );
208 - expect(e.getAttribute('value')).toBe(null);
209 - expect(e.getAttribute('defaultValue')).toBe(null);
210 - expect(e.value).toBe('foo');
211 - });
212 -
213 - itRenders(
214 - 'a textarea value overriding defaultValue no matter the prop order',
215 - async render => {
216 - const e = await render(
217 - <textarea defaultValue="bar" value="foo" readOnly={true} />,
218 - 1,
219 - );
220 - expect(e.getAttribute('value')).toBe(null);
221 - expect(e.getAttribute('defaultValue')).toBe(null);
222 - expect(e.value).toBe('foo');
223 - },
224 - );
225 - });
226 -
227 - describe('selects', function() {
228 - let options;
229 - beforeEach(function() {
230 - options = [
231 - <option key={1} value="foo" id="foo">
232 - Foo
233 - </option>,
234 - <option key={2} value="bar" id="bar">
235 - Bar
236 - </option>,
237 - <option key={3} value="baz" id="baz">
238 - Baz
239 - </option>,
240 - ];
241 - });
242 -
243 - // a helper function to test the selected value of a <select> element.
244 - // takes in a <select> DOM element (element) and a value or array of
245 - // values that should be selected (selected).
246 - const expectSelectValue = (element, selected) => {
247 - if (!Array.isArray(selected)) {
248 - selected = [selected];
249 - }
250 - // the select DOM element shouldn't ever have a value or defaultValue
251 - // attribute; that is not how select values are expressed in the DOM.
252 - expect(element.getAttribute('value')).toBe(null);
253 - expect(element.getAttribute('defaultValue')).toBe(null);
254 -
255 - ['foo', 'bar', 'baz'].forEach(value => {
256 - const expectedValue = selected.indexOf(value) !== -1;
257 - const option = element.querySelector(`#${value}`);
258 - expect(option.selected).toBe(expectedValue);
259 - });
260 - };
261 -
262 - itRenders('a select with a value and an onChange', async render => {
263 - const e = await render(
264 - <select value="bar" onChange={() => {}}>
265 - {options}
266 - </select>,
267 - );
268 - expectSelectValue(e, 'bar');
269 - });
270 -
271 - itRenders('a select with a value and readOnly', async render => {
272 - const e = await render(
273 - <select value="bar" readOnly={true}>
274 - {options}
275 - </select>,
276 - );
277 - expectSelectValue(e, 'bar');
278 - });
279 -
280 - itRenders(
281 - 'a select with a multiple values and an onChange',
282 - async render => {
283 - const e = await render(
284 - <select value={['bar', 'baz']} multiple={true} onChange={() => {}}>
285 - {options}
286 - </select>,
287 - );
288 - expectSelectValue(e, ['bar', 'baz']);
289 - },
290 - );
291 -
292 - itRenders(
293 - 'a select with a multiple values and readOnly',
294 - async render => {
295 - const e = await render(
296 - <select value={['bar', 'baz']} multiple={true} readOnly={true}>
297 - {options}
298 - </select>,
299 - );
300 - expectSelectValue(e, ['bar', 'baz']);
301 - },
302 - );
303 -
304 - itRenders(
305 - 'a select with a value and no onChange/readOnly',
306 - async render => {
307 - // this configuration should raise a dev warning that value without
308 - // onChange or readOnly is a mistake.
309 - const e = await render(<select value="bar">{options}</select>, 1);
310 - expectSelectValue(e, 'bar');
311 - },
312 - );
313 -
314 - itRenders('a select with a defaultValue', async render => {
315 - const e = await render(<select defaultValue="bar">{options}</select>);
316 - expectSelectValue(e, 'bar');
317 - });
318 -
319 - itRenders('a select value overriding defaultValue', async render => {
320 - const e = await render(
321 - <select value="bar" defaultValue="baz" readOnly={true}>
322 - {options}
323 - </select>,
324 - 1,
325 - );
326 - expectSelectValue(e, 'bar');
327 - });
328 -
329 - itRenders(
330 - 'a select with options that use dangerouslySetInnerHTML',
331 - async render => {
332 - const e = await render(
333 - <select defaultValue="baz" value="bar" readOnly={true}>
334 - <option
335 - id="foo"
336 - value="foo"
337 - dangerouslySetInnerHTML={{
338 - __html: 'Foo',
339 - }}>
340 - {undefined}
341 - </option>
342 - <option
343 - id="bar"
344 - value="bar"
345 - dangerouslySetInnerHTML={{
346 - __html: 'Bar',
347 - }}>
348 - {null}
349 - </option>
350 - <option
351 - id="baz"
352 - value="baz"
353 - dangerouslySetInnerHTML={{
354 - __html: 'Baz',
355 - }}
356 - />
357 - </select>,
358 - 1,
359 - );
360 - expectSelectValue(e, 'bar');
361 - },
362 - );
363 -
364 - itThrowsWhenRendering(
365 - 'a select with option that uses dangerouslySetInnerHTML and 0 as child',
366 - async render => {
367 - await render(
368 - <select defaultValue="baz" value="foo" readOnly={true}>
369 - <option
370 - id="foo"
371 - value="foo"
372 - dangerouslySetInnerHTML={{
373 - __html: 'Foo',
374 - }}>
375 - {0}
376 - </option>
377 - </select>,
378 - 1,
379 - );
380 - },
381 - 'Can only set one of `children` or `props.dangerouslySetInnerHTML`.',
382 - );
383 -
384 - itThrowsWhenRendering(
385 - 'a select with option that uses dangerouslySetInnerHTML and empty string as child',
386 - async render => {
387 - await render(
388 - <select defaultValue="baz" value="foo" readOnly={true}>
389 - <option
390 - id="foo"
391 - value="foo"
392 - dangerouslySetInnerHTML={{
393 - __html: 'Foo',
394 - }}>
395 - {''}
396 - </option>
397 - </select>,
398 - 1,
399 - );
400 - },
401 - 'Can only set one of `children` or `props.dangerouslySetInnerHTML`.',
402 - );
403 -
404 - itRenders(
405 - 'a select value overriding defaultValue no matter the prop order',
406 - async render => {
407 - const e = await render(
408 - <select defaultValue="baz" value="bar" readOnly={true}>
409 - {options}
410 - </select>,
411 - 1,
412 - );
413 - expectSelectValue(e, 'bar');
414 - },
415 - );
416 -
417 - itRenders('a select option with flattened children', async render => {
418 - const e = await render(
419 - <select value="bar" readOnly={true}>
420 - <option value="bar">A {'B'}</option>
421 - </select>,
422 - );
423 - const option = e.options[0];
424 - expect(option.childNodes.length).toBe(1);
425 - expect(option.childNodes[0].nodeType).toBe(3);
426 - expect(option.childNodes[0].nodeValue).toBe('A B');
427 - });
428 -
429 - itRenders(
430 - 'a select option with flattened children and a warning',
431 - async render => {
432 - const e = await render(
433 - <select readOnly={true} value="bar">
434 - <option value="bar">
435 - {['Bar', false, 'Foo', <div key="1" />, 'Baz']}
436 - </option>
437 - </select>,
438 - 1,
439 - );
440 - expect(e.getAttribute('value')).toBe(null);
441 - expect(e.getAttribute('defaultValue')).toBe(null);
442 - expect(e.firstChild.innerHTML).toBe('BarFoo[object Object]Baz');
443 - expect(e.firstChild.selected).toBe(true);
444 - },
445 - );
446 - });
447 -
448 - describe('progress', function() {
449 - itRenders('a progress in an indeterminate state', async render => {
450 - // Regression test for https://github.com/facebook/react/issues/6119
451 - const e = await render(<progress value={null} />);
452 - expect(e.hasAttribute('value')).toBe(false);
453 - const e2 = await render(<progress value={50} />);
454 - expect(e2.getAttribute('value')).toBe('50');
455 - });
456 - });
457 -
458 - describe('user interaction', function() {
459 - let ControlledInput,
460 - ControlledTextArea,
461 - ControlledCheckbox,
462 - ControlledSelect;
463 - beforeEach(() => {
464 - ControlledInput = class extends React.Component {
465 - static defaultProps = {
466 - type: 'text',
467 - initialValue: 'Hello',
468 - };
469 - constructor() {
470 - super(...arguments);
471 - this.state = {value: this.props.initialValue};
472 - }
473 - handleChange(event) {
474 - if (this.props.onChange) {
475 - this.props.onChange(event);
476 - }
477 - this.setState({value: event.target.value});
478 - }
479 - render() {
480 - return (
481 - <input
482 - type={this.props.type}
483 - value={this.state.value}
484 - onChange={this.handleChange.bind(this)}
485 - />
486 - );
487 - }
488 - };
489 - ControlledTextArea = class extends React.Component {
490 - constructor() {
491 - super();
492 - this.state = {value: 'Hello'};
493 - }
494 - handleChange(event) {
495 - if (this.props.onChange) {
496 - this.props.onChange(event);
497 - }
498 - this.setState({value: event.target.value});
499 - }
500 - render() {
501 - return (
502 - <textarea
503 - value={this.state.value}
504 - onChange={this.handleChange.bind(this)}
505 - />
506 - );
507 - }
508 - };
509 - ControlledCheckbox = class extends React.Component {
510 - constructor() {
511 - super();
512 - this.state = {value: true};
513 - }
514 - handleChange(event) {
515 - if (this.props.onChange) {
516 - this.props.onChange(event);
517 - }
518 - this.setState({value: event.target.checked});
519 - }
520 - render() {
521 - return (
522 - <input
523 - type="checkbox"
524 - checked={this.state.value}
525 - onChange={this.handleChange.bind(this)}
526 - />
527 - );
528 - }
529 - };
530 - ControlledSelect = class extends React.Component {
531 - constructor() {
532 - super();
533 - this.state = {value: 'Hello'};
534 - }
535 - handleChange(event) {
536 - if (this.props.onChange) {
537 - this.props.onChange(event);
538 - }
539 - this.setState({value: event.target.value});
540 - }
541 - render() {
542 - return (
543 - <select
544 - value={this.state.value}
545 - onChange={this.handleChange.bind(this)}>
546 - <option key="1" value="Hello">
547 - Hello
548 - </option>
549 - <option key="2" value="Goodbye">
550 - Goodbye
551 - </option>
552 - </select>
553 - );
554 - }
555 - };
556 - });
557 -
558 - describe('user interaction with controlled inputs', function() {
559 - itClientRenders('a controlled text input', async render => {
560 - const setUntrackedValue = Object.getOwnPropertyDescriptor(
561 - HTMLInputElement.prototype,
562 - 'value',
563 - ).set;
564 -
565 - let changeCount = 0;
566 - const e = await render(
567 - <ControlledInput onChange={() => changeCount++} />,
568 - );
569 - const container = e.parentNode;
570 - document.body.appendChild(container);
571 -
572 - try {
573 - expect(changeCount).toBe(0);
574 - expect(e.value).toBe('Hello');
575 -
576 - // simulate a user typing.
577 - setUntrackedValue.call(e, 'Goodbye');
578 - e.dispatchEvent(
579 - new Event('input', {bubbles: true, cancelable: false}),
580 - );
581 -
582 - expect(changeCount).toBe(1);
583 - expect(e.value).toBe('Goodbye');
584 - } finally {
585 - document.body.removeChild(container);
586 - }
587 - });
588 -
589 - itClientRenders('a controlled textarea', async render => {
590 - const setUntrackedValue = Object.getOwnPropertyDescriptor(
591 - HTMLTextAreaElement.prototype,
592 - 'value',
593 - ).set;
594 -
595 - let changeCount = 0;
596 - const e = await render(
597 - <ControlledTextArea onChange={() => changeCount++} />,
598 - );
599 - const container = e.parentNode;
600 - document.body.appendChild(container);
601 -
602 - try {
603 - expect(changeCount).toBe(0);
604 - expect(e.value).toBe('Hello');
605 -
606 - // simulate a user typing.
607 - setUntrackedValue.call(e, 'Goodbye');
608 - e.dispatchEvent(
609 - new Event('input', {bubbles: true, cancelable: false}),
610 - );
611 -
612 - expect(changeCount).toBe(1);
613 - expect(e.value).toBe('Goodbye');
614 - } finally {
615 - document.body.removeChild(container);
616 - }
617 - });
618 -
619 - itClientRenders('a controlled checkbox', async render => {
620 - let changeCount = 0;
621 - const e = await render(
622 - <ControlledCheckbox onChange={() => changeCount++} />,
623 - );
624 - const container = e.parentNode;
625 - document.body.appendChild(container);
626 -
627 - try {
628 - expect(changeCount).toBe(0);
629 - expect(e.checked).toBe(true);
630 -
631 - // simulate a user clicking.
632 - e.dispatchEvent(
633 - new Event('click', {bubbles: true, cancelable: true}),
634 - );
635 -
636 - expect(changeCount).toBe(1);
637 - expect(e.checked).toBe(false);
638 - } finally {
639 - document.body.removeChild(container);
640 - }
641 - });
642 -
643 - itClientRenders('a controlled select', async render => {
644 - const setUntrackedValue = Object.getOwnPropertyDescriptor(
645 - HTMLSelectElement.prototype,
646 - 'value',
647 - ).set;
648 -
649 - let changeCount = 0;
650 - const e = await render(
651 - <ControlledSelect onChange={() => changeCount++} />,
652 - );
653 - const container = e.parentNode;
654 - document.body.appendChild(container);
655 -
656 - try {
657 - expect(changeCount).toBe(0);
658 - expect(e.value).toBe('Hello');
659 -
660 - // simulate a user typing.
661 - setUntrackedValue.call(e, 'Goodbye');
662 - e.dispatchEvent(
663 - new Event('change', {bubbles: true, cancelable: false}),
664 - );
665 -
666 - expect(changeCount).toBe(1);
667 - expect(e.value).toBe('Goodbye');
668 - } finally {
669 - document.body.removeChild(container);
670 - }
671 - });
672 - });
673 -
674 - describe('user interaction with inputs before client render', function() {
675 - // renders the element and changes the value **before** the client
676 - // code has a chance to render; this simulates what happens when a
677 - // user starts to interact with a server-rendered form before
678 - // ReactDOM.render is called. the client render should NOT blow away
679 - // the changes the user has made.
680 - const testUserInteractionBeforeClientRender = async (
681 - element,
682 - initialValue = 'Hello',
683 - changedValue = 'Goodbye',
684 - valueKey = 'value',
685 - ) => {
686 - const field = await serverRender(element);
687 - expect(field[valueKey]).toBe(initialValue);
688 -
689 - // simulate a user typing in the field **before** client-side reconnect happens.
690 - field[valueKey] = changedValue;
691 -
692 - resetModules();
693 - // client render on top of the server markup.
694 - const clientField = await renderIntoDom(
695 - element,
696 - field.parentNode,
697 - true,
698 - );
699 - // verify that the input field was not replaced.
700 - // Note that we cannot use expect(clientField).toBe(field) because
701 - // of jest bug #1772
702 - expect(clientField === field).toBe(true);
703 - // confirm that the client render has not changed what the user typed.
704 - expect(clientField[valueKey]).toBe(changedValue);
705 - };
706 -
707 - it('should not blow away user-entered text on successful reconnect to an uncontrolled input', () =>
708 - testUserInteractionBeforeClientRender(
709 - <input defaultValue="Hello" />,
710 - ));
711 -
712 - it('should not blow away user-entered text on successful reconnect to a controlled input', async () => {
713 - let changeCount = 0;
714 - await testUserInteractionBeforeClientRender(
715 - <ControlledInput onChange={() => changeCount++} />,
716 - );
717 - // note that there's a strong argument to be made that the DOM revival
718 - // algorithm should notice that the user has changed the value and fire
719 - // an onChange. however, it does not now, so that's what this tests.
720 - expect(changeCount).toBe(0);
721 - });
722 -
723 - it('should not blow away user-interaction on successful reconnect to an uncontrolled range input', () =>
724 - testUserInteractionBeforeClientRender(
725 - <input type="text" defaultValue="0.5" />,
726 - '0.5',
727 - '1',
728 - ));
729 -
730 - it('should not blow away user-interaction on successful reconnect to a controlled range input', async () => {
731 - let changeCount = 0;
732 - await testUserInteractionBeforeClientRender(
733 - <ControlledInput
734 - type="range"
735 - initialValue="0.25"
736 - onChange={() => changeCount++}
737 - />,
738 - '0.25',
739 - '1',
740 - );
741 - expect(changeCount).toBe(0);
742 - });
743 -
744 - it('should not blow away user-entered text on successful reconnect to an uncontrolled checkbox', () =>
745 - testUserInteractionBeforeClientRender(
746 - <input type="checkbox" defaultChecked={true} />,
747 - true,
748 - false,
749 - 'checked',
750 - ));
751 -
752 - it('should not blow away user-entered text on successful reconnect to a controlled checkbox', async () => {
753 - let changeCount = 0;
754 - await testUserInteractionBeforeClientRender(
755 - <ControlledCheckbox onChange={() => changeCount++} />,
756 - true,
757 - false,
758 - 'checked',
759 - );
760 - expect(changeCount).toBe(0);
761 - });
762 -
763 - // skipping this test because React 15 does the wrong thing. it blows
764 - // away the user's typing in the textarea.
765 - xit('should not blow away user-entered text on successful reconnect to an uncontrolled textarea', () =>
766 - testUserInteractionBeforeClientRender(
767 - <textarea defaultValue="Hello" />,
768 - ));
769 -
770 - // skipping this test because React 15 does the wrong thing. it blows
771 - // away the user's typing in the textarea.
772 - xit('should not blow away user-entered text on successful reconnect to a controlled textarea', async () => {
773 - let changeCount = 0;
774 - await testUserInteractionBeforeClientRender(
775 - <ControlledTextArea onChange={() => changeCount++} />,
776 - );
777 - expect(changeCount).toBe(0);
778 - });
779 -
780 - it('should not blow away user-selected value on successful reconnect to an uncontrolled select', () =>
781 - testUserInteractionBeforeClientRender(
782 - <select defaultValue="Hello">
783 - <option key="1" value="Hello">
784 - Hello
785 - </option>
786 - <option key="2" value="Goodbye">
787 - Goodbye
788 - </option>
789 - </select>,
790 - ));
791 -
792 - it('should not blow away user-selected value on successful reconnect to an controlled select', async () => {
793 - let changeCount = 0;
794 - await testUserInteractionBeforeClientRender(
795 - <ControlledSelect onChange={() => changeCount++} />,
796 - );
797 - expect(changeCount).toBe(0);
798 - });
799 - });
800 - });
801 - });
802 -});
packages/react-dom/src/__tests__/ReactDOMServerIntegrationInput-test.js new
+86
@@ -0,0 +1,86 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its 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 +const ReactDOMServerIntegrationUtils = require('./utils/ReactDOMServerIntegrationTestUtils');
13 +
14 +let React;
15 +let ReactDOM;
16 +let ReactDOMServer;
17 +
18 +function initModules() {
19 + // Reset warning cache.
20 + jest.resetModuleRegistry();
21 + React = require('react');
22 + ReactDOM = require('react-dom');
23 + ReactDOMServer = require('react-dom/server');
24 +
25 + // Make them available to the helpers.
26 + return {
27 + ReactDOM,
28 + ReactDOMServer,
29 + };
30 +}
31 +
32 +const {resetModules, itRenders} = ReactDOMServerIntegrationUtils(initModules);
33 +
34 +describe('ReactDOMServerIntegrationInput', () => {
35 + beforeEach(() => {
36 + resetModules();
37 + });
38 +
39 + itRenders('an input with a value and an onChange', async render => {
40 + const e = await render(<input value="foo" onChange={() => {}} />);
41 + expect(e.value).toBe('foo');
42 + });
43 +
44 + itRenders('an input with a value and readOnly', async render => {
45 + const e = await render(<input value="foo" readOnly={true} />);
46 + expect(e.value).toBe('foo');
47 + });
48 +
49 + itRenders('an input with a value and no onChange/readOnly', async render => {
50 + // this configuration should raise a dev warning that value without
51 + // onChange or readOnly is a mistake.
52 + const e = await render(<input value="foo" />, 1);
53 + expect(e.value).toBe('foo');
54 + expect(e.getAttribute('value')).toBe('foo');
55 + });
56 +
57 + itRenders('an input with a defaultValue', async render => {
58 + const e = await render(<input defaultValue="foo" />);
59 + expect(e.value).toBe('foo');
60 + expect(e.getAttribute('value')).toBe('foo');
61 + expect(e.getAttribute('defaultValue')).toBe(null);
62 + });
63 +
64 + itRenders('an input value overriding defaultValue', async render => {
65 + const e = await render(
66 + <input value="foo" defaultValue="bar" readOnly={true} />,
67 + 1,
68 + );
69 + expect(e.value).toBe('foo');
70 + expect(e.getAttribute('value')).toBe('foo');
71 + expect(e.getAttribute('defaultValue')).toBe(null);
72 + });
73 +
74 + itRenders(
75 + 'an input value overriding defaultValue no matter the prop order',
76 + async render => {
77 + const e = await render(
78 + <input defaultValue="bar" value="foo" readOnly={true} />,
79 + 1,
80 + );
81 + expect(e.value).toBe('foo');
82 + expect(e.getAttribute('value')).toBe('foo');
83 + expect(e.getAttribute('defaultValue')).toBe(null);
84 + },
85 + );
86 +});
packages/react-dom/src/__tests__/ReactDOMServerIntegrationSelect-test.js new
+250
@@ -0,0 +1,250 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its 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 +const ReactDOMServerIntegrationUtils = require('./utils/ReactDOMServerIntegrationTestUtils');
13 +
14 +let React;
15 +let ReactDOM;
16 +let ReactDOMServer;
17 +
18 +function initModules() {
19 + // Reset warning cache.
20 + jest.resetModuleRegistry();
21 + React = require('react');
22 + ReactDOM = require('react-dom');
23 + ReactDOMServer = require('react-dom/server');
24 +
25 + // Make them available to the helpers.
26 + return {
27 + ReactDOM,
28 + ReactDOMServer,
29 + };
30 +}
31 +
32 +const {
33 + resetModules,
34 + itRenders,
35 + itThrowsWhenRendering,
36 +} = ReactDOMServerIntegrationUtils(initModules);
37 +
38 +describe('ReactDOMServerIntegrationSelect', () => {
39 + let options;
40 + beforeEach(() => {
41 + resetModules();
42 +
43 + options = [
44 + <option key={1} value="foo" id="foo">
45 + Foo
46 + </option>,
47 + <option key={2} value="bar" id="bar">
48 + Bar
49 + </option>,
50 + <option key={3} value="baz" id="baz">
51 + Baz
52 + </option>,
53 + ];
54 + });
55 +
56 + // a helper function to test the selected value of a <select> element.
57 + // takes in a <select> DOM element (element) and a value or array of
58 + // values that should be selected (selected).
59 + const expectSelectValue = (element, selected) => {
60 + if (!Array.isArray(selected)) {
61 + selected = [selected];
62 + }
63 + // the select DOM element shouldn't ever have a value or defaultValue
64 + // attribute; that is not how select values are expressed in the DOM.
65 + expect(element.getAttribute('value')).toBe(null);
66 + expect(element.getAttribute('defaultValue')).toBe(null);
67 +
68 + ['foo', 'bar', 'baz'].forEach(value => {
69 + const expectedValue = selected.indexOf(value) !== -1;
70 + const option = element.querySelector(`#${value}`);
71 + expect(option.selected).toBe(expectedValue);
72 + });
73 + };
74 +
75 + itRenders('a select with a value and an onChange', async render => {
76 + const e = await render(
77 + <select value="bar" onChange={() => {}}>
78 + {options}
79 + </select>,
80 + );
81 + expectSelectValue(e, 'bar');
82 + });
83 +
84 + itRenders('a select with a value and readOnly', async render => {
85 + const e = await render(
86 + <select value="bar" readOnly={true}>
87 + {options}
88 + </select>,
89 + );
90 + expectSelectValue(e, 'bar');
91 + });
92 +
93 + itRenders('a select with a multiple values and an onChange', async render => {
94 + const e = await render(
95 + <select value={['bar', 'baz']} multiple={true} onChange={() => {}}>
96 + {options}
97 + </select>,
98 + );
99 + expectSelectValue(e, ['bar', 'baz']);
100 + });
101 +
102 + itRenders('a select with a multiple values and readOnly', async render => {
103 + const e = await render(
104 + <select value={['bar', 'baz']} multiple={true} readOnly={true}>
105 + {options}
106 + </select>,
107 + );
108 + expectSelectValue(e, ['bar', 'baz']);
109 + });
110 +
111 + itRenders('a select with a value and no onChange/readOnly', async render => {
112 + // this configuration should raise a dev warning that value without
113 + // onChange or readOnly is a mistake.
114 + const e = await render(<select value="bar">{options}</select>, 1);
115 + expectSelectValue(e, 'bar');
116 + });
117 +
118 + itRenders('a select with a defaultValue', async render => {
119 + const e = await render(<select defaultValue="bar">{options}</select>);
120 + expectSelectValue(e, 'bar');
121 + });
122 +
123 + itRenders('a select value overriding defaultValue', async render => {
124 + const e = await render(
125 + <select value="bar" defaultValue="baz" readOnly={true}>
126 + {options}
127 + </select>,
128 + 1,
129 + );
130 + expectSelectValue(e, 'bar');
131 + });
132 +
133 + itRenders(
134 + 'a select with options that use dangerouslySetInnerHTML',
135 + async render => {
136 + const e = await render(
137 + <select defaultValue="baz" value="bar" readOnly={true}>
138 + <option
139 + id="foo"
140 + value="foo"
141 + dangerouslySetInnerHTML={{
142 + __html: 'Foo',
143 + }}>
144 + {undefined}
145 + </option>
146 + <option
147 + id="bar"
148 + value="bar"
149 + dangerouslySetInnerHTML={{
150 + __html: 'Bar',
151 + }}>
152 + {null}
153 + </option>
154 + <option
155 + id="baz"
156 + value="baz"
157 + dangerouslySetInnerHTML={{
158 + __html: 'Baz',
159 + }}
160 + />
161 + </select>,
162 + 1,
163 + );
164 + expectSelectValue(e, 'bar');
165 + },
166 + );
167 +
168 + itThrowsWhenRendering(
169 + 'a select with option that uses dangerouslySetInnerHTML and 0 as child',
170 + async render => {
171 + await render(
172 + <select defaultValue="baz" value="foo" readOnly={true}>
173 + <option
174 + id="foo"
175 + value="foo"
176 + dangerouslySetInnerHTML={{
177 + __html: 'Foo',
178 + }}>
179 + {0}
180 + </option>
181 + </select>,
182 + 1,
183 + );
184 + },
185 + 'Can only set one of `children` or `props.dangerouslySetInnerHTML`.',
186 + );
187 +
188 + itThrowsWhenRendering(
189 + 'a select with option that uses dangerouslySetInnerHTML and empty string as child',
190 + async render => {
191 + await render(
192 + <select defaultValue="baz" value="foo" readOnly={true}>
193 + <option
194 + id="foo"
195 + value="foo"
196 + dangerouslySetInnerHTML={{
197 + __html: 'Foo',
198 + }}>
199 + {''}
200 + </option>
201 + </select>,
202 + 1,
203 + );
204 + },
205 + 'Can only set one of `children` or `props.dangerouslySetInnerHTML`.',
206 + );
207 +
208 + itRenders(
209 + 'a select value overriding defaultValue no matter the prop order',
210 + async render => {
211 + const e = await render(
212 + <select defaultValue="baz" value="bar" readOnly={true}>
213 + {options}
214 + </select>,
215 + 1,
216 + );
217 + expectSelectValue(e, 'bar');
218 + },
219 + );
220 +
221 + itRenders('a select option with flattened children', async render => {
222 + const e = await render(
223 + <select value="bar" readOnly={true}>
224 + <option value="bar">A {'B'}</option>
225 + </select>,
226 + );
227 + const option = e.options[0];
228 + expect(option.childNodes.length).toBe(1);
229 + expect(option.childNodes[0].nodeType).toBe(3);
230 + expect(option.childNodes[0].nodeValue).toBe('A B');
231 + });
232 +
233 + itRenders(
234 + 'a select option with flattened children and a warning',
235 + async render => {
236 + const e = await render(
237 + <select readOnly={true} value="bar">
238 + <option value="bar">
239 + {['Bar', false, 'Foo', <div key="1" />, 'Baz']}
240 + </option>
241 + </select>,
242 + 1,
243 + );
244 + expect(e.getAttribute('value')).toBe(null);
245 + expect(e.getAttribute('defaultValue')).toBe(null);
246 + expect(e.firstChild.innerHTML).toBe('BarFoo[object Object]Baz');
247 + expect(e.firstChild.selected).toBe(true);
248 + },
249 + );
250 +});
packages/react-dom/src/__tests__/ReactDOMServerIntegrationTextarea-test.js new
+97
@@ -0,0 +1,97 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its 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 +const ReactDOMServerIntegrationUtils = require('./utils/ReactDOMServerIntegrationTestUtils');
13 +
14 +let React;
15 +let ReactDOM;
16 +let ReactDOMServer;
17 +
18 +function initModules() {
19 + // Reset warning cache.
20 + jest.resetModuleRegistry();
21 + React = require('react');
22 + ReactDOM = require('react-dom');
23 + ReactDOMServer = require('react-dom/server');
24 +
25 + // Make them available to the helpers.
26 + return {
27 + ReactDOM,
28 + ReactDOMServer,
29 + };
30 +}
31 +
32 +const {resetModules, itRenders} = ReactDOMServerIntegrationUtils(initModules);
33 +
34 +describe('ReactDOMServerIntegrationTextarea', () => {
35 + beforeEach(() => {
36 + resetModules();
37 + });
38 +
39 + // textareas
40 + // ---------
41 + itRenders('a textarea with a value and an onChange', async render => {
42 + const e = await render(<textarea value="foo" onChange={() => {}} />);
43 + // textarea DOM elements don't have a value **attribute**, the text is
44 + // a child of the element and accessible via the .value **property**.
45 + expect(e.getAttribute('value')).toBe(null);
46 + expect(e.value).toBe('foo');
47 + });
48 +
49 + itRenders('a textarea with a value and readOnly', async render => {
50 + const e = await render(<textarea value="foo" readOnly={true} />);
51 + // textarea DOM elements don't have a value **attribute**, the text is
52 + // a child of the element and accessible via the .value **property**.
53 + expect(e.getAttribute('value')).toBe(null);
54 + expect(e.value).toBe('foo');
55 + });
56 +
57 + itRenders(
58 + 'a textarea with a value and no onChange/readOnly',
59 + async render => {
60 + // this configuration should raise a dev warning that value without
61 + // onChange or readOnly is a mistake.
62 + const e = await render(<textarea value="foo" />, 1);
63 + expect(e.getAttribute('value')).toBe(null);
64 + expect(e.value).toBe('foo');
65 + },
66 + );
67 +
68 + itRenders('a textarea with a defaultValue', async render => {
69 + const e = await render(<textarea defaultValue="foo" />);
70 + expect(e.getAttribute('value')).toBe(null);
71 + expect(e.getAttribute('defaultValue')).toBe(null);
72 + expect(e.value).toBe('foo');
73 + });
74 +
75 + itRenders('a textarea value overriding defaultValue', async render => {
76 + const e = await render(
77 + <textarea value="foo" defaultValue="bar" readOnly={true} />,
78 + 1,
79 + );
80 + expect(e.getAttribute('value')).toBe(null);
81 + expect(e.getAttribute('defaultValue')).toBe(null);
82 + expect(e.value).toBe('foo');
83 + });
84 +
85 + itRenders(
86 + 'a textarea value overriding defaultValue no matter the prop order',
87 + async render => {
88 + const e = await render(
89 + <textarea defaultValue="bar" value="foo" readOnly={true} />,
90 + 1,
91 + );
92 + expect(e.getAttribute('value')).toBe(null);
93 + expect(e.getAttribute('defaultValue')).toBe(null);
94 + expect(e.value).toBe('foo');
95 + },
96 + );
97 +});
packages/react-dom/src/__tests__/ReactDOMServerIntegrationUserInteraction-test.js new
+366
@@ -0,0 +1,366 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its 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 +const ReactDOMServerIntegrationUtils = require('./utils/ReactDOMServerIntegrationTestUtils');
13 +
14 +let React;
15 +let ReactDOM;
16 +let ReactDOMServer;
17 +
18 +function initModules() {
19 + // Reset warning cache.
20 + jest.resetModuleRegistry();
21 + React = require('react');
22 + ReactDOM = require('react-dom');
23 + ReactDOMServer = require('react-dom/server');
24 +
25 + // Make them available to the helpers.
26 + return {
27 + ReactDOM,
28 + ReactDOMServer,
29 + };
30 +}
31 +
32 +const {
33 + resetModules,
34 + itClientRenders,
35 + renderIntoDom,
36 + serverRender,
37 +} = ReactDOMServerIntegrationUtils(initModules);
38 +
39 +describe('ReactDOMServerIntegrationUserInteraction', () => {
40 + let ControlledInput, ControlledTextArea, ControlledCheckbox, ControlledSelect;
41 +
42 + beforeEach(() => {
43 + resetModules();
44 + ControlledInput = class extends React.Component {
45 + static defaultProps = {
46 + type: 'text',
47 + initialValue: 'Hello',
48 + };
49 + constructor() {
50 + super(...arguments);
51 + this.state = {value: this.props.initialValue};
52 + }
53 + handleChange(event) {
54 + if (this.props.onChange) {
55 + this.props.onChange(event);
56 + }
57 + this.setState({value: event.target.value});
58 + }
59 + render() {
60 + return (
61 + <input
62 + type={this.props.type}
63 + value={this.state.value}
64 + onChange={this.handleChange.bind(this)}
65 + />
66 + );
67 + }
68 + };
69 + ControlledTextArea = class extends React.Component {
70 + constructor() {
71 + super();
72 + this.state = {value: 'Hello'};
73 + }
74 + handleChange(event) {
75 + if (this.props.onChange) {
76 + this.props.onChange(event);
77 + }
78 + this.setState({value: event.target.value});
79 + }
80 + render() {
81 + return (
82 + <textarea
83 + value={this.state.value}
84 + onChange={this.handleChange.bind(this)}
85 + />
86 + );
87 + }
88 + };
89 + ControlledCheckbox = class extends React.Component {
90 + constructor() {
91 + super();
92 + this.state = {value: true};
93 + }
94 + handleChange(event) {
95 + if (this.props.onChange) {
96 + this.props.onChange(event);
97 + }
98 + this.setState({value: event.target.checked});
99 + }
100 + render() {
101 + return (
102 + <input
103 + type="checkbox"
104 + checked={this.state.value}
105 + onChange={this.handleChange.bind(this)}
106 + />
107 + );
108 + }
109 + };
110 + ControlledSelect = class extends React.Component {
111 + constructor() {
112 + super();
113 + this.state = {value: 'Hello'};
114 + }
115 + handleChange(event) {
116 + if (this.props.onChange) {
117 + this.props.onChange(event);
118 + }
119 + this.setState({value: event.target.value});
120 + }
121 + render() {
122 + return (
123 + <select
124 + value={this.state.value}
125 + onChange={this.handleChange.bind(this)}>
126 + <option key="1" value="Hello">
127 + Hello
128 + </option>
129 + <option key="2" value="Goodbye">
130 + Goodbye
131 + </option>
132 + </select>
133 + );
134 + }
135 + };
136 + });
137 +
138 + describe('user interaction with controlled inputs', function() {
139 + itClientRenders('a controlled text input', async render => {
140 + const setUntrackedValue = Object.getOwnPropertyDescriptor(
141 + HTMLInputElement.prototype,
142 + 'value',
143 + ).set;
144 +
145 + let changeCount = 0;
146 + const e = await render(
147 + <ControlledInput onChange={() => changeCount++} />,
148 + );
149 + const container = e.parentNode;
150 + document.body.appendChild(container);
151 +
152 + try {
153 + expect(changeCount).toBe(0);
154 + expect(e.value).toBe('Hello');
155 +
156 + // simulate a user typing.
157 + setUntrackedValue.call(e, 'Goodbye');
158 + e.dispatchEvent(new Event('input', {bubbles: true, cancelable: false}));
159 +
160 + expect(changeCount).toBe(1);
161 + expect(e.value).toBe('Goodbye');
162 + } finally {
163 + document.body.removeChild(container);
164 + }
165 + });
166 +
167 + itClientRenders('a controlled textarea', async render => {
168 + const setUntrackedValue = Object.getOwnPropertyDescriptor(
169 + HTMLTextAreaElement.prototype,
170 + 'value',
171 + ).set;
172 +
173 + let changeCount = 0;
174 + const e = await render(
175 + <ControlledTextArea onChange={() => changeCount++} />,
176 + );
177 + const container = e.parentNode;
178 + document.body.appendChild(container);
179 +
180 + try {
181 + expect(changeCount).toBe(0);
182 + expect(e.value).toBe('Hello');
183 +
184 + // simulate a user typing.
185 + setUntrackedValue.call(e, 'Goodbye');
186 + e.dispatchEvent(new Event('input', {bubbles: true, cancelable: false}));
187 +
188 + expect(changeCount).toBe(1);
189 + expect(e.value).toBe('Goodbye');
190 + } finally {
191 + document.body.removeChild(container);
192 + }
193 + });
194 +
195 + itClientRenders('a controlled checkbox', async render => {
196 + let changeCount = 0;
197 + const e = await render(
198 + <ControlledCheckbox onChange={() => changeCount++} />,
199 + );
200 + const container = e.parentNode;
201 + document.body.appendChild(container);
202 +
203 + try {
204 + expect(changeCount).toBe(0);
205 + expect(e.checked).toBe(true);
206 +
207 + // simulate a user clicking.
208 + e.dispatchEvent(new Event('click', {bubbles: true, cancelable: true}));
209 +
210 + expect(changeCount).toBe(1);
211 + expect(e.checked).toBe(false);
212 + } finally {
213 + document.body.removeChild(container);
214 + }
215 + });
216 +
217 + itClientRenders('a controlled select', async render => {
218 + const setUntrackedValue = Object.getOwnPropertyDescriptor(
219 + HTMLSelectElement.prototype,
220 + 'value',
221 + ).set;
222 +
223 + let changeCount = 0;
224 + const e = await render(
225 + <ControlledSelect onChange={() => changeCount++} />,
226 + );
227 + const container = e.parentNode;
228 + document.body.appendChild(container);
229 +
230 + try {
231 + expect(changeCount).toBe(0);
232 + expect(e.value).toBe('Hello');
233 +
234 + // simulate a user typing.
235 + setUntrackedValue.call(e, 'Goodbye');
236 + e.dispatchEvent(
237 + new Event('change', {bubbles: true, cancelable: false}),
238 + );
239 +
240 + expect(changeCount).toBe(1);
241 + expect(e.value).toBe('Goodbye');
242 + } finally {
243 + document.body.removeChild(container);
244 + }
245 + });
246 + });
247 +
248 + describe('user interaction with inputs before client render', function() {
249 + // renders the element and changes the value **before** the client
250 + // code has a chance to render; this simulates what happens when a
251 + // user starts to interact with a server-rendered form before
252 + // ReactDOM.render is called. the client render should NOT blow away
253 + // the changes the user has made.
254 + const testUserInteractionBeforeClientRender = async (
255 + element,
256 + initialValue = 'Hello',
257 + changedValue = 'Goodbye',
258 + valueKey = 'value',
259 + ) => {
260 + const field = await serverRender(element);
261 + expect(field[valueKey]).toBe(initialValue);
262 +
263 + // simulate a user typing in the field **before** client-side reconnect happens.
264 + field[valueKey] = changedValue;
265 +
266 + resetModules();
267 + // client render on top of the server markup.
268 + const clientField = await renderIntoDom(element, field.parentNode, true);
269 + // verify that the input field was not replaced.
270 + // Note that we cannot use expect(clientField).toBe(field) because
271 + // of jest bug #1772
272 + expect(clientField === field).toBe(true);
273 + // confirm that the client render has not changed what the user typed.
274 + expect(clientField[valueKey]).toBe(changedValue);
275 + };
276 +
277 + it('should not blow away user-entered text on successful reconnect to an uncontrolled input', () =>
278 + testUserInteractionBeforeClientRender(<input defaultValue="Hello" />));
279 +
280 + it('should not blow away user-entered text on successful reconnect to a controlled input', async () => {
281 + let changeCount = 0;
282 + await testUserInteractionBeforeClientRender(
283 + <ControlledInput onChange={() => changeCount++} />,
284 + );
285 + // note that there's a strong argument to be made that the DOM revival
286 + // algorithm should notice that the user has changed the value and fire
287 + // an onChange. however, it does not now, so that's what this tests.
288 + expect(changeCount).toBe(0);
289 + });
290 +
291 + it('should not blow away user-interaction on successful reconnect to an uncontrolled range input', () =>
292 + testUserInteractionBeforeClientRender(
293 + <input type="text" defaultValue="0.5" />,
294 + '0.5',
295 + '1',
296 + ));
297 +
298 + it('should not blow away user-interaction on successful reconnect to a controlled range input', async () => {
299 + let changeCount = 0;
300 + await testUserInteractionBeforeClientRender(
301 + <ControlledInput
302 + type="range"
303 + initialValue="0.25"
304 + onChange={() => changeCount++}
305 + />,
306 + '0.25',
307 + '1',
308 + );
309 + expect(changeCount).toBe(0);
310 + });
311 +
312 + it('should not blow away user-entered text on successful reconnect to an uncontrolled checkbox', () =>
313 + testUserInteractionBeforeClientRender(
314 + <input type="checkbox" defaultChecked={true} />,
315 + true,
316 + false,
317 + 'checked',
318 + ));
319 +
320 + it('should not blow away user-entered text on successful reconnect to a controlled checkbox', async () => {
321 + let changeCount = 0;
322 + await testUserInteractionBeforeClientRender(
323 + <ControlledCheckbox onChange={() => changeCount++} />,
324 + true,
325 + false,
326 + 'checked',
327 + );
328 + expect(changeCount).toBe(0);
329 + });
330 +
331 + // skipping this test because React 15 does the wrong thing. it blows
332 + // away the user's typing in the textarea.
333 + xit('should not blow away user-entered text on successful reconnect to an uncontrolled textarea', () =>
334 + testUserInteractionBeforeClientRender(<textarea defaultValue="Hello" />));
335 +
336 + // skipping this test because React 15 does the wrong thing. it blows
337 + // away the user's typing in the textarea.
338 + xit('should not blow away user-entered text on successful reconnect to a controlled textarea', async () => {
339 + let changeCount = 0;
340 + await testUserInteractionBeforeClientRender(
341 + <ControlledTextArea onChange={() => changeCount++} />,
342 + );
343 + expect(changeCount).toBe(0);
344 + });
345 +
346 + it('should not blow away user-selected value on successful reconnect to an uncontrolled select', () =>
347 + testUserInteractionBeforeClientRender(
348 + <select defaultValue="Hello">
349 + <option key="1" value="Hello">
350 + Hello
351 + </option>
352 + <option key="2" value="Goodbye">
353 + Goodbye
354 + </option>
355 + </select>,
356 + ));
357 +
358 + it('should not blow away user-selected value on successful reconnect to an controlled select', async () => {
359 + let changeCount = 0;
360 + await testUserInteractionBeforeClientRender(
361 + <ControlledSelect onChange={() => changeCount++} />,
362 + );
363 + expect(changeCount).toBe(0);
364 + });
365 + });
366 +});
packages/react-dom/src/__tests__/ReactDOMserverIntegrationProgress-test.js new
+46
@@ -0,0 +1,46 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its 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 +const ReactDOMServerIntegrationUtils = require('./utils/ReactDOMServerIntegrationTestUtils');
13 +
14 +let React;
15 +let ReactDOM;
16 +let ReactDOMServer;
17 +
18 +function initModules() {
19 + // Reset warning cache.
20 + jest.resetModuleRegistry();
21 + React = require('react');
22 + ReactDOM = require('react-dom');
23 + ReactDOMServer = require('react-dom/server');
24 +
25 + // Make them available to the helpers.
26 + return {
27 + ReactDOM,
28 + ReactDOMServer,
29 + };
30 +}
31 +
32 +const {resetModules, itRenders} = ReactDOMServerIntegrationUtils(initModules);
33 +
34 +describe('ReactDOMServerIntegrationProgress', () => {
35 + beforeEach(() => {
36 + resetModules();
37 + });
38 +
39 + itRenders('a progress in an indeterminate state', async render => {
40 + // Regression test for https://github.com/facebook/react/issues/6119
41 + const e = await render(<progress value={null} />);
42 + expect(e.hasAttribute('value')).toBe(false);
43 + const e2 = await render(<progress value={50} />);
44 + expect(e2.getAttribute('value')).toBe('50');
45 + });
46 +});