Skip to content

Commit fe87a08

Browse files
author
pierrezimmermann
committed
feat: add renderHook function
1 parent 1360b73 commit fe87a08

File tree

3 files changed

+119
-0
lines changed

3 files changed

+119
-0
lines changed

src/__tests__/renderHook.test.tsx

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import React, { ReactNode } from 'react';
2+
import { renderHook } from '../pure';
3+
4+
test('gives comitted result', () => {
5+
const { result } = renderHook(() => {
6+
const [state, setState] = React.useState(1);
7+
8+
React.useEffect(() => {
9+
setState(2);
10+
}, []);
11+
12+
return [state, setState];
13+
});
14+
15+
expect(result.current).toEqual([2, expect.any(Function)]);
16+
});
17+
18+
test('allows rerendering', () => {
19+
const { result, rerender } = renderHook(
20+
(props: { branch: 'left' | 'right' }) => {
21+
const [left, setLeft] = React.useState('left');
22+
const [right, setRight] = React.useState('right');
23+
24+
// eslint-disable-next-line jest/no-if
25+
switch (props.branch) {
26+
case 'left':
27+
return [left, setLeft];
28+
case 'right':
29+
return [right, setRight];
30+
31+
default:
32+
throw new Error(
33+
'No Props passed. This is a bug in the implementation'
34+
);
35+
}
36+
},
37+
{ initialProps: { branch: 'left' } }
38+
);
39+
40+
expect(result.current).toEqual(['left', expect.any(Function)]);
41+
42+
rerender({ branch: 'right' });
43+
44+
expect(result.current).toEqual(['right', expect.any(Function)]);
45+
});
46+
47+
test('allows wrapper components', async () => {
48+
const Context = React.createContext('default');
49+
function Wrapper({ children }: { children: ReactNode }) {
50+
return <Context.Provider value="provided">{children}</Context.Provider>;
51+
}
52+
const { result } = renderHook(
53+
() => {
54+
return React.useContext(Context);
55+
},
56+
{
57+
wrapper: Wrapper,
58+
}
59+
);
60+
61+
expect(result.current).toEqual('provided');
62+
});

src/pure.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import waitFor from './waitFor';
66
import waitForElementToBeRemoved from './waitForElementToBeRemoved';
77
import { within, getQueriesForElement } from './within';
88
import { getDefaultNormalizer } from './matches';
9+
import { renderHook } from './renderHook';
910

1011
export { act };
1112
export { cleanup };
@@ -15,3 +16,4 @@ export { waitFor };
1516
export { waitForElementToBeRemoved };
1617
export { within, getQueriesForElement };
1718
export { getDefaultNormalizer };
19+
export { renderHook };

src/renderHook.tsx

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import React from 'react';
2+
import type { ComponentType } from 'react';
3+
import render from './render';
4+
5+
interface RenderHookResult<Result, Props> {
6+
rerender: (props: Props) => void;
7+
result: { current: Result };
8+
unmount: () => void;
9+
}
10+
11+
type RenderHookOptions<Props> = Props extends object | string | number | boolean
12+
? {
13+
initialProps: Props;
14+
wrapper?: ComponentType<any>;
15+
}
16+
: { wrapper?: ComponentType<any>; initialProps?: never } | undefined;
17+
18+
export function renderHook<Result, Props>(
19+
renderCallback: (props: Props) => Result,
20+
options?: RenderHookOptions<Props>
21+
): RenderHookResult<Result, Props> {
22+
const initialProps = options?.initialProps;
23+
const wrapper = options?.wrapper;
24+
25+
const result: React.MutableRefObject<Result | null> = React.createRef();
26+
27+
function TestComponent({
28+
renderCallbackProps,
29+
}: {
30+
renderCallbackProps: Props;
31+
}) {
32+
const renderResult = renderCallback(renderCallbackProps);
33+
34+
React.useEffect(() => {
35+
result.current = renderResult;
36+
});
37+
38+
return null;
39+
}
40+
41+
const { rerender: baseRerender, unmount } = render(
42+
// @ts-expect-error since option can be undefined, initialProps can be undefined when it should'nt
43+
<TestComponent renderCallbackProps={initialProps} />,
44+
{ wrapper }
45+
);
46+
47+
function rerender(rerenderCallbackProps: Props) {
48+
return baseRerender(
49+
<TestComponent renderCallbackProps={rerenderCallbackProps} />
50+
);
51+
}
52+
53+
// @ts-expect-error result is ill typed because ref is initialized to null
54+
return { result, rerender, unmount };
55+
}

0 commit comments

Comments
 (0)