From 3a96a3d0e8876308b9c2dd25d54e71a3c2b0826f Mon Sep 17 00:00:00 2001 From: Vladimir Date: Thu, 20 Jun 2024 13:08:02 +0200 Subject: [PATCH] feat(browser): introduce `expect.dom` method and bundle `jest-dom` matchers with `@vitest/browser` (#5910) --- docs/config/index.md | 4 + docs/guide/browser.md | 449 ++++++++-- eslint.config.js | 1 + packages/browser/jest-dom.d.ts | 816 ++++++++++++++++++ packages/browser/matchers.d.ts | 22 + packages/browser/package.json | 4 + packages/browser/providers/playwright.d.ts | 1 + packages/browser/providers/webdriverio.d.ts | 1 + packages/browser/rollup.config.js | 21 +- .../browser/src/client/tester/expect-dom.ts | 10 + .../browser/src/client/tester/jest-dom.ts | 1 + packages/browser/src/client/tester/tester.ts | 2 + packages/vitest/src/types/index.ts | 1 + pnpm-lock.yaml | 39 + test/browser/test/dom.test.ts | 2 +- test/browser/tsconfig.json | 5 +- 16 files changed, 1284 insertions(+), 95 deletions(-) create mode 100644 packages/browser/jest-dom.d.ts create mode 100644 packages/browser/matchers.d.ts create mode 100644 packages/browser/src/client/tester/expect-dom.ts create mode 100644 packages/browser/src/client/tester/jest-dom.ts diff --git a/docs/config/index.md b/docs/config/index.md index a092e3a28853..eee75fb48c71 100644 --- a/docs/config/index.md +++ b/docs/config/index.md @@ -407,6 +407,10 @@ browser-like environment through either [`jsdom`](https://github.com/jsdom/jsdom or [`happy-dom`](https://github.com/capricorn86/happy-dom) instead. If you are building edge functions, you can use [`edge-runtime`](https://edge-runtime.vercel.app/packages/vm) environment +::: tip +You can also use [Browser Mode](/guide/browser) to run integration or unit tests in the browser without mocking the environment. +::: + By adding a `@vitest-environment` docblock or comment at the top of the file, you can specify another environment to be used for all tests in that file: diff --git a/docs/guide/browser.md b/docs/guide/browser.md index 098759b91672..e7163737057a 100644 --- a/docs/guide/browser.md +++ b/docs/guide/browser.md @@ -79,6 +79,112 @@ export default defineConfig({ }) ``` +If you have not used Vite before, make sure you have your framework's plugin installed and specified in the config. Some frameworks might require extra configuration to work - check their Vite related documentation to be sure. + +::: code-group +```ts [vue] +import { defineConfig } from 'vitest/config' +import vue from '@vitejs/plugin-vue' + +export default defineConfig({ + plugins: [vue()], + test: { + browser: { + enabled: true, + provider: 'playwright', + name: 'chrome', + } + } +}) +``` +```ts [svelte] +import { defineConfig } from 'vitest/config' +import { svelte } from '@sveltejs/vite-plugin-svelte' + +export default defineConfig({ + plugins: [svelte()], + test: { + browser: { + enabled: true, + provider: 'playwright', + name: 'chrome', + } + } +}) +``` +```ts [solid] +import { defineConfig } from 'vitest/config' +import solidPlugin from 'vite-plugin-solid' + +export default defineConfig({ + plugins: [solidPlugin()], + test: { + browser: { + enabled: true, + provider: 'playwright', + name: 'chrome', + } + } +}) +``` +```ts [marko] +import { defineConfig } from 'vitest/config' +import marko from '@marko/vite' + +export default defineConfig({ + plugins: [marko()], + test: { + browser: { + enabled: true, + provider: 'playwright', + name: 'chrome', + } + } +}) +``` +::: + +::: tip +`react` doesn't require a plugin to work, but `preact` requires [extra configuration](https://preactjs.com/guide/v10/getting-started/#create-a-vite-powered-preact-app) to make aliases work. +::: + +If you need to run some tests using Node-based runner, you can define a [workspace](/guide/workspace) file with separate configurations for different testing strategies: + +```ts +// vitest.workspace.ts +import { defineWorkspace } from 'vitest/config' + +export default defineWorkspace([ + { + test: { + // an example of file based convention, + // you don't have to follow it + include: [ + 'tests/unit/**/*.{test,spec}.ts', + 'tests/**/*.unit.{test,spec}.ts', + ], + name: 'unit', + environment: 'node', + }, + }, + { + test: { + // an example of file based convention, + // you don't have to follow it + include: [ + 'tests/browser/**/*.{test,spec}.ts', + 'tests/**/*.browser.{test,spec}.ts', + ], + name: 'browser', + browser: { + enabled: true, + name: 'chrome', + }, + }, + }, +]) +``` + ## Browser Option Types The browser option in Vitest depends on the provider. Vitest will fail, if you pass `--browser` and don't specify its name in the config file. Available options: @@ -176,6 +282,102 @@ In this case, Vitest will run in headless mode using the Chrome browser. Headless mode is not available by default. You need to use either [`playwright`](https://npmjs.com/package/playwright) or [`webdriverio`](https://www.npmjs.com/package/webdriverio) providers to enable this feature. ::: +## Assertion API + +Vitest bundles [`@testing-library/jest-dom`](https://github.com/testing-library/jest-dom) library to provide a wide range of DOM assertions out of the box. For detailed documentation, you can read the `jest-dom` readme: + +- [`toBeDisabled`](https://github.com/testing-library/jest-dom#toBeDisabled) +- [`toBeEnabled`](https://github.com/testing-library/jest-dom#toBeEnabled) +- [`toBeEmptyDOMElement`](https://github.com/testing-library/jest-dom#toBeEmptyDOMElement) +- [`toBeInTheDocument`](https://github.com/testing-library/jest-dom#toBeInTheDocument) +- [`toBeInvalid`](https://github.com/testing-library/jest-dom#toBeInvalid) +- [`toBeRequired`](https://github.com/testing-library/jest-dom#toBeRequired) +- [`toBeValid`](https://github.com/testing-library/jest-dom#toBeValid) +- [`toBeVisible`](https://github.com/testing-library/jest-dom#toBeVisible) +- [`toContainElement`](https://github.com/testing-library/jest-dom#toContainElement) +- [`toContainHTML`](https://github.com/testing-library/jest-dom#toContainHTML) +- [`toHaveAccessibleDescription`](https://github.com/testing-library/jest-dom#toHaveAccessibleDescription) +- [`toHaveAccessibleErrorMessage`](https://github.com/testing-library/jest-dom#toHaveAccessibleErrorMessage) +- [`toHaveAccessibleName`](https://github.com/testing-library/jest-dom#toHaveAccessibleName) +- [`toHaveAttribute`](https://github.com/testing-library/jest-dom#toHaveAttribute) +- [`toHaveClass`](https://github.com/testing-library/jest-dom#toHaveClass) +- [`toHaveFocus`](https://github.com/testing-library/jest-dom#toHaveFocus) +- [`toHaveFormValues`](https://github.com/testing-library/jest-dom#toHaveFormValues) +- [`toHaveStyle`](https://github.com/testing-library/jest-dom#toHaveStyle) +- [`toHaveTextContent`](https://github.com/testing-library/jest-dom#toHaveTextContent) +- [`toHaveValue`](https://github.com/testing-library/jest-dom#toHaveValue) +- [`toHaveDisplayValue`](https://github.com/testing-library/jest-dom#toHaveDisplayValue) +- [`toBeChecked`](https://github.com/testing-library/jest-dom#toBeChecked) +- [`toBePartiallyChecked`](https://github.com/testing-library/jest-dom#toBePartiallyChecked) +- [`toHaveRole`](https://github.com/testing-library/jest-dom#toHaveRole) +- [`toHaveErrorMessage`](https://github.com/testing-library/jest-dom#toHaveErrorMessage) + +If you are using TypeScript or want to have correct type hints in `expect`, make sure you have either `@vitest/browser/providers/playwright` or `@vitest/browser/providers/webdriverio` specified in your `tsconfig` depending on the provider you use. If you use the default `preview` provider, you can specify `@vitest/browser/matchers` instead. + +::: code-group +```json [preview] +{ + "compilerOptions": { + "types": [ + "@vitest/browser/matchers" + ] + } +} +``` +```json [playwright] +{ + "compilerOptions": { + "types": [ + "@vitest/browser/providers/playwright" + ] + } +} +``` +```json [webdriverio] +{ + "compilerOptions": { + "types": [ + "@vitest/browser/providers/webdriverio" + ] + } +} +``` +::: + +## Retry-ability + +Tests in the browser might fail inconsistently due to their asynchronous nature. Because of this, it is important to have a way to guarantee that assertions succeed even if the condition is delayed (by a timeout, network request, or animation, for example). For this purpose, Vitest provides retriable assertions out of the box via the [`expect.poll`](/api/expect#poll) and `expect.element` APIs: + +```ts +import { expect, test } from 'vitest' +import { screen } from '@testing-library/dom' + +test('error banner is rendered', async () => { + triggerError() + + // @testing-library provides queries with built-in retry-ability + // It will try to find the banner until it's rendered + const banner = await screen.findByRole('alert', { + name: /error/i, + }) + + // Vitest provides `expect.element` with built-in retry-ability + // It will check `element.textContent` until it's equal to "Error!" + await expect.element(banner).toHaveTextContent('Error!') +}) +``` + +::: tip +`expect.element` is a shorthand for `expect.poll(() => element)` and works in exactly the same way. + +`toHaveTextContent` and all other [`@testing-library/jest-dom`](https://github.com/testing-library/jest-dom) assertions are still available on a regular `expect` without a built-in retry-ability mechanism: + +```ts +// will fail immediately if .textContent is not `'Error!'` +expect(banner).toHaveTextContent('Error!') +``` +::: + ## Context Vitest exposes a context module via `@vitest/browser/context` entry point. As of 2.0, it exposes a small set of utilities that might be useful to you in tests. @@ -207,111 +409,26 @@ export const server: { } /** - * Handler for user interactions. The support is provided by the browser provider (`playwright` or `webdriverio`). + * Handler for user interactions. The support is implemented by the browser provider (`playwright` or `webdriverio`). * If used with `preview` provider, fallbacks to simulated events via `@testing-library/user-event`. * @experimental */ export const userEvent: { setup: () => UserEvent - /** - * Click on an element. Uses provider's API under the hood and supports all its options. - * @see {@link https://playwright.dev/docs/api/class-locator#locator-click} Playwright API - * @see {@link https://webdriver.io/docs/api/element/click/} WebdriverIO API - * @see {@link https://testing-library.com/docs/user-event/convenience/#click} testing-library API - */ click: (element: Element, options?: UserEventClickOptions) => Promise - /** - * Triggers a double click event on an element. Uses provider's API under the hood. - * @see {@link https://playwright.dev/docs/api/class-locator#locator-dblclick} Playwright API - * @see {@link https://webdriver.io/docs/api/element/doubleClick/} WebdriverIO API - * @see {@link https://testing-library.com/docs/user-event/convenience/#dblClick} testing-library API - */ dblClick: (element: Element, options?: UserEventDoubleClickOptions) => Promise - /** - * Choose one or more values from a select element. Uses provider's API under the hood. - * If select doesn't have `multiple` attribute, only the first value will be selected. - * @see {@link https://playwright.dev/docs/api/class-locator#locator-select-option} Playwright API - * @see {@link https://webdriver.io/docs/api/element/doubleClick/} WebdriverIO API - * @see {@link https://testing-library.com/docs/user-event/utility/#-selectoptions-deselectoptions} testing-library API - */ selectOptions: ( element: Element, values: HTMLElement | HTMLElement[] | string | string[], options?: UserEventSelectOptions, ) => Promise - /** - * Type text on the keyboard. If any input is focused, it will receive the text, - * otherwise it will be typed on the document. Uses provider's API under the hood. - * **Supports** [user-event `keyboard` syntax](https://testing-library.com/docs/user-event/keyboard) (e.g., `{Shift}`) even with `playwright` and `webdriverio` providers. - * @example - * await userEvent.keyboard('foo') // translates to: f, o, o - * await userEvent.keyboard('{{a[[') // translates to: {, a, [ - * await userEvent.keyboard('{Shift}{f}{o}{o}') // translates to: Shift, f, o, o - * @see {@link https://playwright.dev/docs/api/class-locator#locator-press} Playwright API - * @see {@link https://webdriver.io/docs/api/browser/action#key-input-source} WebdriverIO API - * @see {@link https://testing-library.com/docs/user-event/keyboard} testing-library API - */ keyboard: (text: string) => Promise - /** - * Types text into an element. Uses provider's API under the hood. - * **Supports** [user-event `keyboard` syntax](https://testing-library.com/docs/user-event/keyboard) (e.g., `{Shift}`) even with `playwright` and `webdriverio` providers. - * @example - * await userEvent.type(input, 'foo') // translates to: f, o, o - * await userEvent.type(input, '{{a[[') // translates to: {, a, [ - * await userEvent.type(input, '{Shift}{f}{o}{o}') // translates to: Shift, f, o, o - * @see {@link https://playwright.dev/docs/api/class-locator#locator-press} Playwright API - * @see {@link https://webdriver.io/docs/api/browser/action#key-input-source} WebdriverIO API - * @see {@link https://testing-library.com/docs/user-event/utility/#type} testing-library API - */ type: (element: Element, text: string, options?: UserEventTypeOptions) => Promise - /** - * Removes all text from an element. Uses provider's API under the hood. - * @see {@link https://playwright.dev/docs/api/class-locator#locator-clear} Playwright API - * @see {@link https://webdriver.io/docs/api/element/clearValue} WebdriverIO API - * @see {@link https://testing-library.com/docs/user-event/utility/#clear} testing-library API - */ clear: (element: Element) => Promise - /** - * Sends a `Tab` key event. Uses provider's API under the hood. - * @see {@link https://playwright.dev/docs/api/class-locator#locator-press} Playwright API - * @see {@link https://webdriver.io/docs/api/element/keys} WebdriverIO API - * @see {@link https://testing-library.com/docs/user-event/convenience/#tab} testing-library API - */ tab: (options?: UserEventTabOptions) => Promise - /** - * Hovers over an element. Uses provider's API under the hood. - * @see {@link https://playwright.dev/docs/api/class-locator#locator-hover} Playwright API - * @see {@link https://webdriver.io/docs/api/element/moveTo/} WebdriverIO API - * @see {@link https://testing-library.com/docs/user-event/convenience/#hover} testing-library API - */ hover: (element: Element, options?: UserEventHoverOptions) => Promise - /** - * Moves cursor position to the body element. Uses provider's API under the hood. - * By default, the cursor position is in the center (in webdriverio) or in some visible place (in playwright) - * of the body element, so if the current element is already there, this will have no effect. - * @see {@link https://playwright.dev/docs/api/class-locator#locator-hover} Playwright API - * @see {@link https://webdriver.io/docs/api/element/moveTo/} WebdriverIO API - * @see {@link https://testing-library.com/docs/user-event/convenience/#hover} testing-library API - */ unhover: (element: Element, options?: UserEventHoverOptions) => Promise - /** - * Fills an input element with text. This will remove any existing text in the input before typing the new value. - * Uses provider's API under the hood. - * This API is faster than using `userEvent.type` or `userEvent.keyboard`, but it **doesn't support** [user-event `keyboard` syntax](https://testing-library.com/docs/user-event/keyboard) (e.g., `{Shift}`). - * @example - * await userEvent.fill(input, 'foo') // translates to: f, o, o - * await userEvent.fill(input, '{{a[[') // translates to: {, {, a, [, [ - * await userEvent.fill(input, '{Shift}') // translates to: {, S, h, i, f, t, } - * @see {@link https://playwright.dev/docs/api/class-locator#locator-fill} Playwright API - * @see {@link https://webdriver.io/docs/api/element/setValue} WebdriverIO API - * @see {@link https://testing-library.com/docs/user-event/utility/#type} testing-library API - */ fill: (element: Element, text: string, options?: UserEventFillOptions) => Promise - /** - * Drags a source element on top of the target element. This API is not supported by "preview" provider. - * @see {@link https://playwright.dev/docs/api/class-frame#frame-drag-and-drop} Playwright API - * @see {@link https://webdriver.io/docs/api/element/dragAndDrop/} WebdriverIO API - */ dragAndDrop: (source: Element, target: Element, options?: UserEventDragAndDropOptions) => Promise } @@ -480,9 +597,8 @@ Sends a `Tab` key event. This is a shorthand for `userEvent.keyboard('{tab}')`. ```ts import { userEvent } from '@vitest/browser/context' import { screen } from '@testing-library/dom' -import '@testing-library/jest-dom' // adds support for "toHaveFocus" -test('tab works', () => { +test('tab works', async () => { const [input1, input2] = screen.getAllByRole('input') expect(input1).toHaveFocus() @@ -545,7 +661,6 @@ This method clear the input element content. ```ts import { userEvent } from '@vitest/browser/context' import { screen } from '@testing-library/dom' -import '@testing-library/jest-dom' // adds support for "toHaveValue" test('clears input', () => { const input = screen.getByRole('input') @@ -579,7 +694,6 @@ Unlike `@testing-library`, Vitest doesn't support [listbox](https://developer.mo ```ts import { userEvent } from '@vitest/browser/context' import { screen } from '@testing-library/dom' -import '@testing-library/jest-dom' // adds support for "toHaveValue" test('clears input', () => { const select = screen.getByRole('select') @@ -847,6 +961,161 @@ If you are using TypeScript, don't forget to add `@vitest/browser/providers/webd ``` ::: +## Examples + +Browser Mode is framework agnostic so it doesn't provide any method to render your components. However, you should be able to use your framework's test utils packages. + +We recommend using `testing-library` packages depending on your framework: + +- [`@testing-library/dom`](https://testing-library.com/docs/dom-testing-library/intro) if you don't use a framework +- [`@testing-library/vue`](https://testing-library.com/docs/vue-testing-library/intro) to render [vue](https://vuejs.org) components +- [`@testing-library/svelte`](https://testing-library.com/docs/svelte-testing-library/intro) to render [svelte](https://svelte.dev) components +- [`@testing-library/react`](https://testing-library.com/docs/react-testing-library/intro) to render [react](https://react.dev) components +- [`@testing-library/preact`](https://testing-library.com/docs/preact-testing-library/intro) to render [preact](https://preactjs.com) components +- [`@testing-library/solid`](https://testing-library.com/docs/solid-testing-library/intro) to render [solid](https://www.solidjs.com) components +- [`@marko/testing-library`](https://testing-library.com/docs/marko-testing-library/intro) to render [marko](https://markojs.com) components + +::: warning +`testing-library` provides a package `@testing-library/user-event`. We do not recommend using it directly because it simulates events instead of actually triggering them - instead, use [`userEvent`](#interactivity-api) imported from `@vitest/browser/context` that uses Chrome DevTools Protocol or Webdriver (depending on the provider) under the hood. +::: + +::: code-group +```ts [vue] +// based on @testing-library/vue example +// https://testing-library.com/docs/vue-testing-library/examples + +import { userEvent } from '@vitest/browser/context' +import { render, screen } from '@testing-library/vue' +import Component from './Component.vue' + +test('properly handles v-model', async () => { + render(Component) + + // Asserts initial state. + expect(screen.getByText('Hi, my name is Alice')).toBeInTheDocument() + + // Get the input DOM node by querying the associated label. + const usernameInput = await screen.findByLabelText(/username/i) + + // Type the name into the input. This already validates that the input + // is filled correctly, no need to check the value manually. + await userEvent.fill(usernameInput, 'Bob') + + expect(screen.getByText('Hi, my name is Alice')).toBeInTheDocument() +}) +``` +```ts [svelte] +// based on @testing-library/svelte +// https://testing-library.com/docs/svelte-testing-library/example + +import { render, screen } from '@testing-library/svelte' +import { userEvent } from '@vitest/browser/context' +import { expect, test } from 'vitest' + +import Greeter from './greeter.svelte' + +test('greeting appears on click', async () => { + const user = userEvent.setup() + render(Greeter, { name: 'World' }) + + const button = screen.getByRole('button') + await user.click(button) + const greeting = await screen.findByText(/hello world/iu) + + expect(greeting).toBeInTheDocument() +}) +``` +```tsx [react] +// based on @testing-library/react example +// https://testing-library.com/docs/react-testing-library/example-intro + +import { userEvent } from '@vitest/browser/context' +import { render, screen } from '@testing-library/react' +import Fetch from './fetch' + +test('loads and displays greeting', async () => { + // Render a React element into the DOM + render() + + await userEvent.click(screen.getByText('Load Greeting')) + // wait before throwing an error if it cannot find an element + const heading = await screen.findByRole('heading') + + // assert that the alert message is correct + expect(heading).toHaveTextContent('hello there') + expect(screen.getByRole('button')).toBeDisabled() +}) +``` +```tsx [preact] +// based on @testing-library/preact example +// https://testing-library.com/docs/preact-testing-library/example + +import { h } from 'preact' +import { userEvent } from '@vitest/browser/context' +import { render } from '@testing-library/preact' + +import HiddenMessage from '../hidden-message' + +test('shows the children when the checkbox is checked', async () => { + const testMessage = 'Test Message' + + const { queryByText, getByLabelText, getByText } = render( + {testMessage}, + ) + + // query* functions will return the element or null if it cannot be found. + // get* functions will return the element or throw an error if it cannot be found. + expect(queryByText(testMessage)).not.toBeInTheDocument() + + // The queries can accept a regex to make your selectors more + // resilient to content tweaks and changes. + await userEvent.click(getByLabelText(/show/i)) + + expect(getByText(testMessage)).toBeInTheDocument() +}) +``` +```tsx [solid] +// baed on @testing-library/solid API +// https://testing-library.com/docs/solid-testing-library/api + +import { render } from '@testing-library/solid' + +it('uses params', async () => { + const App = () => ( + <> + ( +

+ Id: + {useParams()?.id} +

+ )} + /> +

Start

} /> + + ) + const { findByText } = render(() => , { location: 'ids/1234' }) + expect(await findByText('Id: 1234')).toBeInTheDocument() +}) +``` +```ts [marko] +// baed on @testing-library/marko API +// https://testing-library.com/docs/marko-testing-library/api + +import { render, screen } from '@marko/testing-library' +import Greeting from './greeting.marko' + +test('renders a message', async () => { + const { container } = await render(Greeting, { name: 'Marko' }) + expect(screen.getByText(/Marko/)).toBeInTheDocument() + expect(container.firstChild).toMatchInlineSnapshot(` +

Hello, Marko!

+ `) +}) +``` +::: + ## Limitations ### Thread Blocking Dialogs diff --git a/eslint.config.js b/eslint.config.js index 87d66c1f18d1..1034d607f7e5 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -12,6 +12,7 @@ export default antfu( '**/bench.json', '**/fixtures', '**/assets/**', + '**/*.d.ts', '**/*.timestamp-*', 'test/core/src/self', 'test/cache/cache/.vitest-base/results.json', diff --git a/packages/browser/jest-dom.d.ts b/packages/browser/jest-dom.d.ts new file mode 100644 index 000000000000..9c75da5eef7f --- /dev/null +++ b/packages/browser/jest-dom.d.ts @@ -0,0 +1,816 @@ +// Disable automatic exports. + + +type ARIAWidgetRole = + | "button" + | "checkbox" + | "gridcell" + | "link" + | "menuitem" + | "menuitemcheckbox" + | "menuitemradio" + | "option" + | "progressbar" + | "radio" + | "scrollbar" + | "searchbox" + | "slider" + | "spinbutton" + | "switch" + | "tab" + | "tabpanel" + | "textbox" + | "treeitem"; + +type ARIACompositeWidgetRole = + | "combobox" + | "grid" + | "listbox" + | "menu" + | "menubar" + | "radiogroup" + | "tablist" + | "tree" + | "treegrid"; + +type ARIADocumentStructureRole = + | "application" + | "article" + | "blockquote" + | "caption" + | "cell" + | "columnheader" + | "definition" + | "deletion" + | "directory" + | "document" + | "emphasis" + | "feed" + | "figure" + | "generic" + | "group" + | "heading" + | "img" + | "insertion" + | "list" + | "listitem" + | "math" + | "meter" + | "none" + | "note" + | "paragraph" + | "presentation" + | "row" + | "rowgroup" + | "rowheader" + | "separator" + | "strong" + | "subscript" + | "superscript" + | "table" + | "term" + | "time" + | "toolbar" + | "tooltip"; + +type ARIALandmarkRole = + | "banner" + | "complementary" + | "contentinfo" + | "form" + | "main" + | "navigation" + | "region" + | "search"; + +type ARIALiveRegionRole = "alert" | "log" | "marquee" | "status" | "timer"; + +type ARIAWindowRole = "alertdialog" | "dialog"; + +type ARIAUncategorizedRole = "code"; + +type ARIARole = + | ARIAWidgetRole + | ARIACompositeWidgetRole + | ARIADocumentStructureRole + | ARIALandmarkRole + | ARIALiveRegionRole + | ARIAWindowRole + | ARIAUncategorizedRole; + +declare namespace matchers { + interface TestingLibraryMatchers { + /** + * @deprecated + * since v1.9.0 + * @description + * Assert whether a value is a DOM element, or not. Contrary to what its name implies, this matcher only checks + * that you passed to it a valid DOM element. + * + * It does not have a clear definition of what "the DOM" is. Therefore, it does not check whether that element + * is contained anywhere. + * @see + * [testing-library/jest-dom#toBeInTheDom](https://github.com/testing-library/jest-dom#toBeInTheDom) + */ + toBeInTheDOM(container?: HTMLElement | SVGElement): R + /** + * @description + * Assert whether an element is present in the document or not. + * @example + * + * + * expect(queryByTestId('svg-element')).toBeInTheDocument() + * expect(queryByTestId('does-not-exist')).not.toBeInTheDocument() + * @see + * [testing-library/jest-dom#tobeinthedocument](https://github.com/testing-library/jest-dom#tobeinthedocument) + */ + toBeInTheDocument(): R + /** + * @description + * This allows you to check if an element is currently visible to the user. + * + * An element is visible if **all** the following conditions are met: + * * it does not have its css property display set to none + * * it does not have its css property visibility set to either hidden or collapse + * * it does not have its css property opacity set to 0 + * * its parent element is also visible (and so on up to the top of the DOM tree) + * * it does not have the hidden attribute + * * if `
` it has the open attribute + * @example + *
+ * Zero Opacity + *
+ * + *
Visible Example
+ * + * expect(getByTestId('zero-opacity')).not.toBeVisible() + * expect(getByTestId('visible')).toBeVisible() + * @see + * [testing-library/jest-dom#tobevisible](https://github.com/testing-library/jest-dom#tobevisible) + */ + toBeVisible(): R + /** + * @deprecated + * since v5.9.0 + * @description + * Assert whether an element has content or not. + * @example + * + * + * + * + * expect(getByTestId('empty')).toBeEmpty() + * expect(getByTestId('not-empty')).not.toBeEmpty() + * @see + * [testing-library/jest-dom#tobeempty](https://github.com/testing-library/jest-dom#tobeempty) + */ + toBeEmpty(): R + /** + * @description + * Assert whether an element has content or not. + * @example + * + * + * + * + * expect(getByTestId('empty')).toBeEmptyDOMElement() + * expect(getByTestId('not-empty')).not.toBeEmptyDOMElement() + * @see + * [testing-library/jest-dom#tobeemptydomelement](https://github.com/testing-library/jest-dom#tobeemptydomelement) + */ + toBeEmptyDOMElement(): R + /** + * @description + * Allows you to check whether an element is disabled from the user's perspective. + * + * Matches if the element is a form control and the `disabled` attribute is specified on this element or the + * element is a descendant of a form element with a `disabled` attribute. + * @example + * + * + * expect(getByTestId('button')).toBeDisabled() + * @see + * [testing-library/jest-dom#tobedisabled](https://github.com/testing-library/jest-dom#tobedisabled) + */ + toBeDisabled(): R + /** + * @description + * Allows you to check whether an element is not disabled from the user's perspective. + * + * Works like `not.toBeDisabled()`. + * + * Use this matcher to avoid double negation in your tests. + * @example + * + * + * expect(getByTestId('button')).toBeEnabled() + * @see + * [testing-library/jest-dom#tobeenabled](https://github.com/testing-library/jest-dom#tobeenabled) + */ + toBeEnabled(): R + /** + * @description + * Check if a form element, or the entire `form`, is currently invalid. + * + * An `input`, `select`, `textarea`, or `form` element is invalid if it has an `aria-invalid` attribute with no + * value or a value of "true", or if the result of `checkValidity()` is false. + * @example + * + * + *
+ * + *
+ * + * expect(getByTestId('no-aria-invalid')).not.toBeInvalid() + * expect(getByTestId('invalid-form')).toBeInvalid() + * @see + * [testing-library/jest-dom#tobeinvalid](https://github.com/testing-library/jest-dom#tobeinvalid) + */ + toBeInvalid(): R + /** + * @description + * This allows you to check if a form element is currently required. + * + * An element is required if it is having a `required` or `aria-required="true"` attribute. + * @example + * + *
+ * + * expect(getByTestId('required-input')).toBeRequired() + * expect(getByTestId('supported-role')).not.toBeRequired() + * @see + * [testing-library/jest-dom#toberequired](https://github.com/testing-library/jest-dom#toberequired) + */ + toBeRequired(): R + /** + * @description + * Allows you to check if a form element is currently required. + * + * An `input`, `select`, `textarea`, or `form` element is invalid if it has an `aria-invalid` attribute with no + * value or a value of "false", or if the result of `checkValidity()` is true. + * @example + * + * + *
+ * + *
+ * + * expect(getByTestId('no-aria-invalid')).not.toBeValid() + * expect(getByTestId('invalid-form')).toBeInvalid() + * @see + * [testing-library/jest-dom#tobevalid](https://github.com/testing-library/jest-dom#tobevalid) + */ + toBeValid(): R + /** + * @description + * Allows you to assert whether an element contains another element as a descendant or not. + * @example + * + * + * + * + * const ancestor = getByTestId('ancestor') + * const descendant = getByTestId('descendant') + * const nonExistantElement = getByTestId('does-not-exist') + * expect(ancestor).toContainElement(descendant) + * expect(descendant).not.toContainElement(ancestor) + * expect(ancestor).not.toContainElement(nonExistantElement) + * @see + * [testing-library/jest-dom#tocontainelement](https://github.com/testing-library/jest-dom#tocontainelement) + */ + toContainElement(element: HTMLElement | SVGElement | null): R + /** + * @description + * Assert whether a string representing a HTML element is contained in another element. + * @example + * + * + * expect(getByTestId('parent')).toContainHTML('') + * @see + * [testing-library/jest-dom#tocontainhtml](https://github.com/testing-library/jest-dom#tocontainhtml) + */ + toContainHTML(htmlText: string): R + /** + * @description + * Allows you to check if a given element has an attribute or not. + * + * You can also optionally check that the attribute has a specific expected value or partial match using + * [expect.stringContaining](https://jestjs.io/docs/en/expect.html#expectnotstringcontainingstring) or + * [expect.stringMatching](https://jestjs.io/docs/en/expect.html#expectstringmatchingstring-regexp). + * @example + * + * + * expect(button).toHaveAttribute('disabled') + * expect(button).toHaveAttribute('type', 'submit') + * expect(button).not.toHaveAttribute('type', 'button') + * @see + * [testing-library/jest-dom#tohaveattribute](https://github.com/testing-library/jest-dom#tohaveattribute) + */ + toHaveAttribute(attr: string, value?: unknown): R + /** + * @description + * Check whether the given element has certain classes within its `class` attribute. + * + * You must provide at least one class, unless you are asserting that an element does not have any classes. + * @example + * + * + *
no classes
+ * + * const deleteButton = getByTestId('delete-button') + * const noClasses = getByTestId('no-classes') + * expect(deleteButton).toHaveClass('btn') + * expect(deleteButton).toHaveClass('btn-danger xs') + * expect(deleteButton).toHaveClass(/danger/, 'xs') + * expect(deleteButton).toHaveClass('btn xs btn-danger', {exact: true}) + * expect(deleteButton).not.toHaveClass('btn xs btn-danger', {exact: true}) + * expect(noClasses).not.toHaveClass() + * @see + * [testing-library/jest-dom#tohaveclass](https://github.com/testing-library/jest-dom#tohaveclass) + */ + toHaveClass(...classNames: Array): R + toHaveClass(classNames: string, options?: {exact: boolean}): R + /** + * @description + * This allows you to check whether the given form element has the specified displayed value (the one the + * end user will see). It accepts , + * + * + * + * + * + * + * + * const input = screen.getByLabelText('First name') + * const textarea = screen.getByLabelText('Description') + * const selectSingle = screen.getByLabelText('Fruit') + * const selectMultiple = screen.getByLabelText('Fruits') + * + * expect(input).toHaveDisplayValue('Luca') + * expect(textarea).toHaveDisplayValue('An example description here.') + * expect(selectSingle).toHaveDisplayValue('Select a fruit...') + * expect(selectMultiple).toHaveDisplayValue(['Banana', 'Avocado']) + * + * @see + * [testing-library/jest-dom#tohavedisplayvalue](https://github.com/testing-library/jest-dom#tohavedisplayvalue) + */ + toHaveDisplayValue(value: string | RegExp | Array): R + /** + * @description + * Assert whether an element has focus or not. + * @example + *
+ * + *
+ * + * const input = getByTestId('element-to-focus') + * input.focus() + * expect(input).toHaveFocus() + * input.blur() + * expect(input).not.toHaveFocus() + * @see + * [testing-library/jest-dom#tohavefocus](https://github.com/testing-library/jest-dom#tohavefocus) + */ + toHaveFocus(): R + /** + * @description + * Check if a form or fieldset contains form controls for each given name, and having the specified value. + * + * Can only be invoked on a form or fieldset element. + * @example + *
+ * + * + * + * + *
+ * + * expect(getByTestId('login-form')).toHaveFormValues({ + * username: 'jane.doe', + * rememberMe: true, + * }) + * @see + * [testing-library/jest-dom#tohaveformvalues](https://github.com/testing-library/jest-dom#tohaveformvalues) + */ + toHaveFormValues(expectedValues: Record): R + /** + * @description + * Check if an element has specific css properties with specific values applied. + * + * Only matches if the element has *all* the expected properties applied, not just some of them. + * @example + * + * + * const button = getByTestId('submit-button') + * expect(button).toHaveStyle('background-color: green') + * expect(button).toHaveStyle({ + * 'background-color': 'green', + * display: 'none' + * }) + * @see + * [testing-library/jest-dom#tohavestyle](https://github.com/testing-library/jest-dom#tohavestyle) + */ + toHaveStyle(css: string | Record): R + /** + * @description + * Check whether the given element has a text content or not. + * + * When a string argument is passed through, it will perform a partial case-sensitive match to the element + * content. + * + * To perform a case-insensitive match, you can use a RegExp with the `/i` modifier. + * + * If you want to match the whole content, you can use a RegExp to do it. + * @example + * Text Content + * + * const element = getByTestId('text-content') + * expect(element).toHaveTextContent('Content') + * // to match the whole content + * expect(element).toHaveTextContent(/^Text Content$/) + * // to use case-insentive match + * expect(element).toHaveTextContent(/content$/i) + * expect(element).not.toHaveTextContent('content') + * @see + * [testing-library/jest-dom#tohavetextcontent](https://github.com/testing-library/jest-dom#tohavetextcontent) + */ + toHaveTextContent( + text: string | RegExp, + options?: {normalizeWhitespace: boolean}, + ): R + /** + * @description + * Check whether the given form element has the specified value. + * + * Accepts ``, `