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. // Set by `yarn test-fire`.
    
  13. const {disableInputAttributeSyncing} = require('shared/ReactFeatureFlags');
    
  14. 
    
  15. function emptyFunction() {}
    
  16. 
    
  17. describe('ReactDOMInput', () => {
    
  18.   let React;
    
  19.   let ReactDOM;
    
  20.   let ReactDOMClient;
    
  21.   let ReactDOMServer;
    
  22.   let Scheduler;
    
  23.   let act;
    
  24.   let assertLog;
    
  25.   let setUntrackedValue;
    
  26.   let setUntrackedChecked;
    
  27.   let container;
    
  28. 
    
  29.   function dispatchEventOnNode(node, type) {
    
  30.     node.dispatchEvent(new Event(type, {bubbles: true, cancelable: true}));
    
  31.   }
    
  32. 
    
  33.   function isValueDirty(node) {
    
  34.     // Return the "dirty value flag" as defined in the HTML spec. Cast to text
    
  35.     // input to sidestep complicated value sanitization behaviors.
    
  36.     const copy = node.cloneNode();
    
  37.     copy.type = 'text';
    
  38.     // If modifying the attribute now doesn't change the value, the value was already detached.
    
  39.     copy.defaultValue += Math.random();
    
  40.     return copy.value === node.value;
    
  41.   }
    
  42. 
    
  43.   function isCheckedDirty(node) {
    
  44.     // Return the "dirty checked flag" as defined in the HTML spec.
    
  45.     if (node.checked !== node.defaultChecked) {
    
  46.       return true;
    
  47.     }
    
  48.     const copy = node.cloneNode();
    
  49.     copy.type = 'checkbox';
    
  50.     copy.defaultChecked = !copy.defaultChecked;
    
  51.     return copy.checked === node.checked;
    
  52.   }
    
  53. 
    
  54.   function getTrackedAndCurrentInputValue(elem: HTMLElement): [mixed, mixed] {
    
  55.     const tracker = elem._valueTracker;
    
  56.     if (!tracker) {
    
  57.       throw new Error('No input tracker');
    
  58.     }
    
  59.     return [
    
  60.       tracker.getValue(),
    
  61.       elem.nodeName === 'INPUT' &&
    
  62.       (elem.type === 'checkbox' || elem.type === 'radio')
    
  63.         ? String(elem.checked)
    
  64.         : elem.value,
    
  65.     ];
    
  66.   }
    
  67. 
    
  68.   function assertInputTrackingIsCurrent(parent) {
    
  69.     parent.querySelectorAll('input, textarea, select').forEach(input => {
    
  70.       const [trackedValue, currentValue] =
    
  71.         getTrackedAndCurrentInputValue(input);
    
  72.       if (trackedValue !== currentValue) {
    
  73.         throw new Error(
    
  74.           `Input ${input.outerHTML} is currently ${currentValue} but tracker thinks it's ${trackedValue}`,
    
  75.         );
    
  76.       }
    
  77.     });
    
  78.   }
    
  79. 
    
  80.   beforeEach(() => {
    
  81.     jest.resetModules();
    
  82. 
    
  83.     setUntrackedValue = Object.getOwnPropertyDescriptor(
    
  84.       HTMLInputElement.prototype,
    
  85.       'value',
    
  86.     ).set;
    
  87.     setUntrackedChecked = Object.getOwnPropertyDescriptor(
    
  88.       HTMLInputElement.prototype,
    
  89.       'checked',
    
  90.     ).set;
    
  91. 
    
  92.     React = require('react');
    
  93.     ReactDOM = require('react-dom');
    
  94.     ReactDOMClient = require('react-dom/client');
    
  95.     ReactDOMServer = require('react-dom/server');
    
  96.     Scheduler = require('scheduler');
    
  97.     act = require('internal-test-utils').act;
    
  98.     assertLog = require('internal-test-utils').assertLog;
    
  99. 
    
  100.     container = document.createElement('div');
    
  101.     document.body.appendChild(container);
    
  102.   });
    
  103. 
    
  104.   afterEach(() => {
    
  105.     document.body.removeChild(container);
    
  106.     jest.restoreAllMocks();
    
  107.   });
    
  108. 
    
  109.   it('should warn for controlled value of 0 with missing onChange', () => {
    
  110.     expect(() => {
    
  111.       ReactDOM.render(<input type="text" value={0} />, container);
    
  112.     }).toErrorDev(
    
  113.       'Warning: You provided a `value` prop to a form field without an `onChange` handler.',
    
  114.     );
    
  115.   });
    
  116. 
    
  117.   it('should warn for controlled value of "" with missing onChange', () => {
    
  118.     expect(() => {
    
  119.       ReactDOM.render(<input type="text" value="" />, container);
    
  120.     }).toErrorDev(
    
  121.       'Warning: You provided a `value` prop to a form field without an `onChange` handler.',
    
  122.     );
    
  123.   });
    
  124. 
    
  125.   it('should warn for controlled value of "0" with missing onChange', () => {
    
  126.     expect(() => {
    
  127.       ReactDOM.render(<input type="text" value="0" />, container);
    
  128.     }).toErrorDev(
    
  129.       'Warning: You provided a `value` prop to a form field without an `onChange` handler.',
    
  130.     );
    
  131.   });
    
  132. 
    
  133.   it('should warn for controlled value of false with missing onChange', () => {
    
  134.     expect(() =>
    
  135.       ReactDOM.render(<input type="checkbox" checked={false} />, container),
    
  136.     ).toErrorDev(
    
  137.       'Warning: You provided a `checked` prop to a form field without an `onChange` handler.',
    
  138.     );
    
  139.   });
    
  140. 
    
  141.   it('should warn with checked and no onChange handler with readOnly specified', () => {
    
  142.     ReactDOM.render(
    
  143.       <input type="checkbox" checked={false} readOnly={true} />,
    
  144.       container,
    
  145.     );
    
  146.     ReactDOM.unmountComponentAtNode(container);
    
  147. 
    
  148.     expect(() =>
    
  149.       ReactDOM.render(
    
  150.         <input type="checkbox" checked={false} readOnly={false} />,
    
  151.         container,
    
  152.       ),
    
  153.     ).toErrorDev(
    
  154.       'Warning: You provided a `checked` prop to a form field without an `onChange` handler. ' +
    
  155.         'This will render a read-only field. If the field should be mutable use `defaultChecked`. ' +
    
  156.         'Otherwise, set either `onChange` or `readOnly`.',
    
  157.     );
    
  158.   });
    
  159. 
    
  160.   it('should not warn about missing onChange in uncontrolled inputs', () => {
    
  161.     ReactDOM.render(<input />, container);
    
  162.     ReactDOM.unmountComponentAtNode(container);
    
  163.     ReactDOM.render(<input value={undefined} />, container);
    
  164.     ReactDOM.unmountComponentAtNode(container);
    
  165.     ReactDOM.render(<input type="text" />, container);
    
  166.     ReactDOM.unmountComponentAtNode(container);
    
  167.     ReactDOM.render(<input type="text" value={undefined} />, container);
    
  168.     ReactDOM.unmountComponentAtNode(container);
    
  169.     ReactDOM.render(<input type="checkbox" />, container);
    
  170.     ReactDOM.unmountComponentAtNode(container);
    
  171.     ReactDOM.render(<input type="checkbox" checked={undefined} />, container);
    
  172.   });
    
  173. 
    
  174.   it('should not warn with value and onInput handler', () => {
    
  175.     ReactDOM.render(<input value="..." onInput={() => {}} />, container);
    
  176.   });
    
  177. 
    
  178.   it('should properly control a value even if no event listener exists', () => {
    
  179.     let node;
    
  180. 
    
  181.     expect(() => {
    
  182.       node = ReactDOM.render(<input type="text" value="lion" />, container);
    
  183.     }).toErrorDev(
    
  184.       'Warning: You provided a `value` prop to a form field without an `onChange` handler.',
    
  185.     );
    
  186.     expect(isValueDirty(node)).toBe(true);
    
  187. 
    
  188.     setUntrackedValue.call(node, 'giraffe');
    
  189. 
    
  190.     // This must use the native event dispatching. If we simulate, we will
    
  191.     // bypass the lazy event attachment system so we won't actually test this.
    
  192.     dispatchEventOnNode(node, 'input');
    
  193. 
    
  194.     expect(node.value).toBe('lion');
    
  195.     expect(isValueDirty(node)).toBe(true);
    
  196.   });
    
  197. 
    
  198.   it('should control a value in reentrant events', () => {
    
  199.     class ControlledInputs extends React.Component {
    
  200.       state = {value: 'lion'};
    
  201.       a = null;
    
  202.       b = null;
    
  203.       switchedFocus = false;
    
  204.       change(newValue) {
    
  205.         this.setState({value: newValue});
    
  206.         // Calling focus here will blur the text box which causes a native
    
  207.         // change event. Ideally we shouldn't have to fire this ourselves.
    
  208.         // Don't remove unless you've verified the fix in #8240 is still covered.
    
  209.         dispatchEventOnNode(this.a, 'input');
    
  210.         this.b.focus();
    
  211.       }
    
  212.       blur(currentValue) {
    
  213.         this.switchedFocus = true;
    
  214.         // currentValue should be 'giraffe' here because we should not have
    
  215.         // restored it on the target yet.
    
  216.         this.setState({value: currentValue});
    
  217.       }
    
  218.       render() {
    
  219.         return (
    
  220.           <div>
    
  221.             <input
    
  222.               type="text"
    
  223.               ref={n => (this.a = n)}
    
  224.               value={this.state.value}
    
  225.               onChange={e => this.change(e.target.value)}
    
  226.               onBlur={e => this.blur(e.target.value)}
    
  227.             />
    
  228.             <input type="text" ref={n => (this.b = n)} />
    
  229.           </div>
    
  230.         );
    
  231.       }
    
  232.     }
    
  233. 
    
  234.     const instance = ReactDOM.render(<ControlledInputs />, container);
    
  235. 
    
  236.     // Focus the field so we can later blur it.
    
  237.     // Don't remove unless you've verified the fix in #8240 is still covered.
    
  238.     instance.a.focus();
    
  239.     setUntrackedValue.call(instance.a, 'giraffe');
    
  240.     // This must use the native event dispatching. If we simulate, we will
    
  241.     // bypass the lazy event attachment system so we won't actually test this.
    
  242.     dispatchEventOnNode(instance.a, 'input');
    
  243.     dispatchEventOnNode(instance.a, 'blur');
    
  244.     dispatchEventOnNode(instance.a, 'focusout');
    
  245. 
    
  246.     expect(instance.a.value).toBe('giraffe');
    
  247.     expect(instance.switchedFocus).toBe(true);
    
  248.   });
    
  249. 
    
  250.   it('should control values in reentrant events with different targets', () => {
    
  251.     class ControlledInputs extends React.Component {
    
  252.       state = {value: 'lion'};
    
  253.       a = null;
    
  254.       b = null;
    
  255.       change(newValue) {
    
  256.         // This click will change the checkbox's value to false. Then it will
    
  257.         // invoke an inner change event. When we finally, flush, we need to
    
  258.         // reset the checkbox's value to true since that is its controlled
    
  259.         // value.
    
  260.         this.b.click();
    
  261.       }
    
  262.       render() {
    
  263.         return (
    
  264.           <div>
    
  265.             <input
    
  266.               type="text"
    
  267.               ref={n => (this.a = n)}
    
  268.               value="lion"
    
  269.               onChange={e => this.change(e.target.value)}
    
  270.             />
    
  271.             <input
    
  272.               type="checkbox"
    
  273.               ref={n => (this.b = n)}
    
  274.               checked={true}
    
  275.               onChange={() => {}}
    
  276.             />
    
  277.           </div>
    
  278.         );
    
  279.       }
    
  280.     }
    
  281. 
    
  282.     const instance = ReactDOM.render(<ControlledInputs />, container);
    
  283. 
    
  284.     setUntrackedValue.call(instance.a, 'giraffe');
    
  285.     // This must use the native event dispatching. If we simulate, we will
    
  286.     // bypass the lazy event attachment system so we won't actually test this.
    
  287.     dispatchEventOnNode(instance.a, 'input');
    
  288. 
    
  289.     expect(instance.a.value).toBe('lion');
    
  290.     expect(instance.b.checked).toBe(true);
    
  291.   });
    
  292. 
    
  293.   describe('switching text inputs between numeric and string numbers', () => {
    
  294.     it('does change the number 2 to "2.0" with no change handler', () => {
    
  295.       const stub = <input type="text" value={2} onChange={jest.fn()} />;
    
  296.       const node = ReactDOM.render(stub, container);
    
  297. 
    
  298.       setUntrackedValue.call(node, '2.0');
    
  299.       dispatchEventOnNode(node, 'input');
    
  300. 
    
  301.       expect(node.value).toBe('2');
    
  302.       if (disableInputAttributeSyncing) {
    
  303.         expect(node.hasAttribute('value')).toBe(false);
    
  304.       } else {
    
  305.         expect(node.getAttribute('value')).toBe('2');
    
  306.       }
    
  307.     });
    
  308. 
    
  309.     it('does change the string "2" to "2.0" with no change handler', () => {
    
  310.       const stub = <input type="text" value={'2'} onChange={jest.fn()} />;
    
  311.       const node = ReactDOM.render(stub, container);
    
  312. 
    
  313.       setUntrackedValue.call(node, '2.0');
    
  314.       dispatchEventOnNode(node, 'input');
    
  315. 
    
  316.       expect(node.value).toBe('2');
    
  317.       if (disableInputAttributeSyncing) {
    
  318.         expect(node.hasAttribute('value')).toBe(false);
    
  319.       } else {
    
  320.         expect(node.getAttribute('value')).toBe('2');
    
  321.       }
    
  322.     });
    
  323. 
    
  324.     it('changes the number 2 to "2.0" using a change handler', () => {
    
  325.       class Stub extends React.Component {
    
  326.         state = {
    
  327.           value: 2,
    
  328.         };
    
  329.         onChange = event => {
    
  330.           this.setState({value: event.target.value});
    
  331.         };
    
  332.         render() {
    
  333.           const {value} = this.state;
    
  334. 
    
  335.           return <input type="text" value={value} onChange={this.onChange} />;
    
  336.         }
    
  337.       }
    
  338. 
    
  339.       const stub = ReactDOM.render(<Stub />, container);
    
  340.       const node = ReactDOM.findDOMNode(stub);
    
  341. 
    
  342.       setUntrackedValue.call(node, '2.0');
    
  343.       dispatchEventOnNode(node, 'input');
    
  344. 
    
  345.       expect(node.value).toBe('2.0');
    
  346.       if (disableInputAttributeSyncing) {
    
  347.         expect(node.hasAttribute('value')).toBe(false);
    
  348.       } else {
    
  349.         expect(node.getAttribute('value')).toBe('2.0');
    
  350.       }
    
  351.     });
    
  352.   });
    
  353. 
    
  354.   it('does change the string ".98" to "0.98" with no change handler', () => {
    
  355.     class Stub extends React.Component {
    
  356.       state = {
    
  357.         value: '.98',
    
  358.       };
    
  359.       render() {
    
  360.         return <input type="number" value={this.state.value} />;
    
  361.       }
    
  362.     }
    
  363. 
    
  364.     let stub;
    
  365.     expect(() => {
    
  366.       stub = ReactDOM.render(<Stub />, container);
    
  367.     }).toErrorDev(
    
  368.       'You provided a `value` prop to a form field ' +
    
  369.         'without an `onChange` handler.',
    
  370.     );
    
  371.     const node = ReactDOM.findDOMNode(stub);
    
  372.     stub.setState({value: '0.98'});
    
  373. 
    
  374.     expect(node.value).toEqual('0.98');
    
  375.   });
    
  376. 
    
  377.   it('performs a state change from "" to 0', () => {
    
  378.     class Stub extends React.Component {
    
  379.       state = {
    
  380.         value: '',
    
  381.       };
    
  382.       render() {
    
  383.         return <input type="number" value={this.state.value} readOnly={true} />;
    
  384.       }
    
  385.     }
    
  386. 
    
  387.     const stub = ReactDOM.render(<Stub />, container);
    
  388.     const node = ReactDOM.findDOMNode(stub);
    
  389.     stub.setState({value: 0});
    
  390. 
    
  391.     expect(node.value).toEqual('0');
    
  392.   });
    
  393. 
    
  394.   it('updates the value on radio buttons from "" to 0', function () {
    
  395.     ReactDOM.render(
    
  396.       <input type="radio" value="" onChange={function () {}} />,
    
  397.       container,
    
  398.     );
    
  399.     ReactDOM.render(
    
  400.       <input type="radio" value={0} onChange={function () {}} />,
    
  401.       container,
    
  402.     );
    
  403.     expect(container.firstChild.value).toBe('0');
    
  404.     expect(container.firstChild.getAttribute('value')).toBe('0');
    
  405.   });
    
  406. 
    
  407.   it('updates the value on checkboxes from "" to 0', function () {
    
  408.     ReactDOM.render(
    
  409.       <input type="checkbox" value="" onChange={function () {}} />,
    
  410.       container,
    
  411.     );
    
  412.     ReactDOM.render(
    
  413.       <input type="checkbox" value={0} onChange={function () {}} />,
    
  414.       container,
    
  415.     );
    
  416.     expect(container.firstChild.value).toBe('0');
    
  417.     expect(container.firstChild.getAttribute('value')).toBe('0');
    
  418.   });
    
  419. 
    
  420.   it('distinguishes precision for extra zeroes in string number values', () => {
    
  421.     class Stub extends React.Component {
    
  422.       state = {
    
  423.         value: '3.0000',
    
  424.       };
    
  425.       render() {
    
  426.         return <input type="number" value={this.state.value} />;
    
  427.       }
    
  428.     }
    
  429. 
    
  430.     let stub;
    
  431. 
    
  432.     expect(() => {
    
  433.       stub = ReactDOM.render(<Stub />, container);
    
  434.     }).toErrorDev(
    
  435.       'You provided a `value` prop to a form field ' +
    
  436.         'without an `onChange` handler.',
    
  437.     );
    
  438.     const node = ReactDOM.findDOMNode(stub);
    
  439.     stub.setState({value: '3'});
    
  440. 
    
  441.     expect(node.value).toEqual('3');
    
  442.   });
    
  443. 
    
  444.   it('should display `defaultValue` of number 0', () => {
    
  445.     const stub = <input type="text" defaultValue={0} />;
    
  446.     const node = ReactDOM.render(stub, container);
    
  447. 
    
  448.     expect(node.getAttribute('value')).toBe('0');
    
  449.     expect(node.value).toBe('0');
    
  450.   });
    
  451. 
    
  452.   it('only assigns defaultValue if it changes', () => {
    
  453.     class Test extends React.Component {
    
  454.       render() {
    
  455.         return <input defaultValue="0" />;
    
  456.       }
    
  457.     }
    
  458. 
    
  459.     const component = ReactDOM.render(<Test />, container);
    
  460.     const node = ReactDOM.findDOMNode(component);
    
  461. 
    
  462.     Object.defineProperty(node, 'defaultValue', {
    
  463.       get() {
    
  464.         return '0';
    
  465.       },
    
  466.       set(value) {
    
  467.         throw new Error(
    
  468.           `defaultValue was assigned ${value}, but it did not change!`,
    
  469.         );
    
  470.       },
    
  471.     });
    
  472. 
    
  473.     component.forceUpdate();
    
  474.   });
    
  475. 
    
  476.   it('should display "true" for `defaultValue` of `true`', () => {
    
  477.     const stub = <input type="text" defaultValue={true} />;
    
  478.     const node = ReactDOM.render(stub, container);
    
  479. 
    
  480.     expect(node.value).toBe('true');
    
  481.   });
    
  482. 
    
  483.   it('should display "false" for `defaultValue` of `false`', () => {
    
  484.     const stub = <input type="text" defaultValue={false} />;
    
  485.     const node = ReactDOM.render(stub, container);
    
  486. 
    
  487.     expect(node.value).toBe('false');
    
  488.   });
    
  489. 
    
  490.   it('should update `defaultValue` for uncontrolled input', () => {
    
  491.     const node = ReactDOM.render(
    
  492.       <input type="text" defaultValue="0" />,
    
  493.       container,
    
  494.     );
    
  495. 
    
  496.     expect(node.value).toBe('0');
    
  497.     expect(node.defaultValue).toBe('0');
    
  498.     if (disableInputAttributeSyncing) {
    
  499.       expect(isValueDirty(node)).toBe(false);
    
  500.     } else {
    
  501.       expect(isValueDirty(node)).toBe(true);
    
  502.     }
    
  503. 
    
  504.     ReactDOM.render(<input type="text" defaultValue="1" />, container);
    
  505. 
    
  506.     if (disableInputAttributeSyncing) {
    
  507.       expect(node.value).toBe('1');
    
  508.       expect(node.defaultValue).toBe('1');
    
  509.       expect(isValueDirty(node)).toBe(false);
    
  510.     } else {
    
  511.       expect(node.value).toBe('0');
    
  512.       expect(node.defaultValue).toBe('1');
    
  513.       expect(isValueDirty(node)).toBe(true);
    
  514.     }
    
  515.   });
    
  516. 
    
  517.   it('should update `defaultValue` for uncontrolled date/time input', () => {
    
  518.     const node = ReactDOM.render(
    
  519.       <input type="date" defaultValue="1980-01-01" />,
    
  520.       container,
    
  521.     );
    
  522. 
    
  523.     expect(node.value).toBe('1980-01-01');
    
  524.     expect(node.defaultValue).toBe('1980-01-01');
    
  525. 
    
  526.     ReactDOM.render(<input type="date" defaultValue="2000-01-01" />, container);
    
  527. 
    
  528.     if (disableInputAttributeSyncing) {
    
  529.       expect(node.value).toBe('2000-01-01');
    
  530.       expect(node.defaultValue).toBe('2000-01-01');
    
  531.     } else {
    
  532.       expect(node.value).toBe('1980-01-01');
    
  533.       expect(node.defaultValue).toBe('2000-01-01');
    
  534.     }
    
  535. 
    
  536.     ReactDOM.render(<input type="date" />, container);
    
  537.   });
    
  538. 
    
  539.   it('should take `defaultValue` when changing to uncontrolled input', () => {
    
  540.     const node = ReactDOM.render(
    
  541.       <input type="text" value="0" readOnly={true} />,
    
  542.       container,
    
  543.     );
    
  544.     expect(node.value).toBe('0');
    
  545.     expect(isValueDirty(node)).toBe(true);
    
  546.     expect(() =>
    
  547.       ReactDOM.render(<input type="text" defaultValue="1" />, container),
    
  548.     ).toErrorDev(
    
  549.       'A component is changing a controlled input to be uncontrolled.',
    
  550.     );
    
  551.     expect(node.value).toBe('0');
    
  552.     expect(isValueDirty(node)).toBe(true);
    
  553.   });
    
  554. 
    
  555.   it('should render defaultValue for SSR', () => {
    
  556.     const markup = ReactDOMServer.renderToString(
    
  557.       <input type="text" defaultValue="1" />,
    
  558.     );
    
  559.     const div = document.createElement('div');
    
  560.     div.innerHTML = markup;
    
  561.     expect(div.firstChild.getAttribute('value')).toBe('1');
    
  562.     expect(div.firstChild.getAttribute('defaultValue')).toBe(null);
    
  563.   });
    
  564. 
    
  565.   it('should render value for SSR', () => {
    
  566.     const element = <input type="text" value="1" onChange={() => {}} />;
    
  567.     const markup = ReactDOMServer.renderToString(element);
    
  568.     const div = document.createElement('div');
    
  569.     div.innerHTML = markup;
    
  570.     expect(div.firstChild.getAttribute('value')).toBe('1');
    
  571.     expect(div.firstChild.getAttribute('defaultValue')).toBe(null);
    
  572.   });
    
  573. 
    
  574.   it('should render name attribute if it is supplied', () => {
    
  575.     const node = ReactDOM.render(<input type="text" name="name" />, container);
    
  576.     expect(node.name).toBe('name');
    
  577.     expect(container.firstChild.getAttribute('name')).toBe('name');
    
  578.   });
    
  579. 
    
  580.   it('should render name attribute if it is supplied for SSR', () => {
    
  581.     const element = <input type="text" name="name" />;
    
  582.     const markup = ReactDOMServer.renderToString(element);
    
  583.     const div = document.createElement('div');
    
  584.     div.innerHTML = markup;
    
  585.     expect(div.firstChild.getAttribute('name')).toBe('name');
    
  586.   });
    
  587. 
    
  588.   it('should not render name attribute if it is not supplied', () => {
    
  589.     ReactDOM.render(<input type="text" />, container);
    
  590.     expect(container.firstChild.getAttribute('name')).toBe(null);
    
  591.   });
    
  592. 
    
  593.   it('should not render name attribute if it is not supplied for SSR', () => {
    
  594.     const element = <input type="text" />;
    
  595.     const markup = ReactDOMServer.renderToString(element);
    
  596.     const div = document.createElement('div');
    
  597.     div.innerHTML = markup;
    
  598.     expect(div.firstChild.getAttribute('name')).toBe(null);
    
  599.   });
    
  600. 
    
  601.   it('should display "foobar" for `defaultValue` of `objToString`', () => {
    
  602.     const objToString = {
    
  603.       toString: function () {
    
  604.         return 'foobar';
    
  605.       },
    
  606.     };
    
  607. 
    
  608.     const stub = <input type="text" defaultValue={objToString} />;
    
  609.     const node = ReactDOM.render(stub, container);
    
  610. 
    
  611.     expect(node.value).toBe('foobar');
    
  612.   });
    
  613. 
    
  614.   it('should throw for date inputs if `defaultValue` is an object where valueOf() throws', () => {
    
  615.     class TemporalLike {
    
  616.       valueOf() {
    
  617.         // Throwing here is the behavior of ECMAScript "Temporal" date/time API.
    
  618.         // See https://tc39.es/proposal-temporal/docs/plaindate.html#valueOf
    
  619.         throw new TypeError('prod message');
    
  620.       }
    
  621.       toString() {
    
  622.         return '2020-01-01';
    
  623.       }
    
  624.     }
    
  625.     const test = () =>
    
  626.       ReactDOM.render(
    
  627.         <input defaultValue={new TemporalLike()} type="date" />,
    
  628.         container,
    
  629.       );
    
  630.     expect(() =>
    
  631.       expect(test).toThrowError(new TypeError('prod message')),
    
  632.     ).toErrorDev(
    
  633.       'Form field values (value, checked, defaultValue, or defaultChecked props) must be ' +
    
  634.         'strings, not TemporalLike. This value must be coerced to a string before using it here.',
    
  635.     );
    
  636.   });
    
  637. 
    
  638.   it('should throw for text inputs if `defaultValue` is an object where valueOf() throws', () => {
    
  639.     class TemporalLike {
    
  640.       valueOf() {
    
  641.         // Throwing here is the behavior of ECMAScript "Temporal" date/time API.
    
  642.         // See https://tc39.es/proposal-temporal/docs/plaindate.html#valueOf
    
  643.         throw new TypeError('prod message');
    
  644.       }
    
  645.       toString() {
    
  646.         return '2020-01-01';
    
  647.       }
    
  648.     }
    
  649.     const test = () =>
    
  650.       ReactDOM.render(
    
  651.         <input defaultValue={new TemporalLike()} type="text" />,
    
  652.         container,
    
  653.       );
    
  654.     expect(() =>
    
  655.       expect(test).toThrowError(new TypeError('prod message')),
    
  656.     ).toErrorDev(
    
  657.       'Form field values (value, checked, defaultValue, or defaultChecked props) must be ' +
    
  658.         'strings, not TemporalLike. This value must be coerced to a string before using it here.',
    
  659.     );
    
  660.   });
    
  661. 
    
  662.   it('should throw for date inputs if `value` is an object where valueOf() throws', () => {
    
  663.     class TemporalLike {
    
  664.       valueOf() {
    
  665.         // Throwing here is the behavior of ECMAScript "Temporal" date/time API.
    
  666.         // See https://tc39.es/proposal-temporal/docs/plaindate.html#valueOf
    
  667.         throw new TypeError('prod message');
    
  668.       }
    
  669.       toString() {
    
  670.         return '2020-01-01';
    
  671.       }
    
  672.     }
    
  673.     const test = () =>
    
  674.       ReactDOM.render(
    
  675.         <input value={new TemporalLike()} type="date" onChange={() => {}} />,
    
  676.         container,
    
  677.       );
    
  678.     expect(() =>
    
  679.       expect(test).toThrowError(new TypeError('prod message')),
    
  680.     ).toErrorDev(
    
  681.       'Form field values (value, checked, defaultValue, or defaultChecked props) must be ' +
    
  682.         'strings, not TemporalLike. This value must be coerced to a string before using it here.',
    
  683.     );
    
  684.   });
    
  685. 
    
  686.   it('should throw for text inputs if `value` is an object where valueOf() throws', () => {
    
  687.     class TemporalLike {
    
  688.       valueOf() {
    
  689.         // Throwing here is the behavior of ECMAScript "Temporal" date/time API.
    
  690.         // See https://tc39.es/proposal-temporal/docs/plaindate.html#valueOf
    
  691.         throw new TypeError('prod message');
    
  692.       }
    
  693.       toString() {
    
  694.         return '2020-01-01';
    
  695.       }
    
  696.     }
    
  697.     const test = () =>
    
  698.       ReactDOM.render(
    
  699.         <input value={new TemporalLike()} type="text" onChange={() => {}} />,
    
  700.         container,
    
  701.       );
    
  702.     expect(() =>
    
  703.       expect(test).toThrowError(new TypeError('prod message')),
    
  704.     ).toErrorDev(
    
  705.       'Form field values (value, checked, defaultValue, or defaultChecked props) must be ' +
    
  706.         'strings, not TemporalLike. This value must be coerced to a string before using it here.',
    
  707.     );
    
  708.   });
    
  709. 
    
  710.   it('should display `value` of number 0', () => {
    
  711.     const stub = <input type="text" value={0} onChange={emptyFunction} />;
    
  712.     const node = ReactDOM.render(stub, container);
    
  713. 
    
  714.     expect(node.value).toBe('0');
    
  715.   });
    
  716. 
    
  717.   it('should allow setting `value` to `true`', () => {
    
  718.     let stub = <input type="text" value="yolo" onChange={emptyFunction} />;
    
  719.     const node = ReactDOM.render(stub, container);
    
  720. 
    
  721.     expect(node.value).toBe('yolo');
    
  722. 
    
  723.     stub = ReactDOM.render(
    
  724.       <input type="text" value={true} onChange={emptyFunction} />,
    
  725.       container,
    
  726.     );
    
  727.     expect(node.value).toEqual('true');
    
  728.   });
    
  729. 
    
  730.   it('should allow setting `value` to `false`', () => {
    
  731.     let stub = <input type="text" value="yolo" onChange={emptyFunction} />;
    
  732.     const node = ReactDOM.render(stub, container);
    
  733. 
    
  734.     expect(node.value).toBe('yolo');
    
  735. 
    
  736.     stub = ReactDOM.render(
    
  737.       <input type="text" value={false} onChange={emptyFunction} />,
    
  738.       container,
    
  739.     );
    
  740.     expect(node.value).toEqual('false');
    
  741.   });
    
  742. 
    
  743.   it('should allow setting `value` to `objToString`', () => {
    
  744.     let stub = <input type="text" value="foo" onChange={emptyFunction} />;
    
  745.     const node = ReactDOM.render(stub, container);
    
  746. 
    
  747.     expect(node.value).toBe('foo');
    
  748. 
    
  749.     const objToString = {
    
  750.       toString: function () {
    
  751.         return 'foobar';
    
  752.       },
    
  753.     };
    
  754.     stub = ReactDOM.render(
    
  755.       <input type="text" value={objToString} onChange={emptyFunction} />,
    
  756.       container,
    
  757.     );
    
  758.     expect(node.value).toEqual('foobar');
    
  759.   });
    
  760. 
    
  761.   it('should not incur unnecessary DOM mutations', () => {
    
  762.     ReactDOM.render(<input value="a" onChange={() => {}} />, container);
    
  763. 
    
  764.     const node = container.firstChild;
    
  765.     let nodeValue = 'a';
    
  766.     const nodeValueSetter = jest.fn();
    
  767.     Object.defineProperty(node, 'value', {
    
  768.       get: function () {
    
  769.         return nodeValue;
    
  770.       },
    
  771.       set: nodeValueSetter.mockImplementation(function (newValue) {
    
  772.         nodeValue = newValue;
    
  773.       }),
    
  774.     });
    
  775. 
    
  776.     ReactDOM.render(<input value="a" onChange={() => {}} />, container);
    
  777.     expect(nodeValueSetter).toHaveBeenCalledTimes(0);
    
  778. 
    
  779.     ReactDOM.render(<input value="b" onChange={() => {}} />, container);
    
  780.     expect(nodeValueSetter).toHaveBeenCalledTimes(1);
    
  781.   });
    
  782. 
    
  783.   it('should not incur unnecessary DOM mutations for numeric type conversion', () => {
    
  784.     ReactDOM.render(<input value="0" onChange={() => {}} />, container);
    
  785. 
    
  786.     const node = container.firstChild;
    
  787.     let nodeValue = '0';
    
  788.     const nodeValueSetter = jest.fn();
    
  789.     Object.defineProperty(node, 'value', {
    
  790.       get: function () {
    
  791.         return nodeValue;
    
  792.       },
    
  793.       set: nodeValueSetter.mockImplementation(function (newValue) {
    
  794.         nodeValue = newValue;
    
  795.       }),
    
  796.     });
    
  797. 
    
  798.     ReactDOM.render(<input value={0} onChange={() => {}} />, container);
    
  799.     expect(nodeValueSetter).toHaveBeenCalledTimes(0);
    
  800.   });
    
  801. 
    
  802.   it('should not incur unnecessary DOM mutations for the boolean type conversion', () => {
    
  803.     ReactDOM.render(<input value="true" onChange={() => {}} />, container);
    
  804. 
    
  805.     const node = container.firstChild;
    
  806.     let nodeValue = 'true';
    
  807.     const nodeValueSetter = jest.fn();
    
  808.     Object.defineProperty(node, 'value', {
    
  809.       get: function () {
    
  810.         return nodeValue;
    
  811.       },
    
  812.       set: nodeValueSetter.mockImplementation(function (newValue) {
    
  813.         nodeValue = newValue;
    
  814.       }),
    
  815.     });
    
  816. 
    
  817.     ReactDOM.render(<input value={true} onChange={() => {}} />, container);
    
  818.     expect(nodeValueSetter).toHaveBeenCalledTimes(0);
    
  819.   });
    
  820. 
    
  821.   it('should properly control a value of number `0`', () => {
    
  822.     const stub = <input type="text" value={0} onChange={emptyFunction} />;
    
  823.     const node = ReactDOM.render(stub, container);
    
  824. 
    
  825.     setUntrackedValue.call(node, 'giraffe');
    
  826.     dispatchEventOnNode(node, 'input');
    
  827.     expect(node.value).toBe('0');
    
  828.   });
    
  829. 
    
  830.   it('should properly control 0.0 for a text input', () => {
    
  831.     const stub = <input type="text" value={0} onChange={emptyFunction} />;
    
  832.     const node = ReactDOM.render(stub, container);
    
  833. 
    
  834.     setUntrackedValue.call(node, '0.0');
    
  835.     dispatchEventOnNode(node, 'input');
    
  836.     expect(node.value).toBe('0');
    
  837.   });
    
  838. 
    
  839.   it('should properly control 0.0 for a number input', () => {
    
  840.     const stub = <input type="number" value={0} onChange={emptyFunction} />;
    
  841.     const node = ReactDOM.render(stub, container);
    
  842. 
    
  843.     setUntrackedValue.call(node, '0.0');
    
  844.     dispatchEventOnNode(node, 'input');
    
  845. 
    
  846.     if (disableInputAttributeSyncing) {
    
  847.       expect(node.value).toBe('0.0');
    
  848.       expect(node.hasAttribute('value')).toBe(false);
    
  849.     } else {
    
  850.       dispatchEventOnNode(node, 'blur');
    
  851.       dispatchEventOnNode(node, 'focusout');
    
  852. 
    
  853.       expect(node.value).toBe('0.0');
    
  854.       expect(node.getAttribute('value')).toBe('0.0');
    
  855.     }
    
  856.   });
    
  857. 
    
  858.   it('should properly transition from an empty value to 0', function () {
    
  859.     ReactDOM.render(
    
  860.       <input type="text" value="" onChange={emptyFunction} />,
    
  861.       container,
    
  862.     );
    
  863.     const node = container.firstChild;
    
  864.     expect(isValueDirty(node)).toBe(false);
    
  865. 
    
  866.     ReactDOM.render(
    
  867.       <input type="text" value={0} onChange={emptyFunction} />,
    
  868.       container,
    
  869.     );
    
  870. 
    
  871.     expect(node.value).toBe('0');
    
  872.     expect(isValueDirty(node)).toBe(true);
    
  873. 
    
  874.     if (disableInputAttributeSyncing) {
    
  875.       expect(node.hasAttribute('value')).toBe(false);
    
  876.     } else {
    
  877.       expect(node.defaultValue).toBe('0');
    
  878.     }
    
  879.   });
    
  880. 
    
  881.   it('should properly transition from 0 to an empty value', function () {
    
  882.     ReactDOM.render(
    
  883.       <input type="text" value={0} onChange={emptyFunction} />,
    
  884.       container,
    
  885.     );
    
  886.     const node = container.firstChild;
    
  887.     expect(isValueDirty(node)).toBe(true);
    
  888. 
    
  889.     ReactDOM.render(
    
  890.       <input type="text" value="" onChange={emptyFunction} />,
    
  891.       container,
    
  892.     );
    
  893. 
    
  894.     expect(node.value).toBe('');
    
  895.     expect(node.defaultValue).toBe('');
    
  896.     expect(isValueDirty(node)).toBe(true);
    
  897.   });
    
  898. 
    
  899.   it('should properly transition a text input from 0 to an empty 0.0', function () {
    
  900.     ReactDOM.render(
    
  901.       <input type="text" value={0} onChange={emptyFunction} />,
    
  902.       container,
    
  903.     );
    
  904.     ReactDOM.render(
    
  905.       <input type="text" value="0.0" onChange={emptyFunction} />,
    
  906.       container,
    
  907.     );
    
  908. 
    
  909.     const node = container.firstChild;
    
  910. 
    
  911.     expect(node.value).toBe('0.0');
    
  912.     if (disableInputAttributeSyncing) {
    
  913.       expect(node.hasAttribute('value')).toBe(false);
    
  914.     } else {
    
  915.       expect(node.defaultValue).toBe('0.0');
    
  916.     }
    
  917.   });
    
  918. 
    
  919.   it('should properly transition a number input from "" to 0', function () {
    
  920.     ReactDOM.render(
    
  921.       <input type="number" value="" onChange={emptyFunction} />,
    
  922.       container,
    
  923.     );
    
  924.     ReactDOM.render(
    
  925.       <input type="number" value={0} onChange={emptyFunction} />,
    
  926.       container,
    
  927.     );
    
  928. 
    
  929.     const node = container.firstChild;
    
  930. 
    
  931.     expect(node.value).toBe('0');
    
  932.     if (disableInputAttributeSyncing) {
    
  933.       expect(node.hasAttribute('value')).toBe(false);
    
  934.     } else {
    
  935.       expect(node.defaultValue).toBe('0');
    
  936.     }
    
  937.   });
    
  938. 
    
  939.   it('should properly transition a number input from "" to "0"', function () {
    
  940.     ReactDOM.render(
    
  941.       <input type="number" value="" onChange={emptyFunction} />,
    
  942.       container,
    
  943.     );
    
  944.     ReactDOM.render(
    
  945.       <input type="number" value="0" onChange={emptyFunction} />,
    
  946.       container,
    
  947.     );
    
  948. 
    
  949.     const node = container.firstChild;
    
  950. 
    
  951.     expect(node.value).toBe('0');
    
  952.     if (disableInputAttributeSyncing) {
    
  953.       expect(node.hasAttribute('value')).toBe(false);
    
  954.     } else {
    
  955.       expect(node.defaultValue).toBe('0');
    
  956.     }
    
  957.   });
    
  958. 
    
  959.   it('should have the correct target value', () => {
    
  960.     let handled = false;
    
  961.     const handler = function (event) {
    
  962.       expect(event.target.nodeName).toBe('INPUT');
    
  963.       handled = true;
    
  964.     };
    
  965.     const stub = <input type="text" value={0} onChange={handler} />;
    
  966.     const node = ReactDOM.render(stub, container);
    
  967. 
    
  968.     setUntrackedValue.call(node, 'giraffe');
    
  969. 
    
  970.     dispatchEventOnNode(node, 'input');
    
  971. 
    
  972.     expect(handled).toBe(true);
    
  973.   });
    
  974. 
    
  975.   it('should restore uncontrolled inputs to last defaultValue upon reset', () => {
    
  976.     const inputRef = React.createRef();
    
  977.     ReactDOM.render(
    
  978.       <form>
    
  979.         <input defaultValue="default1" ref={inputRef} />
    
  980.         <input type="reset" />
    
  981.       </form>,
    
  982.       container,
    
  983.     );
    
  984.     expect(inputRef.current.value).toBe('default1');
    
  985.     if (disableInputAttributeSyncing) {
    
  986.       expect(isValueDirty(inputRef.current)).toBe(false);
    
  987.     } else {
    
  988.       expect(isValueDirty(inputRef.current)).toBe(true);
    
  989.     }
    
  990. 
    
  991.     setUntrackedValue.call(inputRef.current, 'changed');
    
  992.     dispatchEventOnNode(inputRef.current, 'input');
    
  993.     expect(inputRef.current.value).toBe('changed');
    
  994.     expect(isValueDirty(inputRef.current)).toBe(true);
    
  995. 
    
  996.     ReactDOM.render(
    
  997.       <form>
    
  998.         <input defaultValue="default2" ref={inputRef} />
    
  999.         <input type="reset" />
    
  1000.       </form>,
    
  1001.       container,
    
  1002.     );
    
  1003.     expect(inputRef.current.value).toBe('changed');
    
  1004.     expect(isValueDirty(inputRef.current)).toBe(true);
    
  1005. 
    
  1006.     container.firstChild.reset();
    
  1007.     // Note: I don't know if we want to always support this.
    
  1008.     // But it's current behavior so worth being intentional if we break it.
    
  1009.     // https://github.com/facebook/react/issues/4618
    
  1010.     expect(inputRef.current.value).toBe('default2');
    
  1011.     expect(isValueDirty(inputRef.current)).toBe(false);
    
  1012.   });
    
  1013. 
    
  1014.   it('should not set a value for submit buttons unnecessarily', () => {
    
  1015.     const stub = <input type="submit" />;
    
  1016.     ReactDOM.render(stub, container);
    
  1017.     const node = container.firstChild;
    
  1018. 
    
  1019.     // The value shouldn't be '', or else the button will have no text; it
    
  1020.     // should have the default "Submit" or "Submit Query" label. Most browsers
    
  1021.     // report this as not having a `value` attribute at all; IE reports it as
    
  1022.     // the actual label that the user sees.
    
  1023.     expect(node.hasAttribute('value')).toBe(false);
    
  1024.   });
    
  1025. 
    
  1026.   it('should remove the value attribute on submit inputs when value is updated to undefined', () => {
    
  1027.     const stub = <input type="submit" value="foo" onChange={emptyFunction} />;
    
  1028.     ReactDOM.render(stub, container);
    
  1029. 
    
  1030.     // Not really relevant to this particular test, but changing to undefined
    
  1031.     // should nonetheless trigger a warning
    
  1032.     expect(() =>
    
  1033.       ReactDOM.render(
    
  1034.         <input type="submit" value={undefined} onChange={emptyFunction} />,
    
  1035.         container,
    
  1036.       ),
    
  1037.     ).toErrorDev(
    
  1038.       'A component is changing a controlled input to be uncontrolled.',
    
  1039.     );
    
  1040. 
    
  1041.     const node = container.firstChild;
    
  1042.     expect(node.getAttribute('value')).toBe(null);
    
  1043.   });
    
  1044. 
    
  1045.   it('should remove the value attribute on reset inputs when value is updated to undefined', () => {
    
  1046.     const stub = <input type="reset" value="foo" onChange={emptyFunction} />;
    
  1047.     ReactDOM.render(stub, container);
    
  1048. 
    
  1049.     // Not really relevant to this particular test, but changing to undefined
    
  1050.     // should nonetheless trigger a warning
    
  1051.     expect(() =>
    
  1052.       ReactDOM.render(
    
  1053.         <input type="reset" value={undefined} onChange={emptyFunction} />,
    
  1054.         container,
    
  1055.       ),
    
  1056.     ).toErrorDev(
    
  1057.       'A component is changing a controlled input to be uncontrolled.',
    
  1058.     );
    
  1059. 
    
  1060.     const node = container.firstChild;
    
  1061.     expect(node.getAttribute('value')).toBe(null);
    
  1062.   });
    
  1063. 
    
  1064.   it('should set a value on a submit input', () => {
    
  1065.     const stub = <input type="submit" value="banana" />;
    
  1066.     ReactDOM.render(stub, container);
    
  1067.     const node = container.firstChild;
    
  1068. 
    
  1069.     expect(node.getAttribute('value')).toBe('banana');
    
  1070.   });
    
  1071. 
    
  1072.   it('should not set an undefined value on a submit input', () => {
    
  1073.     const stub = <input type="submit" value={undefined} />;
    
  1074.     ReactDOM.render(stub, container);
    
  1075.     const node = container.firstChild;
    
  1076. 
    
  1077.     // Note: it shouldn't be an empty string
    
  1078.     // because that would erase the "submit" label.
    
  1079.     expect(node.getAttribute('value')).toBe(null);
    
  1080. 
    
  1081.     ReactDOM.render(stub, container);
    
  1082.     expect(node.getAttribute('value')).toBe(null);
    
  1083.   });
    
  1084. 
    
  1085.   it('should not set an undefined value on a reset input', () => {
    
  1086.     const stub = <input type="reset" value={undefined} />;
    
  1087.     ReactDOM.render(stub, container);
    
  1088.     const node = container.firstChild;
    
  1089. 
    
  1090.     // Note: it shouldn't be an empty string
    
  1091.     // because that would erase the "reset" label.
    
  1092.     expect(node.getAttribute('value')).toBe(null);
    
  1093. 
    
  1094.     ReactDOM.render(stub, container);
    
  1095.     expect(node.getAttribute('value')).toBe(null);
    
  1096.   });
    
  1097. 
    
  1098.   it('should not set a null value on a submit input', () => {
    
  1099.     const stub = <input type="submit" value={null} />;
    
  1100.     expect(() => {
    
  1101.       ReactDOM.render(stub, container);
    
  1102.     }).toErrorDev('`value` prop on `input` should not be null');
    
  1103.     const node = container.firstChild;
    
  1104. 
    
  1105.     // Note: it shouldn't be an empty string
    
  1106.     // because that would erase the "submit" label.
    
  1107.     expect(node.getAttribute('value')).toBe(null);
    
  1108. 
    
  1109.     ReactDOM.render(stub, container);
    
  1110.     expect(node.getAttribute('value')).toBe(null);
    
  1111.   });
    
  1112. 
    
  1113.   it('should not set a null value on a reset input', () => {
    
  1114.     const stub = <input type="reset" value={null} />;
    
  1115.     expect(() => {
    
  1116.       ReactDOM.render(stub, container);
    
  1117.     }).toErrorDev('`value` prop on `input` should not be null');
    
  1118.     const node = container.firstChild;
    
  1119. 
    
  1120.     // Note: it shouldn't be an empty string
    
  1121.     // because that would erase the "reset" label.
    
  1122.     expect(node.getAttribute('value')).toBe(null);
    
  1123. 
    
  1124.     ReactDOM.render(stub, container);
    
  1125.     expect(node.getAttribute('value')).toBe(null);
    
  1126.   });
    
  1127. 
    
  1128.   it('should set a value on a reset input', () => {
    
  1129.     const stub = <input type="reset" value="banana" />;
    
  1130.     ReactDOM.render(stub, container);
    
  1131.     const node = container.firstChild;
    
  1132. 
    
  1133.     expect(node.getAttribute('value')).toBe('banana');
    
  1134.   });
    
  1135. 
    
  1136.   it('should set an empty string value on a submit input', () => {
    
  1137.     const stub = <input type="submit" value="" />;
    
  1138.     ReactDOM.render(stub, container);
    
  1139.     const node = container.firstChild;
    
  1140. 
    
  1141.     expect(node.getAttribute('value')).toBe('');
    
  1142.   });
    
  1143. 
    
  1144.   it('should set an empty string value on a reset input', () => {
    
  1145.     const stub = <input type="reset" value="" />;
    
  1146.     ReactDOM.render(stub, container);
    
  1147.     const node = container.firstChild;
    
  1148. 
    
  1149.     expect(node.getAttribute('value')).toBe('');
    
  1150.   });
    
  1151. 
    
  1152.   it('should control radio buttons', () => {
    
  1153.     class RadioGroup extends React.Component {
    
  1154.       aRef = React.createRef();
    
  1155.       bRef = React.createRef();
    
  1156.       cRef = React.createRef();
    
  1157. 
    
  1158.       render() {
    
  1159.         return (
    
  1160.           <div>
    
  1161.             <input
    
  1162.               ref={this.aRef}
    
  1163.               type="radio"
    
  1164.               name="fruit"
    
  1165.               checked={true}
    
  1166.               onChange={emptyFunction}
    
  1167.               data-which="a"
    
  1168.             />
    
  1169.             A
    
  1170.             <input
    
  1171.               ref={this.bRef}
    
  1172.               type="radio"
    
  1173.               name="fruit"
    
  1174.               onChange={emptyFunction}
    
  1175.               data-which="b"
    
  1176.             />
    
  1177.             B
    
  1178.             <form>
    
  1179.               <input
    
  1180.                 ref={this.cRef}
    
  1181.                 type="radio"
    
  1182.                 name="fruit"
    
  1183.                 defaultChecked={true}
    
  1184.                 onChange={emptyFunction}
    
  1185.                 data-which="c"
    
  1186.               />
    
  1187.             </form>
    
  1188.           </div>
    
  1189.         );
    
  1190.       }
    
  1191.     }
    
  1192. 
    
  1193.     const stub = ReactDOM.render(<RadioGroup />, container);
    
  1194.     const aNode = stub.aRef.current;
    
  1195.     const bNode = stub.bRef.current;
    
  1196.     const cNode = stub.cRef.current;
    
  1197. 
    
  1198.     expect(aNode.checked).toBe(true);
    
  1199.     expect(bNode.checked).toBe(false);
    
  1200.     // c is in a separate form and shouldn't be affected at all here
    
  1201.     expect(cNode.checked).toBe(true);
    
  1202. 
    
  1203.     if (disableInputAttributeSyncing) {
    
  1204.       expect(aNode.hasAttribute('checked')).toBe(false);
    
  1205.       expect(bNode.hasAttribute('checked')).toBe(false);
    
  1206.       expect(cNode.hasAttribute('checked')).toBe(true);
    
  1207.     } else {
    
  1208.       expect(aNode.hasAttribute('checked')).toBe(true);
    
  1209.       expect(bNode.hasAttribute('checked')).toBe(false);
    
  1210.       expect(cNode.hasAttribute('checked')).toBe(true);
    
  1211.     }
    
  1212. 
    
  1213.     expect(isCheckedDirty(aNode)).toBe(true);
    
  1214.     expect(isCheckedDirty(bNode)).toBe(true);
    
  1215.     expect(isCheckedDirty(cNode)).toBe(true);
    
  1216.     assertInputTrackingIsCurrent(container);
    
  1217. 
    
  1218.     setUntrackedChecked.call(bNode, true);
    
  1219.     expect(aNode.checked).toBe(false);
    
  1220.     expect(cNode.checked).toBe(true);
    
  1221. 
    
  1222.     // The original 'checked' attribute should be unchanged
    
  1223.     if (disableInputAttributeSyncing) {
    
  1224.       expect(aNode.hasAttribute('checked')).toBe(false);
    
  1225.       expect(bNode.hasAttribute('checked')).toBe(false);
    
  1226.       expect(cNode.hasAttribute('checked')).toBe(true);
    
  1227.     } else {
    
  1228.       expect(aNode.hasAttribute('checked')).toBe(true);
    
  1229.       expect(bNode.hasAttribute('checked')).toBe(false);
    
  1230.       expect(cNode.hasAttribute('checked')).toBe(true);
    
  1231.     }
    
  1232. 
    
  1233.     // Now let's run the actual ReactDOMInput change event handler
    
  1234.     dispatchEventOnNode(bNode, 'click');
    
  1235. 
    
  1236.     // The original state should have been restored
    
  1237.     expect(aNode.checked).toBe(true);
    
  1238.     expect(cNode.checked).toBe(true);
    
  1239. 
    
  1240.     expect(isCheckedDirty(aNode)).toBe(true);
    
  1241.     expect(isCheckedDirty(bNode)).toBe(true);
    
  1242.     expect(isCheckedDirty(cNode)).toBe(true);
    
  1243.     assertInputTrackingIsCurrent(container);
    
  1244.   });
    
  1245. 
    
  1246.   it('should hydrate controlled radio buttons', async () => {
    
  1247.     function App() {
    
  1248.       const [current, setCurrent] = React.useState('a');
    
  1249.       return (
    
  1250.         <>
    
  1251.           <input
    
  1252.             type="radio"
    
  1253.             name="fruit"
    
  1254.             checked={current === 'a'}
    
  1255.             onChange={() => {
    
  1256.               Scheduler.log('click a');
    
  1257.               setCurrent('a');
    
  1258.             }}
    
  1259.           />
    
  1260.           <input
    
  1261.             type="radio"
    
  1262.             name="fruit"
    
  1263.             checked={current === 'b'}
    
  1264.             onChange={() => {
    
  1265.               Scheduler.log('click b');
    
  1266.               setCurrent('b');
    
  1267.             }}
    
  1268.           />
    
  1269.           <input
    
  1270.             type="radio"
    
  1271.             name="fruit"
    
  1272.             checked={current === 'c'}
    
  1273.             onChange={() => {
    
  1274.               Scheduler.log('click c');
    
  1275.               // Let's say the user can't pick C
    
  1276.             }}
    
  1277.           />
    
  1278.         </>
    
  1279.       );
    
  1280.     }
    
  1281.     const html = ReactDOMServer.renderToString(<App />);
    
  1282.     container.innerHTML = html;
    
  1283.     const [a, b, c] = container.querySelectorAll('input');
    
  1284.     expect(a.checked).toBe(true);
    
  1285.     expect(b.checked).toBe(false);
    
  1286.     expect(c.checked).toBe(false);
    
  1287.     expect(isCheckedDirty(a)).toBe(false);
    
  1288.     expect(isCheckedDirty(b)).toBe(false);
    
  1289.     expect(isCheckedDirty(c)).toBe(false);
    
  1290. 
    
  1291.     // Click on B before hydrating
    
  1292.     b.checked = true;
    
  1293.     expect(isCheckedDirty(a)).toBe(true);
    
  1294.     expect(isCheckedDirty(b)).toBe(true);
    
  1295.     expect(isCheckedDirty(c)).toBe(false);
    
  1296. 
    
  1297.     await act(async () => {
    
  1298.       ReactDOMClient.hydrateRoot(container, <App />);
    
  1299.     });
    
  1300. 
    
  1301.     // Currently, we don't fire onChange when hydrating
    
  1302.     assertLog([]);
    
  1303.     // Strangely, we leave `b` checked even though we rendered A with
    
  1304.     // checked={true} and B with checked={false}. Arguably this is a bug.
    
  1305.     expect(a.checked).toBe(false);
    
  1306.     expect(b.checked).toBe(true);
    
  1307.     expect(c.checked).toBe(false);
    
  1308.     expect(isCheckedDirty(a)).toBe(true);
    
  1309.     expect(isCheckedDirty(b)).toBe(true);
    
  1310.     expect(isCheckedDirty(c)).toBe(true);
    
  1311.     assertInputTrackingIsCurrent(container);
    
  1312. 
    
  1313.     // If we click on C now though...
    
  1314.     await act(async () => {
    
  1315.       setUntrackedChecked.call(c, true);
    
  1316.       dispatchEventOnNode(c, 'click');
    
  1317.     });
    
  1318. 
    
  1319.     // then since C's onClick doesn't set state, A becomes rechecked.
    
  1320.     assertLog(['click c']);
    
  1321.     expect(a.checked).toBe(true);
    
  1322.     expect(b.checked).toBe(false);
    
  1323.     expect(c.checked).toBe(false);
    
  1324.     expect(isCheckedDirty(a)).toBe(true);
    
  1325.     expect(isCheckedDirty(b)).toBe(true);
    
  1326.     expect(isCheckedDirty(c)).toBe(true);
    
  1327.     assertInputTrackingIsCurrent(container);
    
  1328. 
    
  1329.     // And we can also change to B properly after hydration.
    
  1330.     await act(async () => {
    
  1331.       setUntrackedChecked.call(b, true);
    
  1332.       dispatchEventOnNode(b, 'click');
    
  1333.     });
    
  1334.     assertLog(['click b']);
    
  1335.     expect(a.checked).toBe(false);
    
  1336.     expect(b.checked).toBe(true);
    
  1337.     expect(c.checked).toBe(false);
    
  1338.     expect(isCheckedDirty(a)).toBe(true);
    
  1339.     expect(isCheckedDirty(b)).toBe(true);
    
  1340.     expect(isCheckedDirty(c)).toBe(true);
    
  1341.     assertInputTrackingIsCurrent(container);
    
  1342.   });
    
  1343. 
    
  1344.   it('should hydrate uncontrolled radio buttons', async () => {
    
  1345.     function App() {
    
  1346.       return (
    
  1347.         <>
    
  1348.           <input
    
  1349.             type="radio"
    
  1350.             name="fruit"
    
  1351.             defaultChecked={true}
    
  1352.             onChange={() => Scheduler.log('click a')}
    
  1353.           />
    
  1354.           <input
    
  1355.             type="radio"
    
  1356.             name="fruit"
    
  1357.             defaultChecked={false}
    
  1358.             onChange={() => Scheduler.log('click b')}
    
  1359.           />
    
  1360.           <input
    
  1361.             type="radio"
    
  1362.             name="fruit"
    
  1363.             defaultChecked={false}
    
  1364.             onChange={() => Scheduler.log('click c')}
    
  1365.           />
    
  1366.         </>
    
  1367.       );
    
  1368.     }
    
  1369.     const html = ReactDOMServer.renderToString(<App />);
    
  1370.     container.innerHTML = html;
    
  1371.     const [a, b, c] = container.querySelectorAll('input');
    
  1372.     expect(a.checked).toBe(true);
    
  1373.     expect(b.checked).toBe(false);
    
  1374.     expect(c.checked).toBe(false);
    
  1375.     expect(isCheckedDirty(a)).toBe(false);
    
  1376.     expect(isCheckedDirty(b)).toBe(false);
    
  1377.     expect(isCheckedDirty(c)).toBe(false);
    
  1378. 
    
  1379.     // Click on B before hydrating
    
  1380.     b.checked = true;
    
  1381.     expect(isCheckedDirty(a)).toBe(true);
    
  1382.     expect(isCheckedDirty(b)).toBe(true);
    
  1383.     expect(isCheckedDirty(c)).toBe(false);
    
  1384. 
    
  1385.     await act(async () => {
    
  1386.       ReactDOMClient.hydrateRoot(container, <App />);
    
  1387.     });
    
  1388. 
    
  1389.     // Currently, we don't fire onChange when hydrating
    
  1390.     assertLog([]);
    
  1391.     expect(a.checked).toBe(false);
    
  1392.     expect(b.checked).toBe(true);
    
  1393.     expect(c.checked).toBe(false);
    
  1394.     expect(isCheckedDirty(a)).toBe(true);
    
  1395.     expect(isCheckedDirty(b)).toBe(true);
    
  1396.     expect(isCheckedDirty(c)).toBe(true);
    
  1397.     assertInputTrackingIsCurrent(container);
    
  1398. 
    
  1399.     // Click back to A
    
  1400.     await act(async () => {
    
  1401.       setUntrackedChecked.call(a, true);
    
  1402.       dispatchEventOnNode(a, 'click');
    
  1403.     });
    
  1404. 
    
  1405.     assertLog(['click a']);
    
  1406.     expect(a.checked).toBe(true);
    
  1407.     expect(b.checked).toBe(false);
    
  1408.     expect(c.checked).toBe(false);
    
  1409.     expect(isCheckedDirty(a)).toBe(true);
    
  1410.     expect(isCheckedDirty(b)).toBe(true);
    
  1411.     expect(isCheckedDirty(c)).toBe(true);
    
  1412.     assertInputTrackingIsCurrent(container);
    
  1413.   });
    
  1414. 
    
  1415.   it('should check the correct radio when the selected name moves', () => {
    
  1416.     class App extends React.Component {
    
  1417.       state = {
    
  1418.         updated: false,
    
  1419.       };
    
  1420.       onClick = () => {
    
  1421.         this.setState({updated: !this.state.updated});
    
  1422.       };
    
  1423.       render() {
    
  1424.         const {updated} = this.state;
    
  1425.         const radioName = updated ? 'secondName' : 'firstName';
    
  1426.         return (
    
  1427.           <div>
    
  1428.             <button type="button" onClick={this.onClick} />
    
  1429.             <input
    
  1430.               type="radio"
    
  1431.               name={radioName}
    
  1432.               onChange={emptyFunction}
    
  1433.               checked={updated === true}
    
  1434.             />
    
  1435.             <input
    
  1436.               type="radio"
    
  1437.               name={radioName}
    
  1438.               onChange={emptyFunction}
    
  1439.               checked={updated === false}
    
  1440.             />
    
  1441.           </div>
    
  1442.         );
    
  1443.       }
    
  1444.     }
    
  1445. 
    
  1446.     const stub = ReactDOM.render(<App />, container);
    
  1447.     const buttonNode = ReactDOM.findDOMNode(stub).childNodes[0];
    
  1448.     const firstRadioNode = ReactDOM.findDOMNode(stub).childNodes[1];
    
  1449.     expect(isCheckedDirty(firstRadioNode)).toBe(true);
    
  1450.     expect(firstRadioNode.checked).toBe(false);
    
  1451.     assertInputTrackingIsCurrent(container);
    
  1452.     dispatchEventOnNode(buttonNode, 'click');
    
  1453.     expect(firstRadioNode.checked).toBe(true);
    
  1454.     assertInputTrackingIsCurrent(container);
    
  1455.     dispatchEventOnNode(buttonNode, 'click');
    
  1456.     expect(firstRadioNode.checked).toBe(false);
    
  1457.     assertInputTrackingIsCurrent(container);
    
  1458.   });
    
  1459. 
    
  1460.   it("shouldn't get tricked by changing radio names, part 2", () => {
    
  1461.     ReactDOM.render(
    
  1462.       <div>
    
  1463.         <input
    
  1464.           type="radio"
    
  1465.           name="a"
    
  1466.           value="1"
    
  1467.           checked={true}
    
  1468.           onChange={() => {}}
    
  1469.         />
    
  1470.         <input
    
  1471.           type="radio"
    
  1472.           name="a"
    
  1473.           value="2"
    
  1474.           checked={false}
    
  1475.           onChange={() => {}}
    
  1476.         />
    
  1477.       </div>,
    
  1478.       container,
    
  1479.     );
    
  1480.     const one = container.querySelector('input[name="a"][value="1"]');
    
  1481.     const two = container.querySelector('input[name="a"][value="2"]');
    
  1482.     expect(one.checked).toBe(true);
    
  1483.     expect(two.checked).toBe(false);
    
  1484.     expect(isCheckedDirty(one)).toBe(true);
    
  1485.     expect(isCheckedDirty(two)).toBe(true);
    
  1486.     assertInputTrackingIsCurrent(container);
    
  1487. 
    
  1488.     ReactDOM.render(
    
  1489.       <div>
    
  1490.         <input
    
  1491.           type="radio"
    
  1492.           name="a"
    
  1493.           value="1"
    
  1494.           checked={true}
    
  1495.           onChange={() => {}}
    
  1496.         />
    
  1497.         <input
    
  1498.           type="radio"
    
  1499.           name="b"
    
  1500.           value="2"
    
  1501.           checked={true}
    
  1502.           onChange={() => {}}
    
  1503.         />
    
  1504.       </div>,
    
  1505.       container,
    
  1506.     );
    
  1507.     expect(one.checked).toBe(true);
    
  1508.     expect(two.checked).toBe(true);
    
  1509.     expect(isCheckedDirty(one)).toBe(true);
    
  1510.     expect(isCheckedDirty(two)).toBe(true);
    
  1511.     assertInputTrackingIsCurrent(container);
    
  1512.   });
    
  1513. 
    
  1514.   it('should control radio buttons if the tree updates during render', () => {
    
  1515.     const sharedParent = container;
    
  1516.     const container1 = document.createElement('div');
    
  1517.     const container2 = document.createElement('div');
    
  1518. 
    
  1519.     sharedParent.appendChild(container1);
    
  1520. 
    
  1521.     let aNode;
    
  1522.     let bNode;
    
  1523.     class ComponentA extends React.Component {
    
  1524.       state = {changed: false};
    
  1525.       handleChange = () => {
    
  1526.         this.setState({
    
  1527.           changed: true,
    
  1528.         });
    
  1529.       };
    
  1530.       componentDidUpdate() {
    
  1531.         sharedParent.appendChild(container2);
    
  1532.       }
    
  1533.       componentDidMount() {
    
  1534.         ReactDOM.render(<ComponentB />, container2);
    
  1535.       }
    
  1536.       render() {
    
  1537.         return (
    
  1538.           <div>
    
  1539.             <input
    
  1540.               ref={n => (aNode = n)}
    
  1541.               type="radio"
    
  1542.               name="fruit"
    
  1543.               checked={false}
    
  1544.               onChange={this.handleChange}
    
  1545.             />
    
  1546.             A
    
  1547.           </div>
    
  1548.         );
    
  1549.       }
    
  1550.     }
    
  1551. 
    
  1552.     class ComponentB extends React.Component {
    
  1553.       render() {
    
  1554.         return (
    
  1555.           <div>
    
  1556.             <input
    
  1557.               ref={n => (bNode = n)}
    
  1558.               type="radio"
    
  1559.               name="fruit"
    
  1560.               checked={true}
    
  1561.               onChange={emptyFunction}
    
  1562.             />
    
  1563.             B
    
  1564.           </div>
    
  1565.         );
    
  1566.       }
    
  1567.     }
    
  1568. 
    
  1569.     ReactDOM.render(<ComponentA />, container1);
    
  1570. 
    
  1571.     expect(aNode.checked).toBe(false);
    
  1572.     expect(bNode.checked).toBe(true);
    
  1573.     expect(isCheckedDirty(aNode)).toBe(true);
    
  1574.     expect(isCheckedDirty(bNode)).toBe(true);
    
  1575.     assertInputTrackingIsCurrent(container);
    
  1576. 
    
  1577.     setUntrackedChecked.call(aNode, true);
    
  1578.     // This next line isn't necessary in a proper browser environment, but
    
  1579.     // jsdom doesn't uncheck the others in a group (because they are not yet
    
  1580.     // sharing a parent), which makes this whole test a little less effective.
    
  1581.     setUntrackedChecked.call(bNode, false);
    
  1582. 
    
  1583.     // Now let's run the actual ReactDOMInput change event handler
    
  1584.     dispatchEventOnNode(aNode, 'click');
    
  1585. 
    
  1586.     // The original state should have been restored
    
  1587.     expect(aNode.checked).toBe(false);
    
  1588.     expect(bNode.checked).toBe(true);
    
  1589.     expect(isCheckedDirty(aNode)).toBe(true);
    
  1590.     expect(isCheckedDirty(bNode)).toBe(true);
    
  1591.     assertInputTrackingIsCurrent(container);
    
  1592.   });
    
  1593. 
    
  1594.   it('should control radio buttons if the tree updates during render (case 2; #26876)', () => {
    
  1595.     let thunk = null;
    
  1596.     function App() {
    
  1597.       const [disabled, setDisabled] = React.useState(false);
    
  1598.       const [value, setValue] = React.useState('one');
    
  1599.       function handleChange(e) {
    
  1600.         setDisabled(true);
    
  1601.         // Pretend this is in a setTimeout or something
    
  1602.         thunk = () => {
    
  1603.           setDisabled(false);
    
  1604.           setValue(e.target.value);
    
  1605.         };
    
  1606.       }
    
  1607.       return (
    
  1608.         <>
    
  1609.           <input
    
  1610.             type="radio"
    
  1611.             name="fruit"
    
  1612.             value="one"
    
  1613.             checked={value === 'one'}
    
  1614.             onChange={handleChange}
    
  1615.             disabled={disabled}
    
  1616.           />
    
  1617.           <input
    
  1618.             type="radio"
    
  1619.             name="fruit"
    
  1620.             value="two"
    
  1621.             checked={value === 'two'}
    
  1622.             onChange={handleChange}
    
  1623.             disabled={disabled}
    
  1624.           />
    
  1625.         </>
    
  1626.       );
    
  1627.     }
    
  1628.     ReactDOM.render(<App />, container);
    
  1629.     const [one, two] = container.querySelectorAll('input');
    
  1630.     expect(one.checked).toBe(true);
    
  1631.     expect(two.checked).toBe(false);
    
  1632.     expect(isCheckedDirty(one)).toBe(true);
    
  1633.     expect(isCheckedDirty(two)).toBe(true);
    
  1634.     assertInputTrackingIsCurrent(container);
    
  1635. 
    
  1636.     // Click two
    
  1637.     setUntrackedChecked.call(two, true);
    
  1638.     dispatchEventOnNode(two, 'click');
    
  1639.     expect(one.checked).toBe(true);
    
  1640.     expect(two.checked).toBe(false);
    
  1641.     expect(isCheckedDirty(one)).toBe(true);
    
  1642.     expect(isCheckedDirty(two)).toBe(true);
    
  1643.     assertInputTrackingIsCurrent(container);
    
  1644. 
    
  1645.     // After a delay...
    
  1646.     ReactDOM.unstable_batchedUpdates(thunk);
    
  1647.     expect(one.checked).toBe(false);
    
  1648.     expect(two.checked).toBe(true);
    
  1649.     expect(isCheckedDirty(one)).toBe(true);
    
  1650.     expect(isCheckedDirty(two)).toBe(true);
    
  1651.     assertInputTrackingIsCurrent(container);
    
  1652. 
    
  1653.     // Click back to one
    
  1654.     setUntrackedChecked.call(one, true);
    
  1655.     dispatchEventOnNode(one, 'click');
    
  1656.     expect(one.checked).toBe(false);
    
  1657.     expect(two.checked).toBe(true);
    
  1658.     expect(isCheckedDirty(one)).toBe(true);
    
  1659.     expect(isCheckedDirty(two)).toBe(true);
    
  1660.     assertInputTrackingIsCurrent(container);
    
  1661. 
    
  1662.     // After a delay...
    
  1663.     ReactDOM.unstable_batchedUpdates(thunk);
    
  1664.     expect(one.checked).toBe(true);
    
  1665.     expect(two.checked).toBe(false);
    
  1666.     expect(isCheckedDirty(one)).toBe(true);
    
  1667.     expect(isCheckedDirty(two)).toBe(true);
    
  1668.     assertInputTrackingIsCurrent(container);
    
  1669.   });
    
  1670. 
    
  1671.   it('should warn with value and no onChange handler and readOnly specified', () => {
    
  1672.     ReactDOM.render(
    
  1673.       <input type="text" value="zoink" readOnly={true} />,
    
  1674.       container,
    
  1675.     );
    
  1676.     ReactDOM.unmountComponentAtNode(container);
    
  1677. 
    
  1678.     expect(() =>
    
  1679.       ReactDOM.render(
    
  1680.         <input type="text" value="zoink" readOnly={false} />,
    
  1681.         container,
    
  1682.       ),
    
  1683.     ).toErrorDev(
    
  1684.       'Warning: You provided a `value` prop to a form ' +
    
  1685.         'field without an `onChange` handler. This will render a read-only ' +
    
  1686.         'field. If the field should be mutable use `defaultValue`. ' +
    
  1687.         'Otherwise, set either `onChange` or `readOnly`.\n' +
    
  1688.         '    in input (at **)',
    
  1689.     );
    
  1690.   });
    
  1691. 
    
  1692.   it('should have a this value of undefined if bind is not used', () => {
    
  1693.     expect.assertions(1);
    
  1694.     const unboundInputOnChange = function () {
    
  1695.       expect(this).toBe(undefined);
    
  1696.     };
    
  1697. 
    
  1698.     const stub = <input type="text" onChange={unboundInputOnChange} />;
    
  1699.     const node = ReactDOM.render(stub, container);
    
  1700. 
    
  1701.     setUntrackedValue.call(node, 'giraffe');
    
  1702.     dispatchEventOnNode(node, 'input');
    
  1703.   });
    
  1704. 
    
  1705.   it('should update defaultValue to empty string', () => {
    
  1706.     ReactDOM.render(<input type="text" defaultValue={'foo'} />, container);
    
  1707.     if (disableInputAttributeSyncing) {
    
  1708.       expect(isValueDirty(container.firstChild)).toBe(false);
    
  1709.     } else {
    
  1710.       expect(isValueDirty(container.firstChild)).toBe(true);
    
  1711.     }
    
  1712.     ReactDOM.render(<input type="text" defaultValue={''} />, container);
    
  1713.     expect(container.firstChild.defaultValue).toBe('');
    
  1714.     if (disableInputAttributeSyncing) {
    
  1715.       expect(isValueDirty(container.firstChild)).toBe(false);
    
  1716.     } else {
    
  1717.       expect(isValueDirty(container.firstChild)).toBe(true);
    
  1718.     }
    
  1719.   });
    
  1720. 
    
  1721.   it('should warn if value is null', () => {
    
  1722.     expect(() =>
    
  1723.       ReactDOM.render(<input type="text" value={null} />, container),
    
  1724.     ).toErrorDev(
    
  1725.       '`value` prop on `input` should not be null. ' +
    
  1726.         'Consider using an empty string to clear the component or `undefined` ' +
    
  1727.         'for uncontrolled components.',
    
  1728.     );
    
  1729.     ReactDOM.unmountComponentAtNode(container);
    
  1730. 
    
  1731.     ReactDOM.render(<input type="text" value={null} />, container);
    
  1732.   });
    
  1733. 
    
  1734.   it('should warn if checked and defaultChecked props are specified', () => {
    
  1735.     expect(() =>
    
  1736.       ReactDOM.render(
    
  1737.         <input
    
  1738.           type="radio"
    
  1739.           checked={true}
    
  1740.           defaultChecked={true}
    
  1741.           readOnly={true}
    
  1742.         />,
    
  1743.         container,
    
  1744.       ),
    
  1745.     ).toErrorDev(
    
  1746.       'A component contains an input of type radio with both checked and defaultChecked props. ' +
    
  1747.         'Input elements must be either controlled or uncontrolled ' +
    
  1748.         '(specify either the checked prop, or the defaultChecked prop, but not ' +
    
  1749.         'both). Decide between using a controlled or uncontrolled input ' +
    
  1750.         'element and remove one of these props. More info: ' +
    
  1751.         'https://reactjs.org/link/controlled-components',
    
  1752.     );
    
  1753.     ReactDOM.unmountComponentAtNode(container);
    
  1754. 
    
  1755.     ReactDOM.render(
    
  1756.       <input
    
  1757.         type="radio"
    
  1758.         checked={true}
    
  1759.         defaultChecked={true}
    
  1760.         readOnly={true}
    
  1761.       />,
    
  1762.       container,
    
  1763.     );
    
  1764.   });
    
  1765. 
    
  1766.   it('should warn if value and defaultValue props are specified', () => {
    
  1767.     expect(() =>
    
  1768.       ReactDOM.render(
    
  1769.         <input type="text" value="foo" defaultValue="bar" readOnly={true} />,
    
  1770.         container,
    
  1771.       ),
    
  1772.     ).toErrorDev(
    
  1773.       'A component contains an input of type text with both value and defaultValue props. ' +
    
  1774.         'Input elements must be either controlled or uncontrolled ' +
    
  1775.         '(specify either the value prop, or the defaultValue prop, but not ' +
    
  1776.         'both). Decide between using a controlled or uncontrolled input ' +
    
  1777.         'element and remove one of these props. More info: ' +
    
  1778.         'https://reactjs.org/link/controlled-components',
    
  1779.     );
    
  1780.     ReactDOM.unmountComponentAtNode(container);
    
  1781. 
    
  1782.     ReactDOM.render(
    
  1783.       <input type="text" value="foo" defaultValue="bar" readOnly={true} />,
    
  1784.       container,
    
  1785.     );
    
  1786.   });
    
  1787. 
    
  1788.   it('should warn if controlled input switches to uncontrolled (value is undefined)', () => {
    
  1789.     const stub = (
    
  1790.       <input type="text" value="controlled" onChange={emptyFunction} />
    
  1791.     );
    
  1792.     ReactDOM.render(stub, container);
    
  1793.     expect(() => ReactDOM.render(<input type="text" />, container)).toErrorDev(
    
  1794.       'Warning: A component is changing a controlled input to be uncontrolled. ' +
    
  1795.         'This is likely caused by the value changing from a defined to ' +
    
  1796.         'undefined, which should not happen. ' +
    
  1797.         'Decide between using a controlled or uncontrolled input ' +
    
  1798.         'element for the lifetime of the component. More info: https://reactjs.org/link/controlled-components\n' +
    
  1799.         '    in input (at **)',
    
  1800.     );
    
  1801.   });
    
  1802. 
    
  1803.   it('should warn if controlled input switches to uncontrolled (value is null)', () => {
    
  1804.     const stub = (
    
  1805.       <input type="text" value="controlled" onChange={emptyFunction} />
    
  1806.     );
    
  1807.     ReactDOM.render(stub, container);
    
  1808.     expect(() =>
    
  1809.       ReactDOM.render(<input type="text" value={null} />, container),
    
  1810.     ).toErrorDev([
    
  1811.       '`value` prop on `input` should not be null. ' +
    
  1812.         'Consider using an empty string to clear the component or `undefined` for uncontrolled components',
    
  1813.       'Warning: A component is changing a controlled input to be uncontrolled. ' +
    
  1814.         'This is likely caused by the value changing from a defined to ' +
    
  1815.         'undefined, which should not happen. ' +
    
  1816.         'Decide between using a controlled or uncontrolled input ' +
    
  1817.         'element for the lifetime of the component. More info: https://reactjs.org/link/controlled-components\n' +
    
  1818.         '    in input (at **)',
    
  1819.     ]);
    
  1820.   });
    
  1821. 
    
  1822.   it('should warn if controlled input switches to uncontrolled with defaultValue', () => {
    
  1823.     const stub = (
    
  1824.       <input type="text" value="controlled" onChange={emptyFunction} />
    
  1825.     );
    
  1826.     ReactDOM.render(stub, container);
    
  1827.     expect(() =>
    
  1828.       ReactDOM.render(
    
  1829.         <input type="text" defaultValue="uncontrolled" />,
    
  1830.         container,
    
  1831.       ),
    
  1832.     ).toErrorDev(
    
  1833.       'Warning: A component is changing a controlled input to be uncontrolled. ' +
    
  1834.         'This is likely caused by the value changing from a defined to ' +
    
  1835.         'undefined, which should not happen. ' +
    
  1836.         'Decide between using a controlled or uncontrolled input ' +
    
  1837.         'element for the lifetime of the component. More info: https://reactjs.org/link/controlled-components\n' +
    
  1838.         '    in input (at **)',
    
  1839.     );
    
  1840.   });
    
  1841. 
    
  1842.   it('should warn if uncontrolled input (value is undefined) switches to controlled', () => {
    
  1843.     const stub = <input type="text" />;
    
  1844.     ReactDOM.render(stub, container);
    
  1845.     expect(() =>
    
  1846.       ReactDOM.render(<input type="text" value="controlled" />, container),
    
  1847.     ).toErrorDev(
    
  1848.       'Warning: A component is changing an uncontrolled input to be controlled. ' +
    
  1849.         'This is likely caused by the value changing from undefined to ' +
    
  1850.         'a defined value, which should not happen. ' +
    
  1851.         'Decide between using a controlled or uncontrolled input ' +
    
  1852.         'element for the lifetime of the component. More info: https://reactjs.org/link/controlled-components\n' +
    
  1853.         '    in input (at **)',
    
  1854.     );
    
  1855.   });
    
  1856. 
    
  1857.   it('should warn if uncontrolled input (value is null) switches to controlled', () => {
    
  1858.     const stub = <input type="text" value={null} />;
    
  1859.     expect(() => ReactDOM.render(stub, container)).toErrorDev(
    
  1860.       '`value` prop on `input` should not be null. ' +
    
  1861.         'Consider using an empty string to clear the component or `undefined` for uncontrolled components.',
    
  1862.     );
    
  1863.     expect(() =>
    
  1864.       ReactDOM.render(<input type="text" value="controlled" />, container),
    
  1865.     ).toErrorDev(
    
  1866.       'Warning: A component is changing an uncontrolled input to be controlled. ' +
    
  1867.         'This is likely caused by the value changing from undefined to ' +
    
  1868.         'a defined value, which should not happen. ' +
    
  1869.         'Decide between using a controlled or uncontrolled input ' +
    
  1870.         'element for the lifetime of the component. More info: https://reactjs.org/link/controlled-components\n' +
    
  1871.         '    in input (at **)',
    
  1872.     );
    
  1873.   });
    
  1874. 
    
  1875.   it('should warn if controlled checkbox switches to uncontrolled (checked is undefined)', () => {
    
  1876.     const stub = (
    
  1877.       <input type="checkbox" checked={true} onChange={emptyFunction} />
    
  1878.     );
    
  1879.     ReactDOM.render(stub, container);
    
  1880.     expect(() =>
    
  1881.       ReactDOM.render(<input type="checkbox" />, container),
    
  1882.     ).toErrorDev(
    
  1883.       'Warning: A component is changing a controlled input to be uncontrolled. ' +
    
  1884.         'This is likely caused by the value changing from a defined to ' +
    
  1885.         'undefined, which should not happen. ' +
    
  1886.         'Decide between using a controlled or uncontrolled input ' +
    
  1887.         'element for the lifetime of the component. More info: https://reactjs.org/link/controlled-components\n' +
    
  1888.         '    in input (at **)',
    
  1889.     );
    
  1890.   });
    
  1891. 
    
  1892.   it('should warn if controlled checkbox switches to uncontrolled (checked is null)', () => {
    
  1893.     const stub = (
    
  1894.       <input type="checkbox" checked={true} onChange={emptyFunction} />
    
  1895.     );
    
  1896.     ReactDOM.render(stub, container);
    
  1897.     expect(() =>
    
  1898.       ReactDOM.render(<input type="checkbox" checked={null} />, container),
    
  1899.     ).toErrorDev(
    
  1900.       'Warning: A component is changing a controlled input to be uncontrolled. ' +
    
  1901.         'This is likely caused by the value changing from a defined to ' +
    
  1902.         'undefined, which should not happen. ' +
    
  1903.         'Decide between using a controlled or uncontrolled input ' +
    
  1904.         'element for the lifetime of the component. More info: https://reactjs.org/link/controlled-components\n' +
    
  1905.         '    in input (at **)',
    
  1906.     );
    
  1907.   });
    
  1908. 
    
  1909.   it('should warn if controlled checkbox switches to uncontrolled with defaultChecked', () => {
    
  1910.     const stub = (
    
  1911.       <input type="checkbox" checked={true} onChange={emptyFunction} />
    
  1912.     );
    
  1913.     ReactDOM.render(stub, container);
    
  1914.     expect(() =>
    
  1915.       ReactDOM.render(
    
  1916.         <input type="checkbox" defaultChecked={true} />,
    
  1917.         container,
    
  1918.       ),
    
  1919.     ).toErrorDev(
    
  1920.       'Warning: A component is changing a controlled input to be uncontrolled. ' +
    
  1921.         'This is likely caused by the value changing from a defined to ' +
    
  1922.         'undefined, which should not happen. ' +
    
  1923.         'Decide between using a controlled or uncontrolled input ' +
    
  1924.         'element for the lifetime of the component. More info: https://reactjs.org/link/controlled-components\n' +
    
  1925.         '    in input (at **)',
    
  1926.     );
    
  1927.   });
    
  1928. 
    
  1929.   it('should warn if uncontrolled checkbox (checked is undefined) switches to controlled', () => {
    
  1930.     const stub = <input type="checkbox" />;
    
  1931.     ReactDOM.render(stub, container);
    
  1932.     expect(() =>
    
  1933.       ReactDOM.render(<input type="checkbox" checked={true} />, container),
    
  1934.     ).toErrorDev(
    
  1935.       'Warning: A component is changing an uncontrolled input to be controlled. ' +
    
  1936.         'This is likely caused by the value changing from undefined to ' +
    
  1937.         'a defined value, which should not happen. ' +
    
  1938.         'Decide between using a controlled or uncontrolled input ' +
    
  1939.         'element for the lifetime of the component. More info: https://reactjs.org/link/controlled-components\n' +
    
  1940.         '    in input (at **)',
    
  1941.     );
    
  1942.   });
    
  1943. 
    
  1944.   it('should warn if uncontrolled checkbox (checked is null) switches to controlled', () => {
    
  1945.     const stub = <input type="checkbox" checked={null} />;
    
  1946.     ReactDOM.render(stub, container);
    
  1947.     expect(() =>
    
  1948.       ReactDOM.render(<input type="checkbox" checked={true} />, container),
    
  1949.     ).toErrorDev(
    
  1950.       'Warning: A component is changing an uncontrolled input to be controlled. ' +
    
  1951.         'This is likely caused by the value changing from undefined to ' +
    
  1952.         'a defined value, which should not happen. ' +
    
  1953.         'Decide between using a controlled or uncontrolled input ' +
    
  1954.         'element for the lifetime of the component. More info: https://reactjs.org/link/controlled-components\n' +
    
  1955.         '    in input (at **)',
    
  1956.     );
    
  1957.   });
    
  1958. 
    
  1959.   it('should warn if controlled radio switches to uncontrolled (checked is undefined)', () => {
    
  1960.     const stub = <input type="radio" checked={true} onChange={emptyFunction} />;
    
  1961.     ReactDOM.render(stub, container);
    
  1962.     expect(() => ReactDOM.render(<input type="radio" />, container)).toErrorDev(
    
  1963.       'Warning: A component is changing a controlled input to be uncontrolled. ' +
    
  1964.         'This is likely caused by the value changing from a defined to ' +
    
  1965.         'undefined, which should not happen. ' +
    
  1966.         'Decide between using a controlled or uncontrolled input ' +
    
  1967.         'element for the lifetime of the component. More info: https://reactjs.org/link/controlled-components\n' +
    
  1968.         '    in input (at **)',
    
  1969.     );
    
  1970.   });
    
  1971. 
    
  1972.   it('should warn if controlled radio switches to uncontrolled (checked is null)', () => {
    
  1973.     const stub = <input type="radio" checked={true} onChange={emptyFunction} />;
    
  1974.     ReactDOM.render(stub, container);
    
  1975.     expect(() =>
    
  1976.       ReactDOM.render(<input type="radio" checked={null} />, container),
    
  1977.     ).toErrorDev(
    
  1978.       'Warning: A component is changing a controlled input to be uncontrolled. ' +
    
  1979.         'This is likely caused by the value changing from a defined to ' +
    
  1980.         'undefined, which should not happen. ' +
    
  1981.         'Decide between using a controlled or uncontrolled input ' +
    
  1982.         'element for the lifetime of the component. More info: https://reactjs.org/link/controlled-components\n' +
    
  1983.         '    in input (at **)',
    
  1984.     );
    
  1985.   });
    
  1986. 
    
  1987.   it('should warn if controlled radio switches to uncontrolled with defaultChecked', () => {
    
  1988.     const stub = <input type="radio" checked={true} onChange={emptyFunction} />;
    
  1989.     ReactDOM.render(stub, container);
    
  1990.     expect(() =>
    
  1991.       ReactDOM.render(<input type="radio" defaultChecked={true} />, container),
    
  1992.     ).toErrorDev(
    
  1993.       'Warning: A component is changing a controlled input to be uncontrolled. ' +
    
  1994.         'This is likely caused by the value changing from a defined to ' +
    
  1995.         'undefined, which should not happen. ' +
    
  1996.         'Decide between using a controlled or uncontrolled input ' +
    
  1997.         'element for the lifetime of the component. More info: https://reactjs.org/link/controlled-components\n' +
    
  1998.         '    in input (at **)',
    
  1999.     );
    
  2000.   });
    
  2001. 
    
  2002.   it('should warn if uncontrolled radio (checked is undefined) switches to controlled', () => {
    
  2003.     const stub = <input type="radio" />;
    
  2004.     ReactDOM.render(stub, container);
    
  2005.     expect(() =>
    
  2006.       ReactDOM.render(<input type="radio" checked={true} />, container),
    
  2007.     ).toErrorDev(
    
  2008.       'Warning: A component is changing an uncontrolled input to be controlled. ' +
    
  2009.         'This is likely caused by the value changing from undefined to ' +
    
  2010.         'a defined value, which should not happen. ' +
    
  2011.         'Decide between using a controlled or uncontrolled input ' +
    
  2012.         'element for the lifetime of the component. More info: https://reactjs.org/link/controlled-components\n' +
    
  2013.         '    in input (at **)',
    
  2014.     );
    
  2015.   });
    
  2016. 
    
  2017.   it('should warn if uncontrolled radio (checked is null) switches to controlled', () => {
    
  2018.     const stub = <input type="radio" checked={null} />;
    
  2019.     ReactDOM.render(stub, container);
    
  2020.     expect(() =>
    
  2021.       ReactDOM.render(<input type="radio" checked={true} />, container),
    
  2022.     ).toErrorDev(
    
  2023.       'Warning: A component is changing an uncontrolled input to be controlled. ' +
    
  2024.         'This is likely caused by the value changing from undefined to ' +
    
  2025.         'a defined value, which should not happen. ' +
    
  2026.         'Decide between using a controlled or uncontrolled input ' +
    
  2027.         'element for the lifetime of the component. More info: https://reactjs.org/link/controlled-components\n' +
    
  2028.         '    in input (at **)',
    
  2029.     );
    
  2030.   });
    
  2031. 
    
  2032.   it('should not warn if radio value changes but never becomes controlled', () => {
    
  2033.     ReactDOM.render(<input type="radio" value="value" />, container);
    
  2034.     ReactDOM.render(<input type="radio" />, container);
    
  2035.     ReactDOM.render(
    
  2036.       <input type="radio" value="value" defaultChecked={true} />,
    
  2037.       container,
    
  2038.     );
    
  2039.     ReactDOM.render(
    
  2040.       <input type="radio" value="value" onChange={() => null} />,
    
  2041.       container,
    
  2042.     );
    
  2043.     ReactDOM.render(<input type="radio" />, container);
    
  2044.   });
    
  2045. 
    
  2046.   it('should not warn if radio value changes but never becomes uncontrolled', () => {
    
  2047.     ReactDOM.render(
    
  2048.       <input type="radio" checked={false} onChange={() => null} />,
    
  2049.       container,
    
  2050.     );
    
  2051.     const input = container.querySelector('input');
    
  2052.     expect(isCheckedDirty(input)).toBe(true);
    
  2053.     ReactDOM.render(
    
  2054.       <input
    
  2055.         type="radio"
    
  2056.         value="value"
    
  2057.         defaultChecked={true}
    
  2058.         checked={false}
    
  2059.         onChange={() => null}
    
  2060.       />,
    
  2061.       container,
    
  2062.     );
    
  2063.     expect(isCheckedDirty(input)).toBe(true);
    
  2064.     assertInputTrackingIsCurrent(container);
    
  2065.   });
    
  2066. 
    
  2067.   it('should warn if radio checked false changes to become uncontrolled', () => {
    
  2068.     ReactDOM.render(
    
  2069.       <input
    
  2070.         type="radio"
    
  2071.         value="value"
    
  2072.         checked={false}
    
  2073.         onChange={() => null}
    
  2074.       />,
    
  2075.       container,
    
  2076.     );
    
  2077.     expect(() =>
    
  2078.       ReactDOM.render(<input type="radio" value="value" />, container),
    
  2079.     ).toErrorDev(
    
  2080.       'Warning: A component is changing a controlled input to be uncontrolled. ' +
    
  2081.         'This is likely caused by the value changing from a defined to ' +
    
  2082.         'undefined, which should not happen. ' +
    
  2083.         'Decide between using a controlled or uncontrolled input ' +
    
  2084.         'element for the lifetime of the component. More info: https://reactjs.org/link/controlled-components\n' +
    
  2085.         '    in input (at **)',
    
  2086.     );
    
  2087.   });
    
  2088. 
    
  2089.   it('sets type, step, min, max before value always', () => {
    
  2090.     const log = [];
    
  2091.     const originalCreateElement = document.createElement;
    
  2092.     spyOnDevAndProd(document, 'createElement').mockImplementation(
    
  2093.       function (type) {
    
  2094.         const el = originalCreateElement.apply(this, arguments);
    
  2095.         let value = '';
    
  2096.         let typeProp = '';
    
  2097. 
    
  2098.         if (type === 'input') {
    
  2099.           Object.defineProperty(el, 'type', {
    
  2100.             get: function () {
    
  2101.               return typeProp;
    
  2102.             },
    
  2103.             set: function (val) {
    
  2104.               typeProp = String(val);
    
  2105.               log.push('set property type');
    
  2106.             },
    
  2107.           });
    
  2108.           Object.defineProperty(el, 'value', {
    
  2109.             get: function () {
    
  2110.               return value;
    
  2111.             },
    
  2112.             set: function (val) {
    
  2113.               value = String(val);
    
  2114.               log.push('set property value');
    
  2115.             },
    
  2116.           });
    
  2117.           spyOnDevAndProd(el, 'setAttribute').mockImplementation(
    
  2118.             function (name) {
    
  2119.               log.push('set attribute ' + name);
    
  2120.             },
    
  2121.           );
    
  2122.         }
    
  2123.         return el;
    
  2124.       },
    
  2125.     );
    
  2126. 
    
  2127.     ReactDOM.render(
    
  2128.       <input
    
  2129.         value="0"
    
  2130.         onChange={() => {}}
    
  2131.         type="range"
    
  2132.         min="0"
    
  2133.         max="100"
    
  2134.         step="1"
    
  2135.       />,
    
  2136.       container,
    
  2137.     );
    
  2138. 
    
  2139.     expect(log).toEqual([
    
  2140.       'set attribute min',
    
  2141.       'set attribute max',
    
  2142.       'set attribute step',
    
  2143.       'set property type',
    
  2144.       'set property value',
    
  2145.     ]);
    
  2146.   });
    
  2147. 
    
  2148.   it('sets value properly with type coming later in props', () => {
    
  2149.     const input = ReactDOM.render(<input value="hi" type="radio" />, container);
    
  2150.     expect(input.value).toBe('hi');
    
  2151.   });
    
  2152. 
    
  2153.   it('does not raise a validation warning when it switches types', () => {
    
  2154.     class Input extends React.Component {
    
  2155.       state = {type: 'number', value: 1000};
    
  2156. 
    
  2157.       render() {
    
  2158.         const {value, type} = this.state;
    
  2159.         return <input onChange={() => {}} type={type} value={value} />;
    
  2160.       }
    
  2161.     }
    
  2162. 
    
  2163.     const input = ReactDOM.render(<Input />, container);
    
  2164.     const node = ReactDOM.findDOMNode(input);
    
  2165. 
    
  2166.     // If the value is set before the type, a validation warning will raise and
    
  2167.     // the value will not be assigned.
    
  2168.     input.setState({type: 'text', value: 'Test'});
    
  2169.     expect(node.value).toEqual('Test');
    
  2170.   });
    
  2171. 
    
  2172.   it('resets value of date/time input to fix bugs in iOS Safari', () => {
    
  2173.     function strify(x) {
    
  2174.       return JSON.stringify(x, null, 2);
    
  2175.     }
    
  2176. 
    
  2177.     const log = [];
    
  2178.     const originalCreateElement = document.createElement;
    
  2179.     spyOnDevAndProd(document, 'createElement').mockImplementation(
    
  2180.       function (type) {
    
  2181.         const el = originalCreateElement.apply(this, arguments);
    
  2182.         const getDefaultValue = Object.getOwnPropertyDescriptor(
    
  2183.           HTMLInputElement.prototype,
    
  2184.           'defaultValue',
    
  2185.         ).get;
    
  2186.         const setDefaultValue = Object.getOwnPropertyDescriptor(
    
  2187.           HTMLInputElement.prototype,
    
  2188.           'defaultValue',
    
  2189.         ).set;
    
  2190.         const getValue = Object.getOwnPropertyDescriptor(
    
  2191.           HTMLInputElement.prototype,
    
  2192.           'value',
    
  2193.         ).get;
    
  2194.         const setValue = Object.getOwnPropertyDescriptor(
    
  2195.           HTMLInputElement.prototype,
    
  2196.           'value',
    
  2197.         ).set;
    
  2198.         const getType = Object.getOwnPropertyDescriptor(
    
  2199.           HTMLInputElement.prototype,
    
  2200.           'type',
    
  2201.         ).get;
    
  2202.         const setType = Object.getOwnPropertyDescriptor(
    
  2203.           HTMLInputElement.prototype,
    
  2204.           'type',
    
  2205.         ).set;
    
  2206.         if (type === 'input') {
    
  2207.           Object.defineProperty(el, 'defaultValue', {
    
  2208.             get: function () {
    
  2209.               return getDefaultValue.call(this);
    
  2210.             },
    
  2211.             set: function (val) {
    
  2212.               log.push(`node.defaultValue = ${strify(val)}`);
    
  2213.               setDefaultValue.call(this, val);
    
  2214.             },
    
  2215.           });
    
  2216.           Object.defineProperty(el, 'value', {
    
  2217.             get: function () {
    
  2218.               return getValue.call(this);
    
  2219.             },
    
  2220.             set: function (val) {
    
  2221.               log.push(`node.value = ${strify(val)}`);
    
  2222.               setValue.call(this, val);
    
  2223.             },
    
  2224.           });
    
  2225.           Object.defineProperty(el, 'type', {
    
  2226.             get: function () {
    
  2227.               return getType.call(this);
    
  2228.             },
    
  2229.             set: function (val) {
    
  2230.               log.push(`node.type = ${strify(val)}`);
    
  2231.               setType.call(this, val);
    
  2232.             },
    
  2233.           });
    
  2234.           spyOnDevAndProd(el, 'setAttribute').mockImplementation(
    
  2235.             function (name, val) {
    
  2236.               log.push(`node.setAttribute(${strify(name)}, ${strify(val)})`);
    
  2237.             },
    
  2238.           );
    
  2239.         }
    
  2240.         return el;
    
  2241.       },
    
  2242.     );
    
  2243. 
    
  2244.     ReactDOM.render(<input type="date" defaultValue="1980-01-01" />, container);
    
  2245. 
    
  2246.     if (disableInputAttributeSyncing) {
    
  2247.       expect(log).toEqual([
    
  2248.         'node.type = "date"',
    
  2249.         'node.defaultValue = "1980-01-01"',
    
  2250.         // TODO: it's possible this reintroduces the bug because we don't assign `value` at all.
    
  2251.         // Need to check this on mobile Safari and Chrome.
    
  2252.       ]);
    
  2253.     } else {
    
  2254.       expect(log).toEqual([
    
  2255.         'node.type = "date"',
    
  2256.         // value must be assigned before defaultValue. This fixes an issue where the
    
  2257.         // visually displayed value of date inputs disappears on mobile Safari and Chrome:
    
  2258.         // https://github.com/facebook/react/issues/7233
    
  2259.         'node.value = "1980-01-01"',
    
  2260.         'node.defaultValue = "1980-01-01"',
    
  2261.       ]);
    
  2262.     }
    
  2263.   });
    
  2264. 
    
  2265.   describe('assigning the value attribute on controlled inputs', function () {
    
  2266.     function getTestInput() {
    
  2267.       return class extends React.Component {
    
  2268.         state = {
    
  2269.           value: this.props.value == null ? '' : this.props.value,
    
  2270.         };
    
  2271.         onChange = event => {
    
  2272.           this.setState({value: event.target.value});
    
  2273.         };
    
  2274.         render() {
    
  2275.           const type = this.props.type;
    
  2276.           const value = this.state.value;
    
  2277. 
    
  2278.           return <input type={type} value={value} onChange={this.onChange} />;
    
  2279.         }
    
  2280.       };
    
  2281.     }
    
  2282. 
    
  2283.     it('always sets the attribute when values change on text inputs', function () {
    
  2284.       const Input = getTestInput();
    
  2285.       const stub = ReactDOM.render(<Input type="text" />, container);
    
  2286.       const node = ReactDOM.findDOMNode(stub);
    
  2287.       expect(isValueDirty(node)).toBe(false);
    
  2288. 
    
  2289.       setUntrackedValue.call(node, '2');
    
  2290.       dispatchEventOnNode(node, 'input');
    
  2291. 
    
  2292.       expect(isValueDirty(node)).toBe(true);
    
  2293.       if (disableInputAttributeSyncing) {
    
  2294.         expect(node.hasAttribute('value')).toBe(false);
    
  2295.       } else {
    
  2296.         expect(node.getAttribute('value')).toBe('2');
    
  2297.       }
    
  2298.     });
    
  2299. 
    
  2300.     it('does not set the value attribute on number inputs if focused', () => {
    
  2301.       const Input = getTestInput();
    
  2302.       const stub = ReactDOM.render(
    
  2303.         <Input type="number" value="1" />,
    
  2304.         container,
    
  2305.       );
    
  2306.       const node = ReactDOM.findDOMNode(stub);
    
  2307.       expect(isValueDirty(node)).toBe(true);
    
  2308. 
    
  2309.       node.focus();
    
  2310. 
    
  2311.       setUntrackedValue.call(node, '2');
    
  2312.       dispatchEventOnNode(node, 'input');
    
  2313. 
    
  2314.       expect(isValueDirty(node)).toBe(true);
    
  2315.       if (disableInputAttributeSyncing) {
    
  2316.         expect(node.hasAttribute('value')).toBe(false);
    
  2317.       } else {
    
  2318.         expect(node.getAttribute('value')).toBe('1');
    
  2319.       }
    
  2320.     });
    
  2321. 
    
  2322.     it('sets the value attribute on number inputs on blur', () => {
    
  2323.       const Input = getTestInput();
    
  2324.       const stub = ReactDOM.render(
    
  2325.         <Input type="number" value="1" />,
    
  2326.         container,
    
  2327.       );
    
  2328.       const node = ReactDOM.findDOMNode(stub);
    
  2329.       expect(isValueDirty(node)).toBe(true);
    
  2330. 
    
  2331.       node.focus();
    
  2332.       setUntrackedValue.call(node, '2');
    
  2333.       dispatchEventOnNode(node, 'input');
    
  2334.       node.blur();
    
  2335. 
    
  2336.       expect(isValueDirty(node)).toBe(true);
    
  2337.       if (disableInputAttributeSyncing) {
    
  2338.         expect(node.value).toBe('2');
    
  2339.         expect(node.hasAttribute('value')).toBe(false);
    
  2340.       } else {
    
  2341.         expect(node.value).toBe('2');
    
  2342.         expect(node.getAttribute('value')).toBe('2');
    
  2343.       }
    
  2344.     });
    
  2345. 
    
  2346.     it('an uncontrolled number input will not update the value attribute on blur', () => {
    
  2347.       const node = ReactDOM.render(
    
  2348.         <input type="number" defaultValue="1" />,
    
  2349.         container,
    
  2350.       );
    
  2351.       if (disableInputAttributeSyncing) {
    
  2352.         expect(isValueDirty(node)).toBe(false);
    
  2353.       } else {
    
  2354.         expect(isValueDirty(node)).toBe(true);
    
  2355.       }
    
  2356. 
    
  2357.       node.focus();
    
  2358.       setUntrackedValue.call(node, 4);
    
  2359.       dispatchEventOnNode(node, 'input');
    
  2360.       node.blur();
    
  2361. 
    
  2362.       expect(isValueDirty(node)).toBe(true);
    
  2363.       expect(node.getAttribute('value')).toBe('1');
    
  2364.     });
    
  2365. 
    
  2366.     it('an uncontrolled text input will not update the value attribute on blur', () => {
    
  2367.       const node = ReactDOM.render(
    
  2368.         <input type="text" defaultValue="1" />,
    
  2369.         container,
    
  2370.       );
    
  2371.       if (disableInputAttributeSyncing) {
    
  2372.         expect(isValueDirty(node)).toBe(false);
    
  2373.       } else {
    
  2374.         expect(isValueDirty(node)).toBe(true);
    
  2375.       }
    
  2376. 
    
  2377.       node.focus();
    
  2378.       setUntrackedValue.call(node, 4);
    
  2379.       dispatchEventOnNode(node, 'input');
    
  2380.       node.blur();
    
  2381. 
    
  2382.       expect(isValueDirty(node)).toBe(true);
    
  2383.       expect(node.getAttribute('value')).toBe('1');
    
  2384.     });
    
  2385.   });
    
  2386. 
    
  2387.   describe('setting a controlled input to undefined', () => {
    
  2388.     let input;
    
  2389. 
    
  2390.     function renderInputWithStringThenWithUndefined() {
    
  2391.       let setValueToUndefined;
    
  2392.       class Input extends React.Component {
    
  2393.         constructor() {
    
  2394.           super();
    
  2395.           setValueToUndefined = () => this.setState({value: undefined});
    
  2396.         }
    
  2397.         state = {value: 'first'};
    
  2398.         render() {
    
  2399.           return (
    
  2400.             <input
    
  2401.               onChange={e => this.setState({value: e.target.value})}
    
  2402.               value={this.state.value}
    
  2403.             />
    
  2404.           );
    
  2405.         }
    
  2406.       }
    
  2407. 
    
  2408.       const stub = ReactDOM.render(<Input />, container);
    
  2409.       input = ReactDOM.findDOMNode(stub);
    
  2410.       setUntrackedValue.call(input, 'latest');
    
  2411.       dispatchEventOnNode(input, 'input');
    
  2412.       setValueToUndefined();
    
  2413.     }
    
  2414. 
    
  2415.     it('reverts the value attribute to the initial value', () => {
    
  2416.       expect(renderInputWithStringThenWithUndefined).toErrorDev(
    
  2417.         'A component is changing a controlled input to be uncontrolled.',
    
  2418.       );
    
  2419.       if (disableInputAttributeSyncing) {
    
  2420.         expect(input.getAttribute('value')).toBe(null);
    
  2421.       } else {
    
  2422.         expect(input.getAttribute('value')).toBe('latest');
    
  2423.       }
    
  2424.     });
    
  2425. 
    
  2426.     it('preserves the value property', () => {
    
  2427.       expect(renderInputWithStringThenWithUndefined).toErrorDev(
    
  2428.         'A component is changing a controlled input to be uncontrolled.',
    
  2429.       );
    
  2430.       expect(input.value).toBe('latest');
    
  2431.     });
    
  2432.   });
    
  2433. 
    
  2434.   describe('setting a controlled input to null', () => {
    
  2435.     let input;
    
  2436. 
    
  2437.     function renderInputWithStringThenWithNull() {
    
  2438.       let setValueToNull;
    
  2439.       class Input extends React.Component {
    
  2440.         constructor() {
    
  2441.           super();
    
  2442.           setValueToNull = () => this.setState({value: null});
    
  2443.         }
    
  2444.         state = {value: 'first'};
    
  2445.         render() {
    
  2446.           return (
    
  2447.             <input
    
  2448.               onChange={e => this.setState({value: e.target.value})}
    
  2449.               value={this.state.value}
    
  2450.             />
    
  2451.           );
    
  2452.         }
    
  2453.       }
    
  2454. 
    
  2455.       const stub = ReactDOM.render(<Input />, container);
    
  2456.       input = ReactDOM.findDOMNode(stub);
    
  2457.       setUntrackedValue.call(input, 'latest');
    
  2458.       dispatchEventOnNode(input, 'input');
    
  2459.       setValueToNull();
    
  2460.     }
    
  2461. 
    
  2462.     it('reverts the value attribute to the initial value', () => {
    
  2463.       expect(renderInputWithStringThenWithNull).toErrorDev([
    
  2464.         '`value` prop on `input` should not be null. ' +
    
  2465.           'Consider using an empty string to clear the component ' +
    
  2466.           'or `undefined` for uncontrolled components.',
    
  2467.         'A component is changing a controlled input to be uncontrolled.',
    
  2468.       ]);
    
  2469.       if (disableInputAttributeSyncing) {
    
  2470.         expect(input.getAttribute('value')).toBe(null);
    
  2471.       } else {
    
  2472.         expect(input.getAttribute('value')).toBe('latest');
    
  2473.       }
    
  2474.     });
    
  2475. 
    
  2476.     it('preserves the value property', () => {
    
  2477.       expect(renderInputWithStringThenWithNull).toErrorDev([
    
  2478.         '`value` prop on `input` should not be null. ' +
    
  2479.           'Consider using an empty string to clear the component ' +
    
  2480.           'or `undefined` for uncontrolled components.',
    
  2481.         'A component is changing a controlled input to be uncontrolled.',
    
  2482.       ]);
    
  2483.       expect(input.value).toBe('latest');
    
  2484.     });
    
  2485.   });
    
  2486. 
    
  2487.   describe('When given a Symbol value', function () {
    
  2488.     it('treats initial Symbol value as an empty string', function () {
    
  2489.       expect(() =>
    
  2490.         ReactDOM.render(
    
  2491.           <input value={Symbol('foobar')} onChange={() => {}} />,
    
  2492.           container,
    
  2493.         ),
    
  2494.       ).toErrorDev('Invalid value for prop `value`');
    
  2495.       const node = container.firstChild;
    
  2496. 
    
  2497.       expect(node.value).toBe('');
    
  2498.       if (disableInputAttributeSyncing) {
    
  2499.         expect(node.hasAttribute('value')).toBe(false);
    
  2500.       } else {
    
  2501.         expect(node.getAttribute('value')).toBe('');
    
  2502.       }
    
  2503.     });
    
  2504. 
    
  2505.     it('treats updated Symbol value as an empty string', function () {
    
  2506.       ReactDOM.render(<input value="foo" onChange={() => {}} />, container);
    
  2507.       expect(() =>
    
  2508.         ReactDOM.render(
    
  2509.           <input value={Symbol('foobar')} onChange={() => {}} />,
    
  2510.           container,
    
  2511.         ),
    
  2512.       ).toErrorDev('Invalid value for prop `value`');
    
  2513.       const node = container.firstChild;
    
  2514. 
    
  2515.       expect(node.value).toBe('');
    
  2516.       if (disableInputAttributeSyncing) {
    
  2517.         expect(node.hasAttribute('value')).toBe(false);
    
  2518.       } else {
    
  2519.         expect(node.getAttribute('value')).toBe('');
    
  2520.       }
    
  2521.     });
    
  2522. 
    
  2523.     it('treats initial Symbol defaultValue as an empty string', function () {
    
  2524.       ReactDOM.render(<input defaultValue={Symbol('foobar')} />, container);
    
  2525.       const node = container.firstChild;
    
  2526. 
    
  2527.       expect(node.value).toBe('');
    
  2528.       expect(node.getAttribute('value')).toBe('');
    
  2529.       // TODO: we should warn here.
    
  2530.     });
    
  2531. 
    
  2532.     it('treats updated Symbol defaultValue as an empty string', function () {
    
  2533.       ReactDOM.render(<input defaultValue="foo" />, container);
    
  2534.       ReactDOM.render(<input defaultValue={Symbol('foobar')} />, container);
    
  2535.       const node = container.firstChild;
    
  2536. 
    
  2537.       if (disableInputAttributeSyncing) {
    
  2538.         expect(node.value).toBe('');
    
  2539.       } else {
    
  2540.         expect(node.value).toBe('foo');
    
  2541.       }
    
  2542.       expect(node.getAttribute('value')).toBe('');
    
  2543.       // TODO: we should warn here.
    
  2544.     });
    
  2545.   });
    
  2546. 
    
  2547.   describe('When given a function value', function () {
    
  2548.     it('treats initial function value as an empty string', function () {
    
  2549.       expect(() =>
    
  2550.         ReactDOM.render(
    
  2551.           <input value={() => {}} onChange={() => {}} />,
    
  2552.           container,
    
  2553.         ),
    
  2554.       ).toErrorDev('Invalid value for prop `value`');
    
  2555.       const node = container.firstChild;
    
  2556. 
    
  2557.       expect(node.value).toBe('');
    
  2558.       if (disableInputAttributeSyncing) {
    
  2559.         expect(node.hasAttribute('value')).toBe(false);
    
  2560.       } else {
    
  2561.         expect(node.getAttribute('value')).toBe('');
    
  2562.       }
    
  2563.     });
    
  2564. 
    
  2565.     it('treats updated function value as an empty string', function () {
    
  2566.       ReactDOM.render(<input value="foo" onChange={() => {}} />, container);
    
  2567.       expect(() =>
    
  2568.         ReactDOM.render(
    
  2569.           <input value={() => {}} onChange={() => {}} />,
    
  2570.           container,
    
  2571.         ),
    
  2572.       ).toErrorDev('Invalid value for prop `value`');
    
  2573.       const node = container.firstChild;
    
  2574. 
    
  2575.       expect(node.value).toBe('');
    
  2576.       if (disableInputAttributeSyncing) {
    
  2577.         expect(node.hasAttribute('value')).toBe(false);
    
  2578.       } else {
    
  2579.         expect(node.getAttribute('value')).toBe('');
    
  2580.       }
    
  2581.     });
    
  2582. 
    
  2583.     it('treats initial function defaultValue as an empty string', function () {
    
  2584.       ReactDOM.render(<input defaultValue={() => {}} />, container);
    
  2585.       const node = container.firstChild;
    
  2586. 
    
  2587.       expect(node.value).toBe('');
    
  2588.       expect(node.getAttribute('value')).toBe('');
    
  2589.       // TODO: we should warn here.
    
  2590.     });
    
  2591. 
    
  2592.     it('treats updated function defaultValue as an empty string', function () {
    
  2593.       ReactDOM.render(<input defaultValue="foo" />, container);
    
  2594.       ReactDOM.render(<input defaultValue={() => {}} />, container);
    
  2595.       const node = container.firstChild;
    
  2596. 
    
  2597.       if (disableInputAttributeSyncing) {
    
  2598.         expect(node.value).toBe('');
    
  2599.         expect(node.getAttribute('value')).toBe('');
    
  2600.       } else {
    
  2601.         expect(node.value).toBe('foo');
    
  2602.         expect(node.getAttribute('value')).toBe('');
    
  2603.       }
    
  2604.       // TODO: we should warn here.
    
  2605.     });
    
  2606.   });
    
  2607. 
    
  2608.   describe('checked inputs without a value property', function () {
    
  2609.     // In absence of a value, radio and checkboxes report a value of "on".
    
  2610.     // Between 16 and 16.2, we assigned a node's value to it's current
    
  2611.     // value in order to "dettach" it from defaultValue. This had the unfortunate
    
  2612.     // side-effect of assigning value="on" to radio and checkboxes
    
  2613.     it('does not add "on" in absence of value on a checkbox', function () {
    
  2614.       ReactDOM.render(
    
  2615.         <input type="checkbox" defaultChecked={true} />,
    
  2616.         container,
    
  2617.       );
    
  2618.       const node = container.firstChild;
    
  2619. 
    
  2620.       expect(node.value).toBe('on');
    
  2621.       expect(node.hasAttribute('value')).toBe(false);
    
  2622.     });
    
  2623. 
    
  2624.     it('does not add "on" in absence of value on a radio', function () {
    
  2625.       ReactDOM.render(<input type="radio" defaultChecked={true} />, container);
    
  2626.       const node = container.firstChild;
    
  2627. 
    
  2628.       expect(node.value).toBe('on');
    
  2629.       expect(node.hasAttribute('value')).toBe(false);
    
  2630.     });
    
  2631.   });
    
  2632. 
    
  2633.   it('should remove previous `defaultValue`', () => {
    
  2634.     const node = ReactDOM.render(
    
  2635.       <input type="text" defaultValue="0" />,
    
  2636.       container,
    
  2637.     );
    
  2638. 
    
  2639.     expect(node.value).toBe('0');
    
  2640.     expect(node.defaultValue).toBe('0');
    
  2641. 
    
  2642.     ReactDOM.render(<input type="text" />, container);
    
  2643.     expect(node.defaultValue).toBe('');
    
  2644.   });
    
  2645. 
    
  2646.   it('should treat `defaultValue={null}` as missing', () => {
    
  2647.     const node = ReactDOM.render(
    
  2648.       <input type="text" defaultValue="0" />,
    
  2649.       container,
    
  2650.     );
    
  2651. 
    
  2652.     expect(node.value).toBe('0');
    
  2653.     expect(node.defaultValue).toBe('0');
    
  2654. 
    
  2655.     ReactDOM.render(<input type="text" defaultValue={null} />, container);
    
  2656.     expect(node.defaultValue).toBe('');
    
  2657.   });
    
  2658. 
    
  2659.   it('should notice input changes when reverting back to original value', () => {
    
  2660.     const log = [];
    
  2661.     function onChange(e) {
    
  2662.       log.push(e.target.value);
    
  2663.     }
    
  2664.     ReactDOM.render(
    
  2665.       <input type="text" value="" onChange={onChange} />,
    
  2666.       container,
    
  2667.     );
    
  2668.     ReactDOM.render(
    
  2669.       <input type="text" value="a" onChange={onChange} />,
    
  2670.       container,
    
  2671.     );
    
  2672. 
    
  2673.     const node = container.firstChild;
    
  2674.     setUntrackedValue.call(node, '');
    
  2675.     dispatchEventOnNode(node, 'input');
    
  2676. 
    
  2677.     expect(log).toEqual(['']);
    
  2678.     expect(node.value).toBe('a');
    
  2679.   });
    
  2680. });