/** * 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'; // Set by `yarn test-fire`. const {disableInputAttributeSyncing} = require('shared/ReactFeatureFlags'); describe('DOMPropertyOperations', () => { let React; let ReactDOMClient; let act; let assertConsoleErrorDev; beforeEach(() => { jest.resetModules(); React = require('react'); ReactDOMClient = require('react-dom/client'); ({act, assertConsoleErrorDev} = require('internal-test-utils')); }); // Sets a value in a way that React doesn't see, // so that a subsequent "change" event will trigger the event handler. const setUntrackedValue = Object.getOwnPropertyDescriptor( HTMLInputElement.prototype, 'value', ).set; const setUntrackedChecked = Object.getOwnPropertyDescriptor( HTMLInputElement.prototype, 'checked', ).set; describe('setValueForProperty', () => { it('should set values as properties by default', async () => { const container = document.createElement('div'); const root = ReactDOMClient.createRoot(container); await act(() => { root.render(
); }); expect(container.firstChild.title).toBe('Tip!'); }); it('should set values as attributes if necessary', async () => { const container = document.createElement('div'); const root = ReactDOMClient.createRoot(container); await act(() => { root.render(
); }); expect(container.firstChild.getAttribute('role')).toBe('#'); expect(container.firstChild.role).toBeUndefined(); }); it('should set values as namespace attributes if necessary', async () => { const container = document.createElementNS( 'http://www.w3.org/2000/svg', 'svg', ); const root = ReactDOMClient.createRoot(container); await act(() => { root.render(); }); expect( container.firstChild.getAttributeNS( 'http://www.w3.org/1999/xlink', 'href', ), ).toBe('about:blank'); }); it('should set values as boolean properties', async () => { const container = document.createElement('div'); const root = ReactDOMClient.createRoot(container); await act(() => { root.render(
); }); expect(container.firstChild.getAttribute('disabled')).toBe(''); await act(() => { root.render(
); }); expect(container.firstChild.getAttribute('disabled')).toBe(''); await act(() => { root.render(
); }); expect(container.firstChild.getAttribute('disabled')).toBe(null); await act(() => { root.render(
); }); await act(() => { root.render(
); }); expect(container.firstChild.getAttribute('disabled')).toBe(null); await act(() => { root.render(
); }); await act(() => { root.render(
); }); expect(container.firstChild.getAttribute('disabled')).toBe(null); }); it('should convert attribute values to string first', async () => { // Browsers default to this behavior, but some test environments do not. // This ensures that we have consistent behavior. const obj = { toString: function () { return 'css-class'; }, }; const container = document.createElement('div'); const root = ReactDOMClient.createRoot(container); await act(() => { root.render(
); }); expect(container.firstChild.getAttribute('class')).toBe('css-class'); }); it('should not remove empty attributes for special input properties', async () => { const container = document.createElement('div'); const root = ReactDOMClient.createRoot(container); await act(() => { root.render( {}} />); }); if (disableInputAttributeSyncing) { expect(container.firstChild.hasAttribute('value')).toBe(false); } else { expect(container.firstChild.getAttribute('value')).toBe(''); } expect(container.firstChild.value).toBe(''); }); it('should not remove empty attributes for special option properties', async () => { const container = document.createElement('div'); const root = ReactDOMClient.createRoot(container); await act(() => { root.render( , ); }); // Regression test for https://github.com/facebook/react/issues/6219 expect(container.firstChild.firstChild.value).toBe(''); expect(container.firstChild.lastChild.value).toBe('filled'); }); it('should remove for falsey boolean properties', async () => { const container = document.createElement('div'); const root = ReactDOMClient.createRoot(container); await act(() => { root.render(
); }); expect(container.firstChild.hasAttribute('allowFullScreen')).toBe(false); }); it('should remove when setting custom attr to null', async () => { const container = document.createElement('div'); const root = ReactDOMClient.createRoot(container); await act(() => { root.render(
); }); expect(container.firstChild.hasAttribute('data-foo')).toBe(true); await act(() => { root.render(
); }); expect(container.firstChild.hasAttribute('data-foo')).toBe(false); }); it('should set className to empty string instead of null', async () => { const container = document.createElement('div'); const root = ReactDOMClient.createRoot(container); await act(() => { root.render(
); }); expect(container.firstChild.className).toBe('selected'); await act(() => { root.render(
); }); // className should be '', not 'null' or null (which becomes 'null' in // some browsers) expect(container.firstChild.className).toBe(''); expect(container.firstChild.getAttribute('class')).toBe(null); }); it('should remove property properly for boolean properties', async () => { const container = document.createElement('div'); const root = ReactDOMClient.createRoot(container); await act(() => { root.render(