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