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. // Set by `yarn test-fire`.
    
  13. const {
    
  14.   enableCustomElementPropertySupport,
    
  15.   disableInputAttributeSyncing,
    
  16. } = require('shared/ReactFeatureFlags');
    
  17. 
    
  18. describe('DOMPropertyOperations', () => {
    
  19.   let React;
    
  20.   let ReactDOM;
    
  21. 
    
  22.   beforeEach(() => {
    
  23.     jest.resetModules();
    
  24.     React = require('react');
    
  25.     ReactDOM = require('react-dom');
    
  26.   });
    
  27. 
    
  28.   // Sets a value in a way that React doesn't see,
    
  29.   // so that a subsequent "change" event will trigger the event handler.
    
  30.   const setUntrackedValue = Object.getOwnPropertyDescriptor(
    
  31.     HTMLInputElement.prototype,
    
  32.     'value',
    
  33.   ).set;
    
  34.   const setUntrackedChecked = Object.getOwnPropertyDescriptor(
    
  35.     HTMLInputElement.prototype,
    
  36.     'checked',
    
  37.   ).set;
    
  38. 
    
  39.   describe('setValueForProperty', () => {
    
  40.     it('should set values as properties by default', () => {
    
  41.       const container = document.createElement('div');
    
  42.       ReactDOM.render(<div title="Tip!" />, container);
    
  43.       expect(container.firstChild.title).toBe('Tip!');
    
  44.     });
    
  45. 
    
  46.     it('should set values as attributes if necessary', () => {
    
  47.       const container = document.createElement('div');
    
  48.       ReactDOM.render(<div role="#" />, container);
    
  49.       expect(container.firstChild.getAttribute('role')).toBe('#');
    
  50.       expect(container.firstChild.role).toBeUndefined();
    
  51.     });
    
  52. 
    
  53.     it('should set values as namespace attributes if necessary', () => {
    
  54.       const container = document.createElementNS(
    
  55.         'http://www.w3.org/2000/svg',
    
  56.         'svg',
    
  57.       );
    
  58.       ReactDOM.render(<image xlinkHref="about:blank" />, container);
    
  59.       expect(
    
  60.         container.firstChild.getAttributeNS(
    
  61.           'http://www.w3.org/1999/xlink',
    
  62.           'href',
    
  63.         ),
    
  64.       ).toBe('about:blank');
    
  65.     });
    
  66. 
    
  67.     it('should set values as boolean properties', () => {
    
  68.       const container = document.createElement('div');
    
  69.       ReactDOM.render(<div disabled="disabled" />, container);
    
  70.       expect(container.firstChild.getAttribute('disabled')).toBe('');
    
  71.       ReactDOM.render(<div disabled={true} />, container);
    
  72.       expect(container.firstChild.getAttribute('disabled')).toBe('');
    
  73.       ReactDOM.render(<div disabled={false} />, container);
    
  74.       expect(container.firstChild.getAttribute('disabled')).toBe(null);
    
  75.       ReactDOM.render(<div disabled={true} />, container);
    
  76.       ReactDOM.render(<div disabled={null} />, container);
    
  77.       expect(container.firstChild.getAttribute('disabled')).toBe(null);
    
  78.       ReactDOM.render(<div disabled={true} />, container);
    
  79.       ReactDOM.render(<div disabled={undefined} />, container);
    
  80.       expect(container.firstChild.getAttribute('disabled')).toBe(null);
    
  81.     });
    
  82. 
    
  83.     it('should convert attribute values to string first', () => {
    
  84.       // Browsers default to this behavior, but some test environments do not.
    
  85.       // This ensures that we have consistent behavior.
    
  86.       const obj = {
    
  87.         toString: function () {
    
  88.           return 'css-class';
    
  89.         },
    
  90.       };
    
  91. 
    
  92.       const container = document.createElement('div');
    
  93.       ReactDOM.render(<div className={obj} />, container);
    
  94.       expect(container.firstChild.getAttribute('class')).toBe('css-class');
    
  95.     });
    
  96. 
    
  97.     it('should not remove empty attributes for special input properties', () => {
    
  98.       const container = document.createElement('div');
    
  99.       ReactDOM.render(<input value="" onChange={() => {}} />, container);
    
  100.       if (disableInputAttributeSyncing) {
    
  101.         expect(container.firstChild.hasAttribute('value')).toBe(false);
    
  102.       } else {
    
  103.         expect(container.firstChild.getAttribute('value')).toBe('');
    
  104.       }
    
  105.       expect(container.firstChild.value).toBe('');
    
  106.     });
    
  107. 
    
  108.     it('should not remove empty attributes for special option properties', () => {
    
  109.       const container = document.createElement('div');
    
  110.       ReactDOM.render(
    
  111.         <select>
    
  112.           <option value="">empty</option>
    
  113.           <option>filled</option>
    
  114.         </select>,
    
  115.         container,
    
  116.       );
    
  117.       // Regression test for https://github.com/facebook/react/issues/6219
    
  118.       expect(container.firstChild.firstChild.value).toBe('');
    
  119.       expect(container.firstChild.lastChild.value).toBe('filled');
    
  120.     });
    
  121. 
    
  122.     it('should remove for falsey boolean properties', () => {
    
  123.       const container = document.createElement('div');
    
  124.       ReactDOM.render(<div allowFullScreen={false} />, container);
    
  125.       expect(container.firstChild.hasAttribute('allowFullScreen')).toBe(false);
    
  126.     });
    
  127. 
    
  128.     it('should remove when setting custom attr to null', () => {
    
  129.       const container = document.createElement('div');
    
  130.       ReactDOM.render(<div data-foo="bar" />, container);
    
  131.       expect(container.firstChild.hasAttribute('data-foo')).toBe(true);
    
  132.       ReactDOM.render(<div data-foo={null} />, container);
    
  133.       expect(container.firstChild.hasAttribute('data-foo')).toBe(false);
    
  134.     });
    
  135. 
    
  136.     it('should set className to empty string instead of null', () => {
    
  137.       const container = document.createElement('div');
    
  138.       ReactDOM.render(<div className="selected" />, container);
    
  139.       expect(container.firstChild.className).toBe('selected');
    
  140.       ReactDOM.render(<div className={null} />, container);
    
  141.       // className should be '', not 'null' or null (which becomes 'null' in
    
  142.       // some browsers)
    
  143.       expect(container.firstChild.className).toBe('');
    
  144.       expect(container.firstChild.getAttribute('class')).toBe(null);
    
  145.     });
    
  146. 
    
  147.     it('should remove property properly for boolean properties', () => {
    
  148.       const container = document.createElement('div');
    
  149.       ReactDOM.render(<div hidden={true} />, container);
    
  150.       expect(container.firstChild.hasAttribute('hidden')).toBe(true);
    
  151.       ReactDOM.render(<div hidden={false} />, container);
    
  152.       expect(container.firstChild.hasAttribute('hidden')).toBe(false);
    
  153.     });
    
  154. 
    
  155.     it('should always assign the value attribute for non-inputs', function () {
    
  156.       const container = document.createElement('div');
    
  157.       ReactDOM.render(<progress />, container);
    
  158.       spyOnDevAndProd(container.firstChild, 'setAttribute');
    
  159.       ReactDOM.render(<progress value={30} />, container);
    
  160.       ReactDOM.render(<progress value="30" />, container);
    
  161.       expect(container.firstChild.setAttribute).toHaveBeenCalledTimes(2);
    
  162.     });
    
  163. 
    
  164.     it('should return the progress to intermediate state on null value', () => {
    
  165.       const container = document.createElement('div');
    
  166.       ReactDOM.render(<progress value={30} />, container);
    
  167.       ReactDOM.render(<progress value={null} />, container);
    
  168.       // Ensure we move progress back to an indeterminate state.
    
  169.       // Regression test for https://github.com/facebook/react/issues/6119
    
  170.       expect(container.firstChild.hasAttribute('value')).toBe(false);
    
  171.     });
    
  172. 
    
  173.     // @gate enableCustomElementPropertySupport
    
  174.     it('custom element custom events lowercase', () => {
    
  175.       const oncustomevent = jest.fn();
    
  176.       function Test() {
    
  177.         return <my-custom-element oncustomevent={oncustomevent} />;
    
  178.       }
    
  179.       const container = document.createElement('div');
    
  180.       ReactDOM.render(<Test />, container);
    
  181.       container
    
  182.         .querySelector('my-custom-element')
    
  183.         .dispatchEvent(new Event('customevent'));
    
  184.       expect(oncustomevent).toHaveBeenCalledTimes(1);
    
  185.     });
    
  186. 
    
  187.     // @gate enableCustomElementPropertySupport
    
  188.     it('custom element custom events uppercase', () => {
    
  189.       const oncustomevent = jest.fn();
    
  190.       function Test() {
    
  191.         return <my-custom-element onCustomevent={oncustomevent} />;
    
  192.       }
    
  193.       const container = document.createElement('div');
    
  194.       ReactDOM.render(<Test />, container);
    
  195.       container
    
  196.         .querySelector('my-custom-element')
    
  197.         .dispatchEvent(new Event('Customevent'));
    
  198.       expect(oncustomevent).toHaveBeenCalledTimes(1);
    
  199.     });
    
  200. 
    
  201.     // @gate enableCustomElementPropertySupport
    
  202.     it('custom element custom event with dash in name', () => {
    
  203.       const oncustomevent = jest.fn();
    
  204.       function Test() {
    
  205.         return <my-custom-element oncustom-event={oncustomevent} />;
    
  206.       }
    
  207.       const container = document.createElement('div');
    
  208.       ReactDOM.render(<Test />, container);
    
  209.       container
    
  210.         .querySelector('my-custom-element')
    
  211.         .dispatchEvent(new Event('custom-event'));
    
  212.       expect(oncustomevent).toHaveBeenCalledTimes(1);
    
  213.     });
    
  214. 
    
  215.     // @gate enableCustomElementPropertySupport
    
  216.     it('custom element remove event handler', () => {
    
  217.       const oncustomevent = jest.fn();
    
  218.       function Test(props) {
    
  219.         return <my-custom-element oncustomevent={props.handler} />;
    
  220.       }
    
  221. 
    
  222.       const container = document.createElement('div');
    
  223.       ReactDOM.render(<Test handler={oncustomevent} />, container);
    
  224.       const customElement = container.querySelector('my-custom-element');
    
  225.       customElement.dispatchEvent(new Event('customevent'));
    
  226.       expect(oncustomevent).toHaveBeenCalledTimes(1);
    
  227. 
    
  228.       ReactDOM.render(<Test handler={false} />, container);
    
  229.       // Make sure that the second render didn't create a new element. We want
    
  230.       // to make sure removeEventListener actually gets called on the same element.
    
  231.       expect(customElement).toBe(customElement);
    
  232.       customElement.dispatchEvent(new Event('customevent'));
    
  233.       expect(oncustomevent).toHaveBeenCalledTimes(1);
    
  234. 
    
  235.       ReactDOM.render(<Test handler={oncustomevent} />, container);
    
  236.       customElement.dispatchEvent(new Event('customevent'));
    
  237.       expect(oncustomevent).toHaveBeenCalledTimes(2);
    
  238. 
    
  239.       const oncustomevent2 = jest.fn();
    
  240.       ReactDOM.render(<Test handler={oncustomevent2} />, container);
    
  241.       customElement.dispatchEvent(new Event('customevent'));
    
  242.       expect(oncustomevent).toHaveBeenCalledTimes(2);
    
  243.       expect(oncustomevent2).toHaveBeenCalledTimes(1);
    
  244.     });
    
  245. 
    
  246.     it('custom elements shouldnt have non-functions for on* attributes treated as event listeners', () => {
    
  247.       const container = document.createElement('div');
    
  248.       ReactDOM.render(
    
  249.         <my-custom-element
    
  250.           onstring={'hello'}
    
  251.           onobj={{hello: 'world'}}
    
  252.           onarray={['one', 'two']}
    
  253.           ontrue={true}
    
  254.           onfalse={false}
    
  255.         />,
    
  256.         container,
    
  257.       );
    
  258.       const customElement = container.querySelector('my-custom-element');
    
  259.       expect(customElement.getAttribute('onstring')).toBe('hello');
    
  260.       expect(customElement.getAttribute('onobj')).toBe('[object Object]');
    
  261.       expect(customElement.getAttribute('onarray')).toBe('one,two');
    
  262.       expect(customElement.getAttribute('ontrue')).toBe(
    
  263.         enableCustomElementPropertySupport ? '' : 'true',
    
  264.       );
    
  265.       expect(customElement.getAttribute('onfalse')).toBe(
    
  266.         enableCustomElementPropertySupport ? null : 'false',
    
  267.       );
    
  268. 
    
  269.       // Dispatch the corresponding event names to make sure that nothing crashes.
    
  270.       customElement.dispatchEvent(new Event('string'));
    
  271.       customElement.dispatchEvent(new Event('obj'));
    
  272.       customElement.dispatchEvent(new Event('array'));
    
  273.       customElement.dispatchEvent(new Event('true'));
    
  274.       customElement.dispatchEvent(new Event('false'));
    
  275.     });
    
  276. 
    
  277.     it('custom elements should still have onClick treated like regular elements', () => {
    
  278.       let syntheticClickEvent = null;
    
  279.       const syntheticEventHandler = jest.fn(
    
  280.         event => (syntheticClickEvent = event),
    
  281.       );
    
  282.       let nativeClickEvent = null;
    
  283.       const nativeEventHandler = jest.fn(event => (nativeClickEvent = event));
    
  284.       function Test() {
    
  285.         return <my-custom-element onClick={syntheticEventHandler} />;
    
  286.       }
    
  287. 
    
  288.       const container = document.createElement('div');
    
  289.       document.body.appendChild(container);
    
  290.       ReactDOM.render(<Test />, container);
    
  291. 
    
  292.       const customElement = container.querySelector('my-custom-element');
    
  293.       customElement.onclick = nativeEventHandler;
    
  294.       container.querySelector('my-custom-element').click();
    
  295. 
    
  296.       expect(nativeEventHandler).toHaveBeenCalledTimes(1);
    
  297.       expect(syntheticEventHandler).toHaveBeenCalledTimes(1);
    
  298.       expect(syntheticClickEvent.nativeEvent).toBe(nativeClickEvent);
    
  299.     });
    
  300. 
    
  301.     // @gate enableCustomElementPropertySupport
    
  302.     it('custom elements should have working onChange event listeners', () => {
    
  303.       let reactChangeEvent = null;
    
  304.       const eventHandler = jest.fn(event => (reactChangeEvent = event));
    
  305.       const container = document.createElement('div');
    
  306.       document.body.appendChild(container);
    
  307.       ReactDOM.render(<my-custom-element onChange={eventHandler} />, container);
    
  308.       const customElement = container.querySelector('my-custom-element');
    
  309.       let expectedHandlerCallCount = 0;
    
  310. 
    
  311.       const changeEvent = new Event('change', {bubbles: true});
    
  312.       customElement.dispatchEvent(changeEvent);
    
  313.       expectedHandlerCallCount++;
    
  314.       expect(eventHandler).toHaveBeenCalledTimes(expectedHandlerCallCount);
    
  315.       expect(reactChangeEvent.nativeEvent).toBe(changeEvent);
    
  316. 
    
  317.       // Also make sure that removing and re-adding the event listener works
    
  318.       ReactDOM.render(<my-custom-element />, container);
    
  319.       customElement.dispatchEvent(new Event('change', {bubbles: true}));
    
  320.       expect(eventHandler).toHaveBeenCalledTimes(expectedHandlerCallCount);
    
  321.       ReactDOM.render(<my-custom-element onChange={eventHandler} />, container);
    
  322.       customElement.dispatchEvent(new Event('change', {bubbles: true}));
    
  323.       expectedHandlerCallCount++;
    
  324.       expect(eventHandler).toHaveBeenCalledTimes(expectedHandlerCallCount);
    
  325.     });
    
  326. 
    
  327.     it('custom elements should have working onInput event listeners', () => {
    
  328.       let reactInputEvent = null;
    
  329.       const eventHandler = jest.fn(event => (reactInputEvent = event));
    
  330.       const container = document.createElement('div');
    
  331.       document.body.appendChild(container);
    
  332.       ReactDOM.render(<my-custom-element onInput={eventHandler} />, container);
    
  333.       const customElement = container.querySelector('my-custom-element');
    
  334.       let expectedHandlerCallCount = 0;
    
  335. 
    
  336.       const inputEvent = new Event('input', {bubbles: true});
    
  337.       customElement.dispatchEvent(inputEvent);
    
  338.       expectedHandlerCallCount++;
    
  339.       expect(eventHandler).toHaveBeenCalledTimes(expectedHandlerCallCount);
    
  340.       expect(reactInputEvent.nativeEvent).toBe(inputEvent);
    
  341. 
    
  342.       // Also make sure that removing and re-adding the event listener works
    
  343.       ReactDOM.render(<my-custom-element />, container);
    
  344.       customElement.dispatchEvent(new Event('input', {bubbles: true}));
    
  345.       expect(eventHandler).toHaveBeenCalledTimes(expectedHandlerCallCount);
    
  346.       ReactDOM.render(<my-custom-element onInput={eventHandler} />, container);
    
  347.       customElement.dispatchEvent(new Event('input', {bubbles: true}));
    
  348.       expectedHandlerCallCount++;
    
  349.       expect(eventHandler).toHaveBeenCalledTimes(expectedHandlerCallCount);
    
  350.     });
    
  351. 
    
  352.     // @gate enableCustomElementPropertySupport
    
  353.     it('custom elements should have separate onInput and onChange handling', () => {
    
  354.       const container = document.createElement('div');
    
  355.       document.body.appendChild(container);
    
  356.       const inputEventHandler = jest.fn();
    
  357.       const changeEventHandler = jest.fn();
    
  358.       ReactDOM.render(
    
  359.         <my-custom-element
    
  360.           onInput={inputEventHandler}
    
  361.           onChange={changeEventHandler}
    
  362.         />,
    
  363.         container,
    
  364.       );
    
  365.       const customElement = container.querySelector('my-custom-element');
    
  366. 
    
  367.       customElement.dispatchEvent(new Event('input', {bubbles: true}));
    
  368.       expect(inputEventHandler).toHaveBeenCalledTimes(1);
    
  369.       expect(changeEventHandler).toHaveBeenCalledTimes(0);
    
  370. 
    
  371.       customElement.dispatchEvent(new Event('change', {bubbles: true}));
    
  372.       expect(inputEventHandler).toHaveBeenCalledTimes(1);
    
  373.       expect(changeEventHandler).toHaveBeenCalledTimes(1);
    
  374.     });
    
  375. 
    
  376.     // @gate enableCustomElementPropertySupport
    
  377.     it('custom elements should be able to remove and re-add custom event listeners', () => {
    
  378.       const container = document.createElement('div');
    
  379.       document.body.appendChild(container);
    
  380.       const eventHandler = jest.fn();
    
  381.       ReactDOM.render(
    
  382.         <my-custom-element oncustomevent={eventHandler} />,
    
  383.         container,
    
  384.       );
    
  385. 
    
  386.       const customElement = container.querySelector('my-custom-element');
    
  387.       customElement.dispatchEvent(new Event('customevent'));
    
  388.       expect(eventHandler).toHaveBeenCalledTimes(1);
    
  389. 
    
  390.       ReactDOM.render(<my-custom-element />, container);
    
  391.       customElement.dispatchEvent(new Event('customevent'));
    
  392.       expect(eventHandler).toHaveBeenCalledTimes(1);
    
  393. 
    
  394.       ReactDOM.render(
    
  395.         <my-custom-element oncustomevent={eventHandler} />,
    
  396.         container,
    
  397.       );
    
  398.       customElement.dispatchEvent(new Event('customevent'));
    
  399.       expect(eventHandler).toHaveBeenCalledTimes(2);
    
  400.     });
    
  401. 
    
  402.     it('<input is=...> should have the same onChange/onInput/onClick behavior as <input>', () => {
    
  403.       const container = document.createElement('div');
    
  404.       document.body.appendChild(container);
    
  405.       const regularOnInputHandler = jest.fn();
    
  406.       const regularOnChangeHandler = jest.fn();
    
  407.       const regularOnClickHandler = jest.fn();
    
  408.       const customOnInputHandler = jest.fn();
    
  409.       const customOnChangeHandler = jest.fn();
    
  410.       const customOnClickHandler = jest.fn();
    
  411.       function clearMocks() {
    
  412.         regularOnInputHandler.mockClear();
    
  413.         regularOnChangeHandler.mockClear();
    
  414.         regularOnClickHandler.mockClear();
    
  415.         customOnInputHandler.mockClear();
    
  416.         customOnChangeHandler.mockClear();
    
  417.         customOnClickHandler.mockClear();
    
  418.       }
    
  419.       ReactDOM.render(
    
  420.         <div>
    
  421.           <input
    
  422.             onInput={regularOnInputHandler}
    
  423.             onChange={regularOnChangeHandler}
    
  424.             onClick={regularOnClickHandler}
    
  425.           />
    
  426.           <input
    
  427.             is="my-custom-element"
    
  428.             onInput={customOnInputHandler}
    
  429.             onChange={customOnChangeHandler}
    
  430.             onClick={customOnClickHandler}
    
  431.           />
    
  432.         </div>,
    
  433.         container,
    
  434.       );
    
  435. 
    
  436.       const regularInput = container.querySelector(
    
  437.         'input:not([is=my-custom-element])',
    
  438.       );
    
  439.       const customInput = container.querySelector(
    
  440.         'input[is=my-custom-element]',
    
  441.       );
    
  442.       expect(regularInput).not.toBe(customInput);
    
  443. 
    
  444.       // Typing should trigger onInput and onChange for both kinds of inputs.
    
  445.       clearMocks();
    
  446.       setUntrackedValue.call(regularInput, 'hello');
    
  447.       regularInput.dispatchEvent(new Event('input', {bubbles: true}));
    
  448.       expect(regularOnInputHandler).toHaveBeenCalledTimes(1);
    
  449.       expect(regularOnChangeHandler).toHaveBeenCalledTimes(1);
    
  450.       expect(regularOnClickHandler).toHaveBeenCalledTimes(0);
    
  451.       setUntrackedValue.call(customInput, 'hello');
    
  452.       customInput.dispatchEvent(new Event('input', {bubbles: true}));
    
  453.       expect(customOnInputHandler).toHaveBeenCalledTimes(1);
    
  454.       expect(customOnChangeHandler).toHaveBeenCalledTimes(1);
    
  455.       expect(customOnClickHandler).toHaveBeenCalledTimes(0);
    
  456. 
    
  457.       // The native change event itself does not produce extra React events.
    
  458.       clearMocks();
    
  459.       regularInput.dispatchEvent(new Event('change', {bubbles: true}));
    
  460.       expect(regularOnInputHandler).toHaveBeenCalledTimes(0);
    
  461.       expect(regularOnChangeHandler).toHaveBeenCalledTimes(0);
    
  462.       expect(regularOnClickHandler).toHaveBeenCalledTimes(0);
    
  463.       customInput.dispatchEvent(new Event('change', {bubbles: true}));
    
  464.       expect(customOnInputHandler).toHaveBeenCalledTimes(0);
    
  465.       expect(customOnChangeHandler).toHaveBeenCalledTimes(0);
    
  466.       expect(customOnClickHandler).toHaveBeenCalledTimes(0);
    
  467. 
    
  468.       // The click event is handled by both inputs.
    
  469.       clearMocks();
    
  470.       regularInput.dispatchEvent(new Event('click', {bubbles: true}));
    
  471.       expect(regularOnInputHandler).toHaveBeenCalledTimes(0);
    
  472.       expect(regularOnChangeHandler).toHaveBeenCalledTimes(0);
    
  473.       expect(regularOnClickHandler).toHaveBeenCalledTimes(1);
    
  474.       customInput.dispatchEvent(new Event('click', {bubbles: true}));
    
  475.       expect(customOnInputHandler).toHaveBeenCalledTimes(0);
    
  476.       expect(customOnChangeHandler).toHaveBeenCalledTimes(0);
    
  477.       expect(customOnClickHandler).toHaveBeenCalledTimes(1);
    
  478. 
    
  479.       // Typing again should trigger onInput and onChange for both kinds of inputs.
    
  480.       clearMocks();
    
  481.       setUntrackedValue.call(regularInput, 'goodbye');
    
  482.       regularInput.dispatchEvent(new Event('input', {bubbles: true}));
    
  483.       expect(regularOnInputHandler).toHaveBeenCalledTimes(1);
    
  484.       expect(regularOnChangeHandler).toHaveBeenCalledTimes(1);
    
  485.       expect(regularOnClickHandler).toHaveBeenCalledTimes(0);
    
  486.       setUntrackedValue.call(customInput, 'goodbye');
    
  487.       customInput.dispatchEvent(new Event('input', {bubbles: true}));
    
  488.       expect(customOnInputHandler).toHaveBeenCalledTimes(1);
    
  489.       expect(customOnChangeHandler).toHaveBeenCalledTimes(1);
    
  490.       expect(customOnClickHandler).toHaveBeenCalledTimes(0);
    
  491.     });
    
  492. 
    
  493.     it('<input type=radio is=...> should have the same onChange/onInput/onClick behavior as <input type=radio>', () => {
    
  494.       const container = document.createElement('div');
    
  495.       document.body.appendChild(container);
    
  496.       const regularOnInputHandler = jest.fn();
    
  497.       const regularOnChangeHandler = jest.fn();
    
  498.       const regularOnClickHandler = jest.fn();
    
  499.       const customOnInputHandler = jest.fn();
    
  500.       const customOnChangeHandler = jest.fn();
    
  501.       const customOnClickHandler = jest.fn();
    
  502.       function clearMocks() {
    
  503.         regularOnInputHandler.mockClear();
    
  504.         regularOnChangeHandler.mockClear();
    
  505.         regularOnClickHandler.mockClear();
    
  506.         customOnInputHandler.mockClear();
    
  507.         customOnChangeHandler.mockClear();
    
  508.         customOnClickHandler.mockClear();
    
  509.       }
    
  510.       ReactDOM.render(
    
  511.         <div>
    
  512.           <input
    
  513.             type="radio"
    
  514.             onInput={regularOnInputHandler}
    
  515.             onChange={regularOnChangeHandler}
    
  516.             onClick={regularOnClickHandler}
    
  517.           />
    
  518.           <input
    
  519.             is="my-custom-element"
    
  520.             type="radio"
    
  521.             onInput={customOnInputHandler}
    
  522.             onChange={customOnChangeHandler}
    
  523.             onClick={customOnClickHandler}
    
  524.           />
    
  525.         </div>,
    
  526.         container,
    
  527.       );
    
  528. 
    
  529.       const regularInput = container.querySelector(
    
  530.         'input:not([is=my-custom-element])',
    
  531.       );
    
  532.       const customInput = container.querySelector(
    
  533.         'input[is=my-custom-element]',
    
  534.       );
    
  535.       expect(regularInput).not.toBe(customInput);
    
  536. 
    
  537.       // Clicking should trigger onClick and onChange on both inputs.
    
  538.       clearMocks();
    
  539.       setUntrackedChecked.call(regularInput, true);
    
  540.       regularInput.dispatchEvent(new Event('click', {bubbles: true}));
    
  541.       expect(regularOnInputHandler).toHaveBeenCalledTimes(0);
    
  542.       expect(regularOnChangeHandler).toHaveBeenCalledTimes(1);
    
  543.       expect(regularOnClickHandler).toHaveBeenCalledTimes(1);
    
  544.       setUntrackedChecked.call(customInput, true);
    
  545.       customInput.dispatchEvent(new Event('click', {bubbles: true}));
    
  546.       expect(customOnInputHandler).toHaveBeenCalledTimes(0);
    
  547.       expect(customOnChangeHandler).toHaveBeenCalledTimes(1);
    
  548.       expect(customOnClickHandler).toHaveBeenCalledTimes(1);
    
  549. 
    
  550.       // The native input event only produces a React onInput event.
    
  551.       clearMocks();
    
  552.       regularInput.dispatchEvent(new Event('input', {bubbles: true}));
    
  553.       expect(regularOnInputHandler).toHaveBeenCalledTimes(1);
    
  554.       expect(regularOnChangeHandler).toHaveBeenCalledTimes(0);
    
  555.       expect(regularOnClickHandler).toHaveBeenCalledTimes(0);
    
  556.       customInput.dispatchEvent(new Event('input', {bubbles: true}));
    
  557.       expect(customOnInputHandler).toHaveBeenCalledTimes(1);
    
  558.       expect(customOnChangeHandler).toHaveBeenCalledTimes(0);
    
  559.       expect(customOnClickHandler).toHaveBeenCalledTimes(0);
    
  560. 
    
  561.       // Clicking again should trigger onClick and onChange on both inputs.
    
  562.       clearMocks();
    
  563.       setUntrackedChecked.call(regularInput, false);
    
  564.       regularInput.dispatchEvent(new Event('click', {bubbles: true}));
    
  565.       expect(regularOnInputHandler).toHaveBeenCalledTimes(0);
    
  566.       expect(regularOnChangeHandler).toHaveBeenCalledTimes(1);
    
  567.       expect(regularOnClickHandler).toHaveBeenCalledTimes(1);
    
  568.       setUntrackedChecked.call(customInput, false);
    
  569.       customInput.dispatchEvent(new Event('click', {bubbles: true}));
    
  570.       expect(customOnInputHandler).toHaveBeenCalledTimes(0);
    
  571.       expect(customOnChangeHandler).toHaveBeenCalledTimes(1);
    
  572.       expect(customOnClickHandler).toHaveBeenCalledTimes(1);
    
  573.     });
    
  574. 
    
  575.     it('<select is=...> should have the same onChange/onInput/onClick behavior as <select>', () => {
    
  576.       const container = document.createElement('div');
    
  577.       document.body.appendChild(container);
    
  578.       const regularOnInputHandler = jest.fn();
    
  579.       const regularOnChangeHandler = jest.fn();
    
  580.       const regularOnClickHandler = jest.fn();
    
  581.       const customOnInputHandler = jest.fn();
    
  582.       const customOnChangeHandler = jest.fn();
    
  583.       const customOnClickHandler = jest.fn();
    
  584.       function clearMocks() {
    
  585.         regularOnInputHandler.mockClear();
    
  586.         regularOnChangeHandler.mockClear();
    
  587.         regularOnClickHandler.mockClear();
    
  588.         customOnInputHandler.mockClear();
    
  589.         customOnChangeHandler.mockClear();
    
  590.         customOnClickHandler.mockClear();
    
  591.       }
    
  592.       ReactDOM.render(
    
  593.         <div>
    
  594.           <select
    
  595.             onInput={regularOnInputHandler}
    
  596.             onChange={regularOnChangeHandler}
    
  597.             onClick={regularOnClickHandler}
    
  598.           />
    
  599.           <select
    
  600.             is="my-custom-element"
    
  601.             onInput={customOnInputHandler}
    
  602.             onChange={customOnChangeHandler}
    
  603.             onClick={customOnClickHandler}
    
  604.           />
    
  605.         </div>,
    
  606.         container,
    
  607.       );
    
  608. 
    
  609.       const regularSelect = container.querySelector(
    
  610.         'select:not([is=my-custom-element])',
    
  611.       );
    
  612.       const customSelect = container.querySelector(
    
  613.         'select[is=my-custom-element]',
    
  614.       );
    
  615.       expect(regularSelect).not.toBe(customSelect);
    
  616. 
    
  617.       // Clicking should only trigger onClick on both inputs.
    
  618.       clearMocks();
    
  619.       regularSelect.dispatchEvent(new Event('click', {bubbles: true}));
    
  620.       expect(regularOnInputHandler).toHaveBeenCalledTimes(0);
    
  621.       expect(regularOnChangeHandler).toHaveBeenCalledTimes(0);
    
  622.       expect(regularOnClickHandler).toHaveBeenCalledTimes(1);
    
  623.       customSelect.dispatchEvent(new Event('click', {bubbles: true}));
    
  624.       expect(customOnInputHandler).toHaveBeenCalledTimes(0);
    
  625.       expect(customOnChangeHandler).toHaveBeenCalledTimes(0);
    
  626.       expect(customOnClickHandler).toHaveBeenCalledTimes(1);
    
  627. 
    
  628.       // Native input event should only trigger onInput on both inputs.
    
  629.       clearMocks();
    
  630.       regularSelect.dispatchEvent(new Event('input', {bubbles: true}));
    
  631.       expect(regularOnInputHandler).toHaveBeenCalledTimes(1);
    
  632.       expect(regularOnChangeHandler).toHaveBeenCalledTimes(0);
    
  633.       expect(regularOnClickHandler).toHaveBeenCalledTimes(0);
    
  634.       customSelect.dispatchEvent(new Event('input', {bubbles: true}));
    
  635.       expect(customOnInputHandler).toHaveBeenCalledTimes(1);
    
  636.       expect(customOnChangeHandler).toHaveBeenCalledTimes(0);
    
  637.       expect(customOnClickHandler).toHaveBeenCalledTimes(0);
    
  638. 
    
  639.       // Native change event should trigger onChange.
    
  640.       clearMocks();
    
  641.       regularSelect.dispatchEvent(new Event('change', {bubbles: true}));
    
  642.       expect(regularOnInputHandler).toHaveBeenCalledTimes(0);
    
  643.       expect(regularOnChangeHandler).toHaveBeenCalledTimes(1);
    
  644.       expect(regularOnClickHandler).toHaveBeenCalledTimes(0);
    
  645.       customSelect.dispatchEvent(new Event('change', {bubbles: true}));
    
  646.       expect(customOnInputHandler).toHaveBeenCalledTimes(0);
    
  647.       expect(customOnChangeHandler).toHaveBeenCalledTimes(1);
    
  648.       expect(customOnClickHandler).toHaveBeenCalledTimes(0);
    
  649.     });
    
  650. 
    
  651.     // @gate enableCustomElementPropertySupport
    
  652.     it('onChange/onInput/onClick on div with various types of children', () => {
    
  653.       const container = document.createElement('div');
    
  654.       document.body.appendChild(container);
    
  655.       const onChangeHandler = jest.fn();
    
  656.       const onInputHandler = jest.fn();
    
  657.       const onClickHandler = jest.fn();
    
  658.       function clearMocks() {
    
  659.         onChangeHandler.mockClear();
    
  660.         onInputHandler.mockClear();
    
  661.         onClickHandler.mockClear();
    
  662.       }
    
  663.       ReactDOM.render(
    
  664.         <div
    
  665.           onChange={onChangeHandler}
    
  666.           onInput={onInputHandler}
    
  667.           onClick={onClickHandler}>
    
  668.           <my-custom-element />
    
  669.           <input />
    
  670.           <input is="my-custom-element" />
    
  671.         </div>,
    
  672.         container,
    
  673.       );
    
  674.       const customElement = container.querySelector('my-custom-element');
    
  675.       const regularInput = container.querySelector(
    
  676.         'input:not([is="my-custom-element"])',
    
  677.       );
    
  678.       const customInput = container.querySelector(
    
  679.         'input[is="my-custom-element"]',
    
  680.       );
    
  681.       expect(regularInput).not.toBe(customInput);
    
  682. 
    
  683.       // Custom element has no special logic for input/change.
    
  684.       clearMocks();
    
  685.       customElement.dispatchEvent(new Event('input', {bubbles: true}));
    
  686.       expect(onChangeHandler).toBeCalledTimes(0);
    
  687.       expect(onInputHandler).toBeCalledTimes(1);
    
  688.       expect(onClickHandler).toBeCalledTimes(0);
    
  689.       customElement.dispatchEvent(new Event('change', {bubbles: true}));
    
  690.       expect(onChangeHandler).toBeCalledTimes(1);
    
  691.       expect(onInputHandler).toBeCalledTimes(1);
    
  692.       expect(onClickHandler).toBeCalledTimes(0);
    
  693.       customElement.dispatchEvent(new Event('click', {bubbles: true}));
    
  694.       expect(onChangeHandler).toBeCalledTimes(1);
    
  695.       expect(onInputHandler).toBeCalledTimes(1);
    
  696.       expect(onClickHandler).toBeCalledTimes(1);
    
  697. 
    
  698.       // Regular input treats browser input as onChange.
    
  699.       clearMocks();
    
  700.       setUntrackedValue.call(regularInput, 'hello');
    
  701.       regularInput.dispatchEvent(new Event('input', {bubbles: true}));
    
  702.       expect(onChangeHandler).toBeCalledTimes(1);
    
  703.       expect(onInputHandler).toBeCalledTimes(1);
    
  704.       expect(onClickHandler).toBeCalledTimes(0);
    
  705.       regularInput.dispatchEvent(new Event('change', {bubbles: true}));
    
  706.       expect(onChangeHandler).toBeCalledTimes(1);
    
  707.       expect(onInputHandler).toBeCalledTimes(1);
    
  708.       expect(onClickHandler).toBeCalledTimes(0);
    
  709.       regularInput.dispatchEvent(new Event('click', {bubbles: true}));
    
  710.       expect(onChangeHandler).toBeCalledTimes(1);
    
  711.       expect(onInputHandler).toBeCalledTimes(1);
    
  712.       expect(onClickHandler).toBeCalledTimes(1);
    
  713. 
    
  714.       // Custom input treats browser input as onChange.
    
  715.       clearMocks();
    
  716.       setUntrackedValue.call(customInput, 'hello');
    
  717.       customInput.dispatchEvent(new Event('input', {bubbles: true}));
    
  718.       expect(onChangeHandler).toBeCalledTimes(1);
    
  719.       expect(onInputHandler).toBeCalledTimes(1);
    
  720.       expect(onClickHandler).toBeCalledTimes(0);
    
  721.       customInput.dispatchEvent(new Event('change', {bubbles: true}));
    
  722.       expect(onChangeHandler).toBeCalledTimes(1);
    
  723.       expect(onInputHandler).toBeCalledTimes(1);
    
  724.       expect(onClickHandler).toBeCalledTimes(0);
    
  725.       customInput.dispatchEvent(new Event('click', {bubbles: true}));
    
  726.       expect(onChangeHandler).toBeCalledTimes(1);
    
  727.       expect(onInputHandler).toBeCalledTimes(1);
    
  728.       expect(onClickHandler).toBeCalledTimes(1);
    
  729.     });
    
  730. 
    
  731.     it('custom element onChange/onInput/onClick with event target input child', () => {
    
  732.       const container = document.createElement('div');
    
  733.       document.body.appendChild(container);
    
  734.       const onChangeHandler = jest.fn();
    
  735.       const onInputHandler = jest.fn();
    
  736.       const onClickHandler = jest.fn();
    
  737.       ReactDOM.render(
    
  738.         <my-custom-element
    
  739.           onChange={onChangeHandler}
    
  740.           onInput={onInputHandler}
    
  741.           onClick={onClickHandler}>
    
  742.           <input />
    
  743.         </my-custom-element>,
    
  744.         container,
    
  745.       );
    
  746. 
    
  747.       const input = container.querySelector('input');
    
  748.       setUntrackedValue.call(input, 'hello');
    
  749.       input.dispatchEvent(new Event('input', {bubbles: true}));
    
  750.       // Simulated onChange from the child's input event
    
  751.       // bubbles to the parent custom element.
    
  752.       expect(onChangeHandler).toBeCalledTimes(1);
    
  753.       expect(onInputHandler).toBeCalledTimes(1);
    
  754.       expect(onClickHandler).toBeCalledTimes(0);
    
  755.       // Consequently, the native change event is ignored.
    
  756.       input.dispatchEvent(new Event('change', {bubbles: true}));
    
  757.       expect(onChangeHandler).toBeCalledTimes(1);
    
  758.       expect(onInputHandler).toBeCalledTimes(1);
    
  759.       expect(onClickHandler).toBeCalledTimes(0);
    
  760.       input.dispatchEvent(new Event('click', {bubbles: true}));
    
  761.       expect(onChangeHandler).toBeCalledTimes(1);
    
  762.       expect(onInputHandler).toBeCalledTimes(1);
    
  763.       expect(onClickHandler).toBeCalledTimes(1);
    
  764.     });
    
  765. 
    
  766.     it('custom element onChange/onInput/onClick with event target div child', () => {
    
  767.       const container = document.createElement('div');
    
  768.       document.body.appendChild(container);
    
  769.       const onChangeHandler = jest.fn();
    
  770.       const onInputHandler = jest.fn();
    
  771.       const onClickHandler = jest.fn();
    
  772.       ReactDOM.render(
    
  773.         <my-custom-element
    
  774.           onChange={onChangeHandler}
    
  775.           onInput={onInputHandler}
    
  776.           onClick={onClickHandler}>
    
  777.           <div />
    
  778.         </my-custom-element>,
    
  779.         container,
    
  780.       );
    
  781. 
    
  782.       const div = container.querySelector('div');
    
  783.       div.dispatchEvent(new Event('input', {bubbles: true}));
    
  784.       expect(onChangeHandler).toBeCalledTimes(0);
    
  785.       expect(onInputHandler).toBeCalledTimes(1);
    
  786.       expect(onClickHandler).toBeCalledTimes(0);
    
  787. 
    
  788.       div.dispatchEvent(new Event('change', {bubbles: true}));
    
  789.       // React always ignores change event invoked on non-custom and non-input targets.
    
  790.       // So change event emitted on a div does not propagate upwards.
    
  791.       expect(onChangeHandler).toBeCalledTimes(0);
    
  792.       expect(onInputHandler).toBeCalledTimes(1);
    
  793.       expect(onClickHandler).toBeCalledTimes(0);
    
  794. 
    
  795.       div.dispatchEvent(new Event('click', {bubbles: true}));
    
  796.       expect(onChangeHandler).toBeCalledTimes(0);
    
  797.       expect(onInputHandler).toBeCalledTimes(1);
    
  798.       expect(onClickHandler).toBeCalledTimes(1);
    
  799.     });
    
  800. 
    
  801.     it('div onChange/onInput/onClick with event target div child', () => {
    
  802.       const container = document.createElement('div');
    
  803.       document.body.appendChild(container);
    
  804.       const onChangeHandler = jest.fn();
    
  805.       const onInputHandler = jest.fn();
    
  806.       const onClickHandler = jest.fn();
    
  807.       ReactDOM.render(
    
  808.         <div
    
  809.           onChange={onChangeHandler}
    
  810.           onInput={onInputHandler}
    
  811.           onClick={onClickHandler}>
    
  812.           <div />
    
  813.         </div>,
    
  814.         container,
    
  815.       );
    
  816. 
    
  817.       const div = container.querySelector('div > div');
    
  818.       div.dispatchEvent(new Event('input', {bubbles: true}));
    
  819.       expect(onChangeHandler).toBeCalledTimes(0);
    
  820.       expect(onInputHandler).toBeCalledTimes(1);
    
  821.       expect(onClickHandler).toBeCalledTimes(0);
    
  822. 
    
  823.       div.dispatchEvent(new Event('change', {bubbles: true}));
    
  824.       // React always ignores change event invoked on non-custom and non-input targets.
    
  825.       // So change event emitted on a div does not propagate upwards.
    
  826.       expect(onChangeHandler).toBeCalledTimes(0);
    
  827.       expect(onInputHandler).toBeCalledTimes(1);
    
  828.       expect(onClickHandler).toBeCalledTimes(0);
    
  829. 
    
  830.       div.dispatchEvent(new Event('click', {bubbles: true}));
    
  831.       expect(onChangeHandler).toBeCalledTimes(0);
    
  832.       expect(onInputHandler).toBeCalledTimes(1);
    
  833.       expect(onClickHandler).toBeCalledTimes(1);
    
  834.     });
    
  835. 
    
  836.     // @gate enableCustomElementPropertySupport
    
  837.     it('custom element onChange/onInput/onClick with event target custom element child', () => {
    
  838.       const container = document.createElement('div');
    
  839.       document.body.appendChild(container);
    
  840.       const onChangeHandler = jest.fn();
    
  841.       const onInputHandler = jest.fn();
    
  842.       const onClickHandler = jest.fn();
    
  843.       ReactDOM.render(
    
  844.         <my-custom-element
    
  845.           onChange={onChangeHandler}
    
  846.           onInput={onInputHandler}
    
  847.           onClick={onClickHandler}>
    
  848.           <other-custom-element />
    
  849.         </my-custom-element>,
    
  850.         container,
    
  851.       );
    
  852. 
    
  853.       const customChild = container.querySelector('other-custom-element');
    
  854.       customChild.dispatchEvent(new Event('input', {bubbles: true}));
    
  855.       // There is no simulated onChange, only raw onInput is dispatched.
    
  856.       expect(onChangeHandler).toBeCalledTimes(0);
    
  857.       expect(onInputHandler).toBeCalledTimes(1);
    
  858.       expect(onClickHandler).toBeCalledTimes(0);
    
  859.       // The native change event propagates to the parent as onChange.
    
  860.       customChild.dispatchEvent(new Event('change', {bubbles: true}));
    
  861.       expect(onChangeHandler).toBeCalledTimes(1);
    
  862.       expect(onInputHandler).toBeCalledTimes(1);
    
  863.       expect(onClickHandler).toBeCalledTimes(0);
    
  864.       customChild.dispatchEvent(new Event('click', {bubbles: true}));
    
  865.       expect(onChangeHandler).toBeCalledTimes(1);
    
  866.       expect(onInputHandler).toBeCalledTimes(1);
    
  867.       expect(onClickHandler).toBeCalledTimes(1);
    
  868.     });
    
  869. 
    
  870.     // @gate enableCustomElementPropertySupport
    
  871.     it('custom elements should allow custom events with capture event listeners', () => {
    
  872.       const oncustomeventCapture = jest.fn();
    
  873.       const oncustomevent = jest.fn();
    
  874.       function Test() {
    
  875.         return (
    
  876.           <my-custom-element
    
  877.             oncustomeventCapture={oncustomeventCapture}
    
  878.             oncustomevent={oncustomevent}>
    
  879.             <div />
    
  880.           </my-custom-element>
    
  881.         );
    
  882.       }
    
  883.       const container = document.createElement('div');
    
  884.       ReactDOM.render(<Test />, container);
    
  885.       container
    
  886.         .querySelector('my-custom-element > div')
    
  887.         .dispatchEvent(new Event('customevent', {bubbles: false}));
    
  888.       expect(oncustomeventCapture).toHaveBeenCalledTimes(1);
    
  889.       expect(oncustomevent).toHaveBeenCalledTimes(0);
    
  890.     });
    
  891. 
    
  892.     it('innerHTML should not work on custom elements', () => {
    
  893.       const container = document.createElement('div');
    
  894.       ReactDOM.render(<my-custom-element innerHTML="foo" />, container);
    
  895.       const customElement = container.querySelector('my-custom-element');
    
  896.       expect(customElement.getAttribute('innerHTML')).toBe(null);
    
  897.       expect(customElement.hasChildNodes()).toBe(false);
    
  898. 
    
  899.       // Render again to verify the update codepath doesn't accidentally let
    
  900.       // something through.
    
  901.       ReactDOM.render(<my-custom-element innerHTML="bar" />, container);
    
  902.       expect(customElement.getAttribute('innerHTML')).toBe(null);
    
  903.       expect(customElement.hasChildNodes()).toBe(false);
    
  904.     });
    
  905. 
    
  906.     // @gate enableCustomElementPropertySupport
    
  907.     it('innerText should not work on custom elements', () => {
    
  908.       const container = document.createElement('div');
    
  909.       ReactDOM.render(<my-custom-element innerText="foo" />, container);
    
  910.       const customElement = container.querySelector('my-custom-element');
    
  911.       expect(customElement.getAttribute('innerText')).toBe(null);
    
  912.       expect(customElement.hasChildNodes()).toBe(false);
    
  913. 
    
  914.       // Render again to verify the update codepath doesn't accidentally let
    
  915.       // something through.
    
  916.       ReactDOM.render(<my-custom-element innerText="bar" />, container);
    
  917.       expect(customElement.getAttribute('innerText')).toBe(null);
    
  918.       expect(customElement.hasChildNodes()).toBe(false);
    
  919.     });
    
  920. 
    
  921.     // @gate enableCustomElementPropertySupport
    
  922.     it('textContent should not work on custom elements', () => {
    
  923.       const container = document.createElement('div');
    
  924.       ReactDOM.render(<my-custom-element textContent="foo" />, container);
    
  925.       const customElement = container.querySelector('my-custom-element');
    
  926.       expect(customElement.getAttribute('textContent')).toBe(null);
    
  927.       expect(customElement.hasChildNodes()).toBe(false);
    
  928. 
    
  929.       // Render again to verify the update codepath doesn't accidentally let
    
  930.       // something through.
    
  931.       ReactDOM.render(<my-custom-element textContent="bar" />, container);
    
  932.       expect(customElement.getAttribute('textContent')).toBe(null);
    
  933.       expect(customElement.hasChildNodes()).toBe(false);
    
  934.     });
    
  935. 
    
  936.     // @gate enableCustomElementPropertySupport
    
  937.     it('values should not be converted to booleans when assigning into custom elements', () => {
    
  938.       const container = document.createElement('div');
    
  939.       document.body.appendChild(container);
    
  940.       ReactDOM.render(<my-custom-element />, container);
    
  941.       const customElement = container.querySelector('my-custom-element');
    
  942.       customElement.foo = null;
    
  943. 
    
  944.       // true => string
    
  945.       ReactDOM.render(<my-custom-element foo={true} />, container);
    
  946.       expect(customElement.foo).toBe(true);
    
  947.       ReactDOM.render(<my-custom-element foo="bar" />, container);
    
  948.       expect(customElement.foo).toBe('bar');
    
  949. 
    
  950.       // false => string
    
  951.       ReactDOM.render(<my-custom-element foo={false} />, container);
    
  952.       expect(customElement.foo).toBe(false);
    
  953.       ReactDOM.render(<my-custom-element foo="bar" />, container);
    
  954.       expect(customElement.foo).toBe('bar');
    
  955. 
    
  956.       // true => null
    
  957.       ReactDOM.render(<my-custom-element foo={true} />, container);
    
  958.       expect(customElement.foo).toBe(true);
    
  959.       ReactDOM.render(<my-custom-element foo={null} />, container);
    
  960.       expect(customElement.foo).toBe(null);
    
  961. 
    
  962.       // false => null
    
  963.       ReactDOM.render(<my-custom-element foo={false} />, container);
    
  964.       expect(customElement.foo).toBe(false);
    
  965.       ReactDOM.render(<my-custom-element foo={null} />, container);
    
  966.       expect(customElement.foo).toBe(null);
    
  967.     });
    
  968. 
    
  969.     // @gate enableCustomElementPropertySupport
    
  970.     it('boolean props should not be stringified in attributes', () => {
    
  971.       const container = document.createElement('div');
    
  972.       document.body.appendChild(container);
    
  973.       ReactDOM.render(<my-custom-element foo={true} />, container);
    
  974.       const customElement = container.querySelector('my-custom-element');
    
  975. 
    
  976.       expect(customElement.getAttribute('foo')).toBe('');
    
  977. 
    
  978.       // true => false
    
  979.       ReactDOM.render(<my-custom-element foo={false} />, container);
    
  980. 
    
  981.       expect(customElement.getAttribute('foo')).toBe(null);
    
  982.     });
    
  983. 
    
  984.     // @gate enableCustomElementPropertySupport
    
  985.     it('custom element custom event handlers assign multiple types', () => {
    
  986.       const container = document.createElement('div');
    
  987.       document.body.appendChild(container);
    
  988.       const oncustomevent = jest.fn();
    
  989. 
    
  990.       // First render with string
    
  991.       ReactDOM.render(<my-custom-element oncustomevent={'foo'} />, container);
    
  992.       const customelement = container.querySelector('my-custom-element');
    
  993.       customelement.dispatchEvent(new Event('customevent'));
    
  994.       expect(oncustomevent).toHaveBeenCalledTimes(0);
    
  995.       expect(customelement.oncustomevent).toBe(undefined);
    
  996.       expect(customelement.getAttribute('oncustomevent')).toBe('foo');
    
  997. 
    
  998.       // string => event listener
    
  999.       ReactDOM.render(
    
  1000.         <my-custom-element oncustomevent={oncustomevent} />,
    
  1001.         container,
    
  1002.       );
    
  1003.       customelement.dispatchEvent(new Event('customevent'));
    
  1004.       expect(oncustomevent).toHaveBeenCalledTimes(1);
    
  1005.       expect(customelement.oncustomevent).toBe(undefined);
    
  1006.       expect(customelement.getAttribute('oncustomevent')).toBe(null);
    
  1007. 
    
  1008.       // event listener => string
    
  1009.       ReactDOM.render(<my-custom-element oncustomevent={'foo'} />, container);
    
  1010.       customelement.dispatchEvent(new Event('customevent'));
    
  1011.       expect(oncustomevent).toHaveBeenCalledTimes(1);
    
  1012.       expect(customelement.oncustomevent).toBe(undefined);
    
  1013.       expect(customelement.getAttribute('oncustomevent')).toBe('foo');
    
  1014. 
    
  1015.       // string => nothing
    
  1016.       ReactDOM.render(<my-custom-element />, container);
    
  1017.       customelement.dispatchEvent(new Event('customevent'));
    
  1018.       expect(oncustomevent).toHaveBeenCalledTimes(1);
    
  1019.       expect(customelement.oncustomevent).toBe(undefined);
    
  1020.       expect(customelement.getAttribute('oncustomevent')).toBe(null);
    
  1021. 
    
  1022.       // nothing => event listener
    
  1023.       ReactDOM.render(
    
  1024.         <my-custom-element oncustomevent={oncustomevent} />,
    
  1025.         container,
    
  1026.       );
    
  1027.       customelement.dispatchEvent(new Event('customevent'));
    
  1028.       expect(oncustomevent).toHaveBeenCalledTimes(2);
    
  1029.       expect(customelement.oncustomevent).toBe(undefined);
    
  1030.       expect(customelement.getAttribute('oncustomevent')).toBe(null);
    
  1031.     });
    
  1032. 
    
  1033.     // @gate enableCustomElementPropertySupport
    
  1034.     it('custom element custom event handlers assign multiple types with setter', () => {
    
  1035.       const container = document.createElement('div');
    
  1036.       document.body.appendChild(container);
    
  1037.       const oncustomevent = jest.fn();
    
  1038. 
    
  1039.       // First render with nothing
    
  1040.       ReactDOM.render(<my-custom-element />, container);
    
  1041.       const customelement = container.querySelector('my-custom-element');
    
  1042.       // Install a setter to activate the `in` heuristic
    
  1043.       Object.defineProperty(customelement, 'oncustomevent', {
    
  1044.         set: function (x) {
    
  1045.           this._oncustomevent = x;
    
  1046.         },
    
  1047.         get: function () {
    
  1048.           return this._oncustomevent;
    
  1049.         },
    
  1050.       });
    
  1051.       expect(customelement.oncustomevent).toBe(undefined);
    
  1052. 
    
  1053.       // nothing => event listener
    
  1054.       ReactDOM.render(
    
  1055.         <my-custom-element oncustomevent={oncustomevent} />,
    
  1056.         container,
    
  1057.       );
    
  1058.       customelement.dispatchEvent(new Event('customevent'));
    
  1059.       expect(oncustomevent).toHaveBeenCalledTimes(1);
    
  1060.       expect(customelement.oncustomevent).toBe(null);
    
  1061.       expect(customelement.getAttribute('oncustomevent')).toBe(null);
    
  1062. 
    
  1063.       // event listener => string
    
  1064.       ReactDOM.render(<my-custom-element oncustomevent={'foo'} />, container);
    
  1065.       customelement.dispatchEvent(new Event('customevent'));
    
  1066.       expect(oncustomevent).toHaveBeenCalledTimes(1);
    
  1067.       expect(customelement.oncustomevent).toBe('foo');
    
  1068.       expect(customelement.getAttribute('oncustomevent')).toBe(null);
    
  1069. 
    
  1070.       // string => event listener
    
  1071.       ReactDOM.render(
    
  1072.         <my-custom-element oncustomevent={oncustomevent} />,
    
  1073.         container,
    
  1074.       );
    
  1075.       customelement.dispatchEvent(new Event('customevent'));
    
  1076.       expect(oncustomevent).toHaveBeenCalledTimes(2);
    
  1077.       expect(customelement.oncustomevent).toBe(null);
    
  1078.       expect(customelement.getAttribute('oncustomevent')).toBe(null);
    
  1079. 
    
  1080.       // event listener => nothing
    
  1081.       ReactDOM.render(<my-custom-element />, container);
    
  1082.       customelement.dispatchEvent(new Event('customevent'));
    
  1083.       expect(oncustomevent).toHaveBeenCalledTimes(2);
    
  1084.       expect(customelement.oncustomevent).toBe(null);
    
  1085.       expect(customelement.getAttribute('oncustomevent')).toBe(null);
    
  1086.     });
    
  1087. 
    
  1088.     // @gate enableCustomElementPropertySupport
    
  1089.     it('assigning to a custom element property should not remove attributes', () => {
    
  1090.       const container = document.createElement('div');
    
  1091.       document.body.appendChild(container);
    
  1092.       ReactDOM.render(<my-custom-element foo="one" />, container);
    
  1093.       const customElement = container.querySelector('my-custom-element');
    
  1094.       expect(customElement.getAttribute('foo')).toBe('one');
    
  1095. 
    
  1096.       // Install a setter to activate the `in` heuristic
    
  1097.       Object.defineProperty(customElement, 'foo', {
    
  1098.         set: function (x) {
    
  1099.           this._foo = x;
    
  1100.         },
    
  1101.         get: function () {
    
  1102.           return this._foo;
    
  1103.         },
    
  1104.       });
    
  1105.       ReactDOM.render(<my-custom-element foo="two" />, container);
    
  1106.       expect(customElement.foo).toBe('two');
    
  1107.       expect(customElement.getAttribute('foo')).toBe('one');
    
  1108.     });
    
  1109. 
    
  1110.     // @gate enableCustomElementPropertySupport
    
  1111.     it('custom element properties should accept functions', () => {
    
  1112.       const container = document.createElement('div');
    
  1113.       document.body.appendChild(container);
    
  1114.       ReactDOM.render(<my-custom-element />, container);
    
  1115.       const customElement = container.querySelector('my-custom-element');
    
  1116. 
    
  1117.       // Install a setter to activate the `in` heuristic
    
  1118.       Object.defineProperty(customElement, 'foo', {
    
  1119.         set: function (x) {
    
  1120.           this._foo = x;
    
  1121.         },
    
  1122.         get: function () {
    
  1123.           return this._foo;
    
  1124.         },
    
  1125.       });
    
  1126.       function myFunction() {
    
  1127.         return 'this is myFunction';
    
  1128.       }
    
  1129.       ReactDOM.render(<my-custom-element foo={myFunction} />, container);
    
  1130.       expect(customElement.foo).toBe(myFunction);
    
  1131. 
    
  1132.       // Also remove and re-add the property for good measure
    
  1133.       ReactDOM.render(<my-custom-element />, container);
    
  1134.       expect(customElement.foo).toBe(null);
    
  1135.       ReactDOM.render(<my-custom-element foo={myFunction} />, container);
    
  1136.       expect(customElement.foo).toBe(myFunction);
    
  1137.     });
    
  1138.   });
    
  1139. 
    
  1140.   describe('deleteValueForProperty', () => {
    
  1141.     it('should remove attributes for normal properties', () => {
    
  1142.       const container = document.createElement('div');
    
  1143.       ReactDOM.render(<div title="foo" />, container);
    
  1144.       expect(container.firstChild.getAttribute('title')).toBe('foo');
    
  1145.       ReactDOM.render(<div />, container);
    
  1146.       expect(container.firstChild.getAttribute('title')).toBe(null);
    
  1147.     });
    
  1148. 
    
  1149.     it('should not remove attributes for special properties', () => {
    
  1150.       const container = document.createElement('div');
    
  1151.       ReactDOM.render(
    
  1152.         <input type="text" value="foo" onChange={function () {}} />,
    
  1153.         container,
    
  1154.       );
    
  1155.       if (disableInputAttributeSyncing) {
    
  1156.         expect(container.firstChild.hasAttribute('value')).toBe(false);
    
  1157.       } else {
    
  1158.         expect(container.firstChild.getAttribute('value')).toBe('foo');
    
  1159.       }
    
  1160.       expect(container.firstChild.value).toBe('foo');
    
  1161.       expect(() =>
    
  1162.         ReactDOM.render(
    
  1163.           <input type="text" onChange={function () {}} />,
    
  1164.           container,
    
  1165.         ),
    
  1166.       ).toErrorDev(
    
  1167.         'A component is changing a controlled input to be uncontrolled',
    
  1168.       );
    
  1169.       if (disableInputAttributeSyncing) {
    
  1170.         expect(container.firstChild.hasAttribute('value')).toBe(false);
    
  1171.       } else {
    
  1172.         expect(container.firstChild.getAttribute('value')).toBe('foo');
    
  1173.       }
    
  1174.       expect(container.firstChild.value).toBe('foo');
    
  1175.     });
    
  1176. 
    
  1177.     it('should not remove attributes for custom component tag', () => {
    
  1178.       const container = document.createElement('div');
    
  1179.       ReactDOM.render(<my-icon size="5px" />, container);
    
  1180.       expect(container.firstChild.getAttribute('size')).toBe('5px');
    
  1181.     });
    
  1182.   });
    
  1183. });