/** * Copyright (c) Facebook, Inc. and its 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'; describe('ReactDOMComponent', () => { let React; let ReactTestUtils; let ReactDOM; let ReactDOMServer; function normalizeCodeLocInfo(str) { return str && str.replace(/\(at .+?:\d+\)/g, '(at **)'); } beforeEach(() => { jest.resetModules(); React = require('react'); ReactDOM = require('react-dom'); ReactDOMServer = require('react-dom/server'); ReactTestUtils = require('react-dom/test-utils'); }); describe('updateDOM', () => { it('should handle className', () => { const container = document.createElement('div'); ReactDOM.render(
, container); ReactDOM.render(
, container); expect(container.firstChild.className).toEqual('foo'); ReactDOM.render(
, container); expect(container.firstChild.className).toEqual('bar'); ReactDOM.render(
, container); expect(container.firstChild.className).toEqual(''); }); it('should gracefully handle various style value types', () => { const container = document.createElement('div'); ReactDOM.render(
, container); const stubStyle = container.firstChild.style; // set initial style const setup = { display: 'block', left: '1px', top: 2, fontFamily: 'Arial', }; ReactDOM.render(
, container); expect(stubStyle.display).toEqual('block'); expect(stubStyle.left).toEqual('1px'); expect(stubStyle.top).toEqual('2px'); expect(stubStyle.fontFamily).toEqual('Arial'); // reset the style to their default state const reset = {display: '', left: null, top: false, fontFamily: true}; ReactDOM.render(
, container); expect(stubStyle.display).toEqual(''); expect(stubStyle.left).toEqual(''); expect(stubStyle.top).toEqual(''); expect(stubStyle.fontFamily).toEqual(''); }); it('should not update styles when mutating a proxy style object', () => { const styleStore = { display: 'none', fontFamily: 'Arial', lineHeight: 1.2, }; // We use a proxy style object so that we can mutate it even if it is // frozen in DEV. const styles = { get display() { return styleStore.display; }, set display(v) { styleStore.display = v; }, get fontFamily() { return styleStore.fontFamily; }, set fontFamily(v) { styleStore.fontFamily = v; }, get lineHeight() { return styleStore.lineHeight; }, set lineHeight(v) { styleStore.lineHeight = v; }, }; const container = document.createElement('div'); ReactDOM.render(
, container); const stubStyle = container.firstChild.style; stubStyle.display = styles.display; stubStyle.fontFamily = styles.fontFamily; styles.display = 'block'; ReactDOM.render(
, container); expect(stubStyle.display).toEqual('none'); expect(stubStyle.fontFamily).toEqual('Arial'); expect(stubStyle.lineHeight).toEqual('1.2'); styles.fontFamily = 'Helvetica'; ReactDOM.render(
, container); expect(stubStyle.display).toEqual('none'); expect(stubStyle.fontFamily).toEqual('Arial'); expect(stubStyle.lineHeight).toEqual('1.2'); styles.lineHeight = 0.5; ReactDOM.render(
, container); expect(stubStyle.display).toEqual('none'); expect(stubStyle.fontFamily).toEqual('Arial'); expect(stubStyle.lineHeight).toEqual('1.2'); ReactDOM.render(
, container); expect(stubStyle.display).toBe(''); expect(stubStyle.fontFamily).toBe(''); expect(stubStyle.lineHeight).toBe(''); }); it('should throw when mutating style objects', () => { const style = {border: '1px solid black'}; class App extends React.Component { state = {style: style}; render() { return
asd
; } } ReactTestUtils.renderIntoDocument(); if (__DEV__) { expect(() => (style.position = 'absolute')).toThrow(); } }); it('should warn for unknown prop', () => { const container = document.createElement('div'); expect(() => ReactDOM.render(
{}} />, container), ).toWarnDev( 'Warning: Invalid value for prop `foo` on
tag. Either remove it ' + 'from the element, or pass a string or number value to keep ' + 'it in the DOM. For details, see https://fb.me/react-attribute-behavior' + '\n in div (at **)', ); }); it('should group multiple unknown prop warnings together', () => { const container = document.createElement('div'); expect(() => ReactDOM.render(
{}} baz={() => {}} />, container), ).toWarnDev( 'Warning: Invalid values for props `foo`, `baz` on
tag. Either remove ' + 'them from the element, or pass a string or number value to keep ' + 'them in the DOM. For details, see https://fb.me/react-attribute-behavior' + '\n in div (at **)', ); }); it('should warn for onDblClick prop', () => { const container = document.createElement('div'); expect(() => ReactDOM.render(
{}} />, container), ).toWarnDev( 'Warning: Invalid event handler property `onDblClick`. Did you mean `onDoubleClick`?\n in div (at **)', ); }); it('should warn for unknown string event handlers', () => { const container = document.createElement('div'); expect(() => ReactDOM.render(
, container), ).toWarnDev( 'Warning: Unknown event handler property `onUnknown`. It will be ignored.\n in div (at **)', ); expect(container.firstChild.hasAttribute('onUnknown')).toBe(false); expect(container.firstChild.onUnknown).toBe(undefined); expect(() => ReactDOM.render(
, container), ).toWarnDev( 'Warning: Unknown event handler property `onunknown`. It will be ignored.\n in div (at **)', ); expect(container.firstChild.hasAttribute('onunknown')).toBe(false); expect(container.firstChild.onunknown).toBe(undefined); expect(() => ReactDOM.render(
, container, ), ).toWarnDev( 'Warning: Unknown event handler property `on-unknown`. It will be ignored.\n in div (at **)', ); expect(container.firstChild.hasAttribute('on-unknown')).toBe(false); expect(container.firstChild['on-unknown']).toBe(undefined); }); it('should warn for unknown function event handlers', () => { const container = document.createElement('div'); expect(() => ReactDOM.render(
, container), ).toWarnDev( 'Warning: Unknown event handler property `onUnknown`. It will be ignored.\n in div (at **)', ); expect(container.firstChild.hasAttribute('onUnknown')).toBe(false); expect(container.firstChild.onUnknown).toBe(undefined); expect(() => ReactDOM.render(
, container), ).toWarnDev( 'Warning: Unknown event handler property `onunknown`. It will be ignored.\n in div (at **)', ); expect(container.firstChild.hasAttribute('onunknown')).toBe(false); expect(container.firstChild.onunknown).toBe(undefined); expect(() => ReactDOM.render(
, container), ).toWarnDev( 'Warning: Unknown event handler property `on-unknown`. It will be ignored.\n in div (at **)', ); expect(container.firstChild.hasAttribute('on-unknown')).toBe(false); expect(container.firstChild['on-unknown']).toBe(undefined); }); it('should warn for badly cased React attributes', () => { const container = document.createElement('div'); expect(() => ReactDOM.render(
, container)).toWarnDev( 'Warning: Invalid DOM property `CHILDREN`. Did you mean `children`?\n in div (at **)', ); expect(container.firstChild.getAttribute('CHILDREN')).toBe('5'); }); it('should not warn for "0" as a unitless style value', () => { class Component extends React.Component { render() { return
; } } ReactTestUtils.renderIntoDocument(); }); it('should warn nicely about NaN in style', () => { const style = {fontSize: NaN}; const div = document.createElement('div'); expect(() => ReactDOM.render(, div)).toWarnDev( 'Warning: `NaN` is an invalid value for the `fontSize` css style property.' + '\n in span (at **)', ); ReactDOM.render(, div); }); it('should update styles if initially null', () => { let styles = null; const container = document.createElement('div'); ReactDOM.render(
, container); const stubStyle = container.firstChild.style; styles = {display: 'block'}; ReactDOM.render(
, container); expect(stubStyle.display).toEqual('block'); }); it('should update styles if updated to null multiple times', () => { let styles = null; const container = document.createElement('div'); ReactDOM.render(
, container); styles = {display: 'block'}; const stubStyle = container.firstChild.style; ReactDOM.render(
, container); expect(stubStyle.display).toEqual('block'); ReactDOM.render(
, container); expect(stubStyle.display).toEqual(''); ReactDOM.render(
, container); expect(stubStyle.display).toEqual('block'); ReactDOM.render(
, container); expect(stubStyle.display).toEqual(''); }); it('should allow named slot projection on both web components and regular DOM elements', () => { const container = document.createElement('div'); ReactDOM.render( , container, ); const lightDOM = container.firstChild.childNodes; expect(lightDOM[0].getAttribute('slot')).toBe('first'); expect(lightDOM[1].getAttribute('slot')).toBe('second'); }); it('should skip reserved props on web components', () => { const container = document.createElement('div'); ReactDOM.render( , container, ); expect(container.firstChild.hasAttribute('children')).toBe(false); expect( container.firstChild.hasAttribute('suppressContentEditableWarning'), ).toBe(false); expect( container.firstChild.hasAttribute('suppressHydrationWarning'), ).toBe(false); ReactDOM.render( , container, ); expect(container.firstChild.hasAttribute('children')).toBe(false); expect( container.firstChild.hasAttribute('suppressContentEditableWarning'), ).toBe(false); expect( container.firstChild.hasAttribute('suppressHydrationWarning'), ).toBe(false); }); it('should skip dangerouslySetInnerHTML on web components', () => { const container = document.createElement('div'); ReactDOM.render( , container, ); expect(container.firstChild.hasAttribute('dangerouslySetInnerHTML')).toBe( false, ); ReactDOM.render( , container, ); expect(container.firstChild.hasAttribute('dangerouslySetInnerHTML')).toBe( false, ); }); it('should render null and undefined as empty but print other falsy values', () => { const container = document.createElement('div'); ReactDOM.render(
, container, ); expect(container.textContent).toEqual('textContent'); ReactDOM.render(
, container); expect(container.textContent).toEqual('0'); ReactDOM.render(
, container, ); expect(container.textContent).toEqual('false'); ReactDOM.render(
, container, ); expect(container.textContent).toEqual(''); ReactDOM.render(
, container, ); expect(container.textContent).toEqual(''); ReactDOM.render(
, container, ); expect(container.textContent).toEqual(''); }); it('should remove attributes', () => { const container = document.createElement('div'); ReactDOM.render(, container); expect(container.firstChild.hasAttribute('height')).toBe(true); ReactDOM.render(, container); expect(container.firstChild.hasAttribute('height')).toBe(false); }); it('should remove properties', () => { const container = document.createElement('div'); ReactDOM.render(
, container); expect(container.firstChild.className).toEqual('monkey'); ReactDOM.render(
, container); expect(container.firstChild.className).toEqual(''); }); it('should not set null/undefined attributes', () => { const container = document.createElement('div'); // Initial render. ReactDOM.render(, container); const node = container.firstChild; expect(node.hasAttribute('src')).toBe(false); expect(node.hasAttribute('data-foo')).toBe(false); // Update in one direction. ReactDOM.render(, container); expect(node.hasAttribute('src')).toBe(false); expect(node.hasAttribute('data-foo')).toBe(false); // Update in another direction. ReactDOM.render(, container); expect(node.hasAttribute('src')).toBe(false); expect(node.hasAttribute('data-foo')).toBe(false); // Removal. ReactDOM.render(, container); expect(node.hasAttribute('src')).toBe(false); expect(node.hasAttribute('data-foo')).toBe(false); // Addition. ReactDOM.render(, container); expect(node.hasAttribute('src')).toBe(false); expect(node.hasAttribute('data-foo')).toBe(false); }); it('should apply React-specific aliases to HTML elements', () => { const container = document.createElement('div'); ReactDOM.render(
, container); const node = container.firstChild; // Test attribute initialization. expect(node.getAttribute('accept-charset')).toBe('foo'); expect(node.hasAttribute('acceptCharset')).toBe(false); // Test attribute update. ReactDOM.render(, container); expect(node.getAttribute('accept-charset')).toBe('boo'); expect(node.hasAttribute('acceptCharset')).toBe(false); // Test attribute removal by setting to null. ReactDOM.render(, container); expect(node.hasAttribute('accept-charset')).toBe(false); expect(node.hasAttribute('acceptCharset')).toBe(false); // Restore. ReactDOM.render(, container); expect(node.getAttribute('accept-charset')).toBe('foo'); expect(node.hasAttribute('acceptCharset')).toBe(false); // Test attribute removal by setting to undefined. ReactDOM.render(, container); expect(node.hasAttribute('accept-charset')).toBe(false); expect(node.hasAttribute('acceptCharset')).toBe(false); // Restore. ReactDOM.render(, container); expect(node.getAttribute('accept-charset')).toBe('foo'); expect(node.hasAttribute('acceptCharset')).toBe(false); // Test attribute removal. ReactDOM.render(, container); expect(node.hasAttribute('accept-charset')).toBe(false); expect(node.hasAttribute('acceptCharset')).toBe(false); }); it('should apply React-specific aliases to SVG elements', () => { const container = document.createElement('div'); ReactDOM.render(, container); const node = container.firstChild; // Test attribute initialization. expect(node.getAttribute('arabic-form')).toBe('foo'); expect(node.hasAttribute('arabicForm')).toBe(false); // Test attribute update. ReactDOM.render(, container); expect(node.getAttribute('arabic-form')).toBe('boo'); expect(node.hasAttribute('arabicForm')).toBe(false); // Test attribute removal by setting to null. ReactDOM.render(, container); expect(node.hasAttribute('arabic-form')).toBe(false); expect(node.hasAttribute('arabicForm')).toBe(false); // Restore. ReactDOM.render(, container); expect(node.getAttribute('arabic-form')).toBe('foo'); expect(node.hasAttribute('arabicForm')).toBe(false); // Test attribute removal by setting to undefined. ReactDOM.render(, container); expect(node.hasAttribute('arabic-form')).toBe(false); expect(node.hasAttribute('arabicForm')).toBe(false); // Restore. ReactDOM.render(, container); expect(node.getAttribute('arabic-form')).toBe('foo'); expect(node.hasAttribute('arabicForm')).toBe(false); // Test attribute removal. ReactDOM.render(, container); expect(node.hasAttribute('arabic-form')).toBe(false); expect(node.hasAttribute('arabicForm')).toBe(false); }); it('should properly update custom attributes on custom elements', () => { const container = document.createElement('div'); ReactDOM.render(, container); ReactDOM.render(, container); const node = container.firstChild; expect(node.hasAttribute('foo')).toBe(false); expect(node.getAttribute('bar')).toBe('buzz'); }); it('should not apply React-specific aliases to custom elements', () => { const container = document.createElement('div'); ReactDOM.render(, container); const node = container.firstChild; // Should not get transformed to arabic-form as SVG would be. expect(node.getAttribute('arabicForm')).toBe('foo'); expect(node.hasAttribute('arabic-form')).toBe(false); // Test attribute update. ReactDOM.render(, container); expect(node.getAttribute('arabicForm')).toBe('boo'); // Test attribute removal and addition. ReactDOM.render(, container); // Verify the previous attribute was removed. expect(node.hasAttribute('arabicForm')).toBe(false); // Should not get transformed to accept-charset as HTML would be. expect(node.getAttribute('acceptCharset')).toBe('buzz'); expect(node.hasAttribute('accept-charset')).toBe(false); }); it('should clear a single style prop when changing `style`', () => { let styles = {display: 'none', color: 'red'}; const container = document.createElement('div'); ReactDOM.render(
, container); const stubStyle = container.firstChild.style; styles = {color: 'green'}; ReactDOM.render(
, container); expect(stubStyle.display).toEqual(''); expect(stubStyle.color).toEqual('green'); }); it('should reject attribute key injection attack on markup for regular DOM (SSR)', () => { expect(() => { for (let i = 0; i < 3; i++) { const element1 = React.createElement( 'div', {'blah" onclick="beevil" noise="hi': 'selected'}, null, ); const element2 = React.createElement( 'div', {'>
': 'selected'}, null, ); let result1 = ReactDOMServer.renderToString(element1); let result2 = ReactDOMServer.renderToString(element2); expect(result1.toLowerCase()).not.toContain('onclick'); expect(result2.toLowerCase()).not.toContain('script'); } }).toWarnDev([ 'Warning: Invalid attribute name: `blah" onclick="beevil" noise="hi`', 'Warning: Invalid attribute name: `>
`', ]); }); it('should reject attribute key injection attack on markup for custom elements (SSR)', () => { expect(() => { for (let i = 0; i < 3; i++) { const element1 = React.createElement( 'x-foo-component', {'blah" onclick="beevil" noise="hi': 'selected'}, null, ); const element2 = React.createElement( 'x-foo-component', {'>': 'selected'}, null, ); let result1 = ReactDOMServer.renderToString(element1); let result2 = ReactDOMServer.renderToString(element2); expect(result1.toLowerCase()).not.toContain('onclick'); expect(result2.toLowerCase()).not.toContain('script'); } }).toWarnDev([ 'Warning: Invalid attribute name: `blah" onclick="beevil" noise="hi`', 'Warning: Invalid attribute name: `>`', ]); }); it('should reject attribute key injection attack on mount for regular DOM', () => { expect(() => { for (let i = 0; i < 3; i++) { const container = document.createElement('div'); ReactDOM.render( React.createElement( 'div', {'blah" onclick="beevil" noise="hi': 'selected'}, null, ), container, ); expect(container.firstChild.attributes.length).toBe(0); ReactDOM.unmountComponentAtNode(container); ReactDOM.render( React.createElement( 'div', {'>
': 'selected'}, null, ), container, ); expect(container.firstChild.attributes.length).toBe(0); } }).toWarnDev([ 'Warning: Invalid attribute name: `blah" onclick="beevil" noise="hi`', 'Warning: Invalid attribute name: `>
`', ]); }); it('should reject attribute key injection attack on mount for custom elements', () => { expect(() => { for (let i = 0; i < 3; i++) { const container = document.createElement('div'); ReactDOM.render( React.createElement( 'x-foo-component', {'blah" onclick="beevil" noise="hi': 'selected'}, null, ), container, ); expect(container.firstChild.attributes.length).toBe(0); ReactDOM.unmountComponentAtNode(container); ReactDOM.render( React.createElement( 'x-foo-component', {'>': 'selected'}, null, ), container, ); expect(container.firstChild.attributes.length).toBe(0); } }).toWarnDev([ 'Warning: Invalid attribute name: `blah" onclick="beevil" noise="hi`', 'Warning: Invalid attribute name: `>`', ]); }); it('should reject attribute key injection attack on update for regular DOM', () => { expect(() => { for (let i = 0; i < 3; i++) { const container = document.createElement('div'); const beforeUpdate = React.createElement('div', {}, null); ReactDOM.render(beforeUpdate, container); ReactDOM.render( React.createElement( 'div', {'blah" onclick="beevil" noise="hi': 'selected'}, null, ), container, ); expect(container.firstChild.attributes.length).toBe(0); ReactDOM.render( React.createElement( 'div', {'>
': 'selected'}, null, ), container, ); expect(container.firstChild.attributes.length).toBe(0); } }).toWarnDev([ 'Warning: Invalid attribute name: `blah" onclick="beevil" noise="hi`', 'Warning: Invalid attribute name: `>
`', ]); }); it('should reject attribute key injection attack on update for custom elements', () => { expect(() => { for (let i = 0; i < 3; i++) { const container = document.createElement('div'); const beforeUpdate = React.createElement('x-foo-component', {}, null); ReactDOM.render(beforeUpdate, container); ReactDOM.render( React.createElement( 'x-foo-component', {'blah" onclick="beevil" noise="hi': 'selected'}, null, ), container, ); expect(container.firstChild.attributes.length).toBe(0); ReactDOM.render( React.createElement( 'x-foo-component', {'>': 'selected'}, null, ), container, ); expect(container.firstChild.attributes.length).toBe(0); } }).toWarnDev([ 'Warning: Invalid attribute name: `blah" onclick="beevil" noise="hi`', 'Warning: Invalid attribute name: `>`', ]); }); it('should update arbitrary attributes for tags containing dashes', () => { const container = document.createElement('div'); const beforeUpdate = React.createElement('x-foo-component', {}, null); ReactDOM.render(beforeUpdate, container); const afterUpdate = ; ReactDOM.render(afterUpdate, container); expect(container.childNodes[0].getAttribute('myattr')).toBe('myval'); }); it('should clear all the styles when removing `style`', () => { const styles = {display: 'none', color: 'red'}; const container = document.createElement('div'); ReactDOM.render(
, container); const stubStyle = container.firstChild.style; ReactDOM.render(
, container); expect(stubStyle.display).toEqual(''); expect(stubStyle.color).toEqual(''); }); it('should update styles when `style` changes from null to object', () => { const container = document.createElement('div'); const styles = {color: 'red'}; ReactDOM.render(
, container); ReactDOM.render(
, container); ReactDOM.render(
, container); const stubStyle = container.firstChild.style; expect(stubStyle.color).toEqual('red'); }); it('should not reset innerHTML for when children is null', () => { const container = document.createElement('div'); ReactDOM.render(
, container); container.firstChild.innerHTML = 'bonjour'; expect(container.firstChild.innerHTML).toEqual('bonjour'); ReactDOM.render(
, container); expect(container.firstChild.innerHTML).toEqual('bonjour'); }); it('should reset innerHTML when switching from a direct text child to an empty child', () => { const transitionToValues = [null, undefined, false]; transitionToValues.forEach(transitionToValue => { const container = document.createElement('div'); ReactDOM.render(
bonjour
, container); expect(container.firstChild.innerHTML).toEqual('bonjour'); ReactDOM.render(
{transitionToValue}
, container); expect(container.firstChild.innerHTML).toEqual(''); }); }); it('should empty element when removing innerHTML', () => { const container = document.createElement('div'); ReactDOM.render(
, container, ); expect(container.firstChild.innerHTML).toEqual(':)'); ReactDOM.render(
, container); expect(container.firstChild.innerHTML).toEqual(''); }); it('should transition from string content to innerHTML', () => { const container = document.createElement('div'); ReactDOM.render(
hello
, container); expect(container.firstChild.innerHTML).toEqual('hello'); ReactDOM.render(
, container, ); expect(container.firstChild.innerHTML).toEqual('goodbye'); }); it('should transition from innerHTML to string content', () => { const container = document.createElement('div'); ReactDOM.render(
, container, ); expect(container.firstChild.innerHTML).toEqual('bonjour'); ReactDOM.render(
adieu
, container); expect(container.firstChild.innerHTML).toEqual('adieu'); }); it('should transition from innerHTML to children in nested el', () => { const container = document.createElement('div'); ReactDOM.render(
, container, ); expect(container.textContent).toEqual('bonjour'); ReactDOM.render(
adieu
, container, ); expect(container.textContent).toEqual('adieu'); }); it('should transition from children to innerHTML in nested el', () => { const container = document.createElement('div'); ReactDOM.render(
adieu
, container, ); expect(container.textContent).toEqual('adieu'); ReactDOM.render(
, container, ); expect(container.textContent).toEqual('bonjour'); }); it('should not incur unnecessary DOM mutations for attributes', () => { const container = document.createElement('div'); ReactDOM.render(
, container); const node = container.firstChild; const nodeSetAttribute = node.setAttribute; node.setAttribute = jest.fn(); node.setAttribute.mockImplementation(nodeSetAttribute); const nodeRemoveAttribute = node.removeAttribute; node.removeAttribute = jest.fn(); node.removeAttribute.mockImplementation(nodeRemoveAttribute); ReactDOM.render(
, container); expect(node.setAttribute).toHaveBeenCalledTimes(0); expect(node.removeAttribute).toHaveBeenCalledTimes(0); ReactDOM.render(
, container); expect(node.setAttribute).toHaveBeenCalledTimes(1); expect(node.removeAttribute).toHaveBeenCalledTimes(0); ReactDOM.render(
, container); expect(node.setAttribute).toHaveBeenCalledTimes(1); expect(node.removeAttribute).toHaveBeenCalledTimes(0); ReactDOM.render(
, container); expect(node.setAttribute).toHaveBeenCalledTimes(1); expect(node.removeAttribute).toHaveBeenCalledTimes(1); ReactDOM.render(
, container); expect(node.setAttribute).toHaveBeenCalledTimes(2); expect(node.removeAttribute).toHaveBeenCalledTimes(1); ReactDOM.render(
, container); expect(node.setAttribute).toHaveBeenCalledTimes(2); expect(node.removeAttribute).toHaveBeenCalledTimes(2); }); it('should not incur unnecessary DOM mutations for string properties', () => { const container = document.createElement('div'); ReactDOM.render(
, container); const node = container.firstChild; const nodeValueSetter = jest.fn(); const oldSetAttribute = node.setAttribute.bind(node); node.setAttribute = function(key, value) { oldSetAttribute(key, value); nodeValueSetter(key, value); }; ReactDOM.render(
, container); expect(nodeValueSetter).toHaveBeenCalledTimes(1); ReactDOM.render(
, container); expect(nodeValueSetter).toHaveBeenCalledTimes(1); ReactDOM.render(
, container); expect(nodeValueSetter).toHaveBeenCalledTimes(1); ReactDOM.render(
, container); expect(nodeValueSetter).toHaveBeenCalledTimes(1); ReactDOM.render(
, container); expect(nodeValueSetter).toHaveBeenCalledTimes(2); ReactDOM.render(
, container); expect(nodeValueSetter).toHaveBeenCalledTimes(2); }); it('should not incur unnecessary DOM mutations for boolean properties', () => { const container = document.createElement('div'); ReactDOM.render(
, container); const node = container.firstChild; let nodeValue = true; const nodeValueSetter = jest.fn(); Object.defineProperty(node, 'checked', { get: function() { return nodeValue; }, set: nodeValueSetter.mockImplementation(function(newValue) { nodeValue = newValue; }), }); ReactDOM.render(
, container); expect(nodeValueSetter).toHaveBeenCalledTimes(0); ReactDOM.render(
, container); expect(nodeValueSetter).toHaveBeenCalledTimes(1); ReactDOM.render(
, container); expect(nodeValueSetter).toHaveBeenCalledTimes(2); ReactDOM.render(
, container); expect(nodeValueSetter).toHaveBeenCalledTimes(3); }); it('should ignore attribute whitelist for elements with the "is" attribute', () => { const container = document.createElement('div'); ReactDOM.render(