Skip to content
Prev Previous commit
Next Next commit
Allow <html> to be used in integration tests
Full document renders requires server rendering so the client path
just uses the hydration path in this case to simplify writing these tests.
  • Loading branch information
sebmarkbage committed Mar 11, 2019
commit 38a27cb98541dd5c9fea8a985d03ace4cf9cfb57
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,28 @@ module.exports = function(initModules) {
({ReactDOM, ReactDOMServer} = initModules());
}

function shouldUseDocument(reactElement) {
// Used for whole document tests.
return reactElement && reactElement.type === 'html';
}

function getContainerFromMarkup(reactElement, markup) {
if (shouldUseDocument(reactElement)) {
const doc = document.implementation.createHTMLDocument('');
doc.open();
doc.write(
markup ||
'<!doctype html><html><meta charset=utf-8><title>test doc</title>',
);
doc.close();
return doc;
} else {
const container = document.createElement('div');
container.innerHTML = markup;
return container;
}
}

// Helper functions for rendering tests
// ====================================

Expand Down Expand Up @@ -97,9 +119,7 @@ module.exports = function(initModules) {
// Does not render on client or perform client-side revival.
async function serverRender(reactElement, errorCount = 0) {
const markup = await renderIntoString(reactElement, errorCount);
const domElement = document.createElement('div');
domElement.innerHTML = markup;
return domElement.firstChild;
return getContainerFromMarkup(reactElement, markup).firstChild;
}

// this just drains a readable piped into it to a string, which can be accessed
Expand Down Expand Up @@ -133,27 +153,28 @@ module.exports = function(initModules) {
// Does not render on client or perform client-side revival.
async function streamRender(reactElement, errorCount = 0) {
const markup = await renderIntoStream(reactElement, errorCount);
const domElement = document.createElement('div');
domElement.innerHTML = markup;
return domElement.firstChild;
return getContainerFromMarkup(reactElement, markup).firstChild;
}

const clientCleanRender = (element, errorCount = 0) => {
const div = document.createElement('div');
return renderIntoDom(element, div, false, errorCount);
if (shouldUseDocument(element)) {
// Documents can't be rendered from scratch.
return clientRenderOnServerString(element, errorCount);
}
const container = document.createElement('div');
return renderIntoDom(element, container, false, errorCount);
};

const clientRenderOnServerString = async (element, errorCount = 0) => {
const markup = await renderIntoString(element, errorCount);
resetModules();

const domElement = document.createElement('div');
domElement.innerHTML = markup;
let serverNode = domElement.firstChild;
let container = getContainerFromMarkup(element, markup);
let serverNode = container.firstChild;

const firstClientNode = await renderIntoDom(
element,
domElement,
container,
true,
errorCount,
);
Expand All @@ -178,19 +199,35 @@ module.exports = function(initModules) {

const clientRenderOnBadMarkup = async (element, errorCount = 0) => {
// First we render the top of bad mark up.
const domElement = document.createElement('div');
domElement.innerHTML =
'<div id="badIdWhichWillCauseMismatch" data-reactroot="" data-reactid="1"></div>';
await renderIntoDom(element, domElement, true, errorCount + 1);

let container = getContainerFromMarkup(
element,
shouldUseDocument(element)
? '<html><body><div id="badIdWhichWillCauseMismatch" /></body></html>'
: '<div id="badIdWhichWillCauseMismatch" data-reactroot="" data-reactid="1"></div>',
);

await renderIntoDom(element, container, true, errorCount + 1);

// This gives us the resulting text content.
const hydratedTextContent = domElement.textContent;
const hydratedTextContent =
container.lastChild && container.lastChild.textContent;

// Next we render the element into a clean DOM node client side.
const cleanDomElement = document.createElement('div');
await asyncReactDOMRender(element, cleanDomElement, true);
let cleanContainer;
if (shouldUseDocument(element)) {
// We can't render into a document during a clean render,
// so instead, we'll render the children into the document element.
cleanContainer = getContainerFromMarkup(element, '<html></html>')
.documentElement;
element = element.props.children;
} else {
cleanContainer = document.createElement('div');
}
await asyncReactDOMRender(element, cleanContainer, true);
// This gives us the expected text content.
const cleanTextContent = cleanDomElement.textContent;
const cleanTextContent =
cleanContainer.lastChild && cleanContainer.lastChild.textContent;

// The only guarantee is that text content has been patched up if needed.
expect(hydratedTextContent).toBe(cleanTextContent);
Expand Down