/** * 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 ReactDOMClient; let ReactDOMServer; let act; let assertConsoleErrorDev; const noop = function () {}; beforeEach(() => { jest.resetModules(); React = require('react'); ReactDOM = require('react-dom'); ReactDOMClient = require('react-dom/client'); ReactDOMServer = require('react-dom/server'); act = require('internal-test-utils').act; assertConsoleErrorDev = require('internal-test-utils').assertConsoleErrorDev; }); it('should allow setting `defaultValue`', async () => { const stub = ( ); const options = stub.props.children; const container = document.createElement('div'); const root = ReactDOMClient.createRoot(container); await act(() => { root.render(stub); }); const node = container.firstChild; expect(node.value).toBe('giraffe'); await act(() => { root.render(); }); expect(node.value).toEqual('giraffe'); }); it('should not throw with `defaultValue` and without children', () => { const stub = ); const container = document.createElement('div'); const root = ReactDOMClient.createRoot(container); await act(() => { root.render(el); }); const node = container.firstChild; expect(node.value).toBe('giraffe'); node.value = 'monkey'; await act(() => { root.render(el); }); // Uncontrolled selects shouldn't change the value after first mounting expect(node.value).toEqual('monkey'); }); it('should allow setting `defaultValue` with multiple', async () => { const stub = ( ); const options = stub.props.children; const container = document.createElement('div'); const root = ReactDOMClient.createRoot(container); await act(() => { root.render(stub); }); const node = container.firstChild; expect(node.options[0].selected).toBe(false); // monkey expect(node.options[1].selected).toBe(true); // giraffe expect(node.options[2].selected).toBe(true); // gorilla await act(() => { root.render( , ); }); 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`', async () => { const stub = ( ); const options = stub.props.children; const container = document.createElement('div'); const root = ReactDOMClient.createRoot(container); await act(() => { root.render(stub); }); const node = container.firstChild; expect(node.value).toBe('giraffe'); await act(() => { root.render( , ); }); expect(node.value).toEqual('gorilla'); }); it('should default to the first non-disabled option', async () => { const stub = ( ); const container = document.createElement('div'); const root = ReactDOMClient.createRoot(container); await act(() => { root.render(stub); }); const node = container.firstChild; expect(node.options[0].selected).toBe(false); expect(node.options[2].selected).toBe(true); }); it('should allow setting `value` to __proto__', async () => { const stub = ( ); const options = stub.props.children; const container = document.createElement('div'); const root = ReactDOMClient.createRoot(container); await act(() => { root.render(stub); }); const node = container.firstChild; expect(node.value).toBe('__proto__'); await act(() => { root.render( , ); }); expect(node.value).toEqual('gorilla'); }); it('should not throw with `value` and without children', () => { const stub = ); const options = stub.props.children; const container = document.createElement('div'); const root = ReactDOMClient.createRoot(container); await act(() => { root.render(stub); }); const node = container.firstChild; expect(node.options[0].selected).toBe(false); // monkey expect(node.options[1].selected).toBe(true); // giraffe expect(node.options[2].selected).toBe(true); // gorilla await act(() => { root.render( , ); }); 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', async () => { const stub = ( ); const options = stub.props.children; const container = document.createElement('div'); const root = ReactDOMClient.createRoot(container); await act(() => { root.render(stub); }); const node = container.firstChild; expect(node.options[0].selected).toBe(false); // monkey expect(node.options[1].selected).toBe(true); // __proto__ expect(node.options[2].selected).toBe(true); // gorilla await act(() => { root.render( , ); }); 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', async () => { const stub = ( ); const container = document.createElement('div'); const root = ReactDOMClient.createRoot(container); await act(() => { root.render(stub); }); const node = container.firstChild; 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', async () => { const stub = , ); }); 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`', async () => { const objectToString = { animal: 'giraffe', toString: function () { return this.animal; }, }; const el = ( ); const container = document.createElement('div'); const root = ReactDOMClient.createRoot(container); await act(() => { root.render(el); }); const node = container.firstChild; 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 = ( ); await act(() => { root.render(el2); }); 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', async () => { const stub = ( ); const options = stub.props.children; const container = document.createElement('div'); const root = ReactDOMClient.createRoot(container); await act(() => { root.render(stub); }); const node = container.firstChild; expect(node.options[0].selected).toBe(false); // monkey expect(node.options[1].selected).toBe(true); // giraffe expect(node.options[2].selected).toBe(false); // gorilla await act(() => { root.render( , ); }); 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', async () => { const stub = ( ); const options = stub.props.children; const container = document.createElement('div'); const root = ReactDOMClient.createRoot(container); await act(() => { root.render(stub); }); const node = container.firstChild; expect(node.options[0].selected).toBe(false); // monkey expect(node.options[1].selected).toBe(true); // giraffe expect(node.options[2].selected).toBe(true); // gorilla await act(() => { root.render(); }); 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', async () => { const stub = ( ); const container = document.createElement('div'); const root = ReactDOMClient.createRoot(container); await act(() => { root.render(stub); }); const select = container.firstChild; 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', async () => { const stub = ( ); const options = stub.props.children; const container = document.createElement('div'); const root = ReactDOMClient.createRoot(container); await act(() => { root.render(stub); }); const node = container.firstChild; expect(node.options[0].selected).toBe(false); // monkey expect(node.options[1].selected).toBe(true); // giraffe expect(node.options[2].selected).toBe(false); // gorilla await act(() => { root.render(); }); 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', async () => { const stub = ( ); const options = stub.props.children; const container = document.createElement('div'); const root = ReactDOMClient.createRoot(container); await act(() => { root.render(stub); }); const node = container.firstChild; await act(() => { root.render( , ); }); expect(node.options[0].selected).toBe(false); // monkey expect(node.options[1].selected).toBe(false); // giraffe expect(node.options[2].selected).toBe(true); // gorilla await act(() => { root.render(); }); 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 = ( ); 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 = ( ); 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 = ( ); 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 = ( ); 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', async () => { const container = document.createElement('div'); const root = ReactDOMClient.createRoot(container); await act(() => { root.render( , ); }); const node = container.firstChild; expect(node.options[0].selected).toBe(false); // monkey expect(node.options[1].selected).toBe(true); // giraffe expect(node.options[2].selected).toBe(false); // gorilla await act(() => { root.render( , ); }); expect(node.options[0].selected).toBe(false); // monkey expect(node.options[1].selected).toBe(false); // gorilla await act(() => { root.render( , ); }); 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', async () => { const container = document.createElement('div'); let node; function App({value}) { return ( ); } const root = ReactDOMClient.createRoot(container); await act(() => { root.render(); }); expect(node.options[0].selected).toBe(true); // monkey expect(node.options[1].selected).toBe(false); // giraffe expect(node.options[2].selected).toBe(false); // gorilla await act(() => { root.render(); }); 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', async () => { const container = document.createElement('div'); const root = ReactDOMClient.createRoot(container); await act(() => { root.render( , ); }); assertConsoleErrorDev([ '`value` prop on `select` should not be null. ' + 'Consider using an empty string to clear the component or `undefined` ' + 'for uncontrolled components.\n' + ' in select (at **)', ]); await act(() => { root.render( , ); }); }); it('should warn if selected is set on