Skip to content
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
26 changes: 26 additions & 0 deletions .changeset/calm-houses-do.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
'pleasantest': minor
---

Update `@testing-library/dom` to [`v8.11.1`](https://github.com/testing-library/dom-testing-library/releases/tag/v8.11.1)

Read their [release notes](https://github.com/testing-library/dom-testing-library/releases) for all the versions between 8.1.0 and 8.11.1 to see the full changes.

Notably, we have added the ability for TypeScript users to optionally specify an element type as a type parameter for DTL queries:

```ts
import { withBrowser } from 'pleasantest';

test(
'changelog example',
withBrowser(async ({ screen }) => {
// ElementHandle<HTMLButtonElement>
const button = await screen.getByRole<HTMLButtonElement>(/button/);

// ElementHandle<HTMLButtonElement>[]
const buttons = await screen.getAllByRole<HTMLButtonElement>(/button/);
}),
);
```

The return type is automatically determined based on the specified element type. Since Pleasantest DTL queries return `ElementHandle`s, the return type will be wrapped with `Promise<ElementHandle<...>>`. For queries which return arrays of elements, the singular version of the element type is accepted as the type parameter, and the return type will automatically be wrapped with `Promise<Array<ElementHandle<...>>>`.
5 changes: 0 additions & 5 deletions .changeset/wise-apricots-allow.md

This file was deleted.

49 changes: 32 additions & 17 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"@rollup/plugin-alias": "3.1.8",
"@rollup/plugin-babel": "5.3.0",
"@rollup/plugin-node-resolve": "13.0.6",
"@testing-library/dom": "8.6.0",
"@testing-library/dom": "8.11.1",
"@testing-library/jest-dom": "5.15.0",
"@types/jest": "27.0.2",
"@types/node": "12.20.36",
Expand Down
41 changes: 29 additions & 12 deletions src/pptr-testing-library.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,42 @@
import type { queries, BoundFunctions } from '@testing-library/dom';
import type { queries } from '@testing-library/dom';
import { jsHandleToArray, removeFuncFromStackTrace } from './utils';
import type { JSHandle } from 'puppeteer';
import type { ElementHandle, JSHandle } from 'puppeteer';
import { createClientRuntimeServer } from './module-server/client-runtime-server';
import type { AsyncHookTracker } from './async-hooks';

type ElementToElementHandle<Input> = Input extends Element
? import('puppeteer').ElementHandle
: Input extends Element[]
? import('puppeteer').ElementHandle[]
? ElementHandle<Input>
: Input extends (Element | ElementHandle)[]
? { [K in keyof Input]: ElementToElementHandle<Input[K]> }
: Input;

type Promisify<Input> = Input extends Promise<any> ? Input : Promise<Input>;

type UpdateReturnType<Fn> = Fn extends (...args: infer Args) => infer ReturnType
? (...args: Args) => Promisify<ElementToElementHandle<ReturnType>>
type ValueOf<Input> = Input extends any[] ? Input[number] : Input[keyof Input];
type UnArray<Input> = Input extends any[] ? Input[number] : Input;
type UnPromise<Input> = Input extends Promise<infer Inner> ? Inner : Input;
/**
* Changes type signature of an original testing library query function by:
* - Removing the `container` parameter
* - Returning a promise, always
* - Returning ElementHandles instead of Elements
*/
type ChangeDTLFn<DTLFn extends ValueOf<typeof queries>> = DTLFn extends (
container: HTMLElement,
...args: infer Args
) => infer DTLReturn
? <CustomizedReturn extends UnArray<UnPromise<DTLReturn>>>(
...args: Args
) => Promisify<
ElementToElementHandle<
UnPromise<DTLReturn> extends any[]
? CustomizedReturn[]
: CustomizedReturn
>
>
: never;

type AsyncDTLQueries = {
[K in keyof typeof queries]: UpdateReturnType<typeof queries[K]>;
export type BoundQueries = {
[K in keyof typeof queries]: ChangeDTLFn<typeof queries[K]>;
};

const queryNames = [
Expand Down Expand Up @@ -77,8 +96,6 @@ interface DTLError {
messageWithElementsStringified: string;
}

export type BoundQueries = BoundFunctions<AsyncDTLQueries>;

export const getQueriesForElement = (
page: import('puppeteer').Page,
asyncHookTracker: AsyncHookTracker,
Expand Down
3 changes: 2 additions & 1 deletion src/rollup-plugin-aria-query.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ export const rollupPluginAriaQuery = () => ({
const getAriaQueryCode = async () => {
const q = await import('aria-query');
return `export const roles = ${stringify(q.roles)};
export const elementRoles = ${stringify(q.elementRoles)};`;
export const elementRoles = ${stringify(q.elementRoles)};
export const roleElements = ${stringify(q.roleElements)};`;
};

const stringify = (input) => {
Expand Down
8 changes: 4 additions & 4 deletions tests/jest-dom-matchers/toBePartiallyChecked.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import type { ElementHandle } from 'puppeteer';
import { withBrowser } from 'pleasantest';

test(
Expand Down Expand Up @@ -30,9 +29,10 @@ test(
const ariaCheckboxUnchecked = await screen.getByTestId(
'aria-checkbox-unchecked',
);
const inputCheckboxIndeterminate = (await screen.getByTestId(
'input-checkbox-indeterminate',
)) as ElementHandle<HTMLInputElement>;
const inputCheckboxIndeterminate =
await screen.getByTestId<HTMLInputElement>(
'input-checkbox-indeterminate',
);

await expect(ariaCheckboxMixed).toBePartiallyChecked();

Expand Down
5 changes: 1 addition & 4 deletions tests/jest-dom-matchers/toHaveFocus.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import type { ElementHandle } from 'puppeteer';
import { withBrowser } from 'pleasantest';

test(
Expand All @@ -8,9 +7,7 @@ test(
`<div><input type="text" data-testid="element-to-focus" /></div>`,
);

const input: ElementHandle<HTMLElement> = await screen.getByTestId(
'element-to-focus',
);
const input = await screen.getByTestId('element-to-focus');

await input.focus();
await expect(input).toHaveFocus();
Expand Down
36 changes: 35 additions & 1 deletion tests/testing-library-queries/variants-of-queries.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { ElementHandle } from 'pleasantest';
import { withBrowser } from 'pleasantest';

const singleElementMarkup = `
Expand All @@ -9,12 +10,37 @@ const multipleElementMarkup = `
<h1>Hello</h1>
`;

// @ts-expect-error T1 is intentionally unused, assertType is only used at type-time
const assertType = <T1 extends true>() => {};

// Checks if two types are equal (A extends B and B extends A)
// Contains special case for ElementHandle's,
// where ElementHandle<A> extends ElementHandle<B> even if A does not extend A
type Equal<A, B> = A extends (infer T1)[]
? B extends (infer T2)[]
? Equal<T1, T2>
: false
: A extends ElementHandle<infer T1>
? B extends ElementHandle<infer T2>
? Equal<T1, T2>
: false
: B extends A
? A extends B
? true
: false
: false;

test(
'findBy',
withBrowser(async ({ screen, utils }) => {
// This should work because findByText waits for up to 1s to see the element
setTimeout(() => utils.injectHTML(singleElementMarkup), 5);
await screen.findByText(/Hello/);

const t1 = await screen.findByText(/Hello/);
assertType<Equal<ElementHandle<HTMLElement>, typeof t1>>();

const t2 = await screen.findByText<HTMLInputElement>(/Hello/);
assertType<Equal<ElementHandle<HTMLInputElement>, typeof t2>>();

await expect(screen.findByText(/Hellooooo/, {}, { timeout: 5 })).rejects
.toThrowErrorMatchingInlineSnapshot(`
Expand Down Expand Up @@ -106,6 +132,14 @@ test(
setTimeout(() => utils.injectHTML(singleElementMarkup), 5);
expect(await screen.findAllByText(/Hello/)).toHaveLength(1);

const t1 = await screen.findAllByText(/Hello/);
assertType<Equal<ElementHandle<HTMLElement>[], typeof t1>>();

const t2 = await screen.findAllByText<HTMLHeadingElement>(/Hello/);
assertType<Equal<ElementHandle<HTMLHeadingElement>[], typeof t2>>();

assertType<Equal<typeof t1, ElementHandle<HTMLElement>[]>>();

await expect(screen.findAllByText(/Hellooooo/, {}, { timeout: 5 })).rejects
.toThrowErrorMatchingInlineSnapshot(`
"Unable to find an element with the text: /Hellooooo/. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible.
Expand Down