1. /**
    
  2.  * Copyright (c) Meta Platforms, Inc. and affiliates.
    
  3.  *
    
  4.  * This source code is licensed under the MIT license found in the
    
  5.  * LICENSE file in the root directory of this source tree.
    
  6.  *
    
  7.  * @emails react-core
    
  8.  * @jest-environment ./scripts/jest/ReactDOMServerIntegrationEnvironment
    
  9.  */
    
  10. 
    
  11. 'use strict';
    
  12. 
    
  13. const ReactDOMServerIntegrationUtils = require('./utils/ReactDOMServerIntegrationTestUtils');
    
  14. const ReactFeatureFlags = require('shared/ReactFeatureFlags');
    
  15. 
    
  16. let React;
    
  17. let ReactDOM;
    
  18. let ReactTestUtils;
    
  19. let ReactDOMServer;
    
  20. 
    
  21. function initModules() {
    
  22.   // Reset warning cache.
    
  23.   jest.resetModules();
    
  24.   React = require('react');
    
  25.   ReactDOM = require('react-dom');
    
  26.   ReactDOMServer = require('react-dom/server');
    
  27.   ReactTestUtils = require('react-dom/test-utils');
    
  28. 
    
  29.   // Make them available to the helpers.
    
  30.   return {
    
  31.     ReactDOM,
    
  32.     ReactDOMServer,
    
  33.     ReactTestUtils,
    
  34.   };
    
  35. }
    
  36. 
    
  37. const {resetModules, itRenders, clientCleanRender} =
    
  38.   ReactDOMServerIntegrationUtils(initModules);
    
  39. 
    
  40. describe('ReactDOMServerIntegration', () => {
    
  41.   beforeEach(() => {
    
  42.     resetModules();
    
  43.   });
    
  44. 
    
  45.   describe('property to attribute mapping', function () {
    
  46.     describe('string properties', function () {
    
  47.       itRenders('simple numbers', async render => {
    
  48.         const e = await render(<div width={30} />);
    
  49.         expect(e.getAttribute('width')).toBe('30');
    
  50.       });
    
  51. 
    
  52.       itRenders('simple strings', async render => {
    
  53.         const e = await render(<div width={'30'} />);
    
  54.         expect(e.getAttribute('width')).toBe('30');
    
  55.       });
    
  56. 
    
  57.       itRenders('no string prop with true value', async render => {
    
  58.         const e = await render(<a href={true} />, 1);
    
  59.         expect(e.hasAttribute('href')).toBe(false);
    
  60.       });
    
  61. 
    
  62.       itRenders('no string prop with false value', async render => {
    
  63.         const e = await render(<a href={false} />, 1);
    
  64.         expect(e.hasAttribute('href')).toBe(false);
    
  65.       });
    
  66. 
    
  67.       itRenders('no string prop with null value', async render => {
    
  68.         const e = await render(<div width={null} />);
    
  69.         expect(e.hasAttribute('width')).toBe(false);
    
  70.       });
    
  71. 
    
  72.       itRenders('no string prop with function value', async render => {
    
  73.         const e = await render(<div width={function () {}} />, 1);
    
  74.         expect(e.hasAttribute('width')).toBe(false);
    
  75.       });
    
  76. 
    
  77.       itRenders('no string prop with symbol value', async render => {
    
  78.         const e = await render(<div width={Symbol('foo')} />, 1);
    
  79.         expect(e.hasAttribute('width')).toBe(false);
    
  80.       });
    
  81.     });
    
  82. 
    
  83.     describe('boolean properties', function () {
    
  84.       itRenders('boolean prop with true value', async render => {
    
  85.         const e = await render(<div hidden={true} />);
    
  86.         expect(e.getAttribute('hidden')).toBe('');
    
  87.       });
    
  88. 
    
  89.       itRenders('boolean prop with false value', async render => {
    
  90.         const e = await render(<div hidden={false} />);
    
  91.         expect(e.getAttribute('hidden')).toBe(null);
    
  92.       });
    
  93. 
    
  94.       itRenders('boolean prop with self value', async render => {
    
  95.         const e = await render(<div hidden="hidden" />);
    
  96.         expect(e.getAttribute('hidden')).toBe('');
    
  97.       });
    
  98. 
    
  99.       // this does not seem like correct behavior, since hidden="" in HTML indicates
    
  100.       // that the boolean property is present. however, it is how the current code
    
  101.       // behaves, so the test is included here.
    
  102.       itRenders('boolean prop with "" value', async render => {
    
  103.         const e = await render(<div hidden="" />);
    
  104.         expect(e.getAttribute('hidden')).toBe(null);
    
  105.       });
    
  106. 
    
  107.       // this seems like it might mask programmer error, but it's existing behavior.
    
  108.       itRenders('boolean prop with string value', async render => {
    
  109.         const e = await render(<div hidden="foo" />);
    
  110.         expect(e.getAttribute('hidden')).toBe('');
    
  111.       });
    
  112. 
    
  113.       // this seems like it might mask programmer error, but it's existing behavior.
    
  114.       itRenders('boolean prop with array value', async render => {
    
  115.         const e = await render(<div hidden={['foo', 'bar']} />);
    
  116.         expect(e.getAttribute('hidden')).toBe('');
    
  117.       });
    
  118. 
    
  119.       // this seems like it might mask programmer error, but it's existing behavior.
    
  120.       itRenders('boolean prop with object value', async render => {
    
  121.         const e = await render(<div hidden={{foo: 'bar'}} />);
    
  122.         expect(e.getAttribute('hidden')).toBe('');
    
  123.       });
    
  124. 
    
  125.       // this seems like it might mask programmer error, but it's existing behavior.
    
  126.       itRenders('boolean prop with non-zero number value', async render => {
    
  127.         const e = await render(<div hidden={10} />);
    
  128.         expect(e.getAttribute('hidden')).toBe('');
    
  129.       });
    
  130. 
    
  131.       // this seems like it might mask programmer error, but it's existing behavior.
    
  132.       itRenders('boolean prop with zero value', async render => {
    
  133.         const e = await render(<div hidden={0} />);
    
  134.         expect(e.getAttribute('hidden')).toBe(null);
    
  135.       });
    
  136. 
    
  137.       itRenders('no boolean prop with null value', async render => {
    
  138.         const e = await render(<div hidden={null} />);
    
  139.         expect(e.hasAttribute('hidden')).toBe(false);
    
  140.       });
    
  141. 
    
  142.       itRenders('no boolean prop with function value', async render => {
    
  143.         const e = await render(<div hidden={function () {}} />, 1);
    
  144.         expect(e.hasAttribute('hidden')).toBe(false);
    
  145.       });
    
  146. 
    
  147.       itRenders('no boolean prop with symbol value', async render => {
    
  148.         const e = await render(<div hidden={Symbol('foo')} />, 1);
    
  149.         expect(e.hasAttribute('hidden')).toBe(false);
    
  150.       });
    
  151.     });
    
  152. 
    
  153.     describe('download property (combined boolean/string attribute)', function () {
    
  154.       itRenders('download prop with true value', async render => {
    
  155.         const e = await render(<a download={true} />);
    
  156.         expect(e.getAttribute('download')).toBe('');
    
  157.       });
    
  158. 
    
  159.       itRenders('download prop with false value', async render => {
    
  160.         const e = await render(<a download={false} />);
    
  161.         expect(e.getAttribute('download')).toBe(null);
    
  162.       });
    
  163. 
    
  164.       itRenders('download prop with string value', async render => {
    
  165.         const e = await render(<a download="myfile" />);
    
  166.         expect(e.getAttribute('download')).toBe('myfile');
    
  167.       });
    
  168. 
    
  169.       itRenders('download prop with string "false" value', async render => {
    
  170.         const e = await render(<a download="false" />);
    
  171.         expect(e.getAttribute('download')).toBe('false');
    
  172.       });
    
  173. 
    
  174.       itRenders('download prop with string "true" value', async render => {
    
  175.         const e = await render(<a download={'true'} />);
    
  176.         expect(e.getAttribute('download')).toBe('true');
    
  177.       });
    
  178. 
    
  179.       itRenders('download prop with number 0 value', async render => {
    
  180.         const e = await render(<a download={0} />);
    
  181.         expect(e.getAttribute('download')).toBe('0');
    
  182.       });
    
  183. 
    
  184.       itRenders('no download prop with null value', async render => {
    
  185.         const e = await render(<div download={null} />);
    
  186.         expect(e.hasAttribute('download')).toBe(false);
    
  187.       });
    
  188. 
    
  189.       itRenders('no download prop with undefined value', async render => {
    
  190.         const e = await render(<div download={undefined} />);
    
  191.         expect(e.hasAttribute('download')).toBe(false);
    
  192.       });
    
  193. 
    
  194.       itRenders('no download prop with function value', async render => {
    
  195.         const e = await render(<div download={function () {}} />, 1);
    
  196.         expect(e.hasAttribute('download')).toBe(false);
    
  197.       });
    
  198. 
    
  199.       itRenders('no download prop with symbol value', async render => {
    
  200.         const e = await render(<div download={Symbol('foo')} />, 1);
    
  201.         expect(e.hasAttribute('download')).toBe(false);
    
  202.       });
    
  203.     });
    
  204. 
    
  205.     describe('className property', function () {
    
  206.       itRenders('className prop with string value', async render => {
    
  207.         const e = await render(<div className="myClassName" />);
    
  208.         expect(e.getAttribute('class')).toBe('myClassName');
    
  209.       });
    
  210. 
    
  211.       itRenders('className prop with empty string value', async render => {
    
  212.         const e = await render(<div className="" />);
    
  213.         expect(e.getAttribute('class')).toBe('');
    
  214.       });
    
  215. 
    
  216.       itRenders('no className prop with true value', async render => {
    
  217.         const e = await render(<div className={true} />, 1);
    
  218.         expect(e.hasAttribute('class')).toBe(false);
    
  219.       });
    
  220. 
    
  221.       itRenders('no className prop with false value', async render => {
    
  222.         const e = await render(<div className={false} />, 1);
    
  223.         expect(e.hasAttribute('class')).toBe(false);
    
  224.       });
    
  225. 
    
  226.       itRenders('no className prop with null value', async render => {
    
  227.         const e = await render(<div className={null} />);
    
  228.         expect(e.hasAttribute('className')).toBe(false);
    
  229.       });
    
  230. 
    
  231.       itRenders('badly cased className with a warning', async render => {
    
  232.         const e = await render(<div classname="test" />, 1);
    
  233.         expect(e.hasAttribute('class')).toBe(false);
    
  234.         expect(e.hasAttribute('classname')).toBe(true);
    
  235.       });
    
  236. 
    
  237.       itRenders(
    
  238.         'className prop when given the alias with a warning',
    
  239.         async render => {
    
  240.           const e = await render(<div class="test" />, 1);
    
  241.           expect(e.className).toBe('test');
    
  242.         },
    
  243.       );
    
  244. 
    
  245.       itRenders(
    
  246.         'className prop when given a badly cased alias',
    
  247.         async render => {
    
  248.           const e = await render(<div cLASs="test" />, 1);
    
  249.           expect(e.className).toBe('test');
    
  250.         },
    
  251.       );
    
  252.     });
    
  253. 
    
  254.     describe('htmlFor property', function () {
    
  255.       itRenders('htmlFor with string value', async render => {
    
  256.         const e = await render(<div htmlFor="myFor" />);
    
  257.         expect(e.getAttribute('for')).toBe('myFor');
    
  258.       });
    
  259. 
    
  260.       itRenders('no badly cased htmlfor', async render => {
    
  261.         const e = await render(<div htmlfor="myFor" />, 1);
    
  262.         expect(e.hasAttribute('for')).toBe(false);
    
  263.         expect(e.getAttribute('htmlfor')).toBe('myFor');
    
  264.       });
    
  265. 
    
  266.       itRenders('htmlFor with an empty string', async render => {
    
  267.         const e = await render(<div htmlFor="" />);
    
  268.         expect(e.getAttribute('for')).toBe('');
    
  269.       });
    
  270. 
    
  271.       itRenders('no htmlFor prop with true value', async render => {
    
  272.         const e = await render(<div htmlFor={true} />, 1);
    
  273.         expect(e.hasAttribute('for')).toBe(false);
    
  274.       });
    
  275. 
    
  276.       itRenders('no htmlFor prop with false value', async render => {
    
  277.         const e = await render(<div htmlFor={false} />, 1);
    
  278.         expect(e.hasAttribute('for')).toBe(false);
    
  279.       });
    
  280. 
    
  281.       itRenders('no htmlFor prop with null value', async render => {
    
  282.         const e = await render(<div htmlFor={null} />);
    
  283.         expect(e.hasAttribute('htmlFor')).toBe(false);
    
  284.       });
    
  285.     });
    
  286. 
    
  287.     describe('numeric properties', function () {
    
  288.       itRenders(
    
  289.         'positive numeric property with positive value',
    
  290.         async render => {
    
  291.           const e = await render(<input size={2} />);
    
  292.           expect(e.getAttribute('size')).toBe('2');
    
  293.         },
    
  294.       );
    
  295. 
    
  296.       itRenders('numeric property with zero value', async render => {
    
  297.         const e = await render(<ol start={0} />);
    
  298.         expect(e.getAttribute('start')).toBe('0');
    
  299.       });
    
  300. 
    
  301.       itRenders(
    
  302.         'no positive numeric property with zero value',
    
  303.         async render => {
    
  304.           const e = await render(<input size={0} />);
    
  305.           expect(e.hasAttribute('size')).toBe(false);
    
  306.         },
    
  307.       );
    
  308. 
    
  309.       itRenders('no numeric prop with function value', async render => {
    
  310.         const e = await render(<ol start={function () {}} />, 1);
    
  311.         expect(e.hasAttribute('start')).toBe(false);
    
  312.       });
    
  313. 
    
  314.       itRenders('no numeric prop with symbol value', async render => {
    
  315.         const e = await render(<ol start={Symbol('foo')} />, 1);
    
  316.         expect(e.hasAttribute('start')).toBe(false);
    
  317.       });
    
  318. 
    
  319.       itRenders(
    
  320.         'no positive numeric prop with function value',
    
  321.         async render => {
    
  322.           const e = await render(<input size={function () {}} />, 1);
    
  323.           expect(e.hasAttribute('size')).toBe(false);
    
  324.         },
    
  325.       );
    
  326. 
    
  327.       itRenders('no positive numeric prop with symbol value', async render => {
    
  328.         const e = await render(<input size={Symbol('foo')} />, 1);
    
  329.         expect(e.hasAttribute('size')).toBe(false);
    
  330.       });
    
  331.     });
    
  332. 
    
  333.     describe('props with special meaning in React', function () {
    
  334.       itRenders('no ref attribute', async render => {
    
  335.         class RefComponent extends React.Component {
    
  336.           render() {
    
  337.             return <div ref={React.createRef()} />;
    
  338.           }
    
  339.         }
    
  340.         const e = await render(<RefComponent />);
    
  341.         expect(e.getAttribute('ref')).toBe(null);
    
  342.       });
    
  343. 
    
  344.       itRenders('no children attribute', async render => {
    
  345.         const e = await render(React.createElement('div', {}, 'foo'));
    
  346.         expect(e.getAttribute('children')).toBe(null);
    
  347.       });
    
  348. 
    
  349.       itRenders('no key attribute', async render => {
    
  350.         const e = await render(<div key="foo" />);
    
  351.         expect(e.getAttribute('key')).toBe(null);
    
  352.       });
    
  353. 
    
  354.       itRenders('no dangerouslySetInnerHTML attribute', async render => {
    
  355.         const e = await render(
    
  356.           <div dangerouslySetInnerHTML={{__html: '<foo />'}} />,
    
  357.         );
    
  358.         expect(e.getAttribute('dangerouslySetInnerHTML')).toBe(null);
    
  359.       });
    
  360. 
    
  361.       itRenders('no suppressContentEditableWarning attribute', async render => {
    
  362.         const e = await render(<div suppressContentEditableWarning={true} />);
    
  363.         expect(e.getAttribute('suppressContentEditableWarning')).toBe(null);
    
  364.       });
    
  365. 
    
  366.       itRenders('no suppressHydrationWarning attribute', async render => {
    
  367.         const e = await render(<span suppressHydrationWarning={true} />);
    
  368.         expect(e.getAttribute('suppressHydrationWarning')).toBe(null);
    
  369.       });
    
  370.     });
    
  371. 
    
  372.     describe('inline styles', function () {
    
  373.       itRenders('simple styles', async render => {
    
  374.         const e = await render(<div style={{color: 'red', width: '30px'}} />);
    
  375.         expect(e.style.color).toBe('red');
    
  376.         expect(e.style.width).toBe('30px');
    
  377.       });
    
  378. 
    
  379.       itRenders('relevant styles with px', async render => {
    
  380.         const e = await render(
    
  381.           <div
    
  382.             style={{
    
  383.               left: 0,
    
  384.               margin: 16,
    
  385.               opacity: 0.5,
    
  386.               padding: '4px',
    
  387.             }}
    
  388.           />,
    
  389.         );
    
  390.         expect(e.style.left).toBe('0px');
    
  391.         expect(e.style.margin).toBe('16px');
    
  392.         expect(e.style.opacity).toBe('0.5');
    
  393.         expect(e.style.padding).toBe('4px');
    
  394.       });
    
  395. 
    
  396.       itRenders('custom properties', async render => {
    
  397.         const e = await render(<div style={{'--foo': 5}} />);
    
  398.         expect(e.style.getPropertyValue('--foo')).toBe('5');
    
  399.       });
    
  400. 
    
  401.       itRenders('camel cased custom properties', async render => {
    
  402.         const e = await render(<div style={{'--someColor': '#000000'}} />);
    
  403.         expect(e.style.getPropertyValue('--someColor')).toBe('#000000');
    
  404.       });
    
  405. 
    
  406.       itRenders('no undefined styles', async render => {
    
  407.         const e = await render(
    
  408.           <div style={{color: undefined, width: '30px'}} />,
    
  409.         );
    
  410.         expect(e.style.color).toBe('');
    
  411.         expect(e.style.width).toBe('30px');
    
  412.       });
    
  413. 
    
  414.       itRenders('no null styles', async render => {
    
  415.         const e = await render(<div style={{color: null, width: '30px'}} />);
    
  416.         expect(e.style.color).toBe('');
    
  417.         expect(e.style.width).toBe('30px');
    
  418.       });
    
  419. 
    
  420.       itRenders('no empty styles', async render => {
    
  421.         const e = await render(<div style={{color: null, width: null}} />);
    
  422.         expect(e.style.color).toBe('');
    
  423.         expect(e.style.width).toBe('');
    
  424.         expect(e.hasAttribute('style')).toBe(false);
    
  425.       });
    
  426. 
    
  427.       itRenders('unitless-number rules with prefixes', async render => {
    
  428.         const {style} = await render(
    
  429.           <div
    
  430.             style={{
    
  431.               lineClamp: 10,
    
  432.               // TODO: requires https://github.com/jsdom/cssstyle/pull/112
    
  433.               // WebkitLineClamp: 10,
    
  434.               // TODO: revisit once cssstyle or jsdom figures out
    
  435.               // if they want to support other vendors or not
    
  436.               // MozFlexGrow: 10,
    
  437.               // msFlexGrow: 10,
    
  438.               // msGridRow: 10,
    
  439.               // msGridRowEnd: 10,
    
  440.               // msGridRowSpan: 10,
    
  441.               // msGridRowStart: 10,
    
  442.               // msGridColumn: 10,
    
  443.               // msGridColumnEnd: 10,
    
  444.               // msGridColumnSpan: 10,
    
  445.               // msGridColumnStart: 10,
    
  446.             }}
    
  447.           />,
    
  448.         );
    
  449. 
    
  450.         expect(style.lineClamp).toBe('10');
    
  451.         // see comment at inline styles above
    
  452.         // expect(style.WebkitLineClamp).toBe('10');
    
  453.         // expect(style.MozFlexGrow).toBe('10');
    
  454.         // jsdom is inconsistent in the style property name
    
  455.         // it uses on the client and when processing server markup.
    
  456.         // But it should be there either way.
    
  457.         //expect(style.MsFlexGrow || style.msFlexGrow).toBe('10');
    
  458.         // expect(style.MsGridRow || style.msGridRow).toBe('10');
    
  459.         // expect(style.MsGridRowEnd || style.msGridRowEnd).toBe('10');
    
  460.         // expect(style.MsGridRowSpan || style.msGridRowSpan).toBe('10');
    
  461.         // expect(style.MsGridRowStart || style.msGridRowStart).toBe('10');
    
  462.         // expect(style.MsGridColumn || style.msGridColumn).toBe('10');
    
  463.         // expect(style.MsGridColumnEnd || style.msGridColumnEnd).toBe('10');
    
  464.         // expect(style.MsGridColumnSpan || style.msGridColumnSpan).toBe('10');
    
  465.         // expect(style.MsGridColumnStart || style.msGridColumnStart).toBe('10');
    
  466.       });
    
  467.     });
    
  468. 
    
  469.     describe('aria attributes', function () {
    
  470.       itRenders('simple strings', async render => {
    
  471.         const e = await render(<div aria-label="hello" />);
    
  472.         expect(e.getAttribute('aria-label')).toBe('hello');
    
  473.       });
    
  474. 
    
  475.       // this probably is just masking programmer error, but it is existing behavior.
    
  476.       itRenders('aria string prop with false value', async render => {
    
  477.         const e = await render(<div aria-label={false} />);
    
  478.         expect(e.getAttribute('aria-label')).toBe('false');
    
  479.       });
    
  480. 
    
  481.       itRenders('no aria prop with null value', async render => {
    
  482.         const e = await render(<div aria-label={null} />);
    
  483.         expect(e.hasAttribute('aria-label')).toBe(false);
    
  484.       });
    
  485. 
    
  486.       itRenders('"aria" attribute with a warning', async render => {
    
  487.         // Reserved for future use.
    
  488.         const e = await render(<div aria="hello" />, 1);
    
  489.         expect(e.getAttribute('aria')).toBe('hello');
    
  490.       });
    
  491.     });
    
  492. 
    
  493.     describe('cased attributes', function () {
    
  494.       itRenders(
    
  495.         'badly cased aliased HTML attribute with a warning',
    
  496.         async render => {
    
  497.           const e = await render(<form acceptcharset="utf-8" />, 1);
    
  498.           expect(e.hasAttribute('accept-charset')).toBe(false);
    
  499.           expect(e.getAttribute('acceptcharset')).toBe('utf-8');
    
  500.         },
    
  501.       );
    
  502. 
    
  503.       itRenders('badly cased SVG attribute with a warning', async render => {
    
  504.         const e = await render(
    
  505.           <svg>
    
  506.             <text textlength="10" />
    
  507.           </svg>,
    
  508.           1,
    
  509.         );
    
  510.         // The discrepancy is expected as long as we emit a warning
    
  511.         // both on the client and the server.
    
  512.         if (render === clientCleanRender) {
    
  513.           // On the client, "textlength" is treated as a case-sensitive
    
  514.           // SVG attribute so the wrong attribute ("textlength") gets set.
    
  515.           expect(e.firstChild.getAttribute('textlength')).toBe('10');
    
  516.           expect(e.firstChild.hasAttribute('textLength')).toBe(false);
    
  517.         } else {
    
  518.           // When parsing HTML (including the hydration case), the browser
    
  519.           // correctly maps "textlength" to "textLength" SVG attribute.
    
  520.           // So it happens to work on the initial render.
    
  521.           expect(e.firstChild.getAttribute('textLength')).toBe('10');
    
  522.           expect(e.firstChild.hasAttribute('textlength')).toBe(false);
    
  523.         }
    
  524.       });
    
  525. 
    
  526.       itRenders('no badly cased aliased SVG attribute alias', async render => {
    
  527.         const e = await render(
    
  528.           <svg>
    
  529.             <text strokedasharray="10 10" />
    
  530.           </svg>,
    
  531.           1,
    
  532.         );
    
  533.         expect(e.firstChild.hasAttribute('stroke-dasharray')).toBe(false);
    
  534.         expect(e.firstChild.getAttribute('strokedasharray')).toBe('10 10');
    
  535.       });
    
  536. 
    
  537.       itRenders(
    
  538.         'no badly cased original SVG attribute that is aliased',
    
  539.         async render => {
    
  540.           const e = await render(
    
  541.             <svg>
    
  542.               <text stroke-dasharray="10 10" />
    
  543.             </svg>,
    
  544.             1,
    
  545.           );
    
  546.           expect(e.firstChild.getAttribute('stroke-dasharray')).toBe('10 10');
    
  547.         },
    
  548.       );
    
  549.     });
    
  550. 
    
  551.     describe('unknown attributes', function () {
    
  552.       itRenders('unknown attributes', async render => {
    
  553.         const e = await render(<div foo="bar" />);
    
  554.         expect(e.getAttribute('foo')).toBe('bar');
    
  555.       });
    
  556. 
    
  557.       itRenders('unknown data- attributes', async render => {
    
  558.         const e = await render(<div data-foo="bar" />);
    
  559.         expect(e.getAttribute('data-foo')).toBe('bar');
    
  560.       });
    
  561. 
    
  562.       itRenders('badly cased reserved attributes', async render => {
    
  563.         const e = await render(<div CHILDREN="5" />, 1);
    
  564.         expect(e.getAttribute('CHILDREN')).toBe('5');
    
  565.       });
    
  566. 
    
  567.       itRenders('"data" attribute', async render => {
    
  568.         // For `<object />` acts as `src`.
    
  569.         const e = await render(<object data="hello" />);
    
  570.         expect(e.getAttribute('data')).toBe('hello');
    
  571.       });
    
  572. 
    
  573.       itRenders('no unknown data- attributes with null value', async render => {
    
  574.         const e = await render(<div data-foo={null} />);
    
  575.         expect(e.hasAttribute('data-foo')).toBe(false);
    
  576.       });
    
  577. 
    
  578.       itRenders('unknown data- attributes with casing', async render => {
    
  579.         const e = await render(<div data-fooBar="true" />, 1);
    
  580.         expect(e.getAttribute('data-foobar')).toBe('true');
    
  581.       });
    
  582. 
    
  583.       itRenders('unknown data- attributes with boolean true', async render => {
    
  584.         const e = await render(<div data-foobar={true} />);
    
  585.         expect(e.getAttribute('data-foobar')).toBe('true');
    
  586.       });
    
  587. 
    
  588.       itRenders('unknown data- attributes with boolean false', async render => {
    
  589.         const e = await render(<div data-foobar={false} />);
    
  590.         expect(e.getAttribute('data-foobar')).toBe('false');
    
  591.       });
    
  592. 
    
  593.       itRenders(
    
  594.         'no unknown data- attributes with casing and null value',
    
  595.         async render => {
    
  596.           const e = await render(<div data-fooBar={null} />, 1);
    
  597.           expect(e.hasAttribute('data-foobar')).toBe(false);
    
  598.         },
    
  599.       );
    
  600. 
    
  601.       itRenders('custom attributes for non-standard elements', async render => {
    
  602.         // This test suite generally assumes that we get exactly
    
  603.         // the same warnings (or none) for all scenarios including
    
  604.         // SSR + innerHTML, hydration, and client-side rendering.
    
  605.         // However this particular warning fires only when creating
    
  606.         // DOM nodes on the client side. We force it to fire early
    
  607.         // so that it gets deduplicated later, and doesn't fail the test.
    
  608.         expect(() => {
    
  609.           ReactDOM.render(<nonstandard />, document.createElement('div'));
    
  610.         }).toErrorDev('The tag <nonstandard> is unrecognized in this browser.');
    
  611. 
    
  612.         const e = await render(<nonstandard foo="bar" />);
    
  613.         expect(e.getAttribute('foo')).toBe('bar');
    
  614.       });
    
  615. 
    
  616.       itRenders('SVG tags with dashes in them', async render => {
    
  617.         const e = await render(
    
  618.           <svg>
    
  619.             <font-face accentHeight={10} />
    
  620.           </svg>,
    
  621.         );
    
  622.         expect(e.firstChild.hasAttribute('accentHeight')).toBe(false);
    
  623.         expect(e.firstChild.getAttribute('accent-height')).toBe('10');
    
  624.       });
    
  625. 
    
  626.       itRenders('cased custom attributes', async render => {
    
  627.         const e = await render(<div fooBar="test" />, 1);
    
  628.         expect(e.getAttribute('foobar')).toBe('test');
    
  629.       });
    
  630.     });
    
  631. 
    
  632.     itRenders('no HTML events', async render => {
    
  633.       const e = await render(<div onClick={() => {}} />);
    
  634.       expect(e.getAttribute('onClick')).toBe(null);
    
  635.       expect(e.getAttribute('onClick')).toBe(null);
    
  636.       expect(e.getAttribute('click')).toBe(null);
    
  637.     });
    
  638. 
    
  639.     itRenders('no unknown events', async render => {
    
  640.       const e = await render(<div onunknownevent='alert("hack")' />, 1);
    
  641.       expect(e.getAttribute('onunknownevent')).toBe(null);
    
  642.     });
    
  643. 
    
  644.     itRenders('custom attribute named `on`', async render => {
    
  645.       const e = await render(<div on="tap:do-something" />);
    
  646.       expect(e.getAttribute('on')).toEqual('tap:do-something');
    
  647.     });
    
  648.   });
    
  649. 
    
  650.   // These tests mostly verify the existing behavior.
    
  651.   // It may not always make sense but we can't change it in minors.
    
  652.   describe('custom elements', () => {
    
  653.     itRenders('class for custom elements', async render => {
    
  654.       const e = await render(<div is="custom-element" class="test" />, 0);
    
  655.       expect(e.getAttribute('class')).toBe('test');
    
  656.     });
    
  657. 
    
  658.     itRenders('className for is elements', async render => {
    
  659.       const e = await render(<div is="custom-element" className="test" />, 0);
    
  660.       expect(e.getAttribute('className')).toBe(null);
    
  661.       expect(e.getAttribute('class')).toBe('test');
    
  662.     });
    
  663. 
    
  664.     itRenders('className for custom elements', async render => {
    
  665.       const e = await render(<custom-element className="test" />, 0);
    
  666.       if (ReactFeatureFlags.enableCustomElementPropertySupport) {
    
  667.         expect(e.getAttribute('className')).toBe(null);
    
  668.         expect(e.getAttribute('class')).toBe('test');
    
  669.       } else {
    
  670.         expect(e.getAttribute('className')).toBe('test');
    
  671.         expect(e.getAttribute('class')).toBe(null);
    
  672.       }
    
  673.     });
    
  674. 
    
  675.     itRenders('htmlFor property on is elements', async render => {
    
  676.       const e = await render(<div is="custom-element" htmlFor="test" />);
    
  677.       expect(e.getAttribute('htmlFor')).toBe(null);
    
  678.       expect(e.getAttribute('for')).toBe('test');
    
  679.     });
    
  680. 
    
  681.     itRenders('htmlFor attribute on custom elements', async render => {
    
  682.       const e = await render(<custom-element htmlFor="test" />);
    
  683.       expect(e.getAttribute('htmlFor')).toBe('test');
    
  684.       expect(e.getAttribute('for')).toBe(null);
    
  685.     });
    
  686. 
    
  687.     itRenders('for attribute on custom elements', async render => {
    
  688.       const e = await render(<div is="custom-element" for="test" />);
    
  689.       expect(e.getAttribute('htmlFor')).toBe(null);
    
  690.       expect(e.getAttribute('for')).toBe('test');
    
  691.     });
    
  692. 
    
  693.     itRenders('unknown attributes for custom elements', async render => {
    
  694.       const e = await render(<custom-element foo="bar" />);
    
  695.       expect(e.getAttribute('foo')).toBe('bar');
    
  696.     });
    
  697. 
    
  698.     itRenders('unknown `on*` attributes for custom elements', async render => {
    
  699.       const e = await render(<custom-element onunknown="bar" />);
    
  700.       expect(e.getAttribute('onunknown')).toBe('bar');
    
  701.     });
    
  702. 
    
  703.     itRenders('unknown boolean `true` attributes as strings', async render => {
    
  704.       const e = await render(<custom-element foo={true} />);
    
  705.       if (ReactFeatureFlags.enableCustomElementPropertySupport) {
    
  706.         expect(e.getAttribute('foo')).toBe('');
    
  707.       } else {
    
  708.         expect(e.getAttribute('foo')).toBe('true');
    
  709.       }
    
  710.     });
    
  711. 
    
  712.     itRenders('unknown boolean `false` attributes as strings', async render => {
    
  713.       const e = await render(<custom-element foo={false} />);
    
  714.       if (ReactFeatureFlags.enableCustomElementPropertySupport) {
    
  715.         expect(e.getAttribute('foo')).toBe(null);
    
  716.       } else {
    
  717.         expect(e.getAttribute('foo')).toBe('false');
    
  718.       }
    
  719.     });
    
  720. 
    
  721.     itRenders(
    
  722.       'no unknown attributes for custom elements with null value',
    
  723.       async render => {
    
  724.         const e = await render(<custom-element foo={null} />);
    
  725.         expect(e.hasAttribute('foo')).toBe(false);
    
  726.       },
    
  727.     );
    
  728. 
    
  729.     itRenders(
    
  730.       'unknown attributes for custom elements using is',
    
  731.       async render => {
    
  732.         const e = await render(<div is="custom-element" foo="bar" />);
    
  733.         expect(e.getAttribute('foo')).toBe('bar');
    
  734.       },
    
  735.     );
    
  736. 
    
  737.     itRenders(
    
  738.       'no unknown attributes for custom elements using is with null value',
    
  739.       async render => {
    
  740.         const e = await render(<div is="custom-element" foo={null} />);
    
  741.         expect(e.hasAttribute('foo')).toBe(false);
    
  742.       },
    
  743.     );
    
  744.   });
    
  745. });