Skip to content

Commit

Permalink
feat(render): add wrapper component option (#303)
Browse files Browse the repository at this point in the history
* feat(render): add wrapper component option

* feat(render): update types

* fix(types): remove unreferenced type

* fix(render): optional type for wrapper option
  • Loading branch information
alexkrolick authored and Kent C. Dodds committed Feb 19, 2019
1 parent d413665 commit e49a23f
Show file tree
Hide file tree
Showing 4 changed files with 53 additions and 24 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@
},
"devDependencies": {
"@reach/router": "^1.2.1",
"@types/react-dom": "^16.0.9",
"@types/react": "^16.8.3",
"@types/react-dom": "^16.8.2",
"axios": "^0.18.0",
"eslint-import-resolver-jest": "^2.1.1",
"history": "^4.7.2",
Expand Down
21 changes: 21 additions & 0 deletions src/__tests__/render.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,3 +90,24 @@ it('supports fragments', () => {
cleanup()
expect(document.body.innerHTML).toBe('')
})

test('renders options.wrapper around node', () => {
const WrapperComponent = ({children}) => (
<div data-testid="wrapper">{children}</div>
)

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

expect(getByTestId('wrapper')).toBeInTheDocument()
expect(container.firstChild).toMatchInlineSnapshot(`
<div
data-testid="wrapper"
>
<div
data-testid="inner"
/>
</div>
`)
})
46 changes: 27 additions & 19 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,13 @@ const mountedContainers = new Set()

function render(
ui,
{container, baseElement = container, queries, hydrate = false} = {},
{
container,
baseElement = container,
queries,
hydrate = false,
wrapper: WrapperComponent,
} = {},
) {
if (!container) {
// default to document.body instead of documentElement to avoid output of potentially-large
Expand All @@ -25,21 +31,28 @@ function render(
// they're passing us a custom container or not.
mountedContainers.add(container)


const wrapUiIfNeeded = innerElement =>
WrapperComponent
? React.createElement(WrapperComponent, null, innerElement)
: innerElement

act(() => {
if (hydrate) {
ReactDOM.hydrate(ui, container)
ReactDOM.hydrate(wrapUiIfNeeded(ui), container)
} else {
ReactDOM.render(ui, container)
ReactDOM.render(wrapUiIfNeeded(ui), container)
}
})

return {
container,
baseElement,
// eslint-disable-next-line no-console
debug: (el = baseElement) => console.log(prettyDOM(el)),
unmount: () => ReactDOM.unmountComponentAtNode(container),
rerender: rerenderUi => {
render(rerenderUi, {container, baseElement})
render(wrapUiIfNeeded(rerenderUi), {container, baseElement})
// Intentionally do not return anything to avoid unnecessarily complicating the API.
// folks can use all the same utilities we return in the first place that are bound to the container
},
Expand Down Expand Up @@ -68,25 +81,20 @@ function testHook(callback, options = {}) {
const result = {
current: null,
}
const toRender = () => {
const hookRender = (
<TestHook callback={callback}>
{res => {
result.current = res
}}
</TestHook>
)
if (options.wrapper) {
return React.createElement(options.wrapper, null, hookRender)
}
return hookRender
}
const {unmount, rerender: rerenderComponent} = render(toRender())
const toRender = () => (
<TestHook callback={callback}>
{res => {
result.current = res
}}
</TestHook>
)

const {unmount, rerender: rerenderComponent} = render(toRender(), options)
return {
result,
unmount,
rerender: () => {
rerenderComponent(toRender())
rerenderComponent(toRender(), options)
},
}
}
Expand Down
7 changes: 3 additions & 4 deletions typings/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,16 @@ export type HookResult<TResult> = {
unmount: () => boolean
}

export type HookOptions = {
wrapper: React.FunctionComponent
}

export interface RenderOptions<Q extends Queries = typeof queries> {
container?: HTMLElement
baseElement?: HTMLElement
hydrate?: boolean
queries?: Q
wrapper?: React.ComponentType
}

export type HookOptions = RenderOptions

type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>

/**
Expand Down

0 comments on commit e49a23f

Please sign in to comment.