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. describe('ReactDOMOption', () => {
    
  13.   let React;
    
  14.   let ReactDOM;
    
  15.   let ReactDOMServer;
    
  16.   let ReactTestUtils;
    
  17. 
    
  18.   beforeEach(() => {
    
  19.     jest.resetModules();
    
  20.     React = require('react');
    
  21.     ReactDOM = require('react-dom');
    
  22.     ReactDOMServer = require('react-dom/server');
    
  23.     ReactTestUtils = require('react-dom/test-utils');
    
  24.   });
    
  25. 
    
  26.   it('should flatten children to a string', () => {
    
  27.     const stub = (
    
  28.       <option>
    
  29.         {1} {'foo'}
    
  30.       </option>
    
  31.     );
    
  32.     const node = ReactTestUtils.renderIntoDocument(stub);
    
  33. 
    
  34.     expect(node.innerHTML).toBe('1 foo');
    
  35.   });
    
  36. 
    
  37.   it('should warn for invalid child tags', () => {
    
  38.     const el = (
    
  39.       <option value="12">
    
  40.         {1} <div /> {2}
    
  41.       </option>
    
  42.     );
    
  43.     let node;
    
  44.     expect(() => {
    
  45.       node = ReactTestUtils.renderIntoDocument(el);
    
  46.     }).toErrorDev(
    
  47.       'validateDOMNesting(...): <div> cannot appear as a child of <option>.\n' +
    
  48.         '    in div (at **)\n' +
    
  49.         '    in option (at **)',
    
  50.     );
    
  51.     expect(node.innerHTML).toBe('1 <div></div> 2');
    
  52.     ReactTestUtils.renderIntoDocument(el);
    
  53.   });
    
  54. 
    
  55.   it('should warn for component child if no value prop is provided', () => {
    
  56.     function Foo() {
    
  57.       return '2';
    
  58.     }
    
  59.     const el = (
    
  60.       <option>
    
  61.         {1} <Foo /> {3}
    
  62.       </option>
    
  63.     );
    
  64.     let node;
    
  65.     expect(() => {
    
  66.       node = ReactTestUtils.renderIntoDocument(el);
    
  67.     }).toErrorDev(
    
  68.       'Cannot infer the option value of complex children. ' +
    
  69.         'Pass a `value` prop or use a plain string as children to <option>.',
    
  70.     );
    
  71.     expect(node.innerHTML).toBe('1 2 3');
    
  72.     ReactTestUtils.renderIntoDocument(el);
    
  73.   });
    
  74. 
    
  75.   it('should not warn for component child if value prop is provided', () => {
    
  76.     function Foo() {
    
  77.       return '2';
    
  78.     }
    
  79.     const el = (
    
  80.       <option value="123">
    
  81.         {1} <Foo /> {3}
    
  82.       </option>
    
  83.     );
    
  84.     const node = ReactTestUtils.renderIntoDocument(el);
    
  85.     expect(node.innerHTML).toBe('1 2 3');
    
  86.     ReactTestUtils.renderIntoDocument(el);
    
  87.   });
    
  88. 
    
  89.   it('should ignore null/undefined/false children without warning', () => {
    
  90.     const stub = (
    
  91.       <option>
    
  92.         {1} {false}
    
  93.         {true}
    
  94.         {null}
    
  95.         {undefined} {2}
    
  96.       </option>
    
  97.     );
    
  98.     const node = ReactTestUtils.renderIntoDocument(stub);
    
  99. 
    
  100.     expect(node.innerHTML).toBe('1  2');
    
  101.   });
    
  102. 
    
  103.   it('should throw on object children', () => {
    
  104.     expect(() => {
    
  105.       ReactTestUtils.renderIntoDocument(<option>{{}}</option>);
    
  106.     }).toThrow('Objects are not valid as a React child');
    
  107.     expect(() => {
    
  108.       ReactTestUtils.renderIntoDocument(<option>{[{}]}</option>);
    
  109.     }).toThrow('Objects are not valid as a React child');
    
  110.     expect(() => {
    
  111.       ReactTestUtils.renderIntoDocument(
    
  112.         <option>
    
  113.           {{}}
    
  114.           <span />
    
  115.         </option>,
    
  116.       );
    
  117.     }).toThrow('Objects are not valid as a React child');
    
  118.     expect(() => {
    
  119.       ReactTestUtils.renderIntoDocument(
    
  120.         <option>
    
  121.           {'1'}
    
  122.           {{}}
    
  123.           {2}
    
  124.         </option>,
    
  125.       );
    
  126.     }).toThrow('Objects are not valid as a React child');
    
  127.   });
    
  128. 
    
  129.   it('should support element-ish child', () => {
    
  130.     // This is similar to <fbt>.
    
  131.     // We don't toString it because you must instead provide a value prop.
    
  132.     const obj = {
    
  133.       $$typeof: Symbol.for('react.element'),
    
  134.       type: props => props.content,
    
  135.       ref: null,
    
  136.       key: null,
    
  137.       props: {
    
  138.         content: 'hello',
    
  139.       },
    
  140.       toString() {
    
  141.         return this.props.content;
    
  142.       },
    
  143.     };
    
  144. 
    
  145.     let node = ReactTestUtils.renderIntoDocument(
    
  146.       <option value="a">{obj}</option>,
    
  147.     );
    
  148.     expect(node.innerHTML).toBe('hello');
    
  149. 
    
  150.     node = ReactTestUtils.renderIntoDocument(
    
  151.       <option value="b">{[obj]}</option>,
    
  152.     );
    
  153.     expect(node.innerHTML).toBe('hello');
    
  154. 
    
  155.     node = ReactTestUtils.renderIntoDocument(
    
  156.       <option value={obj}>{obj}</option>,
    
  157.     );
    
  158.     expect(node.innerHTML).toBe('hello');
    
  159.     expect(node.value).toBe('hello');
    
  160. 
    
  161.     node = ReactTestUtils.renderIntoDocument(
    
  162.       <option value={obj}>
    
  163.         {'1'}
    
  164.         {obj}
    
  165.         {2}
    
  166.       </option>,
    
  167.     );
    
  168.     expect(node.innerHTML).toBe('1hello2');
    
  169.     expect(node.value).toBe('hello');
    
  170.   });
    
  171. 
    
  172.   it('should be able to use dangerouslySetInnerHTML on option', () => {
    
  173.     const stub = <option dangerouslySetInnerHTML={{__html: 'foobar'}} />;
    
  174.     let node;
    
  175.     expect(() => {
    
  176.       node = ReactTestUtils.renderIntoDocument(stub);
    
  177.     }).toErrorDev(
    
  178.       'Pass a `value` prop if you set dangerouslyInnerHTML so React knows which value should be selected.\n' +
    
  179.         '    in option (at **)',
    
  180.     );
    
  181. 
    
  182.     expect(node.innerHTML).toBe('foobar');
    
  183.   });
    
  184. 
    
  185.   it('should set attribute for empty value', () => {
    
  186.     const container = document.createElement('div');
    
  187.     const option = ReactDOM.render(<option value="" />, container);
    
  188.     expect(option.hasAttribute('value')).toBe(true);
    
  189.     expect(option.getAttribute('value')).toBe('');
    
  190. 
    
  191.     ReactDOM.render(<option value="lava" />, container);
    
  192.     expect(option.hasAttribute('value')).toBe(true);
    
  193.     expect(option.getAttribute('value')).toBe('lava');
    
  194.   });
    
  195. 
    
  196.   it('should allow ignoring `value` on option', () => {
    
  197.     const a = 'a';
    
  198.     const stub = (
    
  199.       <select value="giraffe" onChange={() => {}}>
    
  200.         <option>monkey</option>
    
  201.         <option>gir{a}ffe</option>
    
  202.         <option>gorill{a}</option>
    
  203.       </select>
    
  204.     );
    
  205.     const options = stub.props.children;
    
  206.     const container = document.createElement('div');
    
  207.     const node = ReactDOM.render(stub, container);
    
  208. 
    
  209.     expect(node.selectedIndex).toBe(1);
    
  210. 
    
  211.     ReactDOM.render(<select value="gorilla">{options}</select>, container);
    
  212.     expect(node.selectedIndex).toEqual(2);
    
  213.   });
    
  214. 
    
  215.   it('generates a warning and hydration error when an invalid nested tag is used as a child', () => {
    
  216.     const ref = React.createRef();
    
  217.     const children = (
    
  218.       <select readOnly={true} value="bar">
    
  219.         <option value="bar">
    
  220.           {['Bar', false, 'Foo', <div key="1" ref={ref} />, 'Baz']}
    
  221.         </option>
    
  222.       </select>
    
  223.     );
    
  224. 
    
  225.     const container = document.createElement('div');
    
  226. 
    
  227.     container.innerHTML = ReactDOMServer.renderToString(children);
    
  228. 
    
  229.     expect(container.firstChild.getAttribute('value')).toBe(null);
    
  230.     expect(container.firstChild.getAttribute('defaultValue')).toBe(null);
    
  231. 
    
  232.     const option = container.firstChild.firstChild;
    
  233.     expect(option.nodeName).toBe('OPTION');
    
  234. 
    
  235.     expect(option.textContent).toBe('BarFooBaz');
    
  236.     expect(option.selected).toBe(true);
    
  237. 
    
  238.     expect(() => ReactDOM.hydrate(children, container)).toErrorDev([
    
  239.       'Text content did not match. Server: "FooBaz" Client: "Foo"',
    
  240.       'validateDOMNesting(...): <div> cannot appear as a child of <option>.',
    
  241.     ]);
    
  242. 
    
  243.     expect(option.textContent).toBe('BarFooBaz');
    
  244.     expect(option.selected).toBe(true);
    
  245. 
    
  246.     expect(ref.current.nodeName).toBe('DIV');
    
  247.     expect(ref.current.parentNode).toBe(option);
    
  248.   });
    
  249. });