Skip to content

Commit e49a23f

Browse files
alexkrolickKent C. Dodds
authored and
Kent C. Dodds
committed
feat(render): add wrapper component option (#303)
* feat(render): add wrapper component option * feat(render): update types * fix(types): remove unreferenced type * fix(render): optional type for wrapper option
1 parent d413665 commit e49a23f

File tree

4 files changed

+53
-24
lines changed

4 files changed

+53
-24
lines changed

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@
4747
},
4848
"devDependencies": {
4949
"@reach/router": "^1.2.1",
50-
"@types/react-dom": "^16.0.9",
50+
"@types/react": "^16.8.3",
51+
"@types/react-dom": "^16.8.2",
5152
"axios": "^0.18.0",
5253
"eslint-import-resolver-jest": "^2.1.1",
5354
"history": "^4.7.2",

src/__tests__/render.js

+21
Original file line numberDiff line numberDiff line change
@@ -90,3 +90,24 @@ it('supports fragments', () => {
9090
cleanup()
9191
expect(document.body.innerHTML).toBe('')
9292
})
93+
94+
test('renders options.wrapper around node', () => {
95+
const WrapperComponent = ({children}) => (
96+
<div data-testid="wrapper">{children}</div>
97+
)
98+
99+
const {container, getByTestId} = render(<div data-testid="inner" />, {
100+
wrapper: WrapperComponent,
101+
})
102+
103+
expect(getByTestId('wrapper')).toBeInTheDocument()
104+
expect(container.firstChild).toMatchInlineSnapshot(`
105+
<div
106+
data-testid="wrapper"
107+
>
108+
<div
109+
data-testid="inner"
110+
/>
111+
</div>
112+
`)
113+
})

src/index.js

+27-19
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,13 @@ const mountedContainers = new Set()
1111

1212
function render(
1313
ui,
14-
{container, baseElement = container, queries, hydrate = false} = {},
14+
{
15+
container,
16+
baseElement = container,
17+
queries,
18+
hydrate = false,
19+
wrapper: WrapperComponent,
20+
} = {},
1521
) {
1622
if (!container) {
1723
// default to document.body instead of documentElement to avoid output of potentially-large
@@ -25,21 +31,28 @@ function render(
2531
// they're passing us a custom container or not.
2632
mountedContainers.add(container)
2733

34+
35+
const wrapUiIfNeeded = innerElement =>
36+
WrapperComponent
37+
? React.createElement(WrapperComponent, null, innerElement)
38+
: innerElement
39+
2840
act(() => {
2941
if (hydrate) {
30-
ReactDOM.hydrate(ui, container)
42+
ReactDOM.hydrate(wrapUiIfNeeded(ui), container)
3143
} else {
32-
ReactDOM.render(ui, container)
44+
ReactDOM.render(wrapUiIfNeeded(ui), container)
3345
}
3446
})
47+
3548
return {
3649
container,
3750
baseElement,
3851
// eslint-disable-next-line no-console
3952
debug: (el = baseElement) => console.log(prettyDOM(el)),
4053
unmount: () => ReactDOM.unmountComponentAtNode(container),
4154
rerender: rerenderUi => {
42-
render(rerenderUi, {container, baseElement})
55+
render(wrapUiIfNeeded(rerenderUi), {container, baseElement})
4356
// Intentionally do not return anything to avoid unnecessarily complicating the API.
4457
// folks can use all the same utilities we return in the first place that are bound to the container
4558
},
@@ -68,25 +81,20 @@ function testHook(callback, options = {}) {
6881
const result = {
6982
current: null,
7083
}
71-
const toRender = () => {
72-
const hookRender = (
73-
<TestHook callback={callback}>
74-
{res => {
75-
result.current = res
76-
}}
77-
</TestHook>
78-
)
79-
if (options.wrapper) {
80-
return React.createElement(options.wrapper, null, hookRender)
81-
}
82-
return hookRender
83-
}
84-
const {unmount, rerender: rerenderComponent} = render(toRender())
84+
const toRender = () => (
85+
<TestHook callback={callback}>
86+
{res => {
87+
result.current = res
88+
}}
89+
</TestHook>
90+
)
91+
92+
const {unmount, rerender: rerenderComponent} = render(toRender(), options)
8593
return {
8694
result,
8795
unmount,
8896
rerender: () => {
89-
rerenderComponent(toRender())
97+
rerenderComponent(toRender(), options)
9098
},
9199
}
92100
}

typings/index.d.ts

+3-4
Original file line numberDiff line numberDiff line change
@@ -25,17 +25,16 @@ export type HookResult<TResult> = {
2525
unmount: () => boolean
2626
}
2727

28-
export type HookOptions = {
29-
wrapper: React.FunctionComponent
30-
}
31-
3228
export interface RenderOptions<Q extends Queries = typeof queries> {
3329
container?: HTMLElement
3430
baseElement?: HTMLElement
3531
hydrate?: boolean
3632
queries?: Q
33+
wrapper?: React.ComponentType
3734
}
3835

36+
export type HookOptions = RenderOptions
37+
3938
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>
4039

4140
/**

0 commit comments

Comments
 (0)