diff --git a/.changeset/calm-houses-do.md b/.changeset/calm-houses-do.md new file mode 100644 index 00000000..95f6dbeb --- /dev/null +++ b/.changeset/calm-houses-do.md @@ -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 + const button = await screen.getByRole(/button/); + + // ElementHandle[] + const buttons = await screen.getAllByRole(/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>`. 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>>`. diff --git a/.changeset/wise-apricots-allow.md b/.changeset/wise-apricots-allow.md deleted file mode 100644 index e0b6fa1e..00000000 --- a/.changeset/wise-apricots-allow.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'pleasantest': minor ---- - -Update `@testing-library/dom` to [`v8.2.0`](https://github.com/testing-library/dom-testing-library/releases/v8.2.0) diff --git a/package-lock.json b/package-lock.json index d26e7e07..401e6c93 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,7 +32,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.37", @@ -3992,17 +3992,17 @@ } }, "node_modules/@testing-library/dom": { - "version": "8.6.0", - "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.6.0.tgz", - "integrity": "sha512-EDBMEWK8IVpNF7B7C1knb0lLB4Si9RWte/YTEi6CqmqUK5CYCoecwOOG9pEijU/H6s3u0drUxH5sKT07FCgFIg==", + "version": "8.11.1", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.11.1.tgz", + "integrity": "sha512-3KQDyx9r0RKYailW2MiYrSSKEfH0GTkI51UGEvJenvcoDoeRYs0PZpi2SXqtnMClQvCqdtTTpOfFETDTVADpAg==", "dev": true, "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", "@types/aria-query": "^4.2.0", - "aria-query": "^4.2.2", + "aria-query": "^5.0.0", "chalk": "^4.1.0", - "dom-accessibility-api": "^0.5.6", + "dom-accessibility-api": "^0.5.9", "lz-string": "^1.4.4", "pretty-format": "^27.0.2" }, @@ -4025,10 +4025,19 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/@testing-library/dom/node_modules/aria-query": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.0.0.tgz", + "integrity": "sha512-V+SM7AbUwJ+EBnB8+DXs0hPZHO0W6pqBcc0dW90OwtVG02PswOu/teuARoLQjdDOH+t9pJgGnW5/Qmouf3gPJg==", + "dev": true, + "engines": { + "node": ">=6.0" + } + }, "node_modules/@testing-library/dom/node_modules/chalk": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", - "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "dependencies": { "ansi-styles": "^4.1.0", @@ -23837,17 +23846,17 @@ } }, "@testing-library/dom": { - "version": "8.6.0", - "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.6.0.tgz", - "integrity": "sha512-EDBMEWK8IVpNF7B7C1knb0lLB4Si9RWte/YTEi6CqmqUK5CYCoecwOOG9pEijU/H6s3u0drUxH5sKT07FCgFIg==", + "version": "8.11.1", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.11.1.tgz", + "integrity": "sha512-3KQDyx9r0RKYailW2MiYrSSKEfH0GTkI51UGEvJenvcoDoeRYs0PZpi2SXqtnMClQvCqdtTTpOfFETDTVADpAg==", "dev": true, "requires": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", "@types/aria-query": "^4.2.0", - "aria-query": "^4.2.2", + "aria-query": "^5.0.0", "chalk": "^4.1.0", - "dom-accessibility-api": "^0.5.6", + "dom-accessibility-api": "^0.5.9", "lz-string": "^1.4.4", "pretty-format": "^27.0.2" }, @@ -23861,10 +23870,16 @@ "color-convert": "^2.0.1" } }, + "aria-query": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.0.0.tgz", + "integrity": "sha512-V+SM7AbUwJ+EBnB8+DXs0hPZHO0W6pqBcc0dW90OwtVG02PswOu/teuARoLQjdDOH+t9pJgGnW5/Qmouf3gPJg==", + "dev": true + }, "chalk": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", - "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "requires": { "ansi-styles": "^4.1.0", diff --git a/package.json b/package.json index 05f4ae49..c65297f5 100644 --- a/package.json +++ b/package.json @@ -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.37", diff --git a/src/pptr-testing-library.ts b/src/pptr-testing-library.ts index 38ee4911..1b8ad99b 100644 --- a/src/pptr-testing-library.ts +++ b/src/pptr-testing-library.ts @@ -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 extends Element - ? import('puppeteer').ElementHandle - : Input extends Element[] - ? import('puppeteer').ElementHandle[] + ? ElementHandle + : Input extends (Element | ElementHandle)[] + ? { [K in keyof Input]: ElementToElementHandle } : Input; type Promisify = Input extends Promise ? Input : Promise; - -type UpdateReturnType = Fn extends (...args: infer Args) => infer ReturnType - ? (...args: Args) => Promisify> +type ValueOf = Input extends any[] ? Input[number] : Input[keyof Input]; +type UnArray = Input extends any[] ? Input[number] : Input; +type UnPromise = Input extends Promise ? 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 ( + container: HTMLElement, + ...args: infer Args +) => infer DTLReturn + ? >>( + ...args: Args + ) => Promisify< + ElementToElementHandle< + UnPromise extends any[] + ? CustomizedReturn[] + : CustomizedReturn + > + > : never; -type AsyncDTLQueries = { - [K in keyof typeof queries]: UpdateReturnType; +export type BoundQueries = { + [K in keyof typeof queries]: ChangeDTLFn; }; const queryNames = [ @@ -77,8 +96,6 @@ interface DTLError { messageWithElementsStringified: string; } -export type BoundQueries = BoundFunctions; - export const getQueriesForElement = ( page: import('puppeteer').Page, asyncHookTracker: AsyncHookTracker, diff --git a/src/rollup-plugin-aria-query.js b/src/rollup-plugin-aria-query.js index 8ad2bad7..d1561f3d 100644 --- a/src/rollup-plugin-aria-query.js +++ b/src/rollup-plugin-aria-query.js @@ -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) => { diff --git a/tests/jest-dom-matchers/toBePartiallyChecked.test.ts b/tests/jest-dom-matchers/toBePartiallyChecked.test.ts index a76db04a..81b148ac 100644 --- a/tests/jest-dom-matchers/toBePartiallyChecked.test.ts +++ b/tests/jest-dom-matchers/toBePartiallyChecked.test.ts @@ -1,4 +1,3 @@ -import type { ElementHandle } from 'puppeteer'; import { withBrowser } from 'pleasantest'; test( @@ -30,9 +29,10 @@ test( const ariaCheckboxUnchecked = await screen.getByTestId( 'aria-checkbox-unchecked', ); - const inputCheckboxIndeterminate = (await screen.getByTestId( - 'input-checkbox-indeterminate', - )) as ElementHandle; + const inputCheckboxIndeterminate = + await screen.getByTestId( + 'input-checkbox-indeterminate', + ); await expect(ariaCheckboxMixed).toBePartiallyChecked(); diff --git a/tests/jest-dom-matchers/toHaveFocus.test.ts b/tests/jest-dom-matchers/toHaveFocus.test.ts index dc33536e..369cab73 100644 --- a/tests/jest-dom-matchers/toHaveFocus.test.ts +++ b/tests/jest-dom-matchers/toHaveFocus.test.ts @@ -1,4 +1,3 @@ -import type { ElementHandle } from 'puppeteer'; import { withBrowser } from 'pleasantest'; test( @@ -8,9 +7,7 @@ test( `
`, ); - const input: ElementHandle = await screen.getByTestId( - 'element-to-focus', - ); + const input = await screen.getByTestId('element-to-focus'); await input.focus(); await expect(input).toHaveFocus(); diff --git a/tests/testing-library-queries/variants-of-queries.test.ts b/tests/testing-library-queries/variants-of-queries.test.ts index 977ab16a..15b368ac 100644 --- a/tests/testing-library-queries/variants-of-queries.test.ts +++ b/tests/testing-library-queries/variants-of-queries.test.ts @@ -1,3 +1,4 @@ +import type { ElementHandle } from 'pleasantest'; import { withBrowser } from 'pleasantest'; const singleElementMarkup = ` @@ -9,12 +10,37 @@ const multipleElementMarkup = `

Hello

`; +// @ts-expect-error T1 is intentionally unused, assertType is only used at type-time +const assertType = () => {}; + +// Checks if two types are equal (A extends B and B extends A) +// Contains special case for ElementHandle's, +// where ElementHandle extends ElementHandle even if A does not extend A +type Equal = A extends (infer T1)[] + ? B extends (infer T2)[] + ? Equal + : false + : A extends ElementHandle + ? B extends ElementHandle + ? Equal + : 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, typeof t1>>(); + + const t2 = await screen.findByText(/Hello/); + assertType, typeof t2>>(); await expect(screen.findByText(/Hellooooo/, {}, { timeout: 5 })).rejects .toThrowErrorMatchingInlineSnapshot(` @@ -106,6 +132,14 @@ test( setTimeout(() => utils.injectHTML(singleElementMarkup), 5); expect(await screen.findAllByText(/Hello/)).toHaveLength(1); + const t1 = await screen.findAllByText(/Hello/); + assertType[], typeof t1>>(); + + const t2 = await screen.findAllByText(/Hello/); + assertType[], typeof t2>>(); + + assertType[]>>(); + 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.