/**
* 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}
/>,
));
});
});