From 81c2de90c621df4f0702db02adf0c1f31bc08cd6 Mon Sep 17 00:00:00 2001 From: Sebastian Silbermann <silbermann.sebastian@gmail.com> Date: Tue, 2 Feb 2021 10:30:49 +0100 Subject: [PATCH] fix(render): Default to HTMLElement in returned container (#868) --- types/index.d.ts | 26 ++++++++++++++++++-------- types/test.tsx | 36 ++++++++++++++++++++++++++++++++---- 2 files changed, 50 insertions(+), 12 deletions(-) diff --git a/types/index.d.ts b/types/index.d.ts index 8e0d1c02..4d599256 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -6,12 +6,16 @@ import { BoundFunction, prettyFormat, } from '@testing-library/dom' +import {Renderer} from 'react-dom' import {act as reactAct} from 'react-dom/test-utils' export * from '@testing-library/dom' -export type RenderResult<Q extends Queries = typeof queries> = { - container: Element +export type RenderResult< + Q extends Queries = typeof queries, + Container extends Element | DocumentFragment = HTMLElement +> = { + container: Container baseElement: Element debug: ( baseElement?: @@ -26,8 +30,11 @@ export type RenderResult<Q extends Queries = typeof queries> = { asFragment: () => DocumentFragment } & {[P in keyof Q]: BoundFunction<Q[P]>} -export interface RenderOptions<Q extends Queries = typeof queries> { - container?: Element +export interface RenderOptions< + Q extends Queries = typeof queries, + Container extends Element | DocumentFragment = HTMLElement +> { + container?: Container baseElement?: Element hydrate?: boolean queries?: Q @@ -39,14 +46,17 @@ type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>> /** * Render into a container which is appended to document.body. It should be used with cleanup. */ +export function render< + Q extends Queries, + Container extends Element | DocumentFragment = HTMLElement +>( + ui: React.ReactElement, + options: RenderOptions<Q, Container>, +): RenderResult<Q, Container> export function render( ui: React.ReactElement, options?: Omit<RenderOptions, 'queries'>, ): RenderResult -export function render<Q extends Queries>( - ui: React.ReactElement, - options: RenderOptions<Q>, -): RenderResult<Q> /** * Unmounts React trees that were mounted with render. diff --git a/types/test.tsx b/types/test.tsx index 1366caac..7cc0e015 100644 --- a/types/test.tsx +++ b/types/test.tsx @@ -3,7 +3,7 @@ import {render, fireEvent, screen, waitFor} from '.' import * as pure from './pure' export async function testRender() { - const page = render(<div />) + const page = render(<button />) // single queries page.getByText('foo') @@ -17,11 +17,12 @@ export async function testRender() { // helpers const {container, rerender, debug} = page + expectType<HTMLElement, typeof container>(container) return {container, rerender, debug} } export async function testPureRender() { - const page = pure.render(<div />) + const page = pure.render(<button />) // single queries page.getByText('foo') @@ -35,13 +36,15 @@ export async function testPureRender() { // helpers const {container, rerender, debug} = page + expectType<HTMLElement, typeof container>(container) return {container, rerender, debug} } export function testRenderOptions() { const container = document.createElement('div') const options = {container} - render(<div />, options) + const {container: returnedContainer} = render(<button />, options) + expectType<HTMLDivElement, typeof returnedContainer>(returnedContainer) } export function testSVGRenderOptions() { @@ -50,7 +53,8 @@ export function testSVGRenderOptions() { 'svg', ) const options = {container} - render(<svg />, options) + const {container: returnedContainer} = render(<path />, options) + expectType<SVGSVGElement, typeof returnedContainer>(returnedContainer) } export function testFireEvent() { @@ -87,3 +91,27 @@ eslint testing-library/no-debug: "off", testing-library/prefer-screen-queries: "off" */ + +// https://stackoverflow.com/questions/53807517/how-to-test-if-two-types-are-exactly-the-same +type IfEquals<T, U, Yes = unknown, No = never> = (<G>() => G extends T + ? 1 + : 2) extends <G>() => G extends U ? 1 : 2 + ? Yes + : No + +/** + * Issues a type error if `Expected` is not identical to `Actual`. + * + * `Expected` should be declared when invoking `expectType`. + * `Actual` should almost always we be a `typeof value` statement. + * + * Source: https://github.com/mui-org/material-ui/blob/6221876a4b468a3330ffaafa8472de7613933b87/packages/material-ui-types/index.d.ts#L73-L84 + * + * @example `expectType<number | string, typeof value>(value)` + * TypeScript issues a type error since `value is not assignable to never`. + * This means `typeof value` is not identical to `number | string` + * @param actual + */ +declare function expectType<Expected, Actual>( + actual: IfEquals<Actual, Expected, Actual>, +): void