Skip to content

Commit 21dccbb

Browse files
SimenBKent C. Dodds
authored and
Kent C. Dodds
committed
feat: add asFragment return value from render (testing-library#192)
<!-- Thanks for your interest in the project. Bugs filed and PRs submitted are appreciated! Please make sure that you are familiar with and follow the Code of Conduct for this project (found in the CODE_OF_CONDUCT.md file). Also, please make sure you're familiar with and follow the instructions in the contributing guidelines (found in the CONTRIBUTING.md file). If you're new to contributing to open source projects, you might find this free video course helpful: http://kcd.im/pull-request Please fill out the information below to expedite the review and (hopefully) merge of your pull request! --> <!-- What changes are being made? (What feature/bug is being fixed here?) --> **What**: Adds the possibility to get a `DocumentFragment` version of the rendered component. <!-- Why are these changes necessary? --> **Why**: I have a test of a connected redux component, and I want to snapshot the diff between them after dispatching an action. Since `container` is a live binding, its `innerHTML` changes, meaning comparison becomes meaningless. An extra advantage is IMO that you don't need to do `firstChild` on snapshots, as having the `Fragment` wrapper makes sense. At least in my mind 🙂 <!-- How were these changes implemented? --> **How**: By upgrading `kcd-scripts` to a version with Jest 23, and adding a dependency on `JSDOM` which matches the one found in Jest (to not rely on dependency hoisting). <!-- Have you done all of these things? --> **Checklist**: <!-- add "N/A" to the end of each line that's irrelevant to your changes --> <!-- to check an item, place an "x" in the box like so: "- [x] Documentation" --> - [x] Documentation - [x] Tests - [x] Ready to be merged <!-- In your opinion, is this ready to be merged as soon as it's reviewed? --> - [ ] Added myself to contributors table <!-- this is optional, see the contributing guidelines for instructions --> <!-- feel free to add additional comments -->
1 parent 2a11275 commit 21dccbb

File tree

6 files changed

+80
-3
lines changed

6 files changed

+80
-3
lines changed

README.md

+37-1
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,9 @@ test('Fetch makes an API call and displays the greeting when load-greeting is cl
9999
// Arrange
100100
axiosMock.get.mockResolvedValueOnce({data: {greeting: 'hello there'}})
101101
const url = '/greeting'
102-
const {getByText, getByTestId, container} = render(<Fetch url={url} />)
102+
const {getByText, getByTestId, container, asFragment} = render(
103+
<Fetch url={url} />,
104+
)
103105

104106
// Act
105107
fireEvent.click(getByText('Load Greeting'))
@@ -119,6 +121,8 @@ test('Fetch makes an API call and displays the greeting when load-greeting is cl
119121
expect(getByTestId('ok-button')).toHaveAttribute('disabled')
120122
// snapshots work great with regular DOM nodes!
121123
expect(container.firstChild).toMatchSnapshot()
124+
// you can also use get a `DocumentFragment`, which is useful if you want to compare nodes across render
125+
expect(asFragment()).toMatchSnapshot()
122126
})
123127
```
124128

@@ -547,6 +551,38 @@ const usernameInputElement = getByTestId('username-input')
547551
> about `data-testid`s from the blog post ["Making your UI tests resilient to
548552
> change"][data-testid-blog-post]
549553
554+
#### `asFragment(): DocumentFragment`
555+
556+
Returns a `DocumentFragment` of your rendered component. This can be useful if
557+
you need to avoid live bindings and see how your component reacts to events.
558+
559+
```javascript
560+
import {render, fireEvent} from 'react-testing-library'
561+
562+
class TestComponent extends React.Component {
563+
constructor() {
564+
super()
565+
this.state = {count: 0}
566+
}
567+
568+
render() {
569+
const {count} = this.state
570+
571+
return <button onClick={() => this.setState({count: count + 1})}>Click to increase: {count}</div>
572+
}
573+
}
574+
575+
const {getByText, asFragment} = render(<TestComponent />)
576+
const firstRender = asFragment()
577+
578+
fireEvent.click(getByText(/Click to increase/))
579+
580+
// This will snapshot only the difference between the first render, and the
581+
// state of the DOM after the click event.
582+
// See https://github.com/jest-community/snapshot-diff
583+
expect(firstRender).toMatchDiffSnapshot(asFragment())
584+
```
585+
550586
### `cleanup`
551587
552588
Unmounts React trees that were mounted with [render](#render).

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747
"history": "^4.7.2",
4848
"jest-dom": "^1.3.1",
4949
"jest-in-case": "^1.0.2",
50-
"kcd-scripts": "^0.39.1",
50+
"kcd-scripts": "^0.42.0",
5151
"react": "^16.4.1",
5252
"react-dom": "^16.4.1",
5353
"react-redux": "^5.0.7",
+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`supports fragments 1`] = `
4+
<DocumentFragment>
5+
<div>
6+
<code>
7+
DocumentFragment
8+
</code>
9+
is pretty cool!
10+
</div>
11+
</DocumentFragment>
12+
`;

src/__tests__/render.js

+17
Original file line numberDiff line numberDiff line change
@@ -73,3 +73,20 @@ it('cleansup document', () => {
7373
expect(document.body.innerHTML).toBe('')
7474
expect(spy).toHaveBeenCalledTimes(1)
7575
})
76+
77+
it('supports fragments', () => {
78+
class Test extends React.Component {
79+
render() {
80+
return (
81+
<div>
82+
<code>DocumentFragment</code> is pretty cool!
83+
</div>
84+
)
85+
}
86+
}
87+
88+
const {asFragment} = render(<Test />)
89+
expect(asFragment()).toMatchSnapshot()
90+
cleanup()
91+
expect(document.body.innerHTML).toBe('')
92+
})

src/index.js

+11
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,17 @@ function render(ui, {container, baseElement = container, queries} = {}) {
2929
// Intentionally do not return anything to avoid unnecessarily complicating the API.
3030
// folks can use all the same utilities we return in the first place that are bound to the container
3131
},
32+
asFragment: () => {
33+
if (typeof document.createRange === 'function') {
34+
return document
35+
.createRange()
36+
.createContextualFragment(container.innerHTML)
37+
}
38+
39+
const template = document.createElement('template')
40+
template.innerHTML = container.innerHTML
41+
return template.content
42+
},
3243
...getQueriesForElement(baseElement, queries),
3344
}
3445
}

typings/index.d.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@ type GetsAndQueries = ReturnType<typeof getQueriesForElement>
77
export interface RenderResult extends GetsAndQueries {
88
container: HTMLElement
99
baseElement: HTMLElement
10-
debug: (baseElement?: HTMLElement) => void
10+
debug: (baseElement?: HTMLElement | DocumentFragment) => void
1111
rerender: (ui: React.ReactElement<any>) => void
1212
unmount: () => boolean
13+
asFragment: () => DocumentFragment
1314
}
1415

1516
/**

0 commit comments

Comments
 (0)