/**
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @emails react-core
 */

'use strict';

// Fix JSDOM. setAttribute is supposed to throw on things that can't be implicitly toStringed.
const setAttribute = Element.prototype.setAttribute;
Element.prototype.setAttribute = function (name, value) {
  // eslint-disable-next-line react-internal/safe-string-coercion
  return setAttribute.call(this, name, '' + value);
};

describe('ReactDOMSelect', () => {
  let React;
  let ReactDOM;
  let ReactDOMServer;
  let ReactTestUtils;

  const noop = function () {};

  beforeEach(() => {
    jest.resetModules();
    React = require('react');
    ReactDOM = require('react-dom');
    ReactDOMServer = require('react-dom/server');
    ReactTestUtils = require('react-dom/test-utils');
  });

  it('should allow setting `defaultValue`', () => {
    const stub = (
      <select defaultValue="giraffe">
        <option value="monkey">A monkey!</option>
        <option value="giraffe">A giraffe!</option>
        <option value="gorilla">A gorilla!</option>
      </select>
    );
    const options = stub.props.children;
    const container = document.createElement('div');
    const node = ReactDOM.render(stub, container);

    expect(node.value).toBe('giraffe');

    // Changing `defaultValue` should do nothing.
    ReactDOM.render(
      <select defaultValue="gorilla">{options}</select>,
      container,
    );
    expect(node.value).toEqual('giraffe');
  });

  it('should not throw with `defaultValue` and without children', () => {
    const stub = <select defaultValue="dummy" />;

    expect(() => {
      ReactTestUtils.renderIntoDocument(stub);
    }).not.toThrow();
  });

  it('should not control when using `defaultValue`', () => {
    const el = (
      <select defaultValue="giraffe">
        <option value="monkey">A monkey!</option>
        <option value="giraffe">A giraffe!</option>
        <option value="gorilla">A gorilla!</option>
      </select>
    );
    const container = document.createElement('div');
    const node = ReactDOM.render(el, container);

    expect(node.value).toBe('giraffe');

    node.value = 'monkey';
    ReactDOM.render(el, container);
    // Uncontrolled selects shouldn't change the value after first mounting
    expect(node.value).toEqual('monkey');
  });

  it('should allow setting `defaultValue` with multiple', () => {
    const stub = (
      <select multiple={true} defaultValue={['giraffe', 'gorilla']}>
        <option value="monkey">A monkey!</option>
        <option value="giraffe">A giraffe!</option>
        <option value="gorilla">A gorilla!</option>
      </select>
    );
    const options = stub.props.children;
    const container = document.createElement('div');
    const node = ReactDOM.render(stub, container);

    expect(node.options[0].selected).toBe(false); // monkey
    expect(node.options[1].selected).toBe(true); // giraffe
    expect(node.options[2].selected).toBe(true); // gorilla

    // Changing `defaultValue` should do nothing.
    ReactDOM.render(
      <select multiple={true} defaultValue={['monkey']}>
        {options}
      </select>,
      container,
    );

    expect(node.options[0].selected).toBe(false); // monkey
    expect(node.options[1].selected).toBe(true); // giraffe
    expect(node.options[2].selected).toBe(true); // gorilla
  });

  it('should allow setting `value`', () => {
    const stub = (
      <select value="giraffe" onChange={noop}>
        <option value="monkey">A monkey!</option>
        <option value="giraffe">A giraffe!</option>
        <option value="gorilla">A gorilla!</option>
      </select>
    );
    const options = stub.props.children;
    const container = document.createElement('div');
    const node = ReactDOM.render(stub, container);

    expect(node.value).toBe('giraffe');

    // Changing the `value` prop should change the selected option.
    ReactDOM.render(
      <select value="gorilla" onChange={noop}>
        {options}
      </select>,
      container,
    );
    expect(node.value).toEqual('gorilla');
  });

  it('should default to the first non-disabled option', () => {
    const stub = (
      <select defaultValue="">
        <option disabled={true}>Disabled</option>
        <option disabled={true}>Still Disabled</option>
        <option>0</option>
        <option disabled={true}>Also Disabled</option>
      </select>
    );
    const container = document.createElement('div');
    const node = ReactDOM.render(stub, container);
    expect(node.options[0].selected).toBe(false);
    expect(node.options[2].selected).toBe(true);
  });

  it('should allow setting `value` to __proto__', () => {
    const stub = (
      <select value="__proto__" onChange={noop}>
        <option value="monkey">A monkey!</option>
        <option value="__proto__">A giraffe!</option>
        <option value="gorilla">A gorilla!</option>
      </select>
    );
    const options = stub.props.children;
    const container = document.createElement('div');
    const node = ReactDOM.render(stub, container);

    expect(node.value).toBe('__proto__');

    // Changing the `value` prop should change the selected option.
    ReactDOM.render(
      <select value="gorilla" onChange={noop}>
        {options}
      </select>,
      container,
    );
    expect(node.value).toEqual('gorilla');
  });

  it('should not throw with `value` and without children', () => {
    const stub = <select value="dummy" onChange={noop} />;

    expect(() => {
      ReactTestUtils.renderIntoDocument(stub);
    }).not.toThrow();
  });

  it('should allow setting `value` with multiple', () => {
    const stub = (
      <select multiple={true} value={['giraffe', 'gorilla']} onChange={noop}>
        <option value="monkey">A monkey!</option>
        <option value="giraffe">A giraffe!</option>
        <option value="gorilla">A gorilla!</option>
      </select>
    );
    const options = stub.props.children;
    const container = document.createElement('div');
    const node = ReactDOM.render(stub, container);

    expect(node.options[0].selected).toBe(false); // monkey
    expect(node.options[1].selected).toBe(true); // giraffe
    expect(node.options[2].selected).toBe(true); // gorilla

    // Changing the `value` prop should change the selected options.
    ReactDOM.render(
      <select multiple={true} value={['monkey']} onChange={noop}>
        {options}
      </select>,
      container,
    );

    expect(node.options[0].selected).toBe(true); // monkey
    expect(node.options[1].selected).toBe(false); // giraffe
    expect(node.options[2].selected).toBe(false); // gorilla
  });

  it('should allow setting `value` to __proto__ with multiple', () => {
    const stub = (
      <select multiple={true} value={['__proto__', 'gorilla']} onChange={noop}>
        <option value="monkey">A monkey!</option>
        <option value="__proto__">A __proto__!</option>
        <option value="gorilla">A gorilla!</option>
      </select>
    );
    const options = stub.props.children;
    const container = document.createElement('div');
    const node = ReactDOM.render(stub, container);

    expect(node.options[0].selected).toBe(false); // monkey
    expect(node.options[1].selected).toBe(true); // __proto__
    expect(node.options[2].selected).toBe(true); // gorilla

    // Changing the `value` prop should change the selected options.
    ReactDOM.render(
      <select multiple={true} value={['monkey']} onChange={noop}>
        {options}
      </select>,
      container,
    );

    expect(node.options[0].selected).toBe(true); // monkey
    expect(node.options[1].selected).toBe(false); // __proto__
    expect(node.options[2].selected).toBe(false); // gorilla
  });

  it('should not select other options automatically', () => {
    const stub = (
      <select multiple={true} value={['12']} onChange={noop}>
        <option value="1">one</option>
        <option value="2">two</option>
        <option value="12">twelve</option>
      </select>
    );
    const node = ReactTestUtils.renderIntoDocument(stub);

    expect(node.options[0].selected).toBe(false); // one
    expect(node.options[1].selected).toBe(false); // two
    expect(node.options[2].selected).toBe(true); // twelve
  });

  it('should reset child options selected when they are changed and `value` is set', () => {
    const stub = <select multiple={true} value={['a', 'b']} onChange={noop} />;
    const container = document.createElement('div');
    const node = ReactDOM.render(stub, container);

    ReactDOM.render(
      <select multiple={true} value={['a', 'b']} onChange={noop}>
        <option value="a">a</option>
        <option value="b">b</option>
        <option value="c">c</option>
      </select>,
      container,
    );

    expect(node.options[0].selected).toBe(true); // a
    expect(node.options[1].selected).toBe(true); // b
    expect(node.options[2].selected).toBe(false); // c
  });

  it('should allow setting `value` with `objectToString`', () => {
    const objectToString = {
      animal: 'giraffe',
      toString: function () {
        return this.animal;
      },
    };

    const el = (
      <select multiple={true} value={[objectToString]} onChange={noop}>
        <option value="monkey">A monkey!</option>
        <option value="giraffe">A giraffe!</option>
        <option value="gorilla">A gorilla!</option>
      </select>
    );
    const container = document.createElement('div');
    const node = ReactDOM.render(el, container);

    expect(node.options[0].selected).toBe(false); // monkey
    expect(node.options[1].selected).toBe(true); // giraffe
    expect(node.options[2].selected).toBe(false); // gorilla

    // Changing the `value` prop should change the selected options.
    objectToString.animal = 'monkey';

    const el2 = (
      <select multiple={true} value={[objectToString]}>
        <option value="monkey">A monkey!</option>
        <option value="giraffe">A giraffe!</option>
        <option value="gorilla">A gorilla!</option>
      </select>
    );
    ReactDOM.render(el2, container);

    expect(node.options[0].selected).toBe(true); // monkey
    expect(node.options[1].selected).toBe(false); // giraffe
    expect(node.options[2].selected).toBe(false); // gorilla
  });

  it('should allow switching to multiple', () => {
    const stub = (
      <select defaultValue="giraffe">
        <option value="monkey">A monkey!</option>
        <option value="giraffe">A giraffe!</option>
        <option value="gorilla">A gorilla!</option>
      </select>
    );
    const options = stub.props.children;
    const container = document.createElement('div');
    const node = ReactDOM.render(stub, container);

    expect(node.options[0].selected).toBe(false); // monkey
    expect(node.options[1].selected).toBe(true); // giraffe
    expect(node.options[2].selected).toBe(false); // gorilla

    // When making it multiple, giraffe and gorilla should be selected
    ReactDOM.render(
      <select multiple={true} defaultValue={['giraffe', 'gorilla']}>
        {options}
      </select>,
      container,
    );

    expect(node.options[0].selected).toBe(false); // monkey
    expect(node.options[1].selected).toBe(true); // giraffe
    expect(node.options[2].selected).toBe(true); // gorilla
  });

  it('should allow switching from multiple', () => {
    const stub = (
      <select multiple={true} defaultValue={['giraffe', 'gorilla']}>
        <option value="monkey">A monkey!</option>
        <option value="giraffe">A giraffe!</option>
        <option value="gorilla">A gorilla!</option>
      </select>
    );
    const options = stub.props.children;
    const container = document.createElement('div');
    const node = ReactDOM.render(stub, container);

    expect(node.options[0].selected).toBe(false); // monkey
    expect(node.options[1].selected).toBe(true); // giraffe
    expect(node.options[2].selected).toBe(true); // gorilla

    // When removing multiple, defaultValue is applied again, being omitted
    // means that "monkey" will be selected
    ReactDOM.render(
      <select defaultValue="gorilla">{options}</select>,
      container,
    );

    expect(node.options[0].selected).toBe(false); // monkey
    expect(node.options[1].selected).toBe(false); // giraffe
    expect(node.options[2].selected).toBe(true); // gorilla
  });

  it('does not select an item when size is initially set to greater than 1', () => {
    const stub = (
      <select size="2">
        <option value="monkey">A monkey!</option>
        <option value="giraffe">A giraffe!</option>
        <option value="gorilla">A gorilla!</option>
      </select>
    );
    const container = document.createElement('div');
    const select = ReactDOM.render(stub, container);

    expect(select.options[0].selected).toBe(false);
    expect(select.options[1].selected).toBe(false);
    expect(select.options[2].selected).toBe(false);

    expect(select.value).toBe('');
    expect(select.selectedIndex).toBe(-1);
  });

  it('should remember value when switching to uncontrolled', () => {
    const stub = (
      <select value={'giraffe'} onChange={noop}>
        <option value="monkey">A monkey!</option>
        <option value="giraffe">A giraffe!</option>
        <option value="gorilla">A gorilla!</option>
      </select>
    );
    const options = stub.props.children;
    const container = document.createElement('div');
    const node = ReactDOM.render(stub, container);

    expect(node.options[0].selected).toBe(false); // monkey
    expect(node.options[1].selected).toBe(true); // giraffe
    expect(node.options[2].selected).toBe(false); // gorilla

    ReactDOM.render(<select>{options}</select>, container);

    expect(node.options[0].selected).toBe(false); // monkey
    expect(node.options[1].selected).toBe(true); // giraffe
    expect(node.options[2].selected).toBe(false); // gorilla
  });

  it('should remember updated value when switching to uncontrolled', () => {
    const stub = (
      <select value={'giraffe'} onChange={noop}>
        <option value="monkey">A monkey!</option>
        <option value="giraffe">A giraffe!</option>
        <option value="gorilla">A gorilla!</option>
      </select>
    );
    const options = stub.props.children;
    const container = document.createElement('div');
    const node = ReactDOM.render(stub, container);

    ReactDOM.render(
      <select value="gorilla" onChange={noop}>
        {options}
      </select>,
      container,
    );

    expect(node.options[0].selected).toBe(false); // monkey
    expect(node.options[1].selected).toBe(false); // giraffe
    expect(node.options[2].selected).toBe(true); // gorilla

    ReactDOM.render(<select>{options}</select>, container);

    expect(node.options[0].selected).toBe(false); // monkey
    expect(node.options[1].selected).toBe(false); // giraffe
    expect(node.options[2].selected).toBe(true); // gorilla
  });

  it('should support server-side rendering', () => {
    const stub = (
      <select value="giraffe" onChange={noop}>
        <option value="monkey">A monkey!</option>
        <option value="giraffe">A giraffe!</option>
        <option value="gorilla">A gorilla!</option>
      </select>
    );
    const container = document.createElement('div');
    container.innerHTML = ReactDOMServer.renderToString(stub);
    const options = container.firstChild.options;
    expect(options[0].value).toBe('monkey');
    expect(options[0].selected).toBe(false);
    expect(options[1].value).toBe('giraffe');
    expect(options[1].selected).toBe(true);
    expect(options[2].value).toBe('gorilla');
    expect(options[2].selected).toBe(false);
  });

  it('should support server-side rendering with defaultValue', () => {
    const stub = (
      <select defaultValue="giraffe">
        <option value="monkey">A monkey!</option>
        <option value="giraffe">A giraffe!</option>
        <option value="gorilla">A gorilla!</option>
      </select>
    );
    const container = document.createElement('div');
    container.innerHTML = ReactDOMServer.renderToString(stub);
    const options = container.firstChild.options;
    expect(options[0].value).toBe('monkey');
    expect(options[0].selected).toBe(false);
    expect(options[1].value).toBe('giraffe');
    expect(options[1].selected).toBe(true);
    expect(options[2].value).toBe('gorilla');
    expect(options[2].selected).toBe(false);
  });

  it('should support server-side rendering with dangerouslySetInnerHTML', () => {
    const stub = (
      <select defaultValue="giraffe">
        <option
          value="monkey"
          dangerouslySetInnerHTML={{
            __html: 'A monkey!',
          }}>
          {undefined}
        </option>
        <option
          value="giraffe"
          dangerouslySetInnerHTML={{
            __html: 'A giraffe!',
          }}>
          {null}
        </option>
        <option
          value="gorilla"
          dangerouslySetInnerHTML={{
            __html: 'A gorilla!',
          }}
        />
      </select>
    );
    const container = document.createElement('div');
    container.innerHTML = ReactDOMServer.renderToString(stub);
    const options = container.firstChild.options;
    expect(options[0].value).toBe('monkey');
    expect(options[0].selected).toBe(false);
    expect(options[1].value).toBe('giraffe');
    expect(options[1].selected).toBe(true);
    expect(options[2].value).toBe('gorilla');
    expect(options[2].selected).toBe(false);
  });

  it('should support server-side rendering with multiple', () => {
    const stub = (
      <select multiple={true} value={['giraffe', 'gorilla']} onChange={noop}>
        <option value="monkey">A monkey!</option>
        <option value="giraffe">A giraffe!</option>
        <option value="gorilla">A gorilla!</option>
      </select>
    );
    const container = document.createElement('div');
    container.innerHTML = ReactDOMServer.renderToString(stub);
    const options = container.firstChild.options;
    expect(options[0].value).toBe('monkey');
    expect(options[0].selected).toBe(false);
    expect(options[1].value).toBe('giraffe');
    expect(options[1].selected).toBe(true);
    expect(options[2].value).toBe('gorilla');
    expect(options[2].selected).toBe(true);
  });

  it('should not control defaultValue if re-adding options', () => {
    const container = document.createElement('div');

    const node = ReactDOM.render(
      <select multiple={true} defaultValue={['giraffe']}>
        <option key="monkey" value="monkey">
          A monkey!
        </option>
        <option key="giraffe" value="giraffe">
          A giraffe!
        </option>
        <option key="gorilla" value="gorilla">
          A gorilla!
        </option>
      </select>,
      container,
    );

    expect(node.options[0].selected).toBe(false); // monkey
    expect(node.options[1].selected).toBe(true); // giraffe
    expect(node.options[2].selected).toBe(false); // gorilla

    ReactDOM.render(
      <select multiple={true} defaultValue={['giraffe']}>
        <option key="monkey" value="monkey">
          A monkey!
        </option>
        <option key="gorilla" value="gorilla">
          A gorilla!
        </option>
      </select>,
      container,
    );

    expect(node.options[0].selected).toBe(false); // monkey
    expect(node.options[1].selected).toBe(false); // gorilla

    ReactDOM.render(
      <select multiple={true} defaultValue={['giraffe']}>
        <option key="monkey" value="monkey">
          A monkey!
        </option>
        <option key="giraffe" value="giraffe">
          A giraffe!
        </option>
        <option key="gorilla" value="gorilla">
          A gorilla!
        </option>
      </select>,
      container,
    );

    expect(node.options[0].selected).toBe(false); // monkey
    expect(node.options[1].selected).toBe(false); // giraffe
    expect(node.options[2].selected).toBe(false); // gorilla
  });

  it('should support options with dynamic children', () => {
    const container = document.createElement('div');

    let node;

    function App({value}) {
      return (
        <select value={value} ref={n => (node = n)} onChange={noop}>
          <option key="monkey" value="monkey">
            A monkey {value === 'monkey' ? 'is chosen' : null}!
          </option>
          <option key="giraffe" value="giraffe">
            A giraffe {value === 'giraffe' && 'is chosen'}!
          </option>
          <option key="gorilla" value="gorilla">
            A gorilla {value === 'gorilla' && 'is chosen'}!
          </option>
        </select>
      );
    }

    ReactDOM.render(<App value="monkey" />, container);
    expect(node.options[0].selected).toBe(true); // monkey
    expect(node.options[1].selected).toBe(false); // giraffe
    expect(node.options[2].selected).toBe(false); // gorilla

    ReactDOM.render(<App value="giraffe" />, container);
    expect(node.options[0].selected).toBe(false); // monkey
    expect(node.options[1].selected).toBe(true); // giraffe
    expect(node.options[2].selected).toBe(false); // gorilla
  });

  it('should warn if value is null', () => {
    expect(() =>
      ReactTestUtils.renderIntoDocument(
        <select value={null}>
          <option value="test" />
        </select>,
      ),
    ).toErrorDev(
      '`value` prop on `select` should not be null. ' +
        'Consider using an empty string to clear the component or `undefined` ' +
        'for uncontrolled components.',
    );

    ReactTestUtils.renderIntoDocument(
      <select value={null}>
        <option value="test" />
      </select>,
    );
  });

  it('should warn if selected is set on <option>', () => {
    function App() {
      return (
        <select>
          <option selected={true} />
          <option selected={true} />
        </select>
      );
    }

    expect(() => ReactTestUtils.renderIntoDocument(<App />)).toErrorDev(
      'Use the `defaultValue` or `value` props on <select> instead of ' +
        'setting `selected` on <option>.',
    );

    ReactTestUtils.renderIntoDocument(<App />);
  });

  it('should warn if value is null and multiple is true', () => {
    expect(() =>
      ReactTestUtils.renderIntoDocument(
        <select value={null} multiple={true}>
          <option value="test" />
        </select>,
      ),
    ).toErrorDev(
      '`value` prop on `select` should not be null. ' +
        'Consider using an empty array when `multiple` is ' +
        'set to `true` to clear the component or `undefined` ' +
        'for uncontrolled components.',
    );

    ReactTestUtils.renderIntoDocument(
      <select value={null} multiple={true}>
        <option value="test" />
      </select>,
    );
  });

  it('should refresh state on change', () => {
    const stub = (
      <select value="giraffe" onChange={noop}>
        <option value="monkey">A monkey!</option>
        <option value="giraffe">A giraffe!</option>
        <option value="gorilla">A gorilla!</option>
      </select>
    );
    const container = document.createElement('div');
    document.body.appendChild(container);

    try {
      const node = ReactDOM.render(stub, container);

      node.dispatchEvent(
        new Event('change', {bubbles: true, cancelable: false}),
      );

      expect(node.value).toBe('giraffe');
    } finally {
      document.body.removeChild(container);
    }
  });

  it('should warn if value and defaultValue props are specified', () => {
    expect(() =>
      ReactTestUtils.renderIntoDocument(
        <select value="giraffe" defaultValue="giraffe" readOnly={true}>
          <option value="monkey">A monkey!</option>
          <option value="giraffe">A giraffe!</option>
          <option value="gorilla">A gorilla!</option>
        </select>,
      ),
    ).toErrorDev(
      'Select elements must be either controlled or uncontrolled ' +
        '(specify either the value prop, or the defaultValue prop, but not ' +
        'both). Decide between using a controlled or uncontrolled select ' +
        'element and remove one of these props. More info: ' +
        'https://reactjs.org/link/controlled-components',
    );

    ReactTestUtils.renderIntoDocument(
      <select value="giraffe" defaultValue="giraffe" readOnly={true}>
        <option value="monkey">A monkey!</option>
        <option value="giraffe">A giraffe!</option>
        <option value="gorilla">A gorilla!</option>
      </select>,
    );
  });

  it('should not warn about missing onChange in uncontrolled textareas', () => {
    const container = document.createElement('div');
    ReactDOM.render(<select />, container);
    ReactDOM.unmountComponentAtNode(container);
    ReactDOM.render(<select value={undefined} />, container);
  });

  it('should be able to safely remove select onChange', () => {
    function changeView() {
      ReactDOM.unmountComponentAtNode(container);
    }

    const container = document.createElement('div');
    const stub = (
      <select value="giraffe" onChange={changeView}>
        <option value="monkey">A monkey!</option>
        <option value="giraffe">A giraffe!</option>
        <option value="gorilla">A gorilla!</option>
      </select>
    );
    const node = ReactDOM.render(stub, container);

    expect(() => ReactTestUtils.Simulate.change(node)).not.toThrow();
  });

  it('should select grandchild options nested inside an optgroup', () => {
    const stub = (
      <select value="b" onChange={noop}>
        <optgroup label="group">
          <option value="a">a</option>
          <option value="b">b</option>
          <option value="c">c</option>
        </optgroup>
      </select>
    );
    const container = document.createElement('div');
    const node = ReactDOM.render(stub, container);

    expect(node.options[0].selected).toBe(false); // a
    expect(node.options[1].selected).toBe(true); // b
    expect(node.options[2].selected).toBe(false); // c
  });

  it('should allow controlling `value` in a nested render', () => {
    let selectNode;

    class Parent extends React.Component {
      state = {
        value: 'giraffe',
      };

      componentDidMount() {
        this._renderNested();
      }

      componentDidUpdate() {
        this._renderNested();
      }

      _handleChange(event) {
        this.setState({value: event.target.value});
      }

      _renderNested() {
        ReactDOM.render(
          <select
            onChange={this._handleChange.bind(this)}
            ref={n => (selectNode = n)}
            value={this.state.value}>
            <option value="monkey">A monkey!</option>
            <option value="giraffe">A giraffe!</option>
            <option value="gorilla">A gorilla!</option>
          </select>,
          this._nestingContainer,
        );
      }

      render() {
        return <div ref={n => (this._nestingContainer = n)} />;
      }
    }

    const container = document.createElement('div');

    document.body.appendChild(container);

    ReactDOM.render(<Parent />, container);

    expect(selectNode.value).toBe('giraffe');

    selectNode.value = 'gorilla';

    let nativeEvent = document.createEvent('Event');
    nativeEvent.initEvent('input', true, true);
    selectNode.dispatchEvent(nativeEvent);

    expect(selectNode.value).toEqual('gorilla');

    nativeEvent = document.createEvent('Event');
    nativeEvent.initEvent('change', true, true);
    selectNode.dispatchEvent(nativeEvent);

    expect(selectNode.value).toEqual('gorilla');

    document.body.removeChild(container);
  });

  it('should not select first option by default when multiple is set and no defaultValue is set', () => {
    const stub = (
      <select multiple={true} onChange={noop}>
        <option value="a">a</option>
        <option value="b">b</option>
        <option value="c">c</option>
      </select>
    );
    const container = document.createElement('div');
    const node = ReactDOM.render(stub, container);

    expect(node.options[0].selected).toBe(false); // a
    expect(node.options[1].selected).toBe(false); // b
    expect(node.options[2].selected).toBe(false); // c
  });

  describe('When given a Symbol value', () => {
    it('treats initial Symbol value as missing', () => {
      let node;

      expect(() => {
        node = ReactTestUtils.renderIntoDocument(
          <select onChange={noop} value={Symbol('foobar')}>
            <option value={Symbol('foobar')}>A Symbol!</option>
            <option value="monkey">A monkey!</option>
            <option value="giraffe">A giraffe!</option>
          </select>,
        );
      }).toErrorDev('Invalid value for prop `value`');

      expect(node.value).toBe('A Symbol!');
    });

    it('treats updated Symbol value as missing', () => {
      let node;

      expect(() => {
        node = ReactTestUtils.renderIntoDocument(
          <select onChange={noop} value="monkey">
            <option value={Symbol('foobar')}>A Symbol!</option>
            <option value="monkey">A monkey!</option>
            <option value="giraffe">A giraffe!</option>
          </select>,
        );
      }).toErrorDev('Invalid value for prop `value`');

      expect(node.value).toBe('monkey');

      node = ReactTestUtils.renderIntoDocument(
        <select onChange={noop} value={Symbol('foobar')}>
          <option value={Symbol('foobar')}>A Symbol!</option>
          <option value="monkey">A monkey!</option>
          <option value="giraffe">A giraffe!</option>
        </select>,
      );

      expect(node.value).toBe('A Symbol!');
    });

    it('treats initial Symbol defaultValue as an empty string', () => {
      let node;

      expect(() => {
        node = ReactTestUtils.renderIntoDocument(
          <select defaultValue={Symbol('foobar')}>
            <option value={Symbol('foobar')}>A Symbol!</option>
            <option value="monkey">A monkey!</option>
            <option value="giraffe">A giraffe!</option>
          </select>,
        );
      }).toErrorDev('Invalid value for prop `value`');

      expect(node.value).toBe('A Symbol!');
    });

    it('treats updated Symbol defaultValue as an empty string', () => {
      let node;

      expect(() => {
        node = ReactTestUtils.renderIntoDocument(
          <select defaultValue="monkey">
            <option value={Symbol('foobar')}>A Symbol!</option>
            <option value="monkey">A monkey!</option>
            <option value="giraffe">A giraffe!</option>
          </select>,
        );
      }).toErrorDev('Invalid value for prop `value`');

      expect(node.value).toBe('monkey');

      node = ReactTestUtils.renderIntoDocument(
        <select defaultValue={Symbol('foobar')}>
          <option value={Symbol('foobar')}>A Symbol!</option>
          <option value="monkey">A monkey!</option>
          <option value="giraffe">A giraffe!</option>
        </select>,
      );

      expect(node.value).toBe('A Symbol!');
    });
  });

  describe('When given a function value', () => {
    it('treats initial function value as missing', () => {
      let node;

      expect(() => {
        node = ReactTestUtils.renderIntoDocument(
          <select onChange={noop} value={() => {}}>
            <option value={() => {}}>A function!</option>
            <option value="monkey">A monkey!</option>
            <option value="giraffe">A giraffe!</option>
          </select>,
        );
      }).toErrorDev('Invalid value for prop `value`');

      expect(node.value).toBe('A function!');
    });

    it('treats initial function defaultValue as an empty string', () => {
      let node;

      expect(() => {
        node = ReactTestUtils.renderIntoDocument(
          <select defaultValue={() => {}}>
            <option value={() => {}}>A function!</option>
            <option value="monkey">A monkey!</option>
            <option value="giraffe">A giraffe!</option>
          </select>,
        );
      }).toErrorDev('Invalid value for prop `value`');

      expect(node.value).toBe('A function!');
    });

    it('treats updated function value as an empty string', () => {
      let node;

      expect(() => {
        node = ReactTestUtils.renderIntoDocument(
          <select onChange={noop} value="monkey">
            <option value={() => {}}>A function!</option>
            <option value="monkey">A monkey!</option>
            <option value="giraffe">A giraffe!</option>
          </select>,
        );
      }).toErrorDev('Invalid value for prop `value`');

      expect(node.value).toBe('monkey');

      node = ReactTestUtils.renderIntoDocument(
        <select onChange={noop} value={() => {}}>
          <option value={() => {}}>A function!</option>
          <option value="monkey">A monkey!</option>
          <option value="giraffe">A giraffe!</option>
        </select>,
      );

      expect(node.value).toBe('A function!');
    });

    it('treats updated function defaultValue as an empty string', () => {
      let node;

      expect(() => {
        node = ReactTestUtils.renderIntoDocument(
          <select defaultValue="monkey">
            <option value={() => {}}>A function!</option>
            <option value="monkey">A monkey!</option>
            <option value="giraffe">A giraffe!</option>
          </select>,
        );
      }).toErrorDev('Invalid value for prop `value`');

      expect(node.value).toBe('monkey');

      node = ReactTestUtils.renderIntoDocument(
        <select defaultValue={() => {}}>
          <option value={() => {}}>A function!</option>
          <option value="monkey">A monkey!</option>
          <option value="giraffe">A giraffe!</option>
        </select>,
      );

      expect(node.value).toBe('A function!');
    });
  });

  describe('When given a Temporal.PlainDate-like value', () => {
    class TemporalLike {
      valueOf() {
        // Throwing here is the behavior of ECMAScript "Temporal" date/time API.
        // See https://tc39.es/proposal-temporal/docs/plaindate.html#valueOf
        throw new TypeError('prod message');
      }
      toString() {
        return '2020-01-01';
      }
    }

    it('throws when given a Temporal.PlainDate-like value (select)', () => {
      const test = () => {
        ReactTestUtils.renderIntoDocument(
          <select onChange={noop} value={new TemporalLike()}>
            <option value="2020-01-01">like a Temporal.PlainDate</option>
            <option value="monkey">A monkey!</option>
            <option value="giraffe">A giraffe!</option>
          </select>,
        );
      };
      expect(() =>
        expect(test).toThrowError(new TypeError('prod message')),
      ).toErrorDev(
        'Form field values (value, checked, defaultValue, or defaultChecked props)' +
          ' must be strings, not TemporalLike. ' +
          'This value must be coerced to a string before using it here.',
      );
    });

    it('throws when given a Temporal.PlainDate-like value (option)', () => {
      const test = () => {
        ReactTestUtils.renderIntoDocument(
          <select onChange={noop} value="2020-01-01">
            <option value={new TemporalLike()}>
              like a Temporal.PlainDate
            </option>
            <option value="monkey">A monkey!</option>
            <option value="giraffe">A giraffe!</option>
          </select>,
        );
      };
      expect(() =>
        expect(test).toThrowError(new TypeError('prod message')),
      ).toErrorDev(
        'The provided `value` attribute is an unsupported type TemporalLike.' +
          ' This value must be coerced to a string before using it here.',
      );
    });

    it('throws when given a Temporal.PlainDate-like value (both)', () => {
      const test = () => {
        ReactTestUtils.renderIntoDocument(
          <select onChange={noop} value={new TemporalLike()}>
            <option value={new TemporalLike()}>
              like a Temporal.PlainDate
            </option>
            <option value="monkey">A monkey!</option>
            <option value="giraffe">A giraffe!</option>
          </select>,
        );
      };
      expect(() =>
        expect(test).toThrowError(new TypeError('prod message')),
      ).toErrorDev(
        'The provided `value` attribute is an unsupported type TemporalLike.' +
          ' This value must be coerced to a string before using it here.',
      );
    });

    it('throws with updated Temporal.PlainDate-like value (select)', () => {
      ReactTestUtils.renderIntoDocument(
        <select onChange={noop} value="monkey">
          <option value="2020-01-01">like a Temporal.PlainDate</option>
          <option value="monkey">A monkey!</option>
          <option value="giraffe">A giraffe!</option>
        </select>,
      );
      const test = () => {
        ReactTestUtils.renderIntoDocument(
          <select onChange={noop} value={new TemporalLike()}>
            <option value="2020-01-01">like a Temporal.PlainDate</option>
            <option value="monkey">A monkey!</option>
            <option value="giraffe">A giraffe!</option>
          </select>,
        );
      };
      expect(() =>
        expect(test).toThrowError(new TypeError('prod message')),
      ).toErrorDev(
        'Form field values (value, checked, defaultValue, or defaultChecked props)' +
          ' must be strings, not TemporalLike. ' +
          'This value must be coerced to a string before using it here.',
      );
    });

    it('throws with updated Temporal.PlainDate-like value (option)', () => {
      ReactTestUtils.renderIntoDocument(
        <select onChange={noop} value="2020-01-01">
          <option value="donkey">like a Temporal.PlainDate</option>
          <option value="monkey">A monkey!</option>
          <option value="giraffe">A giraffe!</option>
        </select>,
      );
      const test = () => {
        ReactTestUtils.renderIntoDocument(
          <select onChange={noop} value="2020-01-01">
            <option value={new TemporalLike()}>
              like a Temporal.PlainDate
            </option>
            <option value="monkey">A monkey!</option>
            <option value="giraffe">A giraffe!</option>
          </select>,
        );
      };
      expect(() =>
        expect(test).toThrowError(new TypeError('prod message')),
      ).toErrorDev(
        'The provided `value` attribute is an unsupported type TemporalLike.' +
          ' This value must be coerced to a string before using it here.',
      );
    });

    it('throws with updated Temporal.PlainDate-like value (both)', () => {
      ReactTestUtils.renderIntoDocument(
        <select onChange={noop} value="donkey">
          <option value="donkey">like a Temporal.PlainDate</option>
          <option value="monkey">A monkey!</option>
          <option value="giraffe">A giraffe!</option>
        </select>,
      );
      const test = () => {
        ReactTestUtils.renderIntoDocument(
          <select onChange={noop} value={new TemporalLike()}>
            <option value={new TemporalLike()}>
              like a Temporal.PlainDate
            </option>
            <option value="monkey">A monkey!</option>
            <option value="giraffe">A giraffe!</option>
          </select>,
        );
      };
      expect(() =>
        expect(test).toThrowError(new TypeError('prod message')),
      ).toErrorDev(
        'The provided `value` attribute is an unsupported type TemporalLike.' +
          ' This value must be coerced to a string before using it here.',
      );
    });

    it('throws when given a Temporal.PlainDate-like defaultValue (select)', () => {
      const test = () => {
        ReactTestUtils.renderIntoDocument(
          <select onChange={noop} defaultValue={new TemporalLike()}>
            <option value="2020-01-01">like a Temporal.PlainDate</option>
            <option value="monkey">A monkey!</option>
            <option value="giraffe">A giraffe!</option>
          </select>,
        );
      };
      expect(() =>
        expect(test).toThrowError(new TypeError('prod message')),
      ).toErrorDev(
        'Form field values (value, checked, defaultValue, or defaultChecked props)' +
          ' must be strings, not TemporalLike. ' +
          'This value must be coerced to a string before using it here.',
      );
    });

    it('throws when given a Temporal.PlainDate-like defaultValue (option)', () => {
      const test = () => {
        ReactTestUtils.renderIntoDocument(
          <select onChange={noop} defaultValue="2020-01-01">
            <option value={new TemporalLike()}>
              like a Temporal.PlainDate
            </option>
            <option value="monkey">A monkey!</option>
            <option value="giraffe">A giraffe!</option>
          </select>,
        );
      };
      expect(() =>
        expect(test).toThrowError(new TypeError('prod message')),
      ).toErrorDev(
        'The provided `value` attribute is an unsupported type TemporalLike.' +
          ' This value must be coerced to a string before using it here.',
      );
    });

    it('throws when given a Temporal.PlainDate-like value (both)', () => {
      const test = () => {
        ReactTestUtils.renderIntoDocument(
          <select onChange={noop} defaultValue={new TemporalLike()}>
            <option value={new TemporalLike()}>
              like a Temporal.PlainDate
            </option>
            <option value="monkey">A monkey!</option>
            <option value="giraffe">A giraffe!</option>
          </select>,
        );
      };
      expect(() =>
        expect(test).toThrowError(new TypeError('prod message')),
      ).toErrorDev(
        'The provided `value` attribute is an unsupported type TemporalLike.' +
          ' This value must be coerced to a string before using it here.',
      );
    });

    it('throws with updated Temporal.PlainDate-like defaultValue (select)', () => {
      ReactTestUtils.renderIntoDocument(
        <select onChange={noop} defaultValue="monkey">
          <option value="2020-01-01">like a Temporal.PlainDate</option>
          <option value="monkey">A monkey!</option>
          <option value="giraffe">A giraffe!</option>
        </select>,
      );
      const test = () => {
        ReactTestUtils.renderIntoDocument(
          <select onChange={noop} defaultValue={new TemporalLike()}>
            <option value="2020-01-01">like a Temporal.PlainDate</option>
            <option value="monkey">A monkey!</option>
            <option value="giraffe">A giraffe!</option>
          </select>,
        );
      };
      expect(() =>
        expect(test).toThrowError(new TypeError('prod message')),
      ).toErrorDev(
        'Form field values (value, checked, defaultValue, or defaultChecked props)' +
          ' must be strings, not TemporalLike. ' +
          'This value must be coerced to a string before using it here.',
      );
    });

    it('throws with updated Temporal.PlainDate-like defaultValue (both)', () => {
      ReactTestUtils.renderIntoDocument(
        <select onChange={noop} defaultValue="monkey">
          <option value="donkey">like a Temporal.PlainDate</option>
          <option value="monkey">A monkey!</option>
          <option value="giraffe">A giraffe!</option>
        </select>,
      );
      const test = () => {
        ReactTestUtils.renderIntoDocument(
          <select onChange={noop} value={new TemporalLike()}>
            <option value={new TemporalLike()}>
              like a Temporal.PlainDate
            </option>
            <option value="monkey">A monkey!</option>
            <option value="giraffe">A giraffe!</option>
          </select>,
        );
      };
      expect(() =>
        expect(test).toThrowError(new TypeError('prod message')),
      ).toErrorDev(
        'The provided `value` attribute is an unsupported type TemporalLike.' +
          ' This value must be coerced to a string before using it here.',
      );
    });

    it('should not warn about missing onChange if value is not set', () => {
      expect(() => {
        ReactTestUtils.renderIntoDocument(
          <select>
            <option value="monkey">A monkey!</option>
            <option value="giraffe">A giraffe!</option>
            <option value="gorilla">A gorilla!</option>
          </select>,
        );
      }).not.toThrow();
    });

    it('should not throw an error about missing onChange if value is undefined', () => {
      expect(() => {
        ReactTestUtils.renderIntoDocument(
          <select value={undefined}>
            <option value="monkey">A monkey!</option>
            <option value="giraffe">A giraffe!</option>
            <option value="gorilla">A gorilla!</option>
          </select>,
        );
      }).not.toThrow();
    });

    it('should not warn about missing onChange if onChange is set', () => {
      const change = jest.fn();
      expect(() => {
        ReactTestUtils.renderIntoDocument(
          <select value="monkey" onChange={change}>
            <option value="monkey">A monkey!</option>
            <option value="giraffe">A giraffe!</option>
            <option value="gorilla">A gorilla!</option>
          </select>,
        );
      }).not.toThrow();
    });

    it('should not warn about missing onChange if disabled is true', () => {
      expect(() => {
        ReactTestUtils.renderIntoDocument(
          <select value="monkey" disabled={true}>
            <option value="monkey">A monkey!</option>
            <option value="giraffe">A giraffe!</option>
            <option value="gorilla">A gorilla!</option>
          </select>,
        );
      }).not.toThrow();
    });

    it('should warn about missing onChange if value is false', () => {
      expect(() =>
        ReactTestUtils.renderIntoDocument(
          <select value={false}>
            <option value="monkey">A monkey!</option>
            <option value="giraffe">A giraffe!</option>
            <option value="gorilla">A gorilla!</option>
          </select>,
        ),
      ).toErrorDev(
        'Warning: You provided a `value` prop to a form ' +
          'field without an `onChange` handler. This will render a read-only ' +
          'field. If the field should be mutable use `defaultValue`. ' +
          'Otherwise, set `onChange`.',
      );
    });

    it('should warn about missing onChange if value is 0', () => {
      expect(() =>
        ReactTestUtils.renderIntoDocument(
          <select value={0}>
            <option value="monkey">A monkey!</option>
            <option value="giraffe">A giraffe!</option>
            <option value="gorilla">A gorilla!</option>
          </select>,
        ),
      ).toErrorDev(
        'Warning: You provided a `value` prop to a form ' +
          'field without an `onChange` handler. This will render a read-only ' +
          'field. If the field should be mutable use `defaultValue`. ' +
          'Otherwise, set `onChange`.',
      );
    });

    it('should warn about missing onChange if value is "0"', () => {
      expect(() =>
        ReactTestUtils.renderIntoDocument(
          <select value="0">
            <option value="monkey">A monkey!</option>
            <option value="giraffe">A giraffe!</option>
            <option value="gorilla">A gorilla!</option>
          </select>,
        ),
      ).toErrorDev(
        'Warning: You provided a `value` prop to a form ' +
          'field without an `onChange` handler. This will render a read-only ' +
          'field. If the field should be mutable use `defaultValue`. ' +
          'Otherwise, set `onChange`.',
      );
    });

    it('should warn about missing onChange if value is ""', () => {
      expect(() =>
        ReactTestUtils.renderIntoDocument(
          <select value="">
            <option value="monkey">A monkey!</option>
            <option value="giraffe">A giraffe!</option>
            <option value="gorilla">A gorilla!</option>
          </select>,
        ),
      ).toErrorDev(
        'Warning: You provided a `value` prop to a form ' +
          'field without an `onChange` handler. This will render a read-only ' +
          'field. If the field should be mutable use `defaultValue`. ' +
          'Otherwise, set `onChange`.',
      );
    });
  });
});
