diff --git a/.all-contributorsrc b/.all-contributorsrc index 1dc8d820..91f0d2cd 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -77,6 +77,17 @@ "platform" ] }, + { + "login": "antoaravinth", + "name": "Anto Aravinth", + "avatar_url": "https://avatars1.githubusercontent.com/u/1241511?s=460&v=4", + "profile": "https://github.com/antoaravinth", + "contributions": [ + "code", + "test", + "doc" + ] + }, { "login": "JonahMoses", "name": "Jonah Moses", diff --git a/README.md b/README.md index cfe56d0f..3d767216 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ [![downloads][downloads-badge]][npmtrends] [![MIT License][license-badge]][license] -[![All Contributors](https://img.shields.io/badge/all_contributors-8-orange.svg?style=flat-square)](#contributors) +[![All Contributors](https://img.shields.io/badge/all_contributors-9-orange.svg?style=flat-square)](#contributors) [![PRs Welcome][prs-badge]][prs] [![Code of Conduct][coc-badge]][coc] @@ -48,6 +48,7 @@ components. It provides light utility functions on top of `react-dom` and * [`Simulate`](#simulate) * [`flushPromises`](#flushpromises) * [`render`](#render) +* [Custom Jest Matchers](#custom-jest-matchers) * [`TextMatch`](#textmatch) * [`query` APIs](#query-apis) * [Examples](#examples) @@ -219,6 +220,44 @@ const usernameInputElement = getByTestId('username-input') > Learn more about `data-testid`s from the blog post > ["Making your UI tests resilient to change"][data-testid-blog-post] +## Custom Jest Matchers + +There are two simple API which extend the `expect` API of jest for making assertions easier. + +### `toBeInTheDOM` + +This allows you to assert whether an element present in the DOM or not. + +```javascript +// add the custom expect matchers +import 'react-testing-library/extend-expect' + +// ... +const {queryByTestId} = render(2) +expect(queryByTestId('count-value')).toBeInTheDOM() +expect(queryByTestId('count-value1')).not.toBeInTheDOM() +// ... +``` + +> Note: when using `toBeInTheDOM`, make sure you use a query function +> (like `queryByTestId`) rather than a get function (like `getByTestId`). +> Otherwise the `get*` function could throw an error before your assertion. + +### `toHaveTextContent` + +This API allows you to check whether the given element has a text content or not. + +```javascript +// add the custom expect matchers +import 'react-testing-library/extend-expect' + +// ... +const {getByTestId} = render(2) +expect(getByTestId('count-value')).toHaveTextContent('2') +expect(getByTestId('count-value')).not.toHaveTextContent('21') +// ... +``` + ## `TextMatch` Several APIs accept a `TextMatch` which can be a `string`, `regex` or a @@ -564,7 +603,7 @@ Thanks goes to these people ([emoji key][emojis]): | [
Kent C. Dodds](https://kentcdodds.com)
[๐Ÿ’ป](https://github.com/kentcdodds/react-testing-library/commits?author=kentcdodds "Code") [๐Ÿ“–](https://github.com/kentcdodds/react-testing-library/commits?author=kentcdodds "Documentation") [๐Ÿš‡](#infra-kentcdodds "Infrastructure (Hosting, Build-Tools, etc)") [โš ๏ธ](https://github.com/kentcdodds/react-testing-library/commits?author=kentcdodds "Tests") | [
Ryan Castner](http://audiolion.github.io)
[๐Ÿ“–](https://github.com/kentcdodds/react-testing-library/commits?author=audiolion "Documentation") | [
Daniel Sandiego](https://www.dnlsandiego.com)
[๐Ÿ’ป](https://github.com/kentcdodds/react-testing-library/commits?author=dnlsandiego "Code") | [
Paweล‚ Mikoล‚ajczyk](https://github.com/Miklet)
[๐Ÿ’ป](https://github.com/kentcdodds/react-testing-library/commits?author=Miklet "Code") | [
Alejandro ร‘รกรฑez Ortiz](http://co.linkedin.com/in/alejandronanez/)
[๐Ÿ“–](https://github.com/kentcdodds/react-testing-library/commits?author=alejandronanez "Documentation") | [
Matt Parrish](https://github.com/pbomb)
[๐Ÿ›](https://github.com/kentcdodds/react-testing-library/issues?q=author%3Apbomb "Bug reports") [๐Ÿ’ป](https://github.com/kentcdodds/react-testing-library/commits?author=pbomb "Code") [๐Ÿ“–](https://github.com/kentcdodds/react-testing-library/commits?author=pbomb "Documentation") [โš ๏ธ](https://github.com/kentcdodds/react-testing-library/commits?author=pbomb "Tests") | [
Justin Hall](https://github.com/wKovacs64)
[๐Ÿ“ฆ](#platform-wKovacs64 "Packaging/porting to new platform") | | :---: | :---: | :---: | :---: | :---: | :---: | :---: | -| [
Jonah Moses](https://github.com/JonahMoses)
[๐Ÿ“–](https://github.com/kentcdodds/react-testing-library/commits?author=JonahMoses "Documentation") | +| [
Anto Aravinth](https://github.com/antoaravinth)
[๐Ÿ’ป](https://github.com/kentcdodds/react-testing-library/commits?author=antoaravinth "Code") [โš ๏ธ](https://github.com/kentcdodds/react-testing-library/commits?author=antoaravinth "Tests") [๐Ÿ“–](https://github.com/kentcdodds/react-testing-library/commits?author=antoaravinth "Documentation") | [
Jonah Moses](https://github.com/JonahMoses)
[๐Ÿ“–](https://github.com/kentcdodds/react-testing-library/commits?author=JonahMoses "Documentation") | diff --git a/extend-expect.js b/extend-expect.js new file mode 100644 index 00000000..3cee4049 --- /dev/null +++ b/extend-expect.js @@ -0,0 +1 @@ +require('./dist/extend-expect') diff --git a/package.json b/package.json index 06c161a7..d709aae9 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,8 @@ "eslintConfig": { "extends": "./node_modules/kcd-scripts/eslint.js", "rules": { - "react/prop-types": "off" + "react/prop-types": "off", + "import/no-unassigned-import": "off" } }, "eslintIgnore": [ @@ -61,4 +62,4 @@ "url": "https://github.com/kentcdodds/react-testing-library/issues" }, "homepage": "https://github.com/kentcdodds/react-testing-library#readme" -} +} \ No newline at end of file diff --git a/src/__tests__/element-queries.js b/src/__tests__/element-queries.js index cec3f1ab..a5912c8d 100644 --- a/src/__tests__/element-queries.js +++ b/src/__tests__/element-queries.js @@ -1,5 +1,6 @@ import React from 'react' import {render} from '../' +import '../extend-expect' test('query can return null', () => { const { @@ -66,4 +67,17 @@ test('totally empty label', () => { expect(() => getByLabelText('')).toThrowErrorMatchingSnapshot() }) +test('using jest helpers to assert element states', () => { + const {queryByTestId} = render(2) + + // other ways to assert your test cases, but you don't need all of them. + expect(queryByTestId('count-value')).toBeInTheDOM() + expect(queryByTestId('count-value1')).not.toBeInTheDOM() + expect(queryByTestId('count-value')).toHaveTextContent('2') + expect(queryByTestId('count-value')).not.toHaveTextContent('21') + expect(() => + expect(queryByTestId('count-value2')).toHaveTextContent('2'), + ).toThrowError() +}) + /* eslint jsx-a11y/label-has-for:0 */ diff --git a/src/__tests__/fetch.js b/src/__tests__/fetch.js index c20c7fbc..bddb1489 100644 --- a/src/__tests__/fetch.js +++ b/src/__tests__/fetch.js @@ -35,7 +35,7 @@ test('Fetch makes an API call and displays the greeting when load-greeting is cl }), ) const url = '/greeting' - const {getByText, container} = render() + const {container, getByText} = render() // Act Simulate.click(getByText('Fetch')) diff --git a/src/extend-expect.js b/src/extend-expect.js new file mode 100644 index 00000000..58359822 --- /dev/null +++ b/src/extend-expect.js @@ -0,0 +1,7 @@ +import expect from 'expect' //eslint-disable-line import/no-extraneous-dependencies +import extensions from './jest-extensions' + +const {toBeInTheDOM, toHaveTextContent} = extensions +expect.extend({toBeInTheDOM, toHaveTextContent}) + +export default expect diff --git a/src/jest-extensions.js b/src/jest-extensions.js new file mode 100644 index 00000000..69e1e92c --- /dev/null +++ b/src/jest-extensions.js @@ -0,0 +1,78 @@ +import {matcherHint, printReceived, printExpected} from 'jest-matcher-utils' //eslint-disable-line import/no-extraneous-dependencies +import {matches} from './utils' + +function getDisplayName(subject) { + if (subject && subject.constructor) { + return subject.constructor.name + } else { + return typeof subject + } +} + +const assertMessage = (assertionName, message, received, expected) => + `${matcherHint(`${assertionName}`, 'received', '')} \n${message}: ` + + `${printExpected(expected)} \nReceived: ${printReceived(received)}` + +const extensions = { + toBeInTheDOM(received) { + getDisplayName(received) + if (received) { + return { + message: + `${matcherHint( + '.not.toBeInTheDOM', + 'received', + '', + )} Expected the element not to be present` + + `\nReceived : ${printReceived(received)}`, + pass: true, + } + } else { + return { + message: + `${matcherHint( + '.toBeInTheDOM', + 'received', + '', + )} Expected the element to be present` + + `\nReceived : ${printReceived(received)}`, + pass: false, + } + } + }, + + toHaveTextContent(htmlElement, checkWith) { + if (!(htmlElement instanceof HTMLElement)) + throw new Error( + `The given subject is a ${getDisplayName( + htmlElement, + )}, not an HTMLElement`, + ) + + const textContent = htmlElement.textContent + const pass = matches(textContent, htmlElement, checkWith) + if (pass) { + return { + message: assertMessage( + '.not.toHaveTextContent', + 'Expected value not equals to', + htmlElement, + checkWith, + ), + pass: true, + } + } else { + return { + message: assertMessage( + '.toHaveTextContent', + 'Expected value equals to', + htmlElement, + checkWith, + ), + pass: false, + } + } + }, +} + +export default extensions diff --git a/src/queries.js b/src/queries.js index 94382dab..485e2826 100644 --- a/src/queries.js +++ b/src/queries.js @@ -1,3 +1,5 @@ +import {matches} from './utils' + // Here are the queries for the library. // The queries here should only be things that are accessible to both users who are using a screen reader // and those who are not using a screen reader (with the exception of the data-testid attribute query). @@ -69,16 +71,6 @@ function getText(node) { .join(' ') } -function matches(textToMatch, node, matcher) { - if (typeof matcher === 'string') { - return textToMatch.toLowerCase().includes(matcher.toLowerCase()) - } else if (typeof matcher === 'function') { - return matcher(textToMatch, node) - } else { - return matcher.test(textToMatch) - } -} - // getters // the reason we're not dynamically generating these functions that look so similar: // 1. The error messages are specific to each one and depend on arguments diff --git a/src/utils.js b/src/utils.js new file mode 100644 index 00000000..87ecef8b --- /dev/null +++ b/src/utils.js @@ -0,0 +1,10 @@ +//eslint-disable-next-line import/prefer-default-export +export function matches(textToMatch, node, matcher) { + if (typeof matcher === 'string') { + return textToMatch.toLowerCase().includes(matcher.toLowerCase()) + } else if (typeof matcher === 'function') { + return matcher(textToMatch, node) + } else { + return matcher.test(textToMatch) + } +}