Skip to content

Commit

Permalink
fix(render): Actually hydrate with given ui (#988)
Browse files Browse the repository at this point in the history
  • Loading branch information
eps1lon committed Oct 31, 2021
1 parent 68d2a23 commit d8c6b4d
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 12 deletions.
6 changes: 3 additions & 3 deletions jest.config.js
Expand Up @@ -6,10 +6,10 @@ module.exports = Object.assign(jestConfig, {
// full coverage across the build matrix (React 17, 18) but not in a single job
'./src/pure': {
// minimum coverage of jobs using React 17 and 18
branches: 80,
branches: 75,
functions: 78,
lines: 79,
statements: 79,
lines: 76,
statements: 76,
},
},
})
36 changes: 35 additions & 1 deletion src/__tests__/render.js
@@ -1,6 +1,13 @@
import * as React from 'react'
import ReactDOM from 'react-dom'
import {render, screen} from '../'
import ReactDOMServer from 'react-dom/server'
import {fireEvent, render, screen} from '../'

afterEach(() => {
if (console.error.mockRestore !== undefined) {
console.error.mockRestore()
}
})

test('renders div into document', () => {
const ref = React.createRef()
Expand Down Expand Up @@ -134,3 +141,30 @@ test('can be called multiple times on the same container', () => {

expect(container).toBeEmptyDOMElement()
})

test('hydrate will make the UI interactive', () => {
jest.spyOn(console, 'error').mockImplementation(() => {})
function App() {
const [clicked, handleClick] = React.useReducer(n => n + 1, 0)

return (
<button type="button" onClick={handleClick}>
clicked:{clicked}
</button>
)
}
const ui = <App />
const container = document.createElement('div')
document.body.appendChild(container)
container.innerHTML = ReactDOMServer.renderToString(ui)

expect(container).toHaveTextContent('clicked:0')

render(ui, {container, hydrate: true})

expect(console.error).not.toHaveBeenCalled()

fireEvent.click(container.querySelector('button'))

expect(container).toHaveTextContent('clicked:1')
})
27 changes: 19 additions & 8 deletions src/pure.js
Expand Up @@ -60,25 +60,36 @@ const mountedContainers = new Set()
*/
const mountedRootEntries = []

function createConcurrentRoot(container, options) {
function createConcurrentRoot(
container,
{hydrate, ui, wrapper: WrapperComponent},
) {
if (typeof ReactDOM.createRoot !== 'function') {
throw new TypeError(
`Attempted to use concurrent React with \`react-dom@${ReactDOM.version}\`. Be sure to use the \`next\` or \`experimental\` release channel (https://reactjs.org/docs/release-channels.html).'`,
)
}
const root = options.hydrate
? ReactDOM.hydrateRoot(container)
: ReactDOM.createRoot(container)
let root
if (hydrate) {
act(() => {
root = ReactDOM.hydrateRoot(
container,
WrapperComponent ? React.createElement(WrapperComponent, null, ui) : ui,
)
})
} else {
root = ReactDOM.createRoot(container)
}

return {
hydrate(element) {
hydrate() {
/* istanbul ignore if */
if (!options.hydrate) {
if (!hydrate) {
throw new Error(
'Attempted to hydrate a non-hydrateable root. This is a bug in `@testing-library/react`.',
)
}
root.render(element)
// Nothing to do since hydration happens when creating the root object.
},
render(element) {
root.render(element)
Expand Down Expand Up @@ -183,7 +194,7 @@ function render(
// eslint-disable-next-line no-negated-condition -- we want to map the evolution of this over time. The root is created first. Only later is it re-used so we don't want to read the case that happens later first.
if (!mountedContainers.has(container)) {
const createRootImpl = legacyRoot ? createLegacyRoot : createConcurrentRoot
root = createRootImpl(container, {hydrate})
root = createRootImpl(container, {hydrate, ui, wrapper})

mountedRootEntries.push({container, root})
// we'll add it to the mounted containers regardless of whether it's actually
Expand Down

0 comments on commit d8c6b4d

Please sign in to comment.