diff --git a/.all-contributorsrc b/.all-contributorsrc index 7c01b095..33412190 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -843,6 +843,36 @@ "contributions": [ "bug" ] + }, + { + "login": "InfiniteXyy", + "name": "InfiniteXyy", + "avatar_url": "https://avatars.githubusercontent.com/u/26314308?v=4", + "profile": "https://xyynext.com", + "contributions": [ + "code", + "bug" + ] + }, + { + "login": "kretajak", + "name": "Marcin Kulpa", + "avatar_url": "https://avatars.githubusercontent.com/u/2963053?v=4", + "profile": "https://github.com/kretajak", + "contributions": [ + "code", + "test" + ] + }, + { + "login": "nossbigg", + "name": "Gibson C", + "avatar_url": "https://avatars.githubusercontent.com/u/10845858?v=4", + "profile": "http://nossbigg.github.io", + "contributions": [ + "code", + "test" + ] } ], "repoHost": "https://github.com", diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..bb85b6f9 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +version: 2 +updates: + - package-ecosystem: github-actions + directory: / + schedule: + interval: daily + + - package-ecosystem: npm + directory: / + schedule: + interval: daily diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 92aba307..4d592330 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: [14, 16, 18, 20] + node: [14, 16, 18, 20, 22, 24] runs-on: ubuntu-latest steps: - name: ⬇️ Checkout repo diff --git a/README.md b/README.md index bf0c9cee..98643c2b 100644 --- a/README.md +++ b/README.md @@ -81,11 +81,15 @@ clear to read and to maintain. - [`toBePartiallyChecked`](#tobepartiallychecked) - [`toHaveRole`](#tohaverole) - [`toHaveErrorMessage`](#tohaveerrormessage) - - [`toHaveSelection`](#tohaveselection) + - [`toBePressed`](#tobepressed) + - [`toBePartiallyPressed`](#tobepartiallypressed) + - [`toAppearBefore`](#toappearbefore) + - [`toAppearAfter`](#toappearafter) - [Deprecated matchers](#deprecated-matchers) - [`toBeEmpty`](#tobeempty) - [`toBeInTheDOM`](#tobeinthedom) - [`toHaveDescription`](#tohavedescription) + - [`toHaveSelection`](#tohaveselection) - [Inspiration](#inspiration) - [Other Solutions](#other-solutions) - [Guiding Principles](#guiding-principles) @@ -174,7 +178,7 @@ Also, depending on your local setup, you may need to update your }, "include": [ ... - "./vitest.setup.ts" + "./vitest-setup.ts" ], ``` @@ -1306,6 +1310,150 @@ expect(timeInput).toHaveErrorMessage(expect.stringContaining('Invalid time')) // expect(timeInput).not.toHaveErrorMessage('Pikachu!') ``` +
+ +### `toBePressed` + +This allows to check whether given element is +[pressed](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-pressed). + +It accepts elements with explicit or implicit `button` role and valid +`aria-pressed` attribute of `"true"` or `"false"`. + +```typescript +toBePressed() +``` + +#### Examples + +```html + + + + + + +Pressed span +Released span +``` + +##### Using DOM Testing Library + +```javascript +screen.getByRole('button', {name: 'Pressed'}).toBePressed() +screen.getByRole('button', {name: 'Released'}).not.toBePressed() + +screen.getByRole('button', {name: 'Pressed input button'}).toBePressed() +screen.getByRole('button', {name: 'Released input button'}).not.toBePressed() + +screen.getByRole('button', {name: 'Pressed span'}).toBePressed() +screen.getByRole('button', {name: 'Released span'}).not.toBePressed() +``` + +
+ +### `toBePartiallyPressed` + +This allows to check whether given element is partially +[pressed](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-pressed). + +It accepts elements with explicit or implicit `button` role and valid +`aria-pressed` attribute of `mixed`. + +```typescript +toBePressed() +``` + +#### Examples + +```html + + +Partially pressed span +``` + +##### Using DOM Testing Library + +```javascript +screen.getByRole('button', {name: 'Partially pressed'}).toBePartiallyPressed() +screen + .getByRole('button', {name: 'Partially pressed input button'}) + .toBePartiallyPressed() +screen + .getByRole('button', {name: 'Partially pressed span'}) + .toBePartiallyPressed() +``` + +
+ +### `toAppearBefore` + +This checks if a given element appears before another element in the DOM tree, +as per +[`compareDocumentPosition()`](https://developer.mozilla.org/en-US/docs/Web/API/Node/compareDocumentPosition). + +```typescript +toAppearBefore() +``` + +#### Examples + +```html +
+ Text A + Text B +
+``` + +```javascript +const textA = queryByTestId('text-a') +const textB = queryByTestId('text-b') + +expect(textA).toAppearBefore(textB) +expect(textB).not.toAppearBefore(textA) +``` + +> Note: This matcher does not take into account CSS styles that may modify the +> display order of elements, eg: +> +> - `flex-direction: row-reverse`, +> - `flex-direction: column-reverse`, +> - `display: grid` + +### `toAppearAfter` + +This checks if a given element appears after another element in the DOM tree, as +per +[`compareDocumentPosition()`](https://developer.mozilla.org/en-US/docs/Web/API/Node/compareDocumentPosition). + +```typescript +toAppearAfter() +``` + +#### Examples + +```html +
+ Text A + Text B +
+``` + +```javascript +const textA = queryByTestId('text-a') +const textB = queryByTestId('text-b') + +expect(textB).toAppearAfter(textA) +expect(textA).not.toAppearAfter(textB) +``` + +> Note: This matcher does not take into account CSS styles that may modify the +> display order of elements, see [`toAppearBefore()`](#toappearbefore) + ## Deprecated matchers ### `toBeEmpty` @@ -1642,6 +1790,9 @@ Thanks goes to these people ([emoji key][emojis]): Billy Janitsch
Billy Janitsch

🐛 + InfiniteXyy
InfiniteXyy

💻 🐛 + Marcin Kulpa
Marcin Kulpa

💻 ⚠️ + Gibson C
Gibson C

💻 ⚠️ @@ -1665,7 +1816,7 @@ MIT https://github.com/testing-library/react-testing-library [npm]: https://www.npmjs.com/ [node]: https://nodejs.org -[build-badge]: https://img.shields.io/github/workflow/status/testing-library/jest-dom/validate?logo=github&style=flat-square +[build-badge]: https://img.shields.io/github/actions/workflow/status/testing-library/jest-dom/validate.yml?branch=main&logo=github&style=flat-square [build]: https://github.com/testing-library/jest-dom/actions?query=workflow%3Avalidate [coverage-badge]: https://img.shields.io/codecov/c/github/testing-library/jest-dom.svg?style=flat-square diff --git a/package.json b/package.json index a9ddf16c..e3da3012 100644 --- a/package.json +++ b/package.json @@ -82,10 +82,9 @@ "dependencies": { "@adobe/css-tools": "^4.4.0", "aria-query": "^5.0.0", - "chalk": "^3.0.0", "css.escape": "^1.5.1", "dom-accessibility-api": "^0.6.3", - "lodash": "^4.17.21", + "picocolors": "^1.1.1", "redent": "^3.0.0" }, "devDependencies": { diff --git a/src/__tests__/to-appear-before.js b/src/__tests__/to-appear-before.js new file mode 100644 index 00000000..c6af15e0 --- /dev/null +++ b/src/__tests__/to-appear-before.js @@ -0,0 +1,105 @@ +import {render} from './helpers/test-utils' + +describe('.toAppearBefore', () => { + const {queryByTestId} = render(` +
+
+ Text A + Text B +
+
+ `) + + const textA = queryByTestId('text-a') + const textB = queryByTestId('text-b') + const divA = queryByTestId('div-a') + + it('returns correct result when first element is before second element', () => { + expect(textA).toAppearBefore(textB) + }) + + it('returns correct for .not() invocation', () => { + expect(textB).not.toAppearBefore(textA) + }) + + it('errors out when first element is not before second element', () => { + expect(() => expect(textB).toAppearBefore(textA)).toThrowError( + /Received: Node.DOCUMENT_POSITION_PRECEDING \(2\)/i, + ) + }) + + it('errors out when .not is used but first element is actually before second element', () => { + expect(() => expect(textA).not.toAppearBefore(textB)).toThrowError( + /\.not\.toAppearBefore/i, + ) + }) + + it('errors out when first element is parent of second element', () => { + expect(() => expect(divA).toAppearBefore(textA)).toThrowError( + /Received: Unknown document position \(20\)/i, + ) + }) + + it('errors out when first element is child of second element', () => { + expect(() => expect(textA).toAppearBefore(divA)).toThrowError( + /Received: Unknown document position \(10\)/i, + ) + }) + + it('errors out when either first or second element is not HTMLElement', () => { + expect(() => expect(1).toAppearBefore(textB)).toThrowError() + expect(() => expect(textA).toAppearBefore(1)).toThrowError() + }) +}) + +describe('.toAppearAfter', () => { + const {queryByTestId} = render(` +
+
+ Text A + Text B +
+
+ `) + + const textA = queryByTestId('text-a') + const textB = queryByTestId('text-b') + const divA = queryByTestId('div-a') + + it('returns correct result when first element is after second element', () => { + expect(textB).toAppearAfter(textA) + }) + + it('returns correct for .not() invocation', () => { + expect(textA).not.toAppearAfter(textB) + }) + + it('errors out when first element is not after second element', () => { + expect(() => expect(textA).toAppearAfter(textB)).toThrowError( + /Received: Node.DOCUMENT_POSITION_FOLLOWING \(4\)/i, + ) + }) + + it('errors out when .not is used but first element is actually after second element', () => { + expect(() => expect(textB).not.toAppearAfter(textA)).toThrowError( + /\.not\.toAppearAfter/i, + ) + }) + + it('errors out when first element is parent of second element', () => { + expect(() => expect(divA).toAppearAfter(textA)).toThrowError( + /Received: Unknown document position \(20\)/i, + ) + }) + + it('errors out when first element is child of second element', () => { + expect(() => expect(textA).toAppearAfter(divA)).toThrowError( + /Received: Unknown document position \(10\)/i, + ) + }) + + it('errors out when either first or second element is not HTMLElement', () => { + expect(() => expect(1).toAppearAfter(textB)).toThrowError() + expect(() => expect(textA).toAppearAfter(1)).toThrowError() + }) +}) diff --git a/src/__tests__/to-be-partially-pressed.js b/src/__tests__/to-be-partially-pressed.js new file mode 100644 index 00000000..da6e7feb --- /dev/null +++ b/src/__tests__/to-be-partially-pressed.js @@ -0,0 +1,111 @@ +import {render} from './helpers/test-utils' + +describe('.toBePartiallyPressed', () => { + test('handles button element', () => { + const {queryByTestId} = render(` + + * + * + * + * + * + * Pressed span + * Released span + * + * screen.getByRole('button', { name: 'Pressed' }).toBePressed(); + * screen.getByRole('button', { name: 'Released' }).not.toBePressed(); + * + * screen.getByRole('button', { name: 'Pressed input button' }).toBePressed(); + * screen.getByRole('button', { name: 'Released input button' }).not.toBePressed(); + * + * screen.getByRole('button', { name: 'Pressed span' }).toBePressed(); + * screen.getByRole('button', { name: 'Released span' }).not.toBePressed(); + * + * @see + * [testing-library/jest-dom#tobepressed](https://github.com/testing-library/jest-dom#tobepressed) + */ + toBePressed(): R + /* + * @description + * This allows to check whether given element has been [partially pressed](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-pressed) + * + * It accepts elements with explicit or implicit `button` role and valid `aria-pressed` of `"mixed"`. + * + * @example + * + * + * Partially pressed span + * + * screen.getByRole('button', { name: 'Partially Pressed' }).toBePartiallyPressed(); + * screen.getByRole('button', { name: 'Partially pressed input button' }).toBePartiallyPressed(); + * screen.getByRole('button', { name: 'Partially pressed span' }).toBePartiallyPressed(); + * + * @See + * [testing-library/jest-dom#tobepartiallypressed](https://github.com/testing-library/jest-dom#tobepartiallypressed) + */ + toBePartiallyPressed(): R + /* + * @description + * This checks if a given element appears before another element in the DOM tree, as per [`compareDocumentPosition()`](https://developer.mozilla.org/en-US/docs/Web/API/Node/compareDocumentPosition). + * + * @example + *
+ * Text A + * Text B + *
+ * + * const textA = queryByTestId('text-a') + * const textB = queryByTestId('text-b') + * + * expect(textA).toAppearBefore(textB) + * expect(textB).not.toAppearBefore(textA) + * + * @See + * [testing-library/jest-dom#toappearbefore](https://github.com/testing-library/jest-dom#toappearbefore) + */ + toAppearBefore(element: HTMLElement | SVGElement): R + /* + * @description + * This checks if a given element appears after another element in the DOM tree, as per [`compareDocumentPosition()`](https://developer.mozilla.org/en-US/docs/Web/API/Node/compareDocumentPosition). + * + * @example + *
+ * Text A + * Text B + *
+ * + * const textA = queryByTestId('text-a') + * const textB = queryByTestId('text-b') + * + * expect(textB).toAppearAfter(textA) + * expect(textA).not.toAppearAfter(textB) + * + * @See + * [testing-library/jest-dom#toappearafter](https://github.com/testing-library/jest-dom#toappearafter) + */ + toAppearAfter(element: HTMLElement | SVGElement): R } }