I'm considering submitting a patch (or patches) of the type declarations in @testing-library/react and/or @testing-library/dom. Currently, all queries on RenderResult return variants of HTMLElement, which leaves me writing a lot of code like
const myDiv = wrapper.getByText('some text') as HTMLDivElement;
The simplest solution is to make the queries generic.
export type GetByText = <T extends HTMLElement = HTMLElement>(/* ... */) => T
export const getByText: GetByText
Then I can just do:
const myDiv = wrapper.getByText<HTMLDivElement>('some text');
While this works in TS Playground, something in @testing-library/react is preventing the generic form from being passed along to RenderResult's methods.
These are the relevant snippets of the current versions of @testing-library/react and @testing-library/dom.
@testing-library/dom
types/queries.d.ts
export type GetByText = (
container: HTMLElement,
id: Matcher,
options?: SelectorMatcherOptions,
) => HTMLElement
export const getByText: GetByText
types/get-queries-for-element.d.ts
import * as queries from './queries';
export type BoundFunction<T> = T extends (
attribute: string,
element: HTMLElement,
text: infer P,
options: infer Q,
) => infer R
? (text: P, options?: Q) => R
: T extends (a1: any, text: infer P, options: infer Q, waitForElementOptions: infer W) => infer R
? (text: P, options?: Q, waitForElementOptions?: W) => R
: T extends (a1: any, text: infer P, options: infer Q) => infer R
? (text: P, options?: Q) => R
: never;
export type BoundFunctions<T> = { [P in keyof T]: BoundFunction<T[P]> };
export type Query = (
container: HTMLElement,
...args: any[]
) => Error | Promise<HTMLElement[]> | Promise<HTMLElement> | HTMLElement[] | HTMLElement | null;
export interface Queries {
[T: string]: Query;
}
export function getQueriesForElement<T extends Queries = typeof queries>(
element: HTMLElement,
queriesToBind?: T,
): BoundFunctions<T>;
@testing-library/react
(may or may not be relevant)
types/index.d.ts
export type RenderResult<
Q extends Queries = typeof queries,
Container extends Element | DocumentFragment = HTMLElement
> = {
// ...
} & {[P in keyof Q]: BoundFunction<Q[P]>}
Alongside the change I made above, making each query's type generic, I changed Query in get-queries-for-element.d.ts:
export type Query = <T extends HTMLElement>(
container: HTMLElement,
...args: any[]
) => Error | Promise<T[]> | Promise<T> | T[] | T | null;
I'm nearly certain the issue is in BoundFunction or getQueriesForElement(), as when I made these changes (which I am like 37% confident are right), I get an error:
Type 'typeof import(".../node_modules/@testing-library/dom/types/queries")' does not satisfy the constraint 'Queries'. Property 'getByLabelText' is incompatible with index signature. Type 'GetByText' is not assignable to type 'Query'. Type 'HTMLElement' is not assignable to type 'T | Error | T[] | Promise<T[]> | Promise<T>'. Type 'HTMLElement' is not assignable to type 'T'. 'HTMLElement' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'HTMLElement'. ts(2344)
There might be another problem in @testing-library/react (and thus, the other testing-library packages), but fixing this is the first step.
Part of my problem is that I don't actually understand what BoundFunction is doing. What do I need to do to make make typeof queries satisfy the Queries constraint?