/**
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @emails react-core
 */

'use strict';

const ReactDOMServerIntegrationUtils = require('./utils/ReactDOMServerIntegrationTestUtils');

let React;
let ReactDOM;
let ReactDOMServer;
let ReactTestUtils;

function initModules() {
  // Reset warning cache.
  jest.resetModules();

  React = require('react');
  ReactDOM = require('react-dom');
  ReactDOMServer = require('react-dom/server');
  ReactTestUtils = require('react-dom/test-utils');

  // Make them available to the helpers.
  return {
    ReactDOM,
    ReactDOMServer,
    ReactTestUtils,
  };
}

const {resetModules, expectMarkupMismatch, expectMarkupMatch} =
  ReactDOMServerIntegrationUtils(initModules);

describe('ReactDOMServerIntegration', () => {
  beforeEach(() => {
    resetModules();
  });

  describe('reconnecting to server markup', function () {
    let EmptyComponent;
    beforeEach(() => {
      EmptyComponent = class extends React.Component {
        render() {
          return null;
        }
      };
    });

    describe('elements', function () {
      describe('reconnecting different component implementations', function () {
        let ES6ClassComponent, PureComponent, bareElement;
        beforeEach(() => {
          // try each type of component on client and server.
          ES6ClassComponent = class extends React.Component {
            render() {
              return <div id={this.props.id} />;
            }
          };
          PureComponent = props => <div id={props.id} />;
          bareElement = <div id="foobarbaz" />;
        });

        it('should reconnect ES6 Class to ES6 Class', () =>
          expectMarkupMatch(
            <ES6ClassComponent id="foobarbaz" />,
            <ES6ClassComponent id="foobarbaz" />,
          ));

        it('should reconnect Pure Component to ES6 Class', () =>
          expectMarkupMatch(
            <ES6ClassComponent id="foobarbaz" />,
            <PureComponent id="foobarbaz" />,
          ));

        it('should reconnect Bare Element to ES6 Class', () =>
          expectMarkupMatch(<ES6ClassComponent id="foobarbaz" />, bareElement));

        it('should reconnect ES6 Class to Pure Component', () =>
          expectMarkupMatch(
            <PureComponent id="foobarbaz" />,
            <ES6ClassComponent id="foobarbaz" />,
          ));

        it('should reconnect Pure Component to Pure Component', () =>
          expectMarkupMatch(
            <PureComponent id="foobarbaz" />,
            <PureComponent id="foobarbaz" />,
          ));

        it('should reconnect Bare Element to Pure Component', () =>
          expectMarkupMatch(<PureComponent id="foobarbaz" />, bareElement));

        it('should reconnect ES6 Class to Bare Element', () =>
          expectMarkupMatch(bareElement, <ES6ClassComponent id="foobarbaz" />));

        it('should reconnect Pure Component to Bare Element', () =>
          expectMarkupMatch(bareElement, <PureComponent id="foobarbaz" />));

        it('should reconnect Bare Element to Bare Element', () =>
          expectMarkupMatch(bareElement, bareElement));
      });

      it('should error reconnecting different element types', () =>
        expectMarkupMismatch(<div />, <span />));

      it('should error reconnecting fewer root children', () =>
        expectMarkupMismatch(<span key="a" />, [
          <span key="a" />,
          <span key="b" />,
        ]));

      it('should error reconnecting missing attributes', () =>
        expectMarkupMismatch(<div id="foo" />, <div />));

      it('should error reconnecting added attributes', () =>
        expectMarkupMismatch(<div />, <div id="foo" />));

      it('should error reconnecting different attribute values', () =>
        expectMarkupMismatch(<div id="foo" />, <div id="bar" />));

      it('can explicitly ignore errors reconnecting different element types of children', () =>
        expectMarkupMatch(
          <div>
            <div />
          </div>,
          <div suppressHydrationWarning={true}>
            <span />
          </div>,
        ));

      it('can explicitly ignore errors reconnecting missing attributes', () =>
        expectMarkupMatch(
          <div id="foo" />,
          <div suppressHydrationWarning={true} />,
        ));

      it('can explicitly ignore errors reconnecting added attributes', () =>
        expectMarkupMatch(
          <div />,
          <div id="foo" suppressHydrationWarning={true} />,
        ));

      it('can explicitly ignore errors reconnecting different attribute values', () =>
        expectMarkupMatch(
          <div id="foo" />,
          <div id="bar" suppressHydrationWarning={true} />,
        ));

      it('can not deeply ignore errors reconnecting different attribute values', () =>
        expectMarkupMismatch(
          <div>
            <div id="foo" />
          </div>,
          <div suppressHydrationWarning={true}>
            <div id="bar" />
          </div>,
        ));
    });

    describe('inline styles', function () {
      it('should error reconnecting missing style attribute', () =>
        expectMarkupMismatch(<div style={{width: '1px'}} />, <div />));

      it('should error reconnecting added style attribute', () =>
        expectMarkupMismatch(<div />, <div style={{width: '1px'}} />));

      it('should error reconnecting empty style attribute', () =>
        expectMarkupMismatch(
          <div style={{width: '1px'}} />,
          <div style={{}} />,
        ));

      it('should error reconnecting added style values', () =>
        expectMarkupMismatch(
          <div style={{}} />,
          <div style={{width: '1px'}} />,
        ));

      it('should error reconnecting different style values', () =>
        expectMarkupMismatch(
          <div style={{width: '1px'}} />,
          <div style={{width: '2px'}} />,
        ));

      it('should reconnect number and string versions of a number', () =>
        expectMarkupMatch(
          <div style={{width: '1px', height: 2}} />,
          <div style={{width: 1, height: '2px'}} />,
        ));

      it('should error reconnecting reordered style values', () =>
        expectMarkupMismatch(
          <div style={{width: '1px', fontSize: '2px'}} />,
          <div style={{fontSize: '2px', width: '1px'}} />,
        ));

      it('can explicitly ignore errors reconnecting added style values', () =>
        expectMarkupMatch(
          <div style={{}} />,
          <div style={{width: '1px'}} suppressHydrationWarning={true} />,
        ));

      it('can explicitly ignore reconnecting different style values', () =>
        expectMarkupMatch(
          <div style={{width: '1px'}} />,
          <div style={{width: '2px'}} suppressHydrationWarning={true} />,
        ));
    });

    describe('text nodes', function () {
      it('should error reconnecting different text', () =>
        expectMarkupMismatch(<div>Text</div>, <div>Other Text</div>));

      it('should reconnect a div with a number and string version of number', () =>
        expectMarkupMatch(<div>{2}</div>, <div>2</div>));

      it('should error reconnecting different numbers', () =>
        expectMarkupMismatch(<div>{2}</div>, <div>{3}</div>));

      it('should error reconnecting different number from text', () =>
        expectMarkupMismatch(<div>{2}</div>, <div>3</div>));

      it('should error reconnecting different text in two code blocks', () =>
        expectMarkupMismatch(
          <div>
            {'Text1'}
            {'Text2'}
          </div>,
          <div>
            {'Text1'}
            {'Text3'}
          </div>,
        ));

      it('can explicitly ignore reconnecting different text', () =>
        expectMarkupMatch(
          <div>Text</div>,
          <div suppressHydrationWarning={true}>Other Text</div>,
        ));

      it('can explicitly ignore reconnecting different text in two code blocks', () =>
        expectMarkupMatch(
          <div suppressHydrationWarning={true}>
            {'Text1'}
            {'Text2'}
          </div>,
          <div suppressHydrationWarning={true}>
            {'Text1'}
            {'Text3'}
          </div>,
        ));
    });

    describe('element trees and children', function () {
      it('should error reconnecting missing children', () =>
        expectMarkupMismatch(
          <div>
            <div />
          </div>,
          <div />,
        ));

      it('should error reconnecting added children', () =>
        expectMarkupMismatch(
          <div />,
          <div>
            <div />
          </div>,
        ));

      it('should error reconnecting more children', () =>
        expectMarkupMismatch(
          <div>
            <div />
          </div>,
          <div>
            <div />
            <div />
          </div>,
        ));

      it('should error reconnecting fewer children', () =>
        expectMarkupMismatch(
          <div>
            <div />
            <div />
          </div>,
          <div>
            <div />
          </div>,
        ));

      it('should error reconnecting reordered children', () =>
        expectMarkupMismatch(
          <div>
            <div />
            <span />
          </div>,
          <div>
            <span />
            <div />
          </div>,
        ));

      it('should error reconnecting a div with children separated by whitespace on the client', () =>
        expectMarkupMismatch(
          <div id="parent">
            <div id="child1" />
            <div id="child2" />
          </div>,
          // prettier-ignore
          <div id="parent"><div id="child1" />      <div id="child2" /></div>, // eslint-disable-line no-multi-spaces
        ));

      it('should error reconnecting a div with children separated by different whitespace on the server', () =>
        expectMarkupMismatch(
          // prettier-ignore
          <div id="parent"><div id="child1" />      <div id="child2" /></div>, // eslint-disable-line no-multi-spaces
          <div id="parent">
            <div id="child1" />
            <div id="child2" />
          </div>,
        ));

      it('should error reconnecting a div with children separated by different whitespace', () =>
        expectMarkupMismatch(
          <div id="parent">
            <div id="child1" /> <div id="child2" />
          </div>,
          // prettier-ignore
          <div id="parent"><div id="child1" />      <div id="child2" /></div>, // eslint-disable-line no-multi-spaces
        ));

      it('can distinguish an empty component from a dom node', () =>
        expectMarkupMismatch(
          <div>
            <span />
          </div>,
          <div>
            <EmptyComponent />
          </div>,
        ));

      it('can distinguish an empty component from an empty text component', () =>
        expectMarkupMatch(
          <div>
            <EmptyComponent />
          </div>,
          <div>{''}</div>,
        ));

      it('can explicitly ignore reconnecting more children', () =>
        expectMarkupMatch(
          <div>
            <div />
          </div>,
          <div suppressHydrationWarning={true}>
            <div />
            <div />
          </div>,
        ));

      it('can explicitly ignore reconnecting fewer children', () =>
        expectMarkupMatch(
          <div>
            <div />
            <div />
          </div>,
          <div suppressHydrationWarning={true}>
            <div />
          </div>,
        ));

      it('can explicitly ignore reconnecting reordered children', () =>
        expectMarkupMatch(
          <div suppressHydrationWarning={true}>
            <div />
            <span />
          </div>,
          <div suppressHydrationWarning={true}>
            <span />
            <div />
          </div>,
        ));

      it('can not deeply ignore reconnecting reordered children', () =>
        expectMarkupMismatch(
          <div suppressHydrationWarning={true}>
            <div>
              <div />
              <span />
            </div>
          </div>,
          <div suppressHydrationWarning={true}>
            <div>
              <span />
              <div />
            </div>
          </div>,
        ));
    });

    // Markup Mismatches: misc
    it('should error reconnecting a div with different dangerouslySetInnerHTML', () =>
      expectMarkupMismatch(
        <div dangerouslySetInnerHTML={{__html: "<span id='child1'/>"}} />,
        <div dangerouslySetInnerHTML={{__html: "<span id='child2'/>"}} />,
      ));

    it('should error reconnecting a div with different text dangerouslySetInnerHTML', () =>
      expectMarkupMismatch(
        <div dangerouslySetInnerHTML={{__html: 'foo'}} />,
        <div dangerouslySetInnerHTML={{__html: 'bar'}} />,
      ));

    it('should error reconnecting a div with different number dangerouslySetInnerHTML', () =>
      expectMarkupMismatch(
        <div dangerouslySetInnerHTML={{__html: 10}} />,
        <div dangerouslySetInnerHTML={{__html: 20}} />,
      ));

    it('should error reconnecting a div with different object dangerouslySetInnerHTML', () =>
      expectMarkupMismatch(
        <div
          dangerouslySetInnerHTML={{
            __html: {
              toString() {
                return 'hi';
              },
            },
          }}
        />,
        <div
          dangerouslySetInnerHTML={{
            __html: {
              toString() {
                return 'bye';
              },
            },
          }}
        />,
      ));

    it('can explicitly ignore reconnecting a div with different dangerouslySetInnerHTML', () =>
      expectMarkupMatch(
        <div dangerouslySetInnerHTML={{__html: "<span id='child1'/>"}} />,
        <div
          dangerouslySetInnerHTML={{__html: "<span id='child2'/>"}}
          suppressHydrationWarning={true}
        />,
      ));
  });
});
