diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml
index e6b270ac..6a727c41 100644
--- a/.github/workflows/validate.yml
+++ b/.github/workflows/validate.yml
@@ -16,7 +16,7 @@ jobs:
if: ${{ !contains(github.head_ref, 'all-contributors') }}
strategy:
matrix:
- node: [10.14, 12, 14, 15, 16]
+ node: [14, 16, 18, 20]
runs-on: ubuntu-latest
steps:
- name: ⬇️ Checkout repo
diff --git a/README.md b/README.md
index 7c6d3c65..4f0961d9 100644
--- a/README.md
+++ b/README.md
@@ -51,7 +51,10 @@ clear to read and to maintain.
- [Installation](#installation)
- [Usage](#usage)
+ - [With `@jest/globals`](#with-jestglobals)
+ - [With Vitest](#with-vitest)
- [With TypeScript](#with-typescript)
+ - [With another Jest-compatible `expect`](#with-another-jest-compatible-expect)
- [Custom matchers](#custom-matchers)
- [`toBeDisabled`](#tobedisabled)
- [`toBeEnabled`](#tobeenabled)
@@ -128,6 +131,39 @@ import '@testing-library/jest-dom'
setupFilesAfterEnv: ['/jest-setup.js']
```
+### With `@jest/globals`
+
+If you are using [`@jest/globals`][jest-globals announcement] with
+[`injectGlobals: false`][inject-globals docs], you will need to use a different
+import in your tests setup file:
+
+```javascript
+// In your own jest-setup.js (or any other name)
+import '@testing-library/jest-dom/jest-globals'
+```
+
+[jest-globals announcement]:
+ https://jestjs.io/blog/2020/05/05/jest-26#a-new-way-to-consume-jest---jestglobals
+[inject-globals docs]:
+ https://jestjs.io/docs/configuration#injectglobals-boolean
+
+### With Vitest
+
+If you are using [vitest][], this module will work as-is, but you will need to
+use a different import in your tests setup file. This file should be added to
+the [`setupFiles`][vitest setupfiles] property in your vitest config:
+
+```javascript
+// In your own vitest-setup.js (or any other name)
+import '@testing-library/jest-dom/vitest'
+
+// In vitest.config.js add (if you haven't already)
+setupFiles: ['./vitest-setup.js']
+```
+
+[vitest]: https://vitest.dev/
+[vitest setupfiles]: https://vitest.dev/config/#setupfiles
+
### With TypeScript
If you're using TypeScript, make sure your setup file is a `.ts` and not a `.js`
@@ -144,6 +180,18 @@ haven't already:
],
```
+### With another Jest-compatible `expect`
+
+If you are using a different test runner that is compatible with Jest's `expect`
+interface, it might be possible to use it with this library:
+
+```javascript
+import * as matchers from '@testing-library/jest-dom/matchers'
+import {expect} from 'my-test-runner/expect'
+
+expect.extend(matchers)
+```
+
## Custom matchers
`@testing-library/jest-dom` can work with any library or framework that returns
diff --git a/extend-expect.js b/extend-expect.js
deleted file mode 100644
index e7d19c10..00000000
--- a/extend-expect.js
+++ /dev/null
@@ -1,2 +0,0 @@
-// eslint-disable-next-line
-require('./dist/extend-expect')
diff --git a/jest-globals.d.ts b/jest-globals.d.ts
new file mode 100644
index 00000000..6c8e0b60
--- /dev/null
+++ b/jest-globals.d.ts
@@ -0,0 +1 @@
+///
diff --git a/jest-globals.js b/jest-globals.js
new file mode 100644
index 00000000..eeaf6ac9
--- /dev/null
+++ b/jest-globals.js
@@ -0,0 +1,4 @@
+const globals = require('@jest/globals')
+const extensions = require('./dist/matchers')
+
+globals.expect.extend(extensions)
diff --git a/matchers.d.ts b/matchers.d.ts
new file mode 100644
index 00000000..c1ec8ce5
--- /dev/null
+++ b/matchers.d.ts
@@ -0,0 +1,3 @@
+import * as matchers from './types/matchers'
+
+export = matchers
diff --git a/package.json b/package.json
index 0304d2c1..f8346ee5 100644
--- a/package.json
+++ b/package.json
@@ -3,8 +3,9 @@
"version": "0.0.0-semantically-released",
"description": "Custom jest matchers to test the state of the DOM",
"main": "dist/index.js",
+ "types": "types/index.d.ts",
"engines": {
- "node": ">=8",
+ "node": ">=14",
"npm": ">=6",
"yarn": ">=1"
},
@@ -19,8 +20,11 @@
},
"files": [
"dist",
- "extend-expect.js",
- "matchers.js"
+ "types",
+ "*.d.ts",
+ "jest-globals.js",
+ "matchers.js",
+ "vitest.js"
],
"keywords": [
"testing",
@@ -32,7 +36,6 @@
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.9.2",
- "@types/testing-library__jest-dom": "^5.9.1",
"aria-query": "^5.0.0",
"chalk": "^3.0.0",
"@adobe/css-tools": "^4.0.1",
@@ -42,16 +45,44 @@
"redent": "^3.0.0"
},
"devDependencies": {
+ "@jest/globals": "^29.6.2",
+ "expect": "^29.6.2",
"jest-environment-jsdom-sixteen": "^1.0.3",
"jest-watch-select-projects": "^2.0.0",
"jsdom": "^16.2.1",
- "kcd-scripts": "^11.1.0",
- "pretty-format": "^25.1.0"
+ "kcd-scripts": "^14.0.0",
+ "pretty-format": "^25.1.0",
+ "vitest": "^0.34.1",
+ "typescript": "^5.1.6"
+ },
+ "peerDependencies": {
+ "@jest/globals": ">= 28",
+ "@types/jest": ">= 28",
+ "jest": ">= 28",
+ "vitest": ">= 0.32"
+ },
+ "peerDependenciesMeta": {
+ "@jest/globals": {
+ "optional": true
+ },
+ "@types/jest": {
+ "optional": true
+ },
+ "jest": {
+ "optional": true
+ },
+ "vitest": {
+ "optional": true
+ }
},
"eslintConfig": {
"extends": "./node_modules/kcd-scripts/eslint.js",
+ "parserOptions": {
+ "sourceType": "module",
+ "ecmaVersion": 2020
+ },
"rules": {
- "@babel/no-invalid-this": "off"
+ "no-invalid-this": "off"
},
"overrides": [
{
@@ -61,6 +92,18 @@
"rules": {
"max-lines-per-function": "off"
}
+ },
+ {
+ "files": [
+ "**/*.d.ts"
+ ],
+ "rules": {
+ "@typescript-eslint/no-empty-interface": "off",
+ "@typescript-eslint/no-explicit-any": "off",
+ "@typescript-eslint/no-invalid-void-type": "off",
+ "@typescript-eslint/no-unused-vars": "off",
+ "@typescript-eslint/triple-slash-reference": "off"
+ }
}
]
},
diff --git a/src/extend-expect.js b/src/extend-expect.js
deleted file mode 100644
index 3801a1d5..00000000
--- a/src/extend-expect.js
+++ /dev/null
@@ -1,3 +0,0 @@
-import * as extensions from './matchers'
-
-expect.extend(extensions)
diff --git a/src/index.js b/src/index.js
index 8cecbe35..3801a1d5 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1 +1,3 @@
-import './extend-expect'
+import * as extensions from './matchers'
+
+expect.extend(extensions)
diff --git a/src/to-be-in-the-document.js b/src/to-be-in-the-document.js
index 8ccc451a..a7eda78e 100644
--- a/src/to-be-in-the-document.js
+++ b/src/to-be-in-the-document.js
@@ -29,7 +29,7 @@ export function toBeInTheDocument(element) {
'',
),
'',
- // eslint-disable-next-line @babel/new-cap
+ // eslint-disable-next-line new-cap
this.utils.RECEIVED_COLOR(this.isNot ? errorFound() : errorNotFound()),
].join('\n')
},
diff --git a/src/to-contain-element.js b/src/to-contain-element.js
index c94ddbf9..445a6120 100644
--- a/src/to-contain-element.js
+++ b/src/to-contain-element.js
@@ -17,7 +17,7 @@ export function toContainElement(container, element) {
'element',
),
'',
- // eslint-disable-next-line @babel/new-cap
+ // eslint-disable-next-line new-cap
this.utils.RECEIVED_COLOR(`${this.utils.stringify(
container.cloneNode(false),
)} ${
diff --git a/src/to-contain-html.js b/src/to-contain-html.js
index ccbff5f5..30158ee1 100644
--- a/src/to-contain-html.js
+++ b/src/to-contain-html.js
@@ -23,7 +23,7 @@ export function toContainHTML(container, htmlText) {
'',
),
'Expected:',
- // eslint-disable-next-line @babel/new-cap
+ // eslint-disable-next-line new-cap
` ${this.utils.EXPECTED_COLOR(htmlText)}`,
'Received:',
` ${this.utils.printReceived(container.cloneNode(true))}`,
diff --git a/src/utils.js b/src/utils.js
index cdbb1088..2b45be02 100644
--- a/src/utils.js
+++ b/src/utils.js
@@ -28,7 +28,7 @@ class GenericTypeError extends Error {
'',
),
'',
- // eslint-disable-next-line @babel/new-cap
+ // eslint-disable-next-line new-cap
`${context.utils.RECEIVED_COLOR(
'received',
)} value must ${expectedString}.`,
@@ -91,9 +91,9 @@ class InvalidCSSError extends Error {
this.message = [
received.message,
'',
- // eslint-disable-next-line @babel/new-cap
+ // eslint-disable-next-line new-cap
context.utils.RECEIVED_COLOR(`Failing css:`),
- // eslint-disable-next-line @babel/new-cap
+ // eslint-disable-next-line new-cap
context.utils.RECEIVED_COLOR(`${received.css}`),
].join('\n')
}
@@ -137,11 +137,11 @@ function getMessage(
) {
return [
`${matcher}\n`,
- // eslint-disable-next-line @babel/new-cap
+ // eslint-disable-next-line new-cap
`${expectedLabel}:\n${context.utils.EXPECTED_COLOR(
redent(display(context, expectedValue), 2),
)}`,
- // eslint-disable-next-line @babel/new-cap
+ // eslint-disable-next-line new-cap
`${receivedLabel}:\n${context.utils.RECEIVED_COLOR(
redent(display(context, receivedValue), 2),
)}`,
diff --git a/tests/setup-env.js b/tests/setup-env.js
index a9325d25..151f6e7b 100644
--- a/tests/setup-env.js
+++ b/tests/setup-env.js
@@ -1,4 +1,4 @@
import {plugins} from 'pretty-format'
-import '../src/extend-expect'
+import '../src/index'
expect.addSnapshotSerializer(plugins.ConvertAnsi)
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 00000000..9a426aba
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,7 @@
+{
+ "compilerOptions": {
+ "strict": true,
+ "skipLibCheck": true
+ },
+ "include": ["*.d.ts", "types"]
+}
diff --git a/types/index.d.ts b/types/index.d.ts
new file mode 100644
index 00000000..cebdd1af
--- /dev/null
+++ b/types/index.d.ts
@@ -0,0 +1 @@
+///
diff --git a/types/jest-globals.d.ts b/types/jest-globals.d.ts
new file mode 100644
index 00000000..f7de8014
--- /dev/null
+++ b/types/jest-globals.d.ts
@@ -0,0 +1,8 @@
+import {type expect} from '@jest/globals'
+import {type TestingLibraryMatchers} from './matchers'
+
+export {}
+declare module '@jest/expect' {
+ export interface Matchers>
+ extends TestingLibraryMatchers {}
+}
diff --git a/types/jest.d.ts b/types/jest.d.ts
new file mode 100644
index 00000000..bca4339c
--- /dev/null
+++ b/types/jest.d.ts
@@ -0,0 +1,10 @@
+///
+
+import {type TestingLibraryMatchers} from './matchers'
+
+declare global {
+ namespace jest {
+ interface Matchers
+ extends TestingLibraryMatchers {}
+ }
+}
diff --git a/types/matchers.d.ts b/types/matchers.d.ts
new file mode 100755
index 00000000..af43570d
--- /dev/null
+++ b/types/matchers.d.ts
@@ -0,0 +1,666 @@
+declare namespace matchers {
+ interface TestingLibraryMatchers extends Record {
+ /**
+ * @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('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: string[]): 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 ,
+ *
+ *
+ *
+ * expect(getByTestId('img-alt')).toHaveAccessibleName('Test alt')
+ * expect(getByTestId('img-empty-alt')).not.toHaveAccessibleName()
+ * expect(getByTestId('svg-title')).toHaveAccessibleName('Test title')
+ * expect(getByTestId('button-img-alt')).toHaveAccessibleName()
+ * expect(getByTestId('img-paragraph')).not.toHaveAccessibleName()
+ * expect(getByTestId('svg-button')).toHaveAccessibleName()
+ * expect(getByTestId('svg-without-title')).not.toHaveAccessibleName()
+ * expect(getByTestId('input-title')).toHaveAccessibleName()
+ * @see
+ * [testing-library/jest-dom#tohaveaccessiblename](https://github.com/testing-library/jest-dom#tohaveaccessiblename)
+ */
+ toHaveAccessibleName(text?: string | RegExp | E): R
+ /**
+ * @description
+ * This allows you to check whether the given element is partially checked.
+ * It accepts an input of type checkbox and elements with a role of checkbox
+ * with a aria-checked="mixed", or input of type checkbox with indeterminate
+ * set to true
+ *
+ * @example
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * const ariaCheckboxMixed = getByTestId('aria-checkbox-mixed')
+ * const inputCheckboxChecked = getByTestId('input-checkbox-checked')
+ * const inputCheckboxUnchecked = getByTestId('input-checkbox-unchecked')
+ * const ariaCheckboxChecked = getByTestId('aria-checkbox-checked')
+ * const ariaCheckboxUnchecked = getByTestId('aria-checkbox-unchecked')
+ * const inputCheckboxIndeterminate = getByTestId('input-checkbox-indeterminate')
+ *
+ * expect(ariaCheckboxMixed).toBePartiallyChecked()
+ * expect(inputCheckboxChecked).not.toBePartiallyChecked()
+ * expect(inputCheckboxUnchecked).not.toBePartiallyChecked()
+ * expect(ariaCheckboxChecked).not.toBePartiallyChecked()
+ * expect(ariaCheckboxUnchecked).not.toBePartiallyChecked()
+ *
+ * inputCheckboxIndeterminate.indeterminate = true
+ * expect(inputCheckboxIndeterminate).toBePartiallyChecked()
+ * @see
+ * [testing-library/jest-dom#tobepartiallychecked](https://github.com/testing-library/jest-dom#tobepartiallychecked)
+ */
+ toBePartiallyChecked(): R
+ /**
+ * @deprecated
+ * since v5.17.0
+ *
+ * @description
+ * Check whether the given element has an [ARIA error message](https://www.w3.org/TR/wai-aria/#aria-errormessage) or not.
+ *
+ * Use the `aria-errormessage` attribute to reference another element that contains
+ * custom error message text. Multiple ids is **NOT** allowed. Authors MUST use
+ * `aria-invalid` in conjunction with `aria-errormessage`. Learn more from the
+ * [`aria-errormessage` spec](https://www.w3.org/TR/wai-aria/#aria-errormessage).
+ *
+ * Whitespace is normalized.
+ *
+ * When a `string` argument is passed through, it will perform a whole
+ * case-sensitive match to the error message text.
+ *
+ * To perform a case-insensitive match, you can use a `RegExp` with the `/i`
+ * modifier.
+ *
+ * To perform a partial match, you can pass a `RegExp` or use
+ * expect.stringContaining("partial string")`.
+ *
+ * @example
+ *
+ *
+ *
+ * Invalid time: the time must be between 9:00 AM and 5:00 PM"
+ *
+ *
+ *
+ * const timeInput = getByLabel('startTime')
+ *
+ * expect(timeInput).toHaveErrorMessage(
+ * 'Invalid time: the time must be between 9:00 AM and 5:00 PM',
+ * )
+ * expect(timeInput).toHaveErrorMessage(/invalid time/i) // to partially match
+ * expect(timeInput).toHaveErrorMessage(expect.stringContaining('Invalid time')) // to partially match
+ * expect(timeInput).not.toHaveErrorMessage('Pikachu!')
+ * @see
+ * [testing-library/jest-dom#tohaveerrormessage](https://github.com/testing-library/jest-dom#tohaveerrormessage)
+ */
+ toHaveErrorMessage(text?: string | RegExp | E): R
+ }
+}
+
+declare const matchers: matchers.TestingLibraryMatchers
+export = matchers
diff --git a/types/vitest.d.ts b/types/vitest.d.ts
new file mode 100644
index 00000000..1eefb412
--- /dev/null
+++ b/types/vitest.d.ts
@@ -0,0 +1,8 @@
+import {type expect} from 'vitest'
+import {type TestingLibraryMatchers} from './matchers'
+
+export {}
+declare module '@vitest/expect' {
+ interface JestAssertion
+ extends TestingLibraryMatchers {}
+}
diff --git a/vitest.d.ts b/vitest.d.ts
new file mode 100644
index 00000000..1b17a0d4
--- /dev/null
+++ b/vitest.d.ts
@@ -0,0 +1 @@
+///
diff --git a/vitest.js b/vitest.js
new file mode 100644
index 00000000..e1f318c5
--- /dev/null
+++ b/vitest.js
@@ -0,0 +1,4 @@
+import {expect} from 'vitest'
+import * as extensions from './dist/matchers'
+
+expect.extend(extensions)