Skip to content

fix: Make props optional in rerender function returned from renderHookToSnapshotStream #14

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jan 16, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 49 additions & 1 deletion src/__tests__/renderHookToSnapshotStream.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@
import {EventEmitter} from 'node:events'
import {scheduler} from 'node:timers/promises'
import {test, expect} from '@jest/globals'
import {renderHookToSnapshotStream} from '@testing-library/react-render-stream'
import {
renderHookToSnapshotStream,
SnapshotStream,
} from '@testing-library/react-render-stream'
import * as React from 'react'

const testEvents = new EventEmitter<{
Expand Down Expand Up @@ -72,3 +75,48 @@ test.each<[type: string, initialValue: unknown, ...nextValues: unknown[]]>([
expect(await takeSnapshot()).toBe(nextValue)
}
})

test.skip('type test: render function without an argument -> no argument required for `rerender`', async () => {
{
// prop type has nothing to infer on - defaults to `void`
const stream = await renderHookToSnapshotStream(() => {})
const _test1: SnapshotStream<void, void> = stream
// @ts-expect-error should not be assignable
const _test2: SnapshotStream<void, string> = stream
await stream.rerender()
// @ts-expect-error invalid argument
await stream.rerender('foo')
}
{
// prop type is implicitly set via the render function argument
const stream = await renderHookToSnapshotStream((_arg1: string) => {})
// @ts-expect-error should not be assignable
const _test1: SnapshotStream<void, void> = stream
const _test2: SnapshotStream<void, string> = stream
// @ts-expect-error missing argument
await stream.rerender()
await stream.rerender('foo')
}
{
// prop type is implicitly set via the initialProps argument
const stream = await renderHookToSnapshotStream(() => {}, {
initialProps: 'initial',
})
// @ts-expect-error should not be assignable
const _test1: SnapshotStream<void, void> = stream
const _test2: SnapshotStream<void, string> = stream
// @ts-expect-error missing argument
await stream.rerender()
await stream.rerender('foo')
}
{
// argument is optional
const stream = await renderHookToSnapshotStream((_arg1?: string) => {})

const _test1: SnapshotStream<void, void> = stream
const _test2: SnapshotStream<void, string> = stream
const _test3: SnapshotStream<void, string | undefined> = stream
await stream.rerender()
await stream.rerender('foo')
}
})
19 changes: 15 additions & 4 deletions src/renderHookToSnapshotStream.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,21 @@ export interface SnapshotStream<Snapshot, Props> extends Assertable {
* Does not advance the render iterator.
*/
waitForNextSnapshot(options?: NextRenderOptions): Promise<Snapshot>
rerender: (rerenderCallbackProps: Props) => Promise<void>
rerender: (rerenderCallbackProps: VoidOptionalArg<Props>) => Promise<void>
unmount: () => void
}

export async function renderHookToSnapshotStream<ReturnValue, Props>(
/**
* if `Arg` can be `undefined`, replace it with `void` to make type represent an optional argument in a function argument position
*/
type VoidOptionalArg<Arg> = Arg extends any // distribute members of a potential `Props` union
? undefined extends Arg
? // eslint-disable-next-line @typescript-eslint/no-invalid-void-type
void
: Arg
: Arg

export async function renderHookToSnapshotStream<ReturnValue, Props = void>(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can actually reach the same result, but more situational and "correct" by defaulting Props to void instead of the default unknown.

This way if you have an actual argument defined for the function, it will stay required, but if there is no argument it will be void, making it optional.

We do need a little bit more work to also correctly work if the function argument is optional from the start - the VoidOptionalArg takes care of that.

renderCallback: (props: Props) => ReturnValue,
{initialProps, ...renderOptions}: RenderHookOptions<Props> = {},
): Promise<SnapshotStream<ReturnValue, Props>> {
Expand All @@ -61,8 +71,9 @@ export async function renderHookToSnapshotStream<ReturnValue, Props>(
renderOptions,
)

function rerender(rerenderCallbackProps: Props) {
return baseRerender(<HookComponent arg={rerenderCallbackProps} />)
function rerender(rerenderCallbackProps: VoidOptionalArg<Props>) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
return baseRerender(<HookComponent arg={rerenderCallbackProps as any} />)
}

return {
Expand Down
Loading