Skip to content

Commit

Permalink
test: add tests for checking strict mode works and can be combine wit…
Browse files Browse the repository at this point in the history
…h wrapper
  • Loading branch information
yinm committed Oct 8, 2023
1 parent f8cb04b commit a543597
Show file tree
Hide file tree
Showing 3 changed files with 283 additions and 164 deletions.
2 changes: 1 addition & 1 deletion src/__tests__/__snapshots__/render.js.snap
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`supports fragments 1`] = `
exports[`render API supports fragments 1`] = `
<DocumentFragment>
<div>
<code>
Expand Down
332 changes: 192 additions & 140 deletions src/__tests__/render.js
Original file line number Diff line number Diff line change
@@ -1,84 +1,100 @@
import * as React from 'react'
import ReactDOM from 'react-dom'
import ReactDOMServer from 'react-dom/server'
import {fireEvent, render, screen} from '../'
import {fireEvent, render, screen, configure} from '../'

describe('render API', () => {
let originalConfig
beforeEach(() => {
// Grab the existing configuration so we can restore
// it at the end of the test
configure(existingConfig => {
originalConfig = existingConfig
// Don't change the existing config
return {}
})
})

test('renders div into document', () => {
const ref = React.createRef()
const {container} = render(<div ref={ref} />)
expect(container.firstChild).toBe(ref.current)
})
afterEach(() => {
configure(originalConfig)
})

test('works great with react portals', () => {
class MyPortal extends React.Component {
constructor(...args) {
super(...args)
this.portalNode = document.createElement('div')
this.portalNode.dataset.testid = 'my-portal'
}
componentDidMount() {
document.body.appendChild(this.portalNode)
}
componentWillUnmount() {
this.portalNode.parentNode.removeChild(this.portalNode)
}
render() {
return ReactDOM.createPortal(
<Greet greeting="Hello" subject="World" />,
this.portalNode,
)
}
}

function Greet({greeting, subject}) {
return (
<div>
<strong>
{greeting} {subject}
</strong>
</div>
)
}

const {unmount} = render(<MyPortal />)
expect(screen.getByText('Hello World')).toBeInTheDocument()
const portalNode = screen.getByTestId('my-portal')
expect(portalNode).toBeInTheDocument()
unmount()
expect(portalNode).not.toBeInTheDocument()
})
test('renders div into document', () => {
const ref = React.createRef()
const {container} = render(<div ref={ref} />)
expect(container.firstChild).toBe(ref.current)
})

test('returns baseElement which defaults to document.body', () => {
const {baseElement} = render(<div />)
expect(baseElement).toBe(document.body)
})
test('works great with react portals', () => {
class MyPortal extends React.Component {
constructor(...args) {
super(...args)
this.portalNode = document.createElement('div')
this.portalNode.dataset.testid = 'my-portal'
}
componentDidMount() {
document.body.appendChild(this.portalNode)
}
componentWillUnmount() {
this.portalNode.parentNode.removeChild(this.portalNode)
}
render() {
return ReactDOM.createPortal(
<Greet greeting="Hello" subject="World" />,
this.portalNode,
)
}
}

test('supports fragments', () => {
class Test extends React.Component {
render() {
function Greet({greeting, subject}) {
return (
<div>
<code>DocumentFragment</code> is pretty cool!
<strong>
{greeting} {subject}
</strong>
</div>
)
}
}

const {asFragment} = render(<Test />)
expect(asFragment()).toMatchSnapshot()
})
const {unmount} = render(<MyPortal />)
expect(screen.getByText('Hello World')).toBeInTheDocument()
const portalNode = screen.getByTestId('my-portal')
expect(portalNode).toBeInTheDocument()
unmount()
expect(portalNode).not.toBeInTheDocument()
})

test('renders options.wrapper around node', () => {
const WrapperComponent = ({children}) => (
<div data-testid="wrapper">{children}</div>
)
test('returns baseElement which defaults to document.body', () => {
const {baseElement} = render(<div />)
expect(baseElement).toBe(document.body)
})

test('supports fragments', () => {
class Test extends React.Component {
render() {
return (
<div>
<code>DocumentFragment</code> is pretty cool!
</div>
)
}
}

const {container} = render(<div data-testid="inner" />, {
wrapper: WrapperComponent,
const {asFragment} = render(<Test />)
expect(asFragment()).toMatchSnapshot()
})

expect(screen.getByTestId('wrapper')).toBeInTheDocument()
expect(container.firstChild).toMatchInlineSnapshot(`
test('renders options.wrapper around node', () => {
const WrapperComponent = ({children}) => (
<div data-testid="wrapper">{children}</div>
)

const {container} = render(<div data-testid="inner" />, {
wrapper: WrapperComponent,
})

expect(screen.getByTestId('wrapper')).toBeInTheDocument()
expect(container.firstChild).toMatchInlineSnapshot(`
<div
data-testid=wrapper
>
Expand All @@ -87,102 +103,138 @@ test('renders options.wrapper around node', () => {
/>
</div>
`)
})
})

test('flushes useEffect cleanup functions sync on unmount()', () => {
const spy = jest.fn()
function Component() {
React.useEffect(() => spy, [])
return null
}
const {unmount} = render(<Component />)
expect(spy).toHaveBeenCalledTimes(0)
test('renders options.wrapper around node when reactStrictMode is true', () => {
configure({reactStrictMode: true})

unmount()
const WrapperComponent = ({children}) => (
<div data-testid="wrapper">{children}</div>
)
const {container} = render(<div data-testid="inner" />, {
wrapper: WrapperComponent,
})

expect(spy).toHaveBeenCalledTimes(1)
})
expect(screen.getByTestId('wrapper')).toBeInTheDocument()
expect(container.firstChild).toMatchInlineSnapshot(`
<div
data-testid=wrapper
>
<div
data-testid=inner
/>
</div>
`)
})

test('renders twice when reactStrictMode is true', () => {
configure({reactStrictMode: true})

test('can be called multiple times on the same container', () => {
const container = document.createElement('div')
const spy = jest.fn()
function Component() {
spy()
return null
}

const {unmount} = render(<strong />, {container})
render(<Component />)
expect(spy).toHaveBeenCalledTimes(2)
})

expect(container).toContainHTML('<strong></strong>')
test('flushes useEffect cleanup functions sync on unmount()', () => {
const spy = jest.fn()
function Component() {
React.useEffect(() => spy, [])
return null
}
const {unmount} = render(<Component />)
expect(spy).toHaveBeenCalledTimes(0)

render(<em />, {container})
unmount()

expect(container).toContainHTML('<em></em>')
expect(spy).toHaveBeenCalledTimes(1)
})

unmount()
test('can be called multiple times on the same container', () => {
const container = document.createElement('div')

expect(container).toBeEmptyDOMElement()
})
const {unmount} = render(<strong />, {container})

test('hydrate will make the UI interactive', () => {
function App() {
const [clicked, handleClick] = React.useReducer(n => n + 1, 0)
expect(container).toContainHTML('<strong></strong>')

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)
render(<em />, {container})

expect(container).toHaveTextContent('clicked:0')
expect(container).toContainHTML('<em></em>')

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

fireEvent.click(container.querySelector('button'))
expect(container).toBeEmptyDOMElement()
})

expect(container).toHaveTextContent('clicked:1')
})
test('hydrate will make the UI interactive', () => {
function App() {
const [clicked, handleClick] = React.useReducer(n => n + 1, 0)

test('hydrate can have a wrapper', () => {
const wrapperComponentMountEffect = jest.fn()
function WrapperComponent({children}) {
React.useEffect(() => {
wrapperComponentMountEffect()
})
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)

return children
}
const ui = <div />
const container = document.createElement('div')
document.body.appendChild(container)
container.innerHTML = ReactDOMServer.renderToString(ui)
expect(container).toHaveTextContent('clicked:0')

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

expect(wrapperComponentMountEffect).toHaveBeenCalledTimes(1)
})
fireEvent.click(container.querySelector('button'))

test('legacyRoot uses legacy ReactDOM.render', () => {
expect(() => {
render(<div />, {legacyRoot: true})
}).toErrorDev(
[
"Warning: ReactDOM.render is no longer supported in React 18. Use createRoot instead. Until you switch to the new API, your app will behave as if it's running React 17. Learn more: https://reactjs.org/link/switch-to-createroot",
],
{withoutStack: true},
)
})
expect(container).toHaveTextContent('clicked:1')
})

test('hydrate can have a wrapper', () => {
const wrapperComponentMountEffect = jest.fn()
function WrapperComponent({children}) {
React.useEffect(() => {
wrapperComponentMountEffect()
})

return children
}
const ui = <div />
const container = document.createElement('div')
document.body.appendChild(container)
container.innerHTML = ReactDOMServer.renderToString(ui)

test('legacyRoot uses legacy ReactDOM.hydrate', () => {
const ui = <div />
const container = document.createElement('div')
container.innerHTML = ReactDOMServer.renderToString(ui)
expect(() => {
render(ui, {container, hydrate: true, legacyRoot: true})
}).toErrorDev(
[
"Warning: ReactDOM.hydrate is no longer supported in React 18. Use hydrateRoot instead. Until you switch to the new API, your app will behave as if it's running React 17. Learn more: https://reactjs.org/link/switch-to-createroot",
],
{withoutStack: true},
)
render(ui, {container, hydrate: true, wrapper: WrapperComponent})

expect(wrapperComponentMountEffect).toHaveBeenCalledTimes(1)
})

test('legacyRoot uses legacy ReactDOM.render', () => {
expect(() => {
render(<div />, {legacyRoot: true})
}).toErrorDev(
[
"Warning: ReactDOM.render is no longer supported in React 18. Use createRoot instead. Until you switch to the new API, your app will behave as if it's running React 17. Learn more: https://reactjs.org/link/switch-to-createroot",
],
{withoutStack: true},
)
})

test('legacyRoot uses legacy ReactDOM.hydrate', () => {
const ui = <div />
const container = document.createElement('div')
container.innerHTML = ReactDOMServer.renderToString(ui)
expect(() => {
render(ui, {container, hydrate: true, legacyRoot: true})
}).toErrorDev(
[
"Warning: ReactDOM.hydrate is no longer supported in React 18. Use hydrateRoot instead. Until you switch to the new API, your app will behave as if it's running React 17. Learn more: https://reactjs.org/link/switch-to-createroot",
],
{withoutStack: true},
)
})
})
Loading

0 comments on commit a543597

Please sign in to comment.