diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 00000000..0e75fe55 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,3 @@ +node_modules +dist +coverage diff --git a/.gitignore b/.gitignore index cbf34c78..65058c12 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ node_modules coverage dist +lib .opt-in .opt-out .DS_Store diff --git a/.size-snapshot.json b/.size-snapshot.json deleted file mode 100644 index 87faf7e0..00000000 --- a/.size-snapshot.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "dist/dom-testing-library.umd.js": { - "bundled": 111046, - "minified": 49098, - "gzipped": 14989 - } -} diff --git a/.travis.yml b/.travis.yml index 08be7ec0..2c1e9574 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,7 +7,7 @@ notifications: email: false node_js: '8' install: npm install -script: npm run validate +script: lerna run validate after_success: kcd-scripts travis-after-success branches: only: master diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 00000000..41188761 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,5 @@ +# DOM Testing Packages + +- [dom-testing-library](../packages/dom-testing-library) Core library +- [react-testing-library](../packages/react-testing-library) Wrapper for React +- [@dom-testing/addon-async](../packages/dom-testing-addon-async) Async query addon diff --git a/lerna.json b/lerna.json new file mode 100644 index 00000000..a2bb50ba --- /dev/null +++ b/lerna.json @@ -0,0 +1,6 @@ +{ + "packages": [ + "packages/*" + ], + "version": "independent" +} diff --git a/package.json b/package.json index 29a318b6..3f8bee1c 100644 --- a/package.json +++ b/package.json @@ -1,73 +1,17 @@ { - "name": "dom-testing-library", - "version": "0.0.0-semantically-released", - "description": "Simple and complete DOM testing utilities that encourage good testing practices.", - "main": "dist/index.js", - "umd:main": "dist/dom-testing-library.umd.js", - "source": "src/index.js", - "typings": "typings", - "keywords": [ - "testing", - "ui", - "dom", - "jsdom", - "unit", - "integration", - "functional", - "end-to-end", - "e2e" - ], - "author": "Kent C. Dodds (http://kentcdodds.com/)", + "name": "dom-testing", + "private": true, "license": "MIT", - "engines": { - "node": ">=6" - }, - "scripts": { - "add-contributor": "kcd-scripts contributors add", - "build": "kcd-scripts build && kcd-scripts build --bundle umd --no-clean", - "lint": "kcd-scripts lint", - "test": "kcd-scripts test", - "test:update": "npm test -- --updateSnapshot --coverage", - "validate": "kcd-scripts validate", - "setup": "npm install && npm run validate -s", - "precommit": "kcd-scripts precommit", - "dtslint": "dtslint typings" - }, - "files": [ - "dist", - "typings" + "workspaces": [ + "packages/*" ], - "dependencies": { - "pretty-format": "^22.4.3", - "mutationobserver-shim": "^0.3.2", - "wait-for-expect": "^1.0.0" + "scripts": { + "postinstall": "npm-run-all bootstrap", + "bootstrap": "lerna bootstrap" }, "devDependencies": { - "dtslint": "^0.3.0", - "jest-dom": "^1.7.0", - "jest-in-case": "^1.0.2", - "kcd-scripts": "^0.41.0", - "microbundle": "^0.4.4" - }, - "eslintConfig": { - "extends": "./node_modules/kcd-scripts/eslint.js", - "rules": { - "import/prefer-default-export": "off", - "import/no-unassigned-import": "off", - "import/no-useless-path-segments": "off" - } - }, - "eslintIgnore": [ - "node_modules", - "coverage", - "dist" - ], - "repository": { - "type": "git", - "url": "https://github.com/kentcdodds/dom-testing-library.git" - }, - "bugs": { - "url": "https://github.com/kentcdodds/dom-testing-library/issues" - }, - "homepage": "https://github.com/kentcdodds/dom-testing-library#readme" + "kcd-scripts": "^0.44.0", + "lerna": "^3.3.2", + "npm-run-all": "^4.1.3" + } } diff --git a/packages/dom-testing-addon-async/.eslintignore b/packages/dom-testing-addon-async/.eslintignore new file mode 100644 index 00000000..325c7f64 --- /dev/null +++ b/packages/dom-testing-addon-async/.eslintignore @@ -0,0 +1,6 @@ +node_modules +flow-typed +lib +dist +.cache +coverage diff --git a/packages/dom-testing-addon-async/.eslintrc b/packages/dom-testing-addon-async/.eslintrc new file mode 100644 index 00000000..9f79c4fb --- /dev/null +++ b/packages/dom-testing-addon-async/.eslintrc @@ -0,0 +1,31 @@ +{ + "parser": "babel-eslint", + "plugins": [ + "flowtype", + "prettier", + "jsx-a11y" + ], + "extends": [ + "prettier", + "prettier/flowtype", + "prettier/react", + "plugin:flowtype/recommended", + "plugin:prettier/recommended", + "plugin:jsx-a11y/recommended", + "react-app" + ], + "rules": { + "prettier/prettier": [ + "error", + { + "parser": "flow", + "trailingComma": "all" + } + ] + }, + "env": { + "node": true, + "browser": true, + "jest": true + } +} diff --git a/packages/dom-testing-addon-async/.flowconfig b/packages/dom-testing-addon-async/.flowconfig new file mode 100644 index 00000000..1fed4453 --- /dev/null +++ b/packages/dom-testing-addon-async/.flowconfig @@ -0,0 +1,11 @@ +[ignore] + +[include] + +[libs] + +[lints] + +[options] + +[strict] diff --git a/packages/dom-testing-addon-async/.gitignore b/packages/dom-testing-addon-async/.gitignore new file mode 100644 index 00000000..2110695f --- /dev/null +++ b/packages/dom-testing-addon-async/.gitignore @@ -0,0 +1,123 @@ +# File created using '.gitignore Generator' for Visual Studio Code: https://bit.ly/vscode-gig + +# Created by https://www.gitignore.io/api/macos,visualstudiocode,node + +### macOS ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### Node ### +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# TypeScript v1 declaration files +typings/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env + +# parcel-bundler cache (https://parceljs.org/) +.cache + +# next.js build output +.next + +# nuxt.js build output +.nuxt + +# vuepress build output +.vuepress/dist + +# Serverless directories +.serverless + +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json + + +# End of https://www.gitignore.io/api/macos,visualstudiocode,node + +# Custom rules (everything added below won't be overriden by 'Generate .gitignore File' if you use 'Update' option) + +/lib/* +/dist/* +!/lib/index.js.flow +!/dist/index.js.flow diff --git a/packages/dom-testing-addon-async/CODEOWNERS b/packages/dom-testing-addon-async/CODEOWNERS new file mode 100644 index 00000000..02ba6366 --- /dev/null +++ b/packages/dom-testing-addon-async/CODEOWNERS @@ -0,0 +1,5 @@ +# Maintainers of this addon will be requested for code review + +* @alexkrolick +*.js @alexkrolick +*.md @alexkrolick \ No newline at end of file diff --git a/packages/dom-testing-addon-async/README.md b/packages/dom-testing-addon-async/README.md new file mode 100644 index 00000000..41989b72 --- /dev/null +++ b/packages/dom-testing-addon-async/README.md @@ -0,0 +1,45 @@ +# @dom-testing/addon-async + +Async query addon for dom-testing-library + +## Usage + +### `find*` + +Fach of the `get` and `getAll` queries from `dom-testing-library` are wrapped in an async `find` API. + +The find APIs return a Promise and retry automatically until they time out. The timeout can be specified as an option in the last argument. + +```js +import { findByLabelText, findByText } from '@dom-testing/addon-async' + +fireEvent.click(getByRole('log-in')) +// This element doesn't appear immediately: +const usernameElement = await findByLabelText('username', {timeout: 200}) +usernameElement.value = 'chucknorris' +// wait for error state +expect(await findByText('Error: Name must be capitalized')).not.toBeNull() +// expect NOT to see success state +await expect(findByText('Everything OK!')).rejects.toMatchInlineSnapshot() +``` + +### `waitFor` + +`waitFor` is a utility function that lets you retry queries until they succeed or time out. Unlike `dom-testing-library#wait`, `waitFor` returns the result of the function. It is used internally to create the find APIs. + +The first argument is the query. A new async function is returned if this is the only argument. If there are more arguments, it returns a Promise which resolves with the result of calling the async function with the arguments. If the last argument is an object with a "timeout" key, it will be used as the timeout for the retries. The Promise is rejected with the last error thrown by the callback. + +```js +const usernameElement = await waitFor(getByLabelText, container, 'username') +usernameElement.value = 'chucknorris' +``` + +You can create your own async queries by passing only the first argument: + +```js +const waitForText = waitFor(getByText) + +const headline = await waitForText('news flash') + +expect(headline).toBeDefined() // do something +``` \ No newline at end of file diff --git a/jest.config.js b/packages/dom-testing-addon-async/jest.config.js similarity index 100% rename from jest.config.js rename to packages/dom-testing-addon-async/jest.config.js diff --git a/packages/dom-testing-addon-async/package.json b/packages/dom-testing-addon-async/package.json new file mode 100644 index 00000000..32efa951 --- /dev/null +++ b/packages/dom-testing-addon-async/package.json @@ -0,0 +1,57 @@ +{ + "name": "@dom-testing/addon-async", + "version": "0.1.0", + "description": "Async queries for dom-testing-library", + "author": "Alex Krolick", + "repository": { + "type": "git", + "url": "git+https://github.com/alexkrolick/dom-testing-library.git" + }, + "license": "MIT", + "main": "dist/index.js", + "module": "lib/index.js", + "source": "src/index.js", + "files": [ + "dist/", + "lib/", + "src/" + ], + "scripts": { + "dev": "BABEL_ENV=development babel src -d lib --watch", + "test": "jest", + "check:lint": "eslint ./src --max-warnings=0 --report-unused-disable-directives", + "check:flow": "flow check", + "check:all": "npm-run-all check:lint check:flow test", + "build:lib": "BABEL_ENV=es babel src -d lib", + "build:dist": "BABEL_ENV=cjs babel src -d dist", + "build:flow": "cp index.js.flow.template lib/index.js.flow && cp index.js.flow.template dist/index.js.flow", + "build": "npm-run-all build:lib build:dist build:flow", + "prepublishOnly": "npm-run-all build", + "prebuild": "npm-run-all cleanup check:all", + "cleanup": "rimraf ./lib ./dist" + }, + "sideEffects": false, + "dependencies": { + "dom-testing-library": "^3.5.1" + }, + "devDependencies": { + "babel-cli": "^6.26.0", + "babel-eslint": "^8.2.5", + "babel-preset-env": "^1.7.0", + "babel-preset-react-app": "^3.1.2", + "eslint": "^4.1.1", + "eslint-config-prettier": "^2.9.0", + "eslint-config-react-app": "^3.0.0-next.3e165448", + "eslint-plugin-flowtype": "^2.34.1", + "eslint-plugin-import": "^2.6.0", + "eslint-plugin-jsx-a11y": "^6.0.2", + "eslint-plugin-prettier": "^2.6.1", + "eslint-plugin-react": "^7.8.2", + "flow-bin": "^0.75.0", + "flow-typed": "^2.4.0", + "jest": "^23.2.0", + "npm-run-all": "^4.1.3", + "prettier": "^1.13.7", + "rimraf": "^2.6.2" + } +} diff --git a/packages/dom-testing-addon-async/src/__tests__/helpers/test-utils.js b/packages/dom-testing-addon-async/src/__tests__/helpers/test-utils.js new file mode 100644 index 00000000..bb96e48a --- /dev/null +++ b/packages/dom-testing-addon-async/src/__tests__/helpers/test-utils.js @@ -0,0 +1,16 @@ +import {getQueriesForElement, queries} from 'dom-testing-library' +import * as asyncQueries from '../../' + +const allQueries = { + ...queries, + ...asyncQueries, +} + +function render(html) { + const container = document.createElement('div') + container.innerHTML = html + const containerQueries = getQueriesForElement(container, allQueries) + return {container, ...containerQueries} +} + +export {render} diff --git a/packages/dom-testing-addon-async/src/__tests__/index.test.js b/packages/dom-testing-addon-async/src/__tests__/index.test.js new file mode 100644 index 00000000..7e9613a8 --- /dev/null +++ b/packages/dom-testing-addon-async/src/__tests__/index.test.js @@ -0,0 +1,100 @@ +import 'jest-dom/extend-expect' +import {waitFor} from '../' +import {render} from './helpers/test-utils' + +test('findBy* queries poll for element', async () => { + const {container, findByTestId} = render( + `
`, + ) + + setTimeout(() => { + container.firstChild.setAttribute('data-testid', 'final-id') + }, 50) + + const element = await findByTestId(/final/) + expect(element).not.toBeNull() + expect(element).toMatchInlineSnapshot(` +
+`) +}) + +test('it throws if timeout is exceeded', async () => { + expect.assertions(3) + + const timeout = 50 + + const {findByText} = render(`
The text
`) + + await expect(findByText('Not the text', {timeout})).rejects.toBeDefined() + + await expect(findByText(/Not the/, {timeout})).rejects.toMatchInlineSnapshot(` +[Error: Unable to find an element with the text: /Not the/. 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. + +
 + 
 + The text + 
 +
] +`) + + try { + await findByText('not-there', {timeout}) + } catch (err) { + expect(err).toMatchInlineSnapshot(` +[Error: Unable to find an element with the text: not-there. 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. + +
 + 
 + The text + 
 +
] +`) + } +}) + +test('takes timeout as an option from last argument', async () => { + const {container, findByTestId} = render( + `
`, + ) + + const timeout = 50 + + setTimeout(() => { + container.firstChild.setAttribute('data-testid', 'final-id') + }, timeout * 2) + + await expect(findByTestId('final-id', {timeout})).rejects.toBeDefined() +}) + +test('calling waitFor with additional arguments invokes function', async () => { + const {container, getByTestId} = render( + `
`, + ) + + setTimeout(() => { + container.firstChild.setAttribute('data-testid', 'final-id') + }, 50) + + const el = await waitFor(getByTestId, 'final-id') + + expect(container.firstChild).toBe(el) +}) + +test('waitFor on a function that returns null times out', async () => { + // the main way to keep polling is to throw an error, but returning null works too + const {container, queryByTestId} = render( + `
`, + ) + + const timeout = 50 + + setTimeout(() => { + container.firstChild.setAttribute('data-testid', 'final-id') + }, timeout * 2) + + await expect( + waitFor(queryByTestId, 'final-id', {timeout}), + ).rejects.toBeDefined() +}) diff --git a/packages/dom-testing-addon-async/src/index.js b/packages/dom-testing-addon-async/src/index.js new file mode 100644 index 00000000..c94718f9 --- /dev/null +++ b/packages/dom-testing-addon-async/src/index.js @@ -0,0 +1,71 @@ +import {queries} from 'dom-testing-library' + +const DEFAULT_TIMEOUT = 4500 // milliseconds +const POLL_INTERVAL = 10 // milliseconds + +async function delay(delayMs) { + await new Promise(resolve => setTimeout(resolve, delayMs)) +} + +/** + * creates polling function + * @example waitFor(getByTestId)('my-id') + * @example waitFor(getByTestId, 'my-id') + * @param {function} callback - function or async function + * @param {*} args - if more than 1 argument is passed, + * result function is invoked immediately + * with remaining arguments + * @returns {function} polling function + */ +function waitFor(callback, ...callbackArgs) { + /** + * retries callback until it returns a non-null value or timeout is exceeded + * @param {any} args - argument list for query + * @returns {Promise} HTMLElement or Error + */ + async function pollingFunction(...args) { + // get timeout setting from last argument (should be an "options" object): + const options = args[args.length - 1] + const timeout = options.timeout || DEFAULT_TIMEOUT + + const startedAt = Date.now() + let lastErr = new Error('Query timed out') + let hasTimedOut = false + + while (!hasTimedOut) { + try { + const result = await callback(...args) + if (result !== null) return result + } catch (err) { + lastErr = err + } + hasTimedOut = Date.now() - startedAt > timeout + await delay(POLL_INTERVAL) + } + throw lastErr + } + + if (callbackArgs.length > 0) { + return pollingFunction(...callbackArgs) + } else { + return pollingFunction + } +} + +export const findByAltText = waitFor(queries.getByAltText) +export const findByLabelText = waitFor(queries.getByLabelText) +export const findByPlaceholderText = waitFor(queries.getByPlaceholderText) +export const findByRole = waitFor(queries.getByRole) +export const findByTestId = waitFor(queries.getByTestId) +export const findByText = waitFor(queries.getByText) +export const findByTitle = waitFor(queries.getByTitle) + +export const findAllByAltText = waitFor(queries.getAllByAltText) +export const findAllByLabelText = waitFor(queries.getAllByLabelText) +export const findAllByPlaceholderText = waitFor(queries.getAllByPlaceholderText) +export const findAllByRole = waitFor(queries.getAllByRole) +export const findAllByTestId = waitFor(queries.getAllByTestId) +export const findAllByText = waitFor(queries.getAllByText) +export const findAllByTitle = waitFor(queries.getAllByTitle) + +export {waitFor} diff --git a/packages/dom-testing-library/.all-contributorsrc b/packages/dom-testing-library/.all-contributorsrc new file mode 100644 index 00000000..ebb53535 --- /dev/null +++ b/packages/dom-testing-library/.all-contributorsrc @@ -0,0 +1,329 @@ +{ + "projectName": "dom-testing-library", + "projectOwner": "kentcdodds", + "repoType": "github", + "files": [ + "README.md" + ], + "imageSize": 100, + "commit": false, + "contributors": [ + { + "login": "kentcdodds", + "name": "Kent C. Dodds", + "avatar_url": "https://avatars.githubusercontent.com/u/1500684?v=3", + "profile": "https://kentcdodds.com", + "contributions": [ + "code", + "doc", + "infra", + "test" + ] + }, + { + "login": "audiolion", + "name": "Ryan Castner", + "avatar_url": "https://avatars1.githubusercontent.com/u/2430381?v=4", + "profile": "http://audiolion.github.io", + "contributions": [ + "doc" + ] + }, + { + "login": "dnlsandiego", + "name": "Daniel Sandiego", + "avatar_url": "https://avatars0.githubusercontent.com/u/8008023?v=4", + "profile": "https://www.dnlsandiego.com", + "contributions": [ + "code" + ] + }, + { + "login": "Miklet", + "name": "Paweł Mikołajczyk", + "avatar_url": "https://avatars2.githubusercontent.com/u/12592677?v=4", + "profile": "https://github.com/Miklet", + "contributions": [ + "code" + ] + }, + { + "login": "alejandronanez", + "name": "Alejandro Ñáñez Ortiz", + "avatar_url": "https://avatars3.githubusercontent.com/u/464978?v=4", + "profile": "http://co.linkedin.com/in/alejandronanez/", + "contributions": [ + "doc" + ] + }, + { + "login": "pbomb", + "name": "Matt Parrish", + "avatar_url": "https://avatars0.githubusercontent.com/u/1402095?v=4", + "profile": "https://github.com/pbomb", + "contributions": [ + "bug", + "code", + "doc", + "test" + ] + }, + { + "login": "wKovacs64", + "name": "Justin Hall", + "avatar_url": "https://avatars1.githubusercontent.com/u/1288694?v=4", + "profile": "https://github.com/wKovacs64", + "contributions": [ + "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", + "avatar_url": "https://avatars2.githubusercontent.com/u/3462296?v=4", + "profile": "https://github.com/JonahMoses", + "contributions": [ + "doc" + ] + }, + { + "login": "lgandecki", + "name": "Łukasz Gandecki", + "avatar_url": "https://avatars1.githubusercontent.com/u/4002543?v=4", + "profile": "http://team.thebrain.pro", + "contributions": [ + "code", + "test", + "doc" + ] + }, + { + "login": "sompylasar", + "name": "Ivan Babak", + "avatar_url": "https://avatars2.githubusercontent.com/u/498274?v=4", + "profile": "https://sompylasar.github.io", + "contributions": [ + "bug", + "ideas", + "code", + "doc" + ] + }, + { + "login": "jday3", + "name": "Jesse Day", + "avatar_url": "https://avatars3.githubusercontent.com/u/4439618?v=4", + "profile": "https://github.com/jday3", + "contributions": [ + "code" + ] + }, + { + "login": "gnapse", + "name": "Ernesto García", + "avatar_url": "https://avatars0.githubusercontent.com/u/15199?v=4", + "profile": "http://gnapse.github.io", + "contributions": [ + "question", + "code", + "doc" + ] + }, + { + "login": "jomaxx", + "name": "Josef Maxx Blake", + "avatar_url": "https://avatars2.githubusercontent.com/u/2747424?v=4", + "profile": "http://jomaxx.com", + "contributions": [ + "code", + "doc", + "test" + ] + }, + { + "login": "alecook", + "name": "Alex Cook", + "avatar_url": "https://avatars3.githubusercontent.com/u/725236?v=4", + "profile": "https://github.com/alecook", + "contributions": [ + "doc", + "example" + ] + }, + { + "login": "dfcook", + "name": "Daniel Cook", + "avatar_url": "https://avatars3.githubusercontent.com/u/10348212?v=4", + "profile": "https://github.com/dfcook", + "contributions": [ + "code", + "doc", + "test" + ] + }, + { + "login": "thchia", + "name": "Thomas Chia", + "avatar_url": "https://avatars2.githubusercontent.com/u/21194045?s=400&v=4", + "profile": "https://github.com/thchia", + "contributions": [ + "bug", + "code" + ] + }, + { + "login": "tdeschryver", + "name": "Tim Deschryver", + "avatar_url": "https://avatars1.githubusercontent.com/u/28659384?v=4", + "profile": "https://github.com/tdeschryver", + "contributions": [ + "code", + "test" + ] + }, + { + "login": "alexkrolick", + "name": "Alex Krolick", + "avatar_url": "https://avatars3.githubusercontent.com/u/1571667?v=4", + "profile": "https://alexkrolick.com", + "contributions": [ + "code" + ] + }, + { + "login": "maddijoyce", + "name": "Maddi Joyce", + "avatar_url": "https://avatars2.githubusercontent.com/u/2224291?v=4", + "profile": "http://www.maddijoyce.com", + "contributions": [ + "code" + ] + }, + { + "login": "npeterkamps", + "name": "Peter Kamps", + "avatar_url": "https://avatars1.githubusercontent.com/u/25429764?v=4", + "profile": "https://github.com/npeterkamps", + "contributions": [ + "bug", + "code", + "test" + ] + }, + { + "login": "JonathanStoye", + "name": "Jonathan Stoye", + "avatar_url": "https://avatars2.githubusercontent.com/u/21689428?v=4", + "profile": "http://jonathanstoye.de", + "contributions": [ + "doc" + ] + }, + { + "login": "yongdamsh", + "name": "Sanghyeon Lee", + "avatar_url": "https://avatars2.githubusercontent.com/u/4126644?v=4", + "profile": "https://github.com/yongdamsh", + "contributions": [ + "example" + ] + }, + { + "login": "Dajust", + "name": "Justice Mba ", + "avatar_url": "https://avatars3.githubusercontent.com/u/8015514?v=4", + "profile": "https://github.com/Dajust", + "contributions": [ + "code", + "doc", + "ideas" + ] + }, + { + "login": "wgcrouch", + "name": "Wayne Crouch", + "avatar_url": "https://avatars3.githubusercontent.com/u/340761?v=4", + "profile": "https://github.com/wgcrouch", + "contributions": [ + "code" + ] + }, + { + "login": "benelliott", + "name": "Ben Elliott", + "avatar_url": "https://avatars1.githubusercontent.com/u/4996462?v=4", + "profile": "http://benjaminelliott.co.uk", + "contributions": [ + "code" + ] + }, + { + "login": "rubencosta", + "name": "Ruben Costa", + "avatar_url": "https://avatars3.githubusercontent.com/u/577921?v=4", + "profile": "http://nuances.co", + "contributions": [ + "code" + ] + }, + { + "login": "rbrtsmith", + "name": "Robert Smith", + "avatar_url": "https://avatars2.githubusercontent.com/u/4982001?v=4", + "profile": "http://rbrtsmith.com/", + "contributions": [ + "bug", + "ideas", + "doc" + ] + }, + { + "login": "dadamssg", + "name": "dadamssg", + "avatar_url": "https://avatars3.githubusercontent.com/u/881986?v=4", + "profile": "https://github.com/dadamssg", + "contributions": [ + "code" + ] + }, + { + "login": "wyze", + "name": "Neil Kistner", + "avatar_url": "https://avatars1.githubusercontent.com/u/186971?v=4", + "profile": "https://neilkistner.com/", + "contributions": [ + "code" + ] + }, + { + "login": "bdchauvette", + "name": "Ben Chauvette", + "avatar_url": "https://avatars3.githubusercontent.com/u/1448597?v=4", + "profile": "http://bdchauvette.net/", + "contributions": [ + "code" + ] + }, + { + "login": "JeffBaumgardt", + "name": "Jeff Baumgardt", + "avatar_url": "https://avatars2.githubusercontent.com/u/777527?v=4", + "profile": "https://github.com/JeffBaumgardt", + "contributions": [ + "code", + "doc" + ] + } + ] +} diff --git a/packages/dom-testing-library/.size-snapshot.json b/packages/dom-testing-library/.size-snapshot.json new file mode 100644 index 00000000..77a03c1f --- /dev/null +++ b/packages/dom-testing-library/.size-snapshot.json @@ -0,0 +1,7 @@ +{ + "dist/dom-testing-library.umd.js": { + "bundled": 113109, + "minified": 49916, + "gzipped": 15163 + } +} diff --git a/CHANGELOG.md b/packages/dom-testing-library/CHANGELOG.md similarity index 100% rename from CHANGELOG.md rename to packages/dom-testing-library/CHANGELOG.md diff --git a/CONTRIBUTING.md b/packages/dom-testing-library/CONTRIBUTING.md similarity index 100% rename from CONTRIBUTING.md rename to packages/dom-testing-library/CONTRIBUTING.md diff --git a/LICENSE b/packages/dom-testing-library/LICENSE similarity index 100% rename from LICENSE rename to packages/dom-testing-library/LICENSE diff --git a/README.md b/packages/dom-testing-library/README.md similarity index 100% rename from README.md rename to packages/dom-testing-library/README.md diff --git a/packages/dom-testing-library/jest.config.js b/packages/dom-testing-library/jest.config.js new file mode 100644 index 00000000..4160d66d --- /dev/null +++ b/packages/dom-testing-library/jest.config.js @@ -0,0 +1,5 @@ +const jestConfig = require('kcd-scripts/jest') + +module.exports = Object.assign(jestConfig, { + testEnvironment: 'jest-environment-jsdom', +}) diff --git a/packages/dom-testing-library/other/octopus.png b/packages/dom-testing-library/other/octopus.png new file mode 100644 index 00000000..9fcd321c Binary files /dev/null and b/packages/dom-testing-library/other/octopus.png differ diff --git a/packages/dom-testing-library/package.json b/packages/dom-testing-library/package.json new file mode 100644 index 00000000..3467f891 --- /dev/null +++ b/packages/dom-testing-library/package.json @@ -0,0 +1,68 @@ +{ + "name": "dom-testing-library", + "version": "0.0.0-semantically-released", + "description": "Simple and complete DOM testing utilities that encourage good testing practices.", + "main": "dist/index.js", + "umd:main": "dist/dom-testing-library.umd.js", + "source": "src/index.js", + "typings": "typings", + "keywords": [ + "testing", + "ui", + "dom", + "jsdom", + "unit", + "integration", + "functional", + "end-to-end", + "e2e" + ], + "author": "Kent C. Dodds (http://kentcdodds.com/)", + "license": "MIT", + "engines": { + "node": ">=6" + }, + "scripts": { + "add-contributor": "kcd-scripts contributors add", + "build": "kcd-scripts build && kcd-scripts build --bundle umd --no-clean", + "lint": "kcd-scripts lint", + "test": "kcd-scripts test", + "test:update": "npm test -- --updateSnapshot --coverage", + "validate": "kcd-scripts validate", + "setup": "npm install && npm run validate -s", + "precommit": "kcd-scripts precommit", + "dtslint": "dtslint typings" + }, + "eslintConfig": { + "extends": "./node_modules/kcd-scripts/eslint.js", + "rules": { + "import/prefer-default-export": "off", + "import/no-unassigned-import": "off", + "import/no-useless-path-segments": "off" + } + }, + "files": [ + "dist", + "typings" + ], + "dependencies": { + "pretty-format": "^22.4.3", + "mutationobserver-shim": "^0.3.2", + "wait-for-expect": "^1.0.0" + }, + "devDependencies": { + "dtslint": "^0.3.0", + "jest-dom": "^1.7.0", + "jest-in-case": "^1.0.2", + "kcd-scripts": "^0.41.0", + "microbundle": "^0.4.4" + }, + "repository": { + "type": "git", + "url": "https://github.com/kentcdodds/dom-testing-library.git" + }, + "bugs": { + "url": "https://github.com/kentcdodds/dom-testing-library/issues" + }, + "homepage": "https://github.com/kentcdodds/dom-testing-library#readme" +} diff --git a/rollup.config.js b/packages/dom-testing-library/rollup.config.js similarity index 100% rename from rollup.config.js rename to packages/dom-testing-library/rollup.config.js diff --git a/src/__tests__/__snapshots__/element-queries.js.snap b/packages/dom-testing-library/src/__tests__/__snapshots__/element-queries.js.snap similarity index 100% rename from src/__tests__/__snapshots__/element-queries.js.snap rename to packages/dom-testing-library/src/__tests__/__snapshots__/element-queries.js.snap diff --git a/src/__tests__/__snapshots__/example.js.snap b/packages/dom-testing-library/src/__tests__/__snapshots__/example.js.snap similarity index 100% rename from src/__tests__/__snapshots__/example.js.snap rename to packages/dom-testing-library/src/__tests__/__snapshots__/example.js.snap diff --git a/src/__tests__/__snapshots__/pretty-dom.js.snap b/packages/dom-testing-library/src/__tests__/__snapshots__/pretty-dom.js.snap similarity index 100% rename from src/__tests__/__snapshots__/pretty-dom.js.snap rename to packages/dom-testing-library/src/__tests__/__snapshots__/pretty-dom.js.snap diff --git a/src/__tests__/__snapshots__/wait-for-element.js.snap b/packages/dom-testing-library/src/__tests__/__snapshots__/wait-for-element.js.snap similarity index 100% rename from src/__tests__/__snapshots__/wait-for-element.js.snap rename to packages/dom-testing-library/src/__tests__/__snapshots__/wait-for-element.js.snap diff --git a/src/__tests__/element-queries.js b/packages/dom-testing-library/src/__tests__/element-queries.js similarity index 100% rename from src/__tests__/element-queries.js rename to packages/dom-testing-library/src/__tests__/element-queries.js diff --git a/src/__tests__/events.js b/packages/dom-testing-library/src/__tests__/events.js similarity index 100% rename from src/__tests__/events.js rename to packages/dom-testing-library/src/__tests__/events.js diff --git a/src/__tests__/example.js b/packages/dom-testing-library/src/__tests__/example.js similarity index 100% rename from src/__tests__/example.js rename to packages/dom-testing-library/src/__tests__/example.js diff --git a/src/__tests__/get-queries-for-element.js b/packages/dom-testing-library/src/__tests__/get-queries-for-element.js similarity index 100% rename from src/__tests__/get-queries-for-element.js rename to packages/dom-testing-library/src/__tests__/get-queries-for-element.js diff --git a/src/__tests__/helpers/test-utils.js b/packages/dom-testing-library/src/__tests__/helpers/test-utils.js similarity index 100% rename from src/__tests__/helpers/test-utils.js rename to packages/dom-testing-library/src/__tests__/helpers/test-utils.js diff --git a/src/__tests__/matches.js b/packages/dom-testing-library/src/__tests__/matches.js similarity index 100% rename from src/__tests__/matches.js rename to packages/dom-testing-library/src/__tests__/matches.js diff --git a/src/__tests__/pretty-dom.js b/packages/dom-testing-library/src/__tests__/pretty-dom.js similarity index 100% rename from src/__tests__/pretty-dom.js rename to packages/dom-testing-library/src/__tests__/pretty-dom.js diff --git a/src/__tests__/text-matchers.js b/packages/dom-testing-library/src/__tests__/text-matchers.js similarity index 100% rename from src/__tests__/text-matchers.js rename to packages/dom-testing-library/src/__tests__/text-matchers.js diff --git a/src/__tests__/wait-for-element.js b/packages/dom-testing-library/src/__tests__/wait-for-element.js similarity index 100% rename from src/__tests__/wait-for-element.js rename to packages/dom-testing-library/src/__tests__/wait-for-element.js diff --git a/src/__tests__/wait.js b/packages/dom-testing-library/src/__tests__/wait.js similarity index 100% rename from src/__tests__/wait.js rename to packages/dom-testing-library/src/__tests__/wait.js diff --git a/src/events.js b/packages/dom-testing-library/src/events.js similarity index 100% rename from src/events.js rename to packages/dom-testing-library/src/events.js diff --git a/src/get-node-text.js b/packages/dom-testing-library/src/get-node-text.js similarity index 100% rename from src/get-node-text.js rename to packages/dom-testing-library/src/get-node-text.js diff --git a/src/get-queries-for-element.js b/packages/dom-testing-library/src/get-queries-for-element.js similarity index 100% rename from src/get-queries-for-element.js rename to packages/dom-testing-library/src/get-queries-for-element.js diff --git a/src/index.js b/packages/dom-testing-library/src/index.js similarity index 100% rename from src/index.js rename to packages/dom-testing-library/src/index.js diff --git a/src/matches.js b/packages/dom-testing-library/src/matches.js similarity index 100% rename from src/matches.js rename to packages/dom-testing-library/src/matches.js diff --git a/src/pretty-dom.js b/packages/dom-testing-library/src/pretty-dom.js similarity index 100% rename from src/pretty-dom.js rename to packages/dom-testing-library/src/pretty-dom.js diff --git a/src/queries.js b/packages/dom-testing-library/src/queries.js similarity index 100% rename from src/queries.js rename to packages/dom-testing-library/src/queries.js diff --git a/src/query-helpers.js b/packages/dom-testing-library/src/query-helpers.js similarity index 100% rename from src/query-helpers.js rename to packages/dom-testing-library/src/query-helpers.js diff --git a/src/wait-for-element.js b/packages/dom-testing-library/src/wait-for-element.js similarity index 100% rename from src/wait-for-element.js rename to packages/dom-testing-library/src/wait-for-element.js diff --git a/src/wait.js b/packages/dom-testing-library/src/wait.js similarity index 100% rename from src/wait.js rename to packages/dom-testing-library/src/wait.js diff --git a/typings/events.d.ts b/packages/dom-testing-library/typings/events.d.ts similarity index 100% rename from typings/events.d.ts rename to packages/dom-testing-library/typings/events.d.ts diff --git a/typings/get-node-text.d.ts b/packages/dom-testing-library/typings/get-node-text.d.ts similarity index 100% rename from typings/get-node-text.d.ts rename to packages/dom-testing-library/typings/get-node-text.d.ts diff --git a/typings/get-queries-for-element.d.ts b/packages/dom-testing-library/typings/get-queries-for-element.d.ts similarity index 100% rename from typings/get-queries-for-element.d.ts rename to packages/dom-testing-library/typings/get-queries-for-element.d.ts diff --git a/typings/index.d.ts b/packages/dom-testing-library/typings/index.d.ts similarity index 100% rename from typings/index.d.ts rename to packages/dom-testing-library/typings/index.d.ts diff --git a/typings/matches.d.ts b/packages/dom-testing-library/typings/matches.d.ts similarity index 100% rename from typings/matches.d.ts rename to packages/dom-testing-library/typings/matches.d.ts diff --git a/typings/pretty-dom.d.ts b/packages/dom-testing-library/typings/pretty-dom.d.ts similarity index 100% rename from typings/pretty-dom.d.ts rename to packages/dom-testing-library/typings/pretty-dom.d.ts diff --git a/typings/queries.d.ts b/packages/dom-testing-library/typings/queries.d.ts similarity index 100% rename from typings/queries.d.ts rename to packages/dom-testing-library/typings/queries.d.ts diff --git a/typings/query-helpers.d.ts b/packages/dom-testing-library/typings/query-helpers.d.ts similarity index 100% rename from typings/query-helpers.d.ts rename to packages/dom-testing-library/typings/query-helpers.d.ts diff --git a/typings/tsconfig.json b/packages/dom-testing-library/typings/tsconfig.json similarity index 100% rename from typings/tsconfig.json rename to packages/dom-testing-library/typings/tsconfig.json diff --git a/typings/tslint.json b/packages/dom-testing-library/typings/tslint.json similarity index 100% rename from typings/tslint.json rename to packages/dom-testing-library/typings/tslint.json diff --git a/typings/wait-for-element.d.ts b/packages/dom-testing-library/typings/wait-for-element.d.ts similarity index 100% rename from typings/wait-for-element.d.ts rename to packages/dom-testing-library/typings/wait-for-element.d.ts diff --git a/typings/wait.d.ts b/packages/dom-testing-library/typings/wait.d.ts similarity index 100% rename from typings/wait.d.ts rename to packages/dom-testing-library/typings/wait.d.ts diff --git a/packages/react-testing-library/.all-contributorsrc b/packages/react-testing-library/.all-contributorsrc new file mode 100644 index 00000000..088a1f75 --- /dev/null +++ b/packages/react-testing-library/.all-contributorsrc @@ -0,0 +1,498 @@ +{ + "projectName": "react-testing-library", + "projectOwner": "kentcdodds", + "repoType": "github", + "files": [ + "README.md" + ], + "imageSize": 100, + "commit": false, + "contributors": [ + { + "login": "kentcdodds", + "name": "Kent C. Dodds", + "avatar_url": "https://avatars.githubusercontent.com/u/1500684?v=3", + "profile": "https://kentcdodds.com", + "contributions": [ + "code", + "doc", + "infra", + "test" + ] + }, + { + "login": "audiolion", + "name": "Ryan Castner", + "avatar_url": "https://avatars1.githubusercontent.com/u/2430381?v=4", + "profile": "http://audiolion.github.io", + "contributions": [ + "doc" + ] + }, + { + "login": "dnlsandiego", + "name": "Daniel Sandiego", + "avatar_url": "https://avatars0.githubusercontent.com/u/8008023?v=4", + "profile": "https://www.dnlsandiego.com", + "contributions": [ + "code" + ] + }, + { + "login": "Miklet", + "name": "Paweł Mikołajczyk", + "avatar_url": "https://avatars2.githubusercontent.com/u/12592677?v=4", + "profile": "https://github.com/Miklet", + "contributions": [ + "code" + ] + }, + { + "login": "alejandronanez", + "name": "Alejandro Ñáñez Ortiz", + "avatar_url": "https://avatars3.githubusercontent.com/u/464978?v=4", + "profile": "http://co.linkedin.com/in/alejandronanez/", + "contributions": [ + "doc" + ] + }, + { + "login": "pbomb", + "name": "Matt Parrish", + "avatar_url": "https://avatars0.githubusercontent.com/u/1402095?v=4", + "profile": "https://github.com/pbomb", + "contributions": [ + "bug", + "code", + "doc", + "test" + ] + }, + { + "login": "wKovacs64", + "name": "Justin Hall", + "avatar_url": "https://avatars1.githubusercontent.com/u/1288694?v=4", + "profile": "https://github.com/wKovacs64", + "contributions": [ + "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", + "avatar_url": "https://avatars2.githubusercontent.com/u/3462296?v=4", + "profile": "https://github.com/JonahMoses", + "contributions": [ + "doc" + ] + }, + { + "login": "lgandecki", + "name": "Łukasz Gandecki", + "avatar_url": "https://avatars1.githubusercontent.com/u/4002543?v=4", + "profile": "http://team.thebrain.pro", + "contributions": [ + "code", + "test", + "doc" + ] + }, + { + "login": "sompylasar", + "name": "Ivan Babak", + "avatar_url": "https://avatars2.githubusercontent.com/u/498274?v=4", + "profile": "https://sompylasar.github.io", + "contributions": [ + "bug", + "ideas" + ] + }, + { + "login": "jday3", + "name": "Jesse Day", + "avatar_url": "https://avatars3.githubusercontent.com/u/4439618?v=4", + "profile": "https://github.com/jday3", + "contributions": [ + "code" + ] + }, + { + "login": "gnapse", + "name": "Ernesto García", + "avatar_url": "https://avatars0.githubusercontent.com/u/15199?v=4", + "profile": "http://gnapse.github.io", + "contributions": [ + "question", + "code", + "doc" + ] + }, + { + "login": "jomaxx", + "name": "Josef Maxx Blake", + "avatar_url": "https://avatars2.githubusercontent.com/u/2747424?v=4", + "profile": "http://jomaxx.com", + "contributions": [ + "code", + "doc", + "test" + ] + }, + { + "login": "mbaranovski", + "name": "Michal Baranowski", + "avatar_url": "https://avatars1.githubusercontent.com/u/29602306?v=4", + "profile": "https://twitter.com/baranovskim", + "contributions": [ + "blog", + "tutorial" + ] + }, + { + "login": "aputhin", + "name": "Arthur Puthin", + "avatar_url": "https://avatars3.githubusercontent.com/u/13985684?v=4", + "profile": "https://github.com/aputhin", + "contributions": [ + "doc" + ] + }, + { + "login": "thchia", + "name": "Thomas Chia", + "avatar_url": "https://avatars2.githubusercontent.com/u/21194045?v=4", + "profile": "https://github.com/thchia", + "contributions": [ + "code", + "doc" + ] + }, + { + "login": "thiagopaiva99", + "name": "Thiago Galvani", + "avatar_url": "https://avatars3.githubusercontent.com/u/20430611?v=4", + "profile": "http://ilegra.com/", + "contributions": [ + "doc" + ] + }, + { + "login": "ChrisWcs", + "name": "Christian", + "avatar_url": "https://avatars1.githubusercontent.com/u/19828824?v=4", + "profile": "http://Chriswcs.github.io", + "contributions": [ + "test" + ] + }, + { + "login": "alexkrolick", + "name": "Alex Krolick", + "avatar_url": "https://avatars3.githubusercontent.com/u/1571667?v=4", + "profile": "https://alexkrolick.com", + "contributions": [ + "question", + "doc", + "example", + "ideas" + ] + }, + { + "login": "johann-sonntagbauer", + "name": "Johann Hubert Sonntagbauer", + "avatar_url": "https://avatars3.githubusercontent.com/u/1239401?v=4", + "profile": "https://github.com/johann-sonntagbauer", + "contributions": [ + "code", + "doc", + "test" + ] + }, + { + "login": "maddijoyce", + "name": "Maddi Joyce", + "avatar_url": "https://avatars2.githubusercontent.com/u/2224291?v=4", + "profile": "http://www.maddijoyce.com", + "contributions": [ + "code" + ] + }, + { + "login": "RyanAtViceSoftware", + "name": "Ryan Vice", + "avatar_url": "https://avatars2.githubusercontent.com/u/10080111?v=4", + "profile": "http://www.vicesoftware.com", + "contributions": [ + "doc" + ] + }, + { + "login": "iwilsonq", + "name": "Ian Wilson", + "avatar_url": "https://avatars1.githubusercontent.com/u/7942604?v=4", + "profile": "https://ianwilson.io", + "contributions": [ + "blog", + "tutorial" + ] + }, + { + "login": "InExtremaRes", + "name": "Daniel", + "avatar_url": "https://avatars2.githubusercontent.com/u/1635491?v=4", + "profile": "https://github.com/InExtremaRes", + "contributions": [ + "bug", + "code" + ] + }, + { + "login": "Gpx", + "name": "Giorgio Polvara", + "avatar_url": "https://avatars0.githubusercontent.com/u/767959?v=4", + "profile": "https://twitter.com/Gpx", + "contributions": [ + "bug", + "ideas" + ] + }, + { + "login": "jgoz", + "name": "John Gozde", + "avatar_url": "https://avatars2.githubusercontent.com/u/132233?v=4", + "profile": "https://github.com/jgoz", + "contributions": [ + "code" + ] + }, + { + "login": "SavePointSam", + "name": "Sam Horton", + "avatar_url": "https://avatars0.githubusercontent.com/u/8203211?v=4", + "profile": "https://twitter.com/SavePointSam", + "contributions": [ + "doc", + "example", + "ideas" + ] + }, + { + "login": "rkotze", + "name": "Richard Kotze (mobile)", + "avatar_url": "https://avatars2.githubusercontent.com/u/10452163?v=4", + "profile": "http://www.richardkotze.com", + "contributions": [ + "doc" + ] + }, + { + "login": "sotobuild", + "name": "Brahian E. Soto Mercedes", + "avatar_url": "https://avatars2.githubusercontent.com/u/10819833?v=4", + "profile": "https://github.com/sotobuild", + "contributions": [ + "doc" + ] + }, + { + "login": "bdelaforest", + "name": "Benoit de La Forest", + "avatar_url": "https://avatars2.githubusercontent.com/u/7151559?v=4", + "profile": "https://github.com/bdelaforest", + "contributions": [ + "doc" + ] + }, + { + "login": "thesalah", + "name": "Salah", + "avatar_url": "https://avatars3.githubusercontent.com/u/6624197?v=4", + "profile": "https://github.com/thesalah", + "contributions": [ + "code", + "test" + ] + }, + { + "login": "icfantv", + "name": "Adam Gordon", + "avatar_url": "https://avatars2.githubusercontent.com/u/370054?v=4", + "profile": "http://gordonizer.com", + "contributions": [ + "bug", + "code" + ] + }, + { + "login": "silvenon", + "name": "Matija Marohnić", + "avatar_url": "https://avatars2.githubusercontent.com/u/471278?v=4", + "profile": "https://silvenon.com", + "contributions": [ + "doc" + ] + }, + { + "login": "Dajust", + "name": "Justice Mba", + "avatar_url": "https://avatars3.githubusercontent.com/u/8015514?v=4", + "profile": "https://github.com/Dajust", + "contributions": [ + "doc" + ] + }, + { + "login": "MarkPollmann", + "name": "Mark Pollmann", + "avatar_url": "https://avatars2.githubusercontent.com/u/5286559?v=4", + "profile": "https://markpollmann.com/", + "contributions": [ + "doc" + ] + }, + { + "login": "ehteshamkafeel", + "name": "Ehtesham Kafeel", + "avatar_url": "https://avatars1.githubusercontent.com/u/1213123?v=4", + "profile": "https://github.com/ehteshamkafeel", + "contributions": [ + "code", + "doc" + ] + }, + { + "login": "jpavon", + "name": "Julio Pavón", + "avatar_url": "https://avatars2.githubusercontent.com/u/1493505?v=4", + "profile": "http://jpavon.com", + "contributions": [ + "code" + ] + }, + { + "login": "duncanleung", + "name": "Duncan L", + "avatar_url": "https://avatars3.githubusercontent.com/u/1765048?v=4", + "profile": "http://www.duncanleung.com/", + "contributions": [ + "doc", + "example" + ] + }, + { + "login": "tyagow", + "name": "Tiago Almeida", + "avatar_url": "https://avatars1.githubusercontent.com/u/700778?v=4", + "profile": "https://www.linkedin.com/in/tyagow/?locale=en_US", + "contributions": [ + "doc" + ] + }, + { + "login": "rbrtsmith", + "name": "Robert Smith", + "avatar_url": "https://avatars2.githubusercontent.com/u/4982001?v=4", + "profile": "http://rbrtsmith.com/", + "contributions": [ + "bug" + ] + }, + { + "login": "zgreen", + "name": "Zach Green", + "avatar_url": "https://avatars0.githubusercontent.com/u/1700355?v=4", + "profile": "https://offbyone.tech", + "contributions": [ + "doc" + ] + }, + { + "login": "dadamssg", + "name": "dadamssg", + "avatar_url": "https://avatars3.githubusercontent.com/u/881986?v=4", + "profile": "https://github.com/dadamssg", + "contributions": [ + "doc" + ] + }, + { + "login": "YazanAabeed", + "name": "Yazan Aabed", + "avatar_url": "https://avatars0.githubusercontent.com/u/8734097?v=4", + "profile": "https://www.yaabed.com/", + "contributions": [ + "blog" + ] + }, + { + "login": "timbonicus", + "name": "Tim", + "avatar_url": "https://avatars0.githubusercontent.com/u/556258?v=4", + "profile": "https://github.com/timbonicus", + "contributions": [ + "bug", + "code", + "doc", + "test" + ] + }, + { + "login": "divyanshu013", + "name": "Divyanshu Maithani", + "avatar_url": "https://avatars3.githubusercontent.com/u/6682655?v=4", + "profile": "http://divyanshu.xyz", + "contributions": [ + "tutorial", + "video" + ] + }, + { + "login": "metagrover", + "name": "Deepak Grover", + "avatar_url": "https://avatars2.githubusercontent.com/u/9116042?v=4", + "profile": "https://www.linkedin.com/in/metagrover", + "contributions": [ + "tutorial", + "video" + ] + }, + { + "login": "eyalcohen4", + "name": "Eyal Cohen", + "avatar_url": "https://avatars0.githubusercontent.com/u/16276358?v=4", + "profile": "https://github.com/eyalcohen4", + "contributions": [ + "doc" + ] + }, + { + "login": "petermakowski", + "name": "Peter Makowski", + "avatar_url": "https://avatars3.githubusercontent.com/u/7452681?v=4", + "profile": "https://github.com/petermakowski", + "contributions": [ + "doc" + ] + }, + { + "login": "Michielnuyts", + "name": "Michiel Nuyts", + "avatar_url": "https://avatars2.githubusercontent.com/u/20361668?v=4", + "profile": "https://github.com/Michielnuyts", + "contributions": [ + "doc" + ] + } + ] +} diff --git a/packages/react-testing-library/.gitattributes b/packages/react-testing-library/.gitattributes new file mode 100644 index 00000000..391f0a4e --- /dev/null +++ b/packages/react-testing-library/.gitattributes @@ -0,0 +1,2 @@ +* text=auto +*.js text eol=lf diff --git a/packages/react-testing-library/.github/ISSUE_TEMPLATE.md b/packages/react-testing-library/.github/ISSUE_TEMPLATE.md new file mode 100644 index 00000000..9e04f824 --- /dev/null +++ b/packages/react-testing-library/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,81 @@ + + +- `react-testing-library` version: +- `react` version: +- `node` version: +- `npm` (or `yarn`) version: + +Relevant code or config + +```javascript +``` + +What you did: + +What happened: + + + +Reproduction repository: + + + +Problem description: + + + +Suggested solution: + + diff --git a/packages/react-testing-library/.github/ISSUE_TEMPLATE/Bug_Report.md b/packages/react-testing-library/.github/ISSUE_TEMPLATE/Bug_Report.md new file mode 100644 index 00000000..f1debab9 --- /dev/null +++ b/packages/react-testing-library/.github/ISSUE_TEMPLATE/Bug_Report.md @@ -0,0 +1,63 @@ +--- +name: 🐛 Bug Report +about: Bugs, missing documentation, or unexpected behavior 🤔. +--- + + + +- `react-testing-library` version: +- `react` version: +- `node` version: +- `npm` (or `yarn`) version: + +### Relevant code or config: + +```js +var your => (code) => here; +``` + +### What you did: + + + +### What happened: + + + +### Reproduction: + + + +### Problem description: + + + +### Suggested solution: + + diff --git a/packages/react-testing-library/.github/ISSUE_TEMPLATE/Feature_Request.md b/packages/react-testing-library/.github/ISSUE_TEMPLATE/Feature_Request.md new file mode 100644 index 00000000..357d1df7 --- /dev/null +++ b/packages/react-testing-library/.github/ISSUE_TEMPLATE/Feature_Request.md @@ -0,0 +1,51 @@ +--- +name: 💡 Feature Request +about: I have a suggestion (and might want to implement myself 🙂)! +--- + + + +### Describe the feature you'd like: + + + +### Suggested implementation: + + + +### Describe alternatives you've considered: + + + +### Teachability, Documentation, Adoption, Migration Strategy: + + diff --git a/packages/react-testing-library/.github/ISSUE_TEMPLATE/Question.md b/packages/react-testing-library/.github/ISSUE_TEMPLATE/Question.md new file mode 100644 index 00000000..52790875 --- /dev/null +++ b/packages/react-testing-library/.github/ISSUE_TEMPLATE/Question.md @@ -0,0 +1,20 @@ +--- +name: ❓ Support Question +about: 🛑 If you have a question 💬, please check out our support channels! +--- + +-------------- 👆 Click "Preview"! + +Issues on GitHub are intended to be related to problems with the library itself +and feature requests so we recommend not using this medium to ask them here 😁. + +--- + +## ❓ Support Forums + +- React Spectrum https://spectrum.chat/react-testing-library +- Reactiflux on Discord https://www.reactiflux.com +- Stack Overflow + https://stackoverflow.com/questions/tagged/react-testing-library + +**ISSUES WHICH ARE QUESTIONS WILL BE CLOSED** diff --git a/packages/react-testing-library/.github/PULL_REQUEST_TEMPLATE.md b/packages/react-testing-library/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..ee765ccf --- /dev/null +++ b/packages/react-testing-library/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,44 @@ + + + + +**What**: + + + +**Why**: + + + +**How**: + + + +**Checklist**: + + + + + +- [ ] Documentation +- [ ] Tests +- [ ] Ready to be merged + +- [ ] Added myself to contributors table + + + diff --git a/packages/react-testing-library/.gitignore b/packages/react-testing-library/.gitignore new file mode 100644 index 00000000..09048d22 --- /dev/null +++ b/packages/react-testing-library/.gitignore @@ -0,0 +1,12 @@ +node_modules +coverage +dist +.opt-in +.opt-out +.DS_Store +.eslintcache + +# these cause more harm than good +# when working with contributors +package-lock.json +yarn.lock diff --git a/packages/react-testing-library/.npmrc b/packages/react-testing-library/.npmrc new file mode 100644 index 00000000..d2722898 --- /dev/null +++ b/packages/react-testing-library/.npmrc @@ -0,0 +1,2 @@ +registry=http://registry.npmjs.org/ +package-lock=false diff --git a/packages/react-testing-library/.prettierignore b/packages/react-testing-library/.prettierignore new file mode 100644 index 00000000..30117ea2 --- /dev/null +++ b/packages/react-testing-library/.prettierignore @@ -0,0 +1,4 @@ +package.json +node_modules +dist +coverage diff --git a/packages/react-testing-library/.prettierrc b/packages/react-testing-library/.prettierrc new file mode 100644 index 00000000..f3685197 --- /dev/null +++ b/packages/react-testing-library/.prettierrc @@ -0,0 +1,11 @@ +{ + "printWidth": 80, + "tabWidth": 2, + "useTabs": false, + "semi": false, + "singleQuote": true, + "trailingComma": "all", + "bracketSpacing": false, + "jsxBracketSameLine": false, + "proseWrap": "always" +} diff --git a/packages/react-testing-library/CHANGELOG.md b/packages/react-testing-library/CHANGELOG.md new file mode 100644 index 00000000..2a675299 --- /dev/null +++ b/packages/react-testing-library/CHANGELOG.md @@ -0,0 +1,5 @@ +# CHANGELOG + +The changelog is automatically updated using +[semantic-release](https://github.com/semantic-release/semantic-release). You +can see it on the [releases page](../../releases). diff --git a/packages/react-testing-library/CODE_OF_CONDUCT.md b/packages/react-testing-library/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..070cb5f6 --- /dev/null +++ b/packages/react-testing-library/CODE_OF_CONDUCT.md @@ -0,0 +1,75 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, gender identity and expression, level of +experience, nationality, personal appearance, race, religion, or sexual identity +and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +- Using welcoming and inclusive language +- Being respectful of differing viewpoints and experiences +- Gracefully accepting constructive criticism +- Focusing on what is best for the community +- Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +- The use of sexualized language or imagery and unwelcome sexual attention or + advances +- Trolling, insulting/derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or electronic + address, without explicit permission +- Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, or to ban temporarily or permanently any +contributor for other behaviors that they deem inappropriate, threatening, +offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at kent+coc@doddsfamily.us. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an +incident. Further details of specific enforcement policies may be posted +separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 1.4, available at [http://contributor-covenant.org/version/1/4][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ diff --git a/packages/react-testing-library/CONTRIBUTING.md b/packages/react-testing-library/CONTRIBUTING.md new file mode 100644 index 00000000..8642e953 --- /dev/null +++ b/packages/react-testing-library/CONTRIBUTING.md @@ -0,0 +1,71 @@ +# Contributing + +Thanks for being willing to contribute! + +**Working on your first Pull Request?** You can learn how from this _free_ +series [How to Contribute to an Open Source Project on GitHub][egghead] + +## Project setup + +1. Fork and clone the repo +2. Run `npm run setup -s` to install dependencies and run validation +3. Create a branch for your PR with `git checkout -b pr/your-branch-name` + +> Tip: Keep your `master` branch pointing at the original repository and make +> pull requests from branches on your fork. To do this, run: +> +> ``` +> git remote add upstream https://github.com/kentcdodds/react-testing-library.git +> git fetch upstream +> git branch --set-upstream-to=upstream/master master +> ``` +> +> This will add the original repository as a "remote" called "upstream," Then +> fetch the git information from that remote, then set your local `master` +> branch to use the upstream master branch whenever you run `git pull`. Then you +> can make all of your pull request branches based on this `master` branch. +> Whenever you want to update your version of `master`, do a regular `git pull`. + +## Add yourself as a contributor + +This project follows the [all contributors][all-contributors] specification. To +add yourself to the table of contributors on the `README.md`, please use the +automated script as part of your PR: + +```console +npm run add-contributor +``` + +Follow the prompt and commit `.all-contributorsrc` and `README.md` in the PR. If +you've already added yourself to the list and are making a new type of +contribution, you can run it again and select the added contribution type. + +## Committing and Pushing changes + +Please make sure to run the tests before you commit your changes. You can run +`npm run test:update` which will update any snapshots that need updating. Make +sure to include those changes (if they exist) in your commit. + +### opt into git hooks + +There are git hooks set up with this project that are automatically installed +when you install dependencies. They're really handy, but are turned off by +default (so as to not hinder new contributors). You can opt into these by +creating a file called `.opt-in` at the root of the project and putting this +inside: + +``` +pre-commit +``` + +## Help needed + +Please checkout the [the open issues][issues] + +Also, please watch the repo and respond to questions/bug reports/feature +requests! Thanks! + +[egghead]: + https://egghead.io/series/how-to-contribute-to-an-open-source-project-on-github +[all-contributors]: https://github.com/kentcdodds/all-contributors +[issues]: https://github.com/kentcdodds/react-testing-library/issues diff --git a/packages/react-testing-library/LICENSE b/packages/react-testing-library/LICENSE new file mode 100644 index 00000000..4c43675b --- /dev/null +++ b/packages/react-testing-library/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) +Copyright (c) 2017 Kent C. Dodds + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/react-testing-library/README.md b/packages/react-testing-library/README.md new file mode 100644 index 00000000..39e7307c --- /dev/null +++ b/packages/react-testing-library/README.md @@ -0,0 +1,1219 @@ +
+

react-testing-library

+ + +goat + + +

Simple and complete React DOM testing utilities that encourage good testing practices.

+
+ +
+ +[![Build Status][build-badge]][build] +[![Code Coverage][coverage-badge]][coverage] +[![version][version-badge]][package] [![downloads][downloads-badge]][npmtrends] +[![MIT License][license-badge]][license] + +[![All Contributors](https://img.shields.io/badge/all_contributors-50-orange.svg?style=flat-square)](#contributors) +[![PRs Welcome][prs-badge]][prs] [![Code of Conduct][coc-badge]][coc] +[![Join the community on Spectrum][spectrum-badge]][spectrum] + +[![Watch on GitHub][github-watch-badge]][github-watch] +[![Star on GitHub][github-star-badge]][github-star] +[![Tweet][twitter-badge]][twitter] + +## The problem + +You want to write maintainable tests for your React components. As a part of +this goal, you want your tests to avoid including implementation details of your +components and rather focus on making your tests give you the confidence for +which they are intended. As part of this, you want your testbase to be +maintainable in the long run so refactors of your components (changes to +implementation but not functionality) don't break your tests and slow you and +your team down. + +## This solution + +The `react-testing-library` is a very light-weight solution for testing React +components. It provides light utility functions on top of `react-dom` and +`react-dom/test-utils`, in a way that encourages better testing practices. Its +primary guiding principle is: + +> [The more your tests resemble the way your software is used, the more +> confidence they can give you.][guiding-principle] + +So rather than dealing with instances of rendered react components, your tests +will work with actual DOM nodes. The utilities this library provides facilitate +querying the DOM in the same way the user would. Finding for elements by their +label text (just like a user would), finding links and buttons from their text +(like a user would). It also exposes a recommended way to find elements by a +`data-testid` as an "escape hatch" for elements where the text content and label +do not make sense or is not practical. + +This library encourages your applications to be more accessible and allows you +to get your tests closer to using your components the way a user will, which +allows your tests to give you more confidence that your application will work +when a real user uses it. + +This library is a replacement for [enzyme](http://airbnb.io/enzyme/). While you +_can_ follow these guidelines using enzyme itself, enforcing this is harder +because of all the extra utilities that enzyme provides (utilities which +facilitate testing implementation details). Read more about this in +[the FAQ](#faq) below. + +**What this library is not**: + +1. A test runner or framework +2. Specific to a testing framework (though we recommend Jest as our preference, + the library works with any framework. See + [Using Without Jest](https://github.com/kentcdodds/dom-testing-library#using-without-jest)) + +> NOTE: This library is built on top of +> [`dom-testing-library`](https://github.com/kentcdodds/dom-testing-library) +> which is where most of the logic behind the queries is. + +## Example + +```javascript +// __tests__/fetch.js +import React from 'react' +import {render, fireEvent, cleanup, waitForElement} from 'react-testing-library' +// this adds custom jest matchers from jest-dom +import 'jest-dom/extend-expect' +import axiosMock from 'axios' // the mock lives in a __mocks__ directory +import Fetch from '../fetch' // see the tests for a full implementation + +// automatically unmount and cleanup DOM after the test is finished. +afterEach(cleanup) + +test('Fetch makes an API call and displays the greeting when load-greeting is clicked', async () => { + // Arrange + axiosMock.get.mockResolvedValueOnce({data: {greeting: 'hello there'}}) + const url = '/greeting' + const {getByText, getByTestId, container} = render() + + // Act + fireEvent.click(getByText('Load Greeting')) + + // Let's wait until our mocked `get` request promise resolves and + // the component calls setState and re-renders. + // getByTestId throws an error if it cannot find an element with the given ID + // and waitForElement will wait until the callback doesn't throw an error + const greetingTextNode = await waitForElement(() => + getByTestId('greeting-text'), + ) + + // Assert + expect(axiosMock.get).toHaveBeenCalledTimes(1) + expect(axiosMock.get).toHaveBeenCalledWith(url) + expect(getByTestId('greeting-text')).toHaveTextContent('hello there') + expect(getByTestId('ok-button')).toHaveAttribute('disabled') + // snapshots work great with regular DOM nodes! + expect(container.firstChild).toMatchSnapshot() +}) +``` + +## Table of Contents + + + + +- [Installation](#installation) +- [Setup](#setup) + - [Global Config](#global-config) + - [Custom Render](#custom-render) +- [Usage](#usage) + - [`render`](#render) + - [`cleanup`](#cleanup) +- [`dom-testing-library` APIs](#dom-testing-library-apis) + - [`fireEvent(node: HTMLElement, event: Event)`](#fireeventnode-htmlelement-event-event) + - [`waitForElement`](#waitforelement) + - [`wait`](#wait) + - [`within`](#within) +- [`TextMatch`](#textmatch) +- [`query` APIs](#query-apis) +- [`queryAll` and `getAll` APIs](#queryall-and-getall-apis) +- [Examples](#examples) +- [Learning Material](#learning-material) +- [FAQ](#faq) +- [Other Solutions](#other-solutions) +- [Guiding Principles](#guiding-principles) +- [Contributors](#contributors) +- [Issues](#issues) + - [🐛 Bugs](#-bugs) + - [💡 Feature Requests](#-feature-requests) + - [❓ Questions](#-questions) +- [LICENSE](#license) + + + +## Installation + +This module is distributed via [npm][npm] which is bundled with [node][node] and +should be installed as one of your project's `devDependencies`: + +``` +npm install --save-dev react-testing-library +``` + +This library has a `peerDependencies` listing for `react-dom`. + +You may also be interested in installing `jest-dom` so you can use +[the custom jest matchers](https://github.com/gnapse/jest-dom#readme) + +## Setup + +`react-testing-library` does not require any configuration to be used (as +demonstrated in the example above). However, there are some things you can do to +when configuring your testing framework to reduce some boilerplate. In these +docs we'll demonstrate configuring Jest, but you should be able to do similar +things with any testing framework (react-testing-library does not require that +you use Jest). + +### Global Config + +There are several options you can add to your global test config that simplify +the setup and teardown of tests in individual files. For example, you can ensure +[`cleanup`](#cleanup) is called after each test and import additional +assertions. + +To do this with Jest, you can add the +[`setupTestFrameworkScriptFile`](https://facebook.github.io/jest/docs/en/configuration.html#setuptestframeworkscriptfile-string) +option to your Jest config. The setup file can be anywhere, for example +`jest.setup.js` or `./utils/setupTests.js`. + +If you are using the default setup from create-react-app, this option is set to +`src/setupTests.js`. You should create this file if it doesn't exist and put the +setup code there. + +```javascript +// jest.config.js +module.exports = { + setupTestFrameworkScriptFile: require.resolve('./jest.setup.js'), + // ... other options ... +} +``` + +```javascript +// jest.setup.js + +// add some helpful assertions +import 'jest-dom/extend-expect' + +// this is basically: afterEach(cleanup) +import 'react-testing-library/cleanup-after-each' +``` + +### Custom Render + +It's often useful to define a custom render method that includes things like +global context providers, data stores, etc. To make this available globally, one +approach is to define a utility file that re-exports everything from +`react-testing-library`. You can replace react-testing-library with this file in +all your imports. + +```diff +// my-component.test.js +- import { render, fireEvent } from 'react-testing-library'; ++ import { render, fireEvent } from '../test-utils'; +``` + +```js +// test-utils.js +import {render} from 'react-testing-library' +import {ThemeProvider} from 'my-ui-lib' +import {TranslationProvider} from 'my-i18n-lib' +import defaultStrings from 'i18n/en-x-default' + +const customRender = (node, ...options) => { + return render( + + + {node} + + , + ...options, + ) +} + +// re-export everything +export * from 'react-testing-library' + +// override render method +export {customRender as render} +``` + +To make this file accessible without using relative imports, add the folder +containing the file to the Jest `moduleDirectories` option. Note: this will make +_all_ the .js files in that directory importable without `../`. + +```diff +// my-component.test.js +- import { render, fireEvent } from '../test-utils'; ++ import { render, fireEvent } from 'test-utils'; +``` + +```diff +// jest.config.js +module.exports = { + moduleDirectories: [ + 'node_modules', ++ // add the directory with the test-utils.js file, for example: ++ 'utils', // a utility folder ++ __dirname, // the root directory + ], + // ... other options ... +} +``` + +## Usage + +### `render` + +Defined as: + +```typescript +function render( + ui: React.ReactElement, + options?: { + /* You wont often use this, expand below for docs on options */ + }, +): RenderResult +``` + +Render into a container which is appended to `document.body`. It should be used +with [cleanup](#cleanup): + +```javascript +import {render} from 'react-testing-library' + +render(
) +``` + +
+ +Expand to see documentation on the options + +You wont often need to specify options, but if you ever do, here are the +available options which you could provide as a second argument to `render`. + +**container**: By default, `react-testing-library` will create a `div` and +append that div to the `document.body` and this is where your react component +will be rendered. If you provide your own HTMLElement `container` via this +option, it will not be appended to the `document.body` automatically. + +For Example: If you are unit testing a `tablebody` element, it cannot be a child +of a `div`. In this case, you can specify a `table` as the render `container`. + +```javascript +const table = document.createElement('table') + +const {container} = render(, { + container: document.body.appendChild(table), +}) +``` + +**baseElement**: If the `container` is specified, then this defaults to that, +otherwise this defaults to `document.documentElement`. This is used as the base +element for the queries as well as what is printed when you use `debug()`. + +
+ +In the example above, the `render` method returns an object that has a few +properties: + +#### `container` + +The containing DOM node of your rendered React Element (rendered using +`ReactDOM.render`). It's a `div`. This is a regular DOM node, so you can call +`container.querySelector` etc. to inspect the children. + +> Tip: To get the root element of your rendered element, use +> `container.firstChild`. +> +> NOTE: When that root element is a +> [React Fragment](https://reactjs.org/docs/fragments.html), +> `container.firstChild` will only get the first child of that Fragment, not the +> Fragment itself. + +#### `baseElement` + +The containing DOM node where your React Element is rendered in the container. +If you don't specify the `baseElement` in the options of `render`, it will +default to `document.body`. + +This is useful when the component you want to test renders something outside the +container div, e.g. when you want to snapshot test your portal component which +renders it's HTML directly in the body. + +> Note: the queries returned by the `render` looks into baseElement, so you can +> use queries to test your portal component without the baseElement. + +#### `debug` + +This method is a shortcut for `console.log(prettyDOM(baseElement))`. + +```javascript +import React from 'react' +import {render} from 'react-testing-library' + +const HelloWorld = () =>

Hello World

+const {debug} = render() +debug() +//
+//

Hello World

+//
+// you can also pass an element: debug(getByTestId('messages')) +``` + +This is a simple wrapper around `prettyDOM` which is also exposed and comes from +[`dom-testing-library`](https://github.com/kentcdodds/dom-testing-library/blob/master/README.md#prettydom). + +#### `rerender` + +It'd probably be better if you test the component that's doing the prop updating +to ensure that the props are being updated correctly (see +[the Guiding Principles section](#guiding-principles)). That said, if you'd +prefer to update the props of a rendered component in your test, this function +can be used to update props of the rendered component. + +```javascript +import {render} from 'react-testing-library' + +const {rerender} = render() + +// re-render the same component with different props +rerender() +``` + +[Open the tests](https://github.com/kentcdodds/react-testing-library/blob/master/examples/__tests__/update-props.js) +for a full example of this. + +#### `unmount` + +This will cause the rendered component to be unmounted. This is useful for +testing what happens when your component is removed from the page (like testing +that you don't leave event handlers hanging around causing memory leaks). + +> This method is a pretty small abstraction over +> `ReactDOM.unmountComponentAtNode` + +```javascript +import {render} from 'react-testing-library' + +const {container, unmount} = render() +unmount() +// your component has been unmounted and now: container.innerHTML === '' +``` + +#### `getByLabelText(text: TextMatch, options: {selector: string = '*'}): HTMLElement` + +This will search for the label that matches the given [`TextMatch`](#textmatch), +then find the element associated with that label. + +```javascript +import {render} from 'react-testing-library' + +const {getByLabelText} = render() +const inputNode = getByLabelText('Username') + +// this would find the input node for the following DOM structures: +// The "for" attribute (NOTE: in JSX with React you'll write "htmlFor" rather than "for") +// +// +// +// The aria-labelledby attribute +// +// +// +// Wrapper labels +// +// +// It will NOT find the input node for this: +// +// +// For this case, you can provide a `selector` in the options: +const inputNode = getByLabelText('username', {selector: 'input'}) +// and that would work +// Note that will also work, but take +// care because this is not a label that users can see on the page. So +// the purpose of your input should be obvious for those users. +``` + +> Note: This method will throw an error if it cannot find the node. If you don't +> want this behavior (for example you wish to assert that it doesn't exist), +> then use `queryByLabelText` instead. + +#### `getByPlaceholderText(text: TextMatch): HTMLElement` + +This will search for all elements with a placeholder attribute and find one that +matches the given [`TextMatch`](#textmatch). + +```javascript +import {render} from 'react-testing-library' + +const {getByPlaceholderText} = render() +const inputNode = getByPlaceholderText('Username') +``` + +> NOTE: a placeholder is not a good substitute for a label so you should +> generally use `getByLabelText` instead. + +#### `getByText(text: TextMatch): HTMLElement` + +This will search for all elements that have a text node with `textContent` +matching the given [`TextMatch`](#textmatch). + +```javascript +import {render} from 'react-testing-library' + +const {getByText} = render(About ℹ️) +const aboutAnchorNode = getByText('about') +``` + +#### `getByAltText(text: TextMatch): HTMLElement` + +This will return the element (normally an ``) that has the given `alt` +text. Note that it only supports elements which accept an `alt` attribute: +[``](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img), +[``](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input), +and [``](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/area) +(intentionally excluding +[``](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/applet) +as it's deprecated). + +```javascript +import {render} from 'react-testing-library' + +const {getByAltText} = render( + Incredibles 2 Poster, +) +const incrediblesPosterImg = getByAltText(/incredibles.*poster$/i) +``` + +#### `getByTestId(text: TextMatch): HTMLElement` + +A shortcut to `` container.querySelector(`[data-testid="${yourId}"]`) `` (and it +also accepts a [`TextMatch`](#textmatch)). + +```javascript +import {render} from 'react-testing-library' + +const {getByTestId} = render() +const usernameInputElement = getByTestId('username-input') +``` + +> In the spirit of [the guiding principles](#guiding-principles), it is +> recommended to use this only after `getByLabel`, `getByPlaceholderText` or +> `getByText` don't work for your use case. Using `data-testid` attributes do +> not resemble how your software is used and should be avoided if possible. That +> said, they are _way_ better than querying based on DOM structure. Learn more +> about `data-testid`s from the blog post ["Making your UI tests resilient to +> change"][data-testid-blog-post] + +### `cleanup` + +Unmounts React trees that were mounted with [render](#render). + +```javascript +import {cleanup, render} from 'react-testing-library' + +afterEach(cleanup) // <-- add this + +test('renders into document', () => { + render(
) + // ... +}) + +// ... more tests ... +``` + +Failing to call `cleanup` when you've called `render` could result in a memory +leak and tests which are not "idempotent" (which can lead to difficult to debug +errors in your tests). + +**If you don't want to add this to _every single test file_** then we recommend +that you configure your test framework to run a file before your tests which +does this automatically. See the [setup](#setup) section for guidance on how to +set up your framework. + +## `dom-testing-library` APIs + +`react-testing-library` is built on top of +[`dom-testing-library`](https://github.com/kentcdodds/dom-testing-library) and +re-exports everything from `dom-testing-library`. Some notable included exports: + +### `fireEvent(node: HTMLElement, event: Event)` + +Fire DOM events. + +React attaches an event handler on the `document` and handles some DOM events +via event delegation (events bubbling up from a `target` to an ancestor). +Because of this, your `node` must be in the `document.body` for `fireEvent` to +work with React. This is why `render` appends your container to `document.body`. +This is an alternative to simulating Synthetic React Events via +[`Simulate`](https://reactjs.org/docs/test-utils.html#simulate). The benefit of +using `fireEvent` over `Simulate` is that you are testing real DOM events +instead of Synthetic Events. This aligns better with +[the Guiding Principles](#guiding-principles). +([Also Dan Abramov told me to stop use Simulate](https://twitter.com/dan_abramov/status/980807288444383232)). + +> NOTE: If you don't like having to use `cleanup` (which we have to do because +> we render into `document.body`) to get `fireEvent` working, then feel free to +> try to chip into making it possible for React to attach event handlers to the +> rendered node rather than the `document`. Learn more here: +> [facebook/react#2043](https://github.com/facebook/react/issues/2043) + +```javascript +import {render, cleanup, fireEvent} from 'react-testing-library' + +// don't forget to clean up the document.body +afterEach(cleanup) + +test('clicks submit button', () => { + const spy = jest.fn() + const {getByText} = render() + + fireEvent( + getByText('Submit'), + new MouseEvent('click', { + bubbles: true, // click events must bubble for React to see it + cancelable: true, + }), + ) + + expect(spy).toHaveBeenCalledTimes(1) +}) +``` + +#### `fireEvent[eventName](node: HTMLElement, eventProperties: Object)` + +Convenience methods for firing DOM events. Check out +[dom-testing-library/src/events.js](https://github.com/kentcdodds/dom-testing-library/blob/master/src/events.js) +for a full list as well as default `eventProperties`. + +```javascript +import {render, fireEvent} from 'react-testing-library' + +const {getByText} = render(
) + +// similar to the above example +// click will bubble for React to see it +const rightClick = {button: 2} +fireEvent.click(getByText('Submit'), rightClick) +// default `button` property for click events is set to `0` which is a left click. +``` + +If you want to trigger the +[`onChange`](https://reactjs.org/docs/dom-elements.html#onchange) handler of a +[controlled component](https://reactjs.org/docs/forms.html#controlled-components) +with a different `event.target.value`, sending `value` through `eventProperties` +won't work like it does with `Simulate`. You need to use `fireEvent` to fire a +`change` DOM event with `value` property set on `target` + +```javascript +import {render, fireEvent} from 'react-testing-library' + +const {getByLabelText} = render() + +const comment = getByLabelText('Comment') +fireEvent.change(comment, { + target: {value: 'Great advice, I love your posts!'}, +}) +``` + +### `waitForElement` + +> [Read full docs from `dom-testing-library`](https://github.com/kentcdodds/dom-testing-library/blob/master/README.md#waitforelement) + +```js +import {render, waitForElement} from 'react-testing-library' + +test('waiting for an element', async () => { + const {getByText} = render() + + await waitForElement(() => getByText('Search')) +}) +``` + +### `wait` + +> [Read full docs from `dom-testing-library`](https://github.com/kentcdodds/dom-testing-library/blob/master/README.md#wait) + +It's recommended to prefer `waitForElement`, but this can be helpful on occasion + +```javascript +import 'jest-dom/extend-expect' +import {render, wait} from 'react-testing-library' + +test('can fill in the form after loaded', async () => { + const {queryByText, getByLabelText} = render() + + // wait until the callback does not throw an error. In this case, that means + // it'll wait until the element with the text that says "loading..." is gone. + await wait(() => + expect(queryByText(/loading\.\.\./i)).not.toBeInTheDocument(), + ) + getByLabelText('username').value = 'chucknorris' + // continue doing stuff +}) +``` + +### `within` + +> [Read full docs from `dom-testing-library`](https://github.com/kentcdodds/dom-testing-library/blob/master/README.md#within-and-getqueriesforelement-apis) + +The queries returned from `render` are scoped to the entire page. Sometimes, +there is no guarantee that the text, placeholder, or label you want to query is +unique on the page. So you might want to explicitly tell react-render-dom to get +an element only within a particular section of the page. within is a helper +function for this case. + +Example: To get the text 'hello' only within a section called 'messages', you +could do: + +```javascript +import {render, within} from 'react-testing-library' + +// ... + +const {getByTestId} = render(/* stuff */) +const messagesSection = getByTestId('messages') +const hello = within(messagesSection).getByText('hello') +``` + +## `TextMatch` + +Several APIs accept a `TextMatch` which can be a `string`, `regex` or a +`function` which returns `true` for a match and `false` for a mismatch. + +See [dom-testing-library#textmatch][dom-testing-lib-textmatch] for options. + +Examples: + +```javascript +import {render, getByText} from 'react-testing-library' + +const {container} = render(
Hello World
) + +// WILL find the div: + +// Matching a string: +getByText(container, 'Hello World') // full string match +getByText(container, 'llo Worl', {exact: false}) // substring match +getByText(container, 'hello world', {exact: false}) // ignore case + +// Matching a regex: +getByText(container, /World/) // substring match +getByText(container, /world/i) // substring match, ignore case +getByText(container, /^hello world$/i) // full string match, ignore case +getByText(container, /Hello W?oRlD/i) // advanced regex + +// Matching with a custom function: +getByText(container, (content, element) => content.startsWith('Hello')) + +// WILL NOT find the div: + +getByText(container, 'Goodbye World') // full string does not match +getByText(container, /hello world/) // case-sensitive regex with different case +// function looking for a span when it's actually a div: +getByText(container, (content, element) => { + return element.tagName.toLowerCase() === 'span' && content.startsWith('Hello') +}) +``` + +## `query` APIs + +Each of the `get` APIs listed in [the `render`](#render) section above have a +complimentary `query` API. The `get` APIs will throw errors if a proper node +cannot be found. This is normally the desired effect. However, if you want to +make an assertion that an element is _not_ present in the DOM, then you can use +the `query` API instead: + +```javascript +import {render} from 'react-testing-library' + +const {queryByText} = render() +const submitButton = queryByText('submit') +expect(submitButton).toBeNull() // it doesn't exist +``` + +## `queryAll` and `getAll` APIs + +Each of the `query` APIs have a corresponding `queryAll` version that always +returns an Array of matching nodes. `getAll` is the same but throws when the +array has a length of 0. + +```javascript +import {render} from 'react-testing-library' + +const {queryAllByText} = render() +const submitButtons = queryAllByText('submit') +expect(submitButtons).toHaveLength(3) // expect 3 elements +expect(submitButtons[0]).toBeInTheDocument() +``` + +## Examples + +> We're in the process of moving examples to +> [`react-testing-library-examples`](https://codesandbox.io/s/github/kentcdodds/react-testing-library-examples). + +You'll find examples of testing with different libraries in +[the `examples` directory](https://github.com/kentcdodds/react-testing-library/blob/master/examples). +Some included are: + +- [`react-redux`](https://github.com/kentcdodds/react-testing-library/blob/master/examples/__tests__/react-redux.js) +- [`react-router`](https://github.com/kentcdodds/react-testing-library/blob/master/examples/__tests__/react-router.js) +- [`react-context`](https://github.com/kentcdodds/react-testing-library/blob/master/examples/__tests__/react-context.js) + +## Learning Material + +- [Migrating from Enzyme shallow rendering to explicit component mocks](https://www.youtube.com/watch?v=LHUdxkThTM0&list=PLV5CVI1eNcJgCrPH_e6d57KRUTiDZgs0u) + +- [Confident React](https://www.youtube.com/watch?v=qXRPHRgcXJ0&list=PLV5CVI1eNcJgNqzNwcs4UKrlJdhfDjshf) +- [Test Driven Development with react-testing-library](https://www.youtube.com/watch?v=kCR3JAR7CHE&list=PLV5CVI1eNcJgCrPH_e6d57KRUTiDZgs0u) +- [Testing React and Web Applications](https://kentcdodds.com/workshops/#testing-react-and-web-applications) +- [Build a joke app with TDD](https://medium.com/@mbaranovski/quick-guide-to-tdd-in-react-81888be67c64) + by [@mbaranovski](https://github.com/mbaranovski) +- [Build a comment feed with TDD](https://medium.freecodecamp.org/how-to-build-sturdy-react-apps-with-tdd-and-the-react-testing-library-47ad3c5c8e47) + by [@iwilsonq](https://github.com/iwilsonq) +- [A clear way to unit testing React JS components using Jest and react-testing-library](https://www.richardkotze.com/coding/react-testing-library-jest) + by [Richard Kotze](https://github.com/rkotze) + +- [Intro to react-testing-library](https://chrisnoring.gitbooks.io/react/content/testing/react-testing-library.html) + by [Chris Noring](https://github.com/softchris) +- [Integration testing in React](https://medium.com/@jeffreyrussom/integration-testing-in-react-21f92a55a894) + by [Jeffrey Russom](https://github.com/qswitcher) + +- [React-testing-library have fantastic testing 🐐](https://medium.com/yazanaabed/react-testing-library-have-a-fantastic-testing-198b04699237) + by [Yazan Aabed](https://github.com/YazanAabeed) + +- [Building a React Tooltip Library](https://www.youtube.com/playlist?list=PLMV09mSPNaQmFLPyrfFtpUdClVfutjF5G) + by [divyanshu013](https://github.com/divyanshu013) and + [metagrover](https://github.com/metagrover) + +- [A sample repo using react-testing-library to test a Relay Modern GraphQL app](https://github.com/zth/relay-modern-flow-jest-example) + +- [Creating Readable Tests Using React Testing Library](https://medium.com/flatiron-labs/creating-readable-tests-using-react-testing-library-2bd03c49c284) + by [Lukeghenco](https://github.com/Lukeghenco) + +Feel free to contribute more! + +## FAQ + +
+ +How do I test input onChange handlers? + +TL;DR: +[Go to the `on-change.js` example](https://codesandbox.io/s/github/kentcdodds/react-testing-library-examples/tree/master/?module=%2Fsrc%2F__tests__%2Fon-change.js&previewwindow=tests) + +In summary: + +```javascript +import React from 'react' +import 'react-testing-library/cleanup-after-each' +import {render, fireEvent} from 'react-testing-library' + +test('change values via the fireEvent.change method', () => { + const handleChange = jest.fn() + const {container} = render() + const input = container.firstChild + fireEvent.change(input, {target: {value: 'a'}}) + expect(handleChange).toHaveBeenCalledTimes(1) + expect(input.value).toBe('a') +}) + +test('checkboxes (and radios) must use fireEvent.click', () => { + const handleChange = jest.fn() + const {container} = render() + const checkbox = container.firstChild + fireEvent.click(checkbox) + expect(handleChange).toHaveBeenCalledTimes(1) + expect(checkbox.checked).toBe(true) +}) +``` + +If you've used enzyme or React's TestUtils, you may be accustomed to changing +inputs like so: + +```javascript +input.value = 'a' +Simulate.change(input) +``` + +We can't do this with react-testing-library because React actually keeps track +of any time you assign the `value` property on an `input` and so when you fire +the `change` event, React thinks that the value hasn't actually been changed. + +This works for Simulate because they use internal APIs to fire special simulated +events. With react-testing-library, we try to avoid implementation details to +make your tests more resiliant. + +So we have it worked out for the change event handler to set the property for +you in a way that's not trackable by React. This is why you must pass the value +as part of the `change` method call. + +
+ +
+ +Which get method should I use? + +Based on [the Guiding Principles](#guiding-principles), your test should +resemble how your code (component, page, etc.) is used as much as possible. With +this in mind, we recommend this order of priority: + +1. `getByLabelText`: Only really good for form fields, but this is the number 1 + method a user finds those elements, so it should be your top preference. +2. `getByPlaceholderText`: + [A placeholder is not a substitute for a label](https://www.nngroup.com/articles/form-design-placeholders/). + But if that's all you have, then it's better than alternatives. +3. `getByText`: Not useful for forms, but this is the number 1 method a user + finds other elements (like buttons to click), so it should be your top + preference for non-form elements. +4. `getByAltText`: If your element is one which supports `alt` text (`img`, + `area`, and `input`), then you can use this to find that element. +5. `getByTestId`: The user cannot see (or hear) these, so this is only + recommended for cases where you can't match by text or it doesn't make sense + (the text is dynamic). + +Other than that, you can also use the `container` to query the rendered +component as well (using the regular +[`querySelector` API](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector)). + +
+ +
+ +Can I write unit tests with this library? + +Definitely yes! You can write unit and integration tests with this library. See +below for more on how to mock dependencies (because this library intentionally +does NOT support shallow rendering) if you want to unit test a high level +component. The tests in this project show several examples of unit testing with +this library. + +As you write your tests, keep in mind: + +> The more your tests resemble the way your software is used, the more +> confidence they can give you. - [17 Feb 2018][guiding-principle] + +
+ +
+ +What if my app is localized and I don't have access to the text in test? + +This is fairly common. Our first bit of advice is to try to get the default text +used in your tests. That will make everything much easier (more than just using +this utility). If that's not possible, then you're probably best to just stick +with `data-testid`s (which is not bad anyway). + +
+ +
+ +If I can't use shallow rendering, how do I mock out components in tests? + +In general, you should avoid mocking out components (see +[the Guiding Principles section](#guiding-principles)). However if you need to, +then it's pretty trivial using +[Jest's mocking feature](https://facebook.github.io/jest/docs/en/manual-mocks.html). +One case that I've found mocking to be especially useful is for animation +libraries. I don't want my tests to wait for animations to end. + +```javascript +jest.mock('react-transition-group', () => { + const FakeTransition = jest.fn(({children}) => children) + const FakeCSSTransition = jest.fn( + props => + props.in ? {props.children} : null, + ) + return {CSSTransition: FakeCSSTransition, Transition: FakeTransition} +}) + +test('you can mock things with jest.mock', () => { + const {getByTestId, queryByTestId} = render( + , + ) + expect(queryByTestId('hidden-message')).toBeTruthy() // we just care it exists + // hide the message + fireEvent.click(getByTestId('toggle-message')) + // in the real world, the CSSTransition component would take some time + // before finishing the animation which would actually hide the message. + // So we've mocked it out for our tests to make it happen instantly + expect(queryByTestId('hidden-message')).toBeNull() // we just care it doesn't exist +}) +``` + +Note that because they're Jest mock functions (`jest.fn()`), you could also make +assertions on those as well if you wanted. + +[Open full test](https://github.com/kentcdodds/react-testing-library/blob/master/examples/__tests__/mock.react-transition-group.js) +for the full example. + +This looks like more work that shallow rendering (and it is), but it gives you +more confidence so long as your mock resembles the thing you're mocking closly +enough. + +If you want to make things more like shallow rendering, then you could do +something more +[like this](https://github.com/kentcdodds/react-testing-library/blob/master/examples/__tests__/shallow.react-transition-group.js). + +Learn more about how Jest mocks work from my blog post: +["But really, what is a JavaScript mock?"](https://blog.kentcdodds.com/but-really-what-is-a-javascript-mock-10d060966f7d) + +
+ +
+ +What if I want to verify that an element does NOT exist? + +You typically will get access to rendered elements using the `getByTestId` +utility. However, that function will throw an error if the element isn't found. +If you want to specifically test for the absence of an element, then you should +use the `queryByTestId` utility which will return the element if found or `null` +if not. + +```javascript +expect(queryByTestId('thing-that-does-not-exist')).toBeNull() +``` + +
+ +
+ +I really don't like data-testids, but none of the other queries make sense. Do I have to use a data-testid? + +Definitely not. That said, a common reason people don't like the `data-testid` +attribute is they're concerned about shipping that to production. I'd suggest +that you probably want some simple E2E tests that run in production on occasion +to make certain that things are working smoothly. In that case the `data-testid` +attributes will be very useful. Even if you don't run these in production, you +may want to run some E2E tests that run on the same code you're about to ship to +production. In that case, the `data-testid` attributes will be valuable there as +well. + +All that said, if you really don't want to ship `data-testid` attributes, then +you can use +[this simple babel plugin](https://www.npmjs.com/package/babel-plugin-react-remove-properties) +to remove them. + +If you don't want to use them at all, then you can simply use regular DOM +methods and properties to query elements off your container. + +```javascript +const firstLiInDiv = container.querySelector('div li') +const allLisInDiv = container.querySelectorAll('div li') +const rootElement = container.firstChild +``` + +
+ +
+ +What if I’m iterating over a list of items that I want to put the data-testid="item" attribute on. How do I distinguish them from each other? + +You can make your selector just choose the one you want by including :nth-child +in the selector. + +```javascript +const thirdLiInUl = container.querySelector('ul > li:nth-child(3)') +``` + +Or you could include the index or an ID in your attribute: + +```javascript +
  • {item.text}
  • +``` + +And then you could use the `getByTestId` utility: + +```javascript +const items = [ + /* your items */ +] +const {getByTestId} = render(/* your component with the items */) +const thirdItem = getByTestId(`item-${items[2].id}`) +``` + +
    + +
    + +What about enzyme is "bloated with complexity and features" and "encourage +poor testing practices"? + +Most of the damaging features have to do with encouraging testing implementation +details. Primarily, these are +[shallow rendering](http://airbnb.io/enzyme/docs/api/shallow.html), APIs which +allow selecting rendered elements by component constructors, and APIs which +allow you to get and interact with component instances (and their +state/properties) (most of enzyme's wrapper APIs allow this). + +The guiding principle for this library is: + +> The more your tests resemble the way your software is used, the more +> confidence they can give you. - [17 Feb 2018][guiding-principle] + +Because users can't directly interact with your app's component instances, +assert on their internal state or what components they render, or call their +internal methods, doing those things in your tests reduce the confidence they're +able to give you. + +That's not to say that there's never a use case for doing those things, so they +should be possible to accomplish, just not the default and natural way to test +react components. + +
    + +
    + +Why isn't snapshot diffing working? + +If you use the [snapshot-diff](https://github.com/jest-community/snapshot-diff) +library to save snapshot diffs, it won't work out of the box because this +library uses the DOM which is mutable. Changes don't return new objects so +snapshot-diff will think it's the same object and avoid diffing it. + +Luckily there's an easy way to make it work: clone the DOM when passing it into +snapshot-diff. It looks like this: + +```js +const firstVersion = container.cloneNode(true) +// Do some changes +snapshotDiff(firstVersion, container.cloneNode(true)) +``` + +
    + +## Other Solutions + +In preparing this project, +[I tweeted about it](https://twitter.com/kentcdodds/status/974278185540964352) +and [Sune Simonsen](https://github.com/sunesimonsen) +[took up the challenge](https://twitter.com/sunesimonsen/status/974784783908818944). +We had different ideas of what to include in the library, so I decided to create +this one instead. + +## Guiding Principles + +> [The more your tests resemble the way your software is used, the more +> confidence they can give you.][guiding-principle] + +We try to only expose methods and utilities that encourage you to write tests +that closely resemble how your react components are used. + +Utilities are included in this project based on the following guiding +principles: + +1. If it relates to rendering components, it deals with DOM nodes rather than + component instances, nor should it encourage dealing with component + instances. +2. It should be generally useful for testing individual React components or + full React applications. While this library is focused on `react-dom`, + utilities could be included even if they don't directly relate to + `react-dom`. +3. Utility implementations and APIs should be simple and flexible. + +At the end of the day, what we want is for this library to be pretty +light-weight, simple, and understandable. + +## Contributors + +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") | +| :---: | :---: | :---: | :---: | :---: | :---: | :---: | +| [
    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") | [
    Łukasz Gandecki](http://team.thebrain.pro)
    [💻](https://github.com/kentcdodds/react-testing-library/commits?author=lgandecki "Code") [⚠️](https://github.com/kentcdodds/react-testing-library/commits?author=lgandecki "Tests") [📖](https://github.com/kentcdodds/react-testing-library/commits?author=lgandecki "Documentation") | [
    Ivan Babak](https://sompylasar.github.io)
    [🐛](https://github.com/kentcdodds/react-testing-library/issues?q=author%3Asompylasar "Bug reports") [🤔](#ideas-sompylasar "Ideas, Planning, & Feedback") | [
    Jesse Day](https://github.com/jday3)
    [💻](https://github.com/kentcdodds/react-testing-library/commits?author=jday3 "Code") | [
    Ernesto García](http://gnapse.github.io)
    [💬](#question-gnapse "Answering Questions") [💻](https://github.com/kentcdodds/react-testing-library/commits?author=gnapse "Code") [📖](https://github.com/kentcdodds/react-testing-library/commits?author=gnapse "Documentation") | [
    Josef Maxx Blake](http://jomaxx.com)
    [💻](https://github.com/kentcdodds/react-testing-library/commits?author=jomaxx "Code") [📖](https://github.com/kentcdodds/react-testing-library/commits?author=jomaxx "Documentation") [⚠️](https://github.com/kentcdodds/react-testing-library/commits?author=jomaxx "Tests") | +| [
    Michal Baranowski](https://twitter.com/baranovskim)
    [📝](#blog-mbaranovski "Blogposts") [✅](#tutorial-mbaranovski "Tutorials") | [
    Arthur Puthin](https://github.com/aputhin)
    [📖](https://github.com/kentcdodds/react-testing-library/commits?author=aputhin "Documentation") | [
    Thomas Chia](https://github.com/thchia)
    [💻](https://github.com/kentcdodds/react-testing-library/commits?author=thchia "Code") [📖](https://github.com/kentcdodds/react-testing-library/commits?author=thchia "Documentation") | [
    Thiago Galvani](http://ilegra.com/)
    [📖](https://github.com/kentcdodds/react-testing-library/commits?author=thiagopaiva99 "Documentation") | [
    Christian](http://Chriswcs.github.io)
    [⚠️](https://github.com/kentcdodds/react-testing-library/commits?author=ChrisWcs "Tests") | [
    Alex Krolick](https://alexkrolick.com)
    [💬](#question-alexkrolick "Answering Questions") [📖](https://github.com/kentcdodds/react-testing-library/commits?author=alexkrolick "Documentation") [💡](#example-alexkrolick "Examples") [🤔](#ideas-alexkrolick "Ideas, Planning, & Feedback") | [
    Johann Hubert Sonntagbauer](https://github.com/johann-sonntagbauer)
    [💻](https://github.com/kentcdodds/react-testing-library/commits?author=johann-sonntagbauer "Code") [📖](https://github.com/kentcdodds/react-testing-library/commits?author=johann-sonntagbauer "Documentation") [⚠️](https://github.com/kentcdodds/react-testing-library/commits?author=johann-sonntagbauer "Tests") | +| [
    Maddi Joyce](http://www.maddijoyce.com)
    [💻](https://github.com/kentcdodds/react-testing-library/commits?author=maddijoyce "Code") | [
    Ryan Vice](http://www.vicesoftware.com)
    [📖](https://github.com/kentcdodds/react-testing-library/commits?author=RyanAtViceSoftware "Documentation") | [
    Ian Wilson](https://ianwilson.io)
    [📝](#blog-iwilsonq "Blogposts") [✅](#tutorial-iwilsonq "Tutorials") | [
    Daniel](https://github.com/InExtremaRes)
    [🐛](https://github.com/kentcdodds/react-testing-library/issues?q=author%3AInExtremaRes "Bug reports") [💻](https://github.com/kentcdodds/react-testing-library/commits?author=InExtremaRes "Code") | [
    Giorgio Polvara](https://twitter.com/Gpx)
    [🐛](https://github.com/kentcdodds/react-testing-library/issues?q=author%3AGpx "Bug reports") [🤔](#ideas-Gpx "Ideas, Planning, & Feedback") | [
    John Gozde](https://github.com/jgoz)
    [💻](https://github.com/kentcdodds/react-testing-library/commits?author=jgoz "Code") | [
    Sam Horton](https://twitter.com/SavePointSam)
    [📖](https://github.com/kentcdodds/react-testing-library/commits?author=SavePointSam "Documentation") [💡](#example-SavePointSam "Examples") [🤔](#ideas-SavePointSam "Ideas, Planning, & Feedback") | +| [
    Richard Kotze (mobile)](http://www.richardkotze.com)
    [📖](https://github.com/kentcdodds/react-testing-library/commits?author=rkotze "Documentation") | [
    Brahian E. Soto Mercedes](https://github.com/sotobuild)
    [📖](https://github.com/kentcdodds/react-testing-library/commits?author=sotobuild "Documentation") | [
    Benoit de La Forest](https://github.com/bdelaforest)
    [📖](https://github.com/kentcdodds/react-testing-library/commits?author=bdelaforest "Documentation") | [
    Salah](https://github.com/thesalah)
    [💻](https://github.com/kentcdodds/react-testing-library/commits?author=thesalah "Code") [⚠️](https://github.com/kentcdodds/react-testing-library/commits?author=thesalah "Tests") | [
    Adam Gordon](http://gordonizer.com)
    [🐛](https://github.com/kentcdodds/react-testing-library/issues?q=author%3Aicfantv "Bug reports") [💻](https://github.com/kentcdodds/react-testing-library/commits?author=icfantv "Code") | [
    Matija Marohnić](https://silvenon.com)
    [📖](https://github.com/kentcdodds/react-testing-library/commits?author=silvenon "Documentation") | [
    Justice Mba](https://github.com/Dajust)
    [📖](https://github.com/kentcdodds/react-testing-library/commits?author=Dajust "Documentation") | +| [
    Mark Pollmann](https://markpollmann.com/)
    [📖](https://github.com/kentcdodds/react-testing-library/commits?author=MarkPollmann "Documentation") | [
    Ehtesham Kafeel](https://github.com/ehteshamkafeel)
    [💻](https://github.com/kentcdodds/react-testing-library/commits?author=ehteshamkafeel "Code") [📖](https://github.com/kentcdodds/react-testing-library/commits?author=ehteshamkafeel "Documentation") | [
    Julio Pavón](http://jpavon.com)
    [💻](https://github.com/kentcdodds/react-testing-library/commits?author=jpavon "Code") | [
    Duncan L](http://www.duncanleung.com/)
    [📖](https://github.com/kentcdodds/react-testing-library/commits?author=duncanleung "Documentation") [💡](#example-duncanleung "Examples") | [
    Tiago Almeida](https://www.linkedin.com/in/tyagow/?locale=en_US)
    [📖](https://github.com/kentcdodds/react-testing-library/commits?author=tyagow "Documentation") | [
    Robert Smith](http://rbrtsmith.com/)
    [🐛](https://github.com/kentcdodds/react-testing-library/issues?q=author%3Arbrtsmith "Bug reports") | [
    Zach Green](https://offbyone.tech)
    [📖](https://github.com/kentcdodds/react-testing-library/commits?author=zgreen "Documentation") | +| [
    dadamssg](https://github.com/dadamssg)
    [📖](https://github.com/kentcdodds/react-testing-library/commits?author=dadamssg "Documentation") | [
    Yazan Aabed](https://www.yaabed.com/)
    [📝](#blog-YazanAabeed "Blogposts") | [
    Tim](https://github.com/timbonicus)
    [🐛](https://github.com/kentcdodds/react-testing-library/issues?q=author%3Atimbonicus "Bug reports") [💻](https://github.com/kentcdodds/react-testing-library/commits?author=timbonicus "Code") [📖](https://github.com/kentcdodds/react-testing-library/commits?author=timbonicus "Documentation") [⚠️](https://github.com/kentcdodds/react-testing-library/commits?author=timbonicus "Tests") | [
    Divyanshu Maithani](http://divyanshu.xyz)
    [✅](#tutorial-divyanshu013 "Tutorials") [📹](#video-divyanshu013 "Videos") | [
    Deepak Grover](https://www.linkedin.com/in/metagrover)
    [✅](#tutorial-metagrover "Tutorials") [📹](#video-metagrover "Videos") | [
    Eyal Cohen](https://github.com/eyalcohen4)
    [📖](https://github.com/kentcdodds/react-testing-library/commits?author=eyalcohen4 "Documentation") | [
    Peter Makowski](https://github.com/petermakowski)
    [📖](https://github.com/kentcdodds/react-testing-library/commits?author=petermakowski "Documentation") | +| [
    Michiel Nuyts](https://github.com/Michielnuyts)
    [📖](https://github.com/kentcdodds/react-testing-library/commits?author=Michielnuyts "Documentation") | + + + +This project follows the [all-contributors][all-contributors] specification. +Contributions of any kind welcome! + +## Issues + +_Looking to contribute? Look for the [Good First Issue][good-first-issue] +label._ + +### 🐛 Bugs + +Please file an issue for bugs, missing documentation, or unexpected behavior. + +[**See Bugs**][bugs] + +### 💡 Feature Requests + +Please file an issue to suggest new features. Vote on feature requests by adding +a 👍. This helps maintainers prioritize what to work on. + +[**See Feature Requests**][requests] + +### ❓ Questions + +For questions related to using the library, please visit a support community +instead of filing an issue on GitHub. + +- [Spectrum][spectrum] +- [Reactiflux on Discord][reactiflux] +- [Stack Overflow][stackoverflow] + +## LICENSE + +MIT + + + + + +[npm]: https://www.npmjs.com/ +[node]: https://nodejs.org +[build-badge]: https://img.shields.io/travis/kentcdodds/react-testing-library.svg?style=flat-square +[build]: https://travis-ci.org/kentcdodds/react-testing-library +[coverage-badge]: https://img.shields.io/codecov/c/github/kentcdodds/react-testing-library.svg?style=flat-square +[coverage]: https://codecov.io/github/kentcdodds/react-testing-library +[version-badge]: https://img.shields.io/npm/v/react-testing-library.svg?style=flat-square +[package]: https://www.npmjs.com/package/react-testing-library +[downloads-badge]: https://img.shields.io/npm/dm/react-testing-library.svg?style=flat-square +[npmtrends]: http://www.npmtrends.com/react-testing-library +[spectrum-badge]: https://withspectrum.github.io/badge/badge.svg +[spectrum]: https://spectrum.chat/react-testing-library +[license-badge]: https://img.shields.io/npm/l/react-testing-library.svg?style=flat-square +[license]: https://github.com/kentcdodds/react-testing-library/blob/master/LICENSE +[prs-badge]: https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square +[prs]: http://makeapullrequest.com +[donate-badge]: https://img.shields.io/badge/$-support-green.svg?style=flat-square +[coc-badge]: https://img.shields.io/badge/code%20of-conduct-ff69b4.svg?style=flat-square +[coc]: https://github.com/kentcdodds/react-testing-library/blob/master/CODE_OF_CONDUCT.md +[github-watch-badge]: https://img.shields.io/github/watchers/kentcdodds/react-testing-library.svg?style=social +[github-watch]: https://github.com/kentcdodds/react-testing-library/watchers +[github-star-badge]: https://img.shields.io/github/stars/kentcdodds/react-testing-library.svg?style=social +[github-star]: https://github.com/kentcdodds/react-testing-library/stargazers +[twitter]: https://twitter.com/intent/tweet?text=Check%20out%20react-testing-library%20by%20%40kentcdodds%20https%3A%2F%2Fgithub.com%2Fkentcdodds%2Freact-testing-library%20%F0%9F%91%8D +[twitter-badge]: https://img.shields.io/twitter/url/https/github.com/kentcdodds/react-testing-library.svg?style=social +[emojis]: https://github.com/kentcdodds/all-contributors#emoji-key +[all-contributors]: https://github.com/kentcdodds/all-contributors +[set-immediate]: https://developer.mozilla.org/en-US/docs/Web/API/Window/setImmediate +[guiding-principle]: https://twitter.com/kentcdodds/status/977018512689455106 +[data-testid-blog-post]: https://blog.kentcdodds.com/making-your-ui-tests-resilient-to-change-d37a6ee37269 +[dom-testing-lib-textmatch]: https://github.com/kentcdodds/dom-testing-library#textmatch +[bugs]: https://github.com/kentcdodds/react-testing-library/issues?q=is%3Aissue+is%3Aopen+label%3Abug+sort%3Acreated-desc +[requests]: https://github.com/kentcdodds/react-testing-library/issues?q=is%3Aissue+sort%3Areactions-%2B1-desc+label%3Aenhancement+is%3Aopen +[good-first-issue]: https://github.com/kentcdodds/react-testing-library/issues?utf8=✓&q=is%3Aissue+is%3Aopen+sort%3Areactions-%2B1-desc+label%3A"good+first+issue"+ +[reactiflux]: https://www.reactiflux.com/ +[stackoverflow]: https://stackoverflow.com/questions/tagged/react-testing-library + + diff --git a/packages/react-testing-library/cleanup-after-each.js b/packages/react-testing-library/cleanup-after-each.js new file mode 100644 index 00000000..f0a17c95 --- /dev/null +++ b/packages/react-testing-library/cleanup-after-each.js @@ -0,0 +1 @@ +afterEach(require('./dist').cleanup) diff --git a/packages/react-testing-library/examples/.eslintrc.js b/packages/react-testing-library/examples/.eslintrc.js new file mode 100644 index 00000000..67ff778c --- /dev/null +++ b/packages/react-testing-library/examples/.eslintrc.js @@ -0,0 +1,15 @@ +module.exports = { + // we have to do this so our tests can reference 'react-testing-library' + overrides: [ + { + files: ['**/__tests__/**'], + settings: { + 'import/resolver': { + jest: { + jestConfigFile: require.resolve('./jest.config.js'), + }, + }, + }, + }, + ], +} diff --git a/packages/react-testing-library/examples/README.md b/packages/react-testing-library/examples/README.md new file mode 100644 index 00000000..9c89757e --- /dev/null +++ b/packages/react-testing-library/examples/README.md @@ -0,0 +1,31 @@ +# Examples + +Here we have some examples for you to know how to not only use +`react-testing-library` but also in general how to test common scenarios that +pop up with React. Check out the `__tests__` directory for the different +examples. + +## Setup + +The examples have a unique jest/eslint set up so the test files will resemble +how they might appear in your project. (You'll see in the tests that we can +`import {render} from 'react-testing-library'`). + +## Contribute + +We're always happy to accept contributions to the examples. Can't have too many +of these as there are TONs of different ways to test React. Examples of testing +components that use different and common libraries is always welcome. Try to +keep examples simple enough for people to understand the main thing we're trying +to demonstrate from the example. + +Please follow the guidelines found in [CONTRIBUTING.md][contributing] to set up +the project. + +To run the tests, you can run `npm test examples`, or if you're working on a +specific example, you can run `npm test name-of-your-file`. This will put you +into Jest's interactive watch mode with a filter based on the name you provided. + +[contributing]: + https://github.com/kentcdodds/react-testing-library/blob/master/CONTRIBUTING.md +[jest-dom]: https://github.com/gnapse/jest-dom diff --git a/packages/react-testing-library/examples/__tests__/mock.react-transition-group.js b/packages/react-testing-library/examples/__tests__/mock.react-transition-group.js new file mode 100644 index 00000000..ef22636f --- /dev/null +++ b/packages/react-testing-library/examples/__tests__/mock.react-transition-group.js @@ -0,0 +1,50 @@ +import React from 'react' +import {CSSTransition} from 'react-transition-group' +import {render, fireEvent, cleanup} from 'react-testing-library' + +function Fade({children, ...props}) { + return ( + + {children} + + ) +} + +class HiddenMessage extends React.Component { + state = {show: this.props.initialShow || false} + toggle = () => { + this.setState(({show}) => ({show: !show})) + } + render() { + return ( +
    + + +
    Hello world
    +
    +
    + ) + } +} + +afterEach(cleanup) + +jest.mock('react-transition-group', () => { + const FakeTransition = jest.fn(({children}) => children) + const FakeCSSTransition = jest.fn( + props => + props.in ? {props.children} : null, + ) + return {CSSTransition: FakeCSSTransition, Transition: FakeTransition} +}) + +test('you can mock things with jest.mock', () => { + const {getByText, queryByText} = render() + expect(getByText('Hello world')).toBeTruthy() // we just care it exists + // hide the message + fireEvent.click(getByText('Toggle')) + // in the real world, the CSSTransition component would take some time + // before finishing the animation which would actually hide the message. + // So we've mocked it out for our tests to make it happen instantly + expect(queryByText('Hello World')).toBeNull() // we just care it doesn't exist +}) diff --git a/packages/react-testing-library/examples/__tests__/react-context.js b/packages/react-testing-library/examples/__tests__/react-context.js new file mode 100644 index 00000000..4c709429 --- /dev/null +++ b/packages/react-testing-library/examples/__tests__/react-context.js @@ -0,0 +1,58 @@ +import React from 'react' +import {render, cleanup} from 'react-testing-library' +import 'jest-dom/extend-expect' +import {NameContext, NameProvider, NameConsumer} from '../react-context' + +afterEach(cleanup) + +/** + * Test default values by rendering a context consumer without a + * matching provider + */ +test('NameConsumer shows default value', () => { + const {getByText} = render() + expect(getByText(/^My Name Is:/)).toHaveTextContent('My Name Is: Unknown') +}) + +/** + * To test a component tree that uses a context consumer but not the provider, + * wrap the tree with a matching provider + */ +test('NameConsumer shows value from provider', () => { + const tree = ( + + + + ) + const {getByText} = render(tree) + expect(getByText(/^My Name Is:/)).toHaveTextContent('My Name Is: C3P0') +}) + +/** + * To test a component that provides a context value, render a matching + * consumer as the child + */ +test('NameProvider composes full name from first, last', () => { + const tree = ( + + + {value => Received: {value}} + + + ) + const {getByText} = render(tree) + expect(getByText(/^Received:/).textContent).toBe('Received: Boba Fett') +}) + +/** + * A tree containing both a providers and consumer can be rendered normally + */ +test('NameProvider/Consumer shows name of character', () => { + const tree = ( + + + + ) + const {getByText} = render(tree) + expect(getByText(/^My Name Is:/).textContent).toBe('My Name Is: Leia Organa') +}) diff --git a/packages/react-testing-library/examples/__tests__/react-redux.js b/packages/react-testing-library/examples/__tests__/react-redux.js new file mode 100644 index 00000000..acfb3369 --- /dev/null +++ b/packages/react-testing-library/examples/__tests__/react-redux.js @@ -0,0 +1,106 @@ +import React from 'react' +import {createStore} from 'redux' +import {Provider, connect} from 'react-redux' +import {render, fireEvent, cleanup} from 'react-testing-library' + +// counter.js +class Counter extends React.Component { + increment = () => { + this.props.dispatch({type: 'INCREMENT'}) + } + + decrement = () => { + this.props.dispatch({type: 'DECREMENT'}) + } + + render() { + return ( +
    +

    Counter

    +
    + + {this.props.count} + +
    +
    + ) + } +} + +// normally this would be: +// export default connect(state => ({count: state.count}))(Counter) +// but for this test we'll give it a variable name +// because we're doing this all in one file +const ConnectedCounter = connect(state => ({count: state.count}))(Counter) + +// app.js +function reducer(state = {count: 0}, action) { + switch (action.type) { + case 'INCREMENT': + return { + count: state.count + 1, + } + case 'DECREMENT': + return { + count: state.count - 1, + } + default: + return state + } +} + +// normally here you'd do: +// const store = createStore(reducer) +// ReactDOM.render( +// +// +// , +// document.getElementById('root'), +// ) +// but for this test we'll umm... not do that :) + +// Now here's what your test will look like: + +afterEach(cleanup) + +// this is a handy function that I normally make available for all my tests +// that deal with connected components. +// you can provide initialState or the entire store that the ui is rendered with +function renderWithRedux( + ui, + {initialState, store = createStore(reducer, initialState)} = {}, +) { + return { + ...render({ui}), + // adding `store` to the returned utilities to allow us + // to reference it in our tests (just try to avoid using + // this to test implementation details). + store, + } +} + +test('can render with redux with defaults', () => { + const {getByTestId, getByText} = renderWithRedux() + fireEvent.click(getByText('+')) + expect(getByTestId('count-value').textContent).toBe('1') +}) + +test('can render with redux with custom initial state', () => { + const {getByTestId, getByText} = renderWithRedux(, { + initialState: {count: 3}, + }) + fireEvent.click(getByText('-')) + expect(getByTestId('count-value').textContent).toBe('2') +}) + +test('can render with redux with custom store', () => { + // this is a silly store that can never be changed + const store = createStore(() => ({count: 1000})) + const {getByTestId, getByText} = renderWithRedux(, { + store, + }) + fireEvent.click(getByText('+')) + expect(getByTestId('count-value').textContent).toBe('1000') + fireEvent.click(getByText('-')) + expect(getByTestId('count-value').textContent).toBe('1000') +}) diff --git a/packages/react-testing-library/examples/__tests__/react-router.js b/packages/react-testing-library/examples/__tests__/react-router.js new file mode 100644 index 00000000..02f4c7d2 --- /dev/null +++ b/packages/react-testing-library/examples/__tests__/react-router.js @@ -0,0 +1,71 @@ +import React from 'react' +import {withRouter} from 'react-router' +import {Link, Route, Router, Switch} from 'react-router-dom' +import {createMemoryHistory} from 'history' +import {render, fireEvent, cleanup} from 'react-testing-library' + +const About = () =>
    You are on the about page
    +const Home = () =>
    You are home
    +const NoMatch = () =>
    No match
    + +const LocationDisplay = withRouter(({location}) => ( +
    {location.pathname}
    +)) + +function App() { + return ( +
    + Home + About + + + + + + +
    + ) +} + +// Ok, so here's what your tests might look like + +afterEach(cleanup) + +// this is a handy function that I would utilize for any component +// that relies on the router being in context +function renderWithRouter( + ui, + {route = '/', history = createMemoryHistory({initialEntries: [route]})} = {}, +) { + return { + ...render({ui}), + // adding `history` to the returned utilities to allow us + // to reference it in our tests (just try to avoid using + // this to test implementation details). + history, + } +} + +test('full app rendering/navigating', () => { + const {container, getByText} = renderWithRouter() + // normally I'd use a data-testid, but just wanted to show this is also possible + expect(container.innerHTML).toMatch('You are home') + const leftClick = {button: 0} + fireEvent.click(getByText(/about/i), leftClick) + // normally I'd use a data-testid, but just wanted to show this is also possible + expect(container.innerHTML).toMatch('You are on the about page') +}) + +test('landing on a bad page', () => { + const {container} = renderWithRouter(, { + route: '/something-that-does-not-match', + }) + // normally I'd use a data-testid, but just wanted to show this is also possible + expect(container.innerHTML).toMatch('No match') +}) + +test('rendering a component that uses withRouter', () => { + const route = '/some-route' + const {getByTestId} = renderWithRouter(, {route}) + expect(getByTestId('location-display').textContent).toBe(route) +}) diff --git a/packages/react-testing-library/examples/__tests__/shallow.react-transition-group.js b/packages/react-testing-library/examples/__tests__/shallow.react-transition-group.js new file mode 100644 index 00000000..fe7ddead --- /dev/null +++ b/packages/react-testing-library/examples/__tests__/shallow.react-transition-group.js @@ -0,0 +1,51 @@ +import React from 'react' +import {CSSTransition} from 'react-transition-group' +import {render, fireEvent, cleanup} from 'react-testing-library' + +function Fade({children, ...props}) { + return ( + + {children} + + ) +} + +class HiddenMessage extends React.Component { + state = {show: this.props.initialShow || false} + toggle = () => { + this.setState(({show}) => ({show: !show})) + } + render() { + return ( +
    + + +
    Hello world
    +
    +
    + ) + } +} + +afterEach(cleanup) + +jest.mock('react-transition-group', () => { + const FakeCSSTransition = jest.fn(() => null) + return {CSSTransition: FakeCSSTransition} +}) + +test('you can mock things with jest.mock', () => { + const {getByText} = render() + const context = expect.any(Object) + const children = expect.any(Object) + const defaultProps = {children, timeout: 1000, className: 'fade'} + expect(CSSTransition).toHaveBeenCalledWith( + {in: true, ...defaultProps}, + context, + ) + fireEvent.click(getByText(/toggle/i)) + expect(CSSTransition).toHaveBeenCalledWith( + {in: true, ...defaultProps}, + expect.any(Object), + ) +}) diff --git a/packages/react-testing-library/examples/__tests__/update-props.js b/packages/react-testing-library/examples/__tests__/update-props.js new file mode 100644 index 00000000..c245b9ff --- /dev/null +++ b/packages/react-testing-library/examples/__tests__/update-props.js @@ -0,0 +1,33 @@ +// This is an example of how to update the props of a rendered component. +// the basic idea is to simply call `render` again and provide the same container +// that your first call created for you. + +import React from 'react' +import {render, cleanup} from 'react-testing-library' + +let idCounter = 1 + +class NumberDisplay extends React.Component { + id = idCounter++ // to ensure we don't remount a different instance + render() { + return ( +
    + {this.props.number} + {this.id} +
    + ) + } +} + +afterEach(cleanup) + +test('calling render with the same component on the same container does not remount', () => { + const {getByTestId, rerender} = render() + expect(getByTestId('number-display').textContent).toBe('1') + + // re-render the same component with different props + rerender() + expect(getByTestId('number-display').textContent).toBe('2') + + expect(getByTestId('instance-id').textContent).toBe('1') +}) diff --git a/packages/react-testing-library/examples/jest.config.js b/packages/react-testing-library/examples/jest.config.js new file mode 100644 index 00000000..8dc4330d --- /dev/null +++ b/packages/react-testing-library/examples/jest.config.js @@ -0,0 +1,11 @@ +const jestConfig = require('kcd-scripts/jest') + +module.exports = Object.assign(jestConfig, { + rootDir: __dirname, + roots: [__dirname], + displayName: 'example', + moduleNameMapper: { + // this is just here so our examples look like they would in a real project + 'react-testing-library': require.resolve('../src'), + }, +}) diff --git a/packages/react-testing-library/examples/react-context.js b/packages/react-testing-library/examples/react-context.js new file mode 100644 index 00000000..4ec24f6c --- /dev/null +++ b/packages/react-testing-library/examples/react-context.js @@ -0,0 +1,18 @@ +import React from 'react' + +const NameContext = React.createContext('Unknown') + +const NameProvider = ({children, first, last}) => { + const fullName = `${first} ${last}` + return ( + {children} + ) +} + +const NameConsumer = () => ( + + {value =>
    My Name Is: {value}
    } +
    +) + +export {NameContext, NameConsumer, NameProvider} diff --git a/packages/react-testing-library/jest.config.js b/packages/react-testing-library/jest.config.js new file mode 100644 index 00000000..23133e67 --- /dev/null +++ b/packages/react-testing-library/jest.config.js @@ -0,0 +1,5 @@ +const jestConfig = require('kcd-scripts/jest') + +module.exports = Object.assign(jestConfig, { + displayName: 'library', +}) diff --git a/packages/react-testing-library/other/MAINTAINING.md b/packages/react-testing-library/other/MAINTAINING.md new file mode 100644 index 00000000..703126da --- /dev/null +++ b/packages/react-testing-library/other/MAINTAINING.md @@ -0,0 +1,70 @@ +# Maintaining + +This is documentation for maintainers of this project. + +## Code of Conduct + +Please review, understand, and be an example of it. Violations of the code of +conduct are taken seriously, even (especially) for maintainers. + +## Issues + +We want to support and build the community. We do that best by helping people +learn to solve their own problems. We have an issue template and hopefully most +folks follow it. If it's not clear what the issue is, invite them to create a +minimal reproduction of what they're trying to accomplish or the bug they think +they've found. + +Once it's determined that a code change is necessary, point people to +[makeapullrequest.com](http://makeapullrequest.com) and invite them to make a +pull request. If they're the one who needs the feature, they're the one who can +build it. If they need some hand holding and you have time to lend a hand, +please do so. It's an investment into another human being, and an investment +into a potential maintainer. + +Remember that this is open source, so the code is not yours, it's ours. If +someone needs a change in the codebase, you don't have to make it happen +yourself. Commit as much time to the project as you want/need to. Nobody can ask +any more of you than that. + +## Pull Requests + +As a maintainer, you're fine to make your branches on the main repo or on your +own fork. Either way is fine. + +When we receive a pull request, a travis build is kicked off automatically (see +the `.travis.yml` for what runs in the travis build). We avoid merging anything +that breaks the travis build. + +Please review PRs and focus on the code rather than the individual. You never +know when this is someone's first ever PR and we want their experience to be as +positive as possible, so be uplifting and constructive. + +When you merge the pull request, 99% of the time you should use the +[Squash and merge](https://help.github.com/articles/merging-a-pull-request/) +feature. This keeps our git history clean, but more importantly, this allows us +to make any necessary changes to the commit message so we release what we want +to release. See the next section on Releases for more about that. + +## Release + +Our releases are automatic. They happen whenever code lands into `master`. A +travis build gets kicked off and if it's successful, a tool called +[`semantic-release`](https://github.com/semantic-release/semantic-release) is +used to automatically publish a new release to npm as well as a changelog to +GitHub. It is only able to determine the version and whether a release is +necessary by the git commit messages. With this in mind, **please brush up on +[the commit message convention][commit] which drives our releases.** + +> One important note about this: Please make sure that commit messages do NOT +> contain the words "BREAKING CHANGE" in them unless we want to push a major +> version. I've been burned by this more than once where someone will include +> "BREAKING CHANGE: None" and it will end up releasing a new major version. Not +> a huge deal honestly, but kind of annoying... + +## Thanks! + +Thank you so much for helping to maintain this project! + +[commit]: + https://github.com/conventional-changelog-archived-repos/conventional-changelog-angular/blob/ed32559941719a130bb0327f886d6a32a8cbc2ba/convention.md diff --git a/packages/react-testing-library/other/USERS.md b/packages/react-testing-library/other/USERS.md new file mode 100644 index 00000000..4bc12816 --- /dev/null +++ b/packages/react-testing-library/other/USERS.md @@ -0,0 +1,12 @@ +# Users + +If you or your company uses this project, add your name to this list! Eventually +we may have a website to showcase these (wanna build it!?) + +> No users have been added yet! + + diff --git a/packages/react-testing-library/other/goat.png b/packages/react-testing-library/other/goat.png new file mode 100644 index 00000000..fe29faaa Binary files /dev/null and b/packages/react-testing-library/other/goat.png differ diff --git a/packages/react-testing-library/other/jest.config.js b/packages/react-testing-library/other/jest.config.js new file mode 100644 index 00000000..3efec572 --- /dev/null +++ b/packages/react-testing-library/other/jest.config.js @@ -0,0 +1,9 @@ +module.exports = { + coverageDirectory: '../coverage', + collectCoverageFrom: [ + '**/src/**/*.js', + '!**/__tests__/**', + '!**/node_modules/**', + ], + projects: ['./', './examples'], +} diff --git a/packages/react-testing-library/other/manual-releases.md b/packages/react-testing-library/other/manual-releases.md new file mode 100644 index 00000000..1e9a7365 --- /dev/null +++ b/packages/react-testing-library/other/manual-releases.md @@ -0,0 +1,44 @@ +# manual-releases + +This project has an automated release set up. So things are only released when +there are useful changes in the code that justify a release. But sometimes +things get messed up one way or another and we need to trigger the release +ourselves. When this happens, simply bump the number below and commit that with +the following commit message based on your needs: + +**Major** + +``` +fix(release): manually release a major version + +There was an issue with a major release, so this manual-releases.md +change is to release a new major version. + +Reference: # + +BREAKING CHANGE: +``` + +**Minor** + +``` +feat(release): manually release a minor version + +There was an issue with a minor release, so this manual-releases.md +change is to release a new minor version. + +Reference: # +``` + +**Patch** + +``` +fix(release): manually release a patch version + +There was an issue with a patch release, so this manual-releases.md +change is to release a new patch version. + +Reference: # +``` + +The number of times we've had to do a manual release is: 2 diff --git a/packages/react-testing-library/package.json b/packages/react-testing-library/package.json new file mode 100644 index 00000000..994df0be --- /dev/null +++ b/packages/react-testing-library/package.json @@ -0,0 +1,83 @@ +{ + "name": "react-testing-library", + "version": "0.0.0-semantically-released", + "description": "Simple and complete React DOM testing utilities that encourage good testing practices.", + "main": "dist/index.js", + "typings": "typings/index.d.ts", + "engines": { + "node": ">=8" + }, + "scripts": { + "add-contributor": "kcd-scripts contributors add", + "build": "kcd-scripts build", + "lint": "kcd-scripts lint", + "test": "kcd-scripts test --config=other/jest.config.js", + "test:update": "npm test -- --updateSnapshot --coverage", + "validate": "kcd-scripts validate", + "setup": "npm install && npm run validate -s", + "precommit": "kcd-scripts precommit" + }, + "files": [ + "dist", + "typings", + "cleanup-after-each.js" + ], + "keywords": [ + "testing", + "react", + "ui", + "dom", + "jsdom", + "unit", + "integration", + "functional", + "end-to-end", + "e2e" + ], + "author": "Kent C. Dodds (http://kentcdodds.com/)", + "license": "MIT", + "dependencies": { + "dom-testing-library": "^3.1.0", + "wait-for-expect": "^1.0.0" + }, + "devDependencies": { + "@types/react-dom": "^16.0.6", + "axios": "^0.18.0", + "eslint-import-resolver-jest": "^2.1.1", + "history": "^4.7.2", + "jest-dom": "^1.3.1", + "jest-in-case": "^1.0.2", + "kcd-scripts": "^0.39.1", + "react": "^16.4.1", + "react-dom": "^16.4.1", + "react-redux": "^5.0.7", + "react-router": "^4.3.1", + "react-router-dom": "^4.3.1", + "react-transition-group": "^2.3.1", + "redux": "^4.0.0" + }, + "peerDependencies": { + "react-dom": "*" + }, + "eslintConfig": { + "extends": "./node_modules/kcd-scripts/eslint.js", + "rules": { + "react/prop-types": "off", + "import/no-unassigned-import": "off", + "import/named": "off" + } + }, + "eslintIgnore": [ + "node_modules", + "coverage", + "dist" + ], + "repository": { + "type": "git", + "url": "git+https://github.com/kentcdodds/react-testing-library.git" + }, + "bugs": { + "url": "https://github.com/kentcdodds/react-testing-library/issues" + }, + "homepage": "https://github.com/kentcdodds/react-testing-library#readme" +} diff --git a/packages/react-testing-library/src/__mocks__/axios.js b/packages/react-testing-library/src/__mocks__/axios.js new file mode 100644 index 00000000..9d70d624 --- /dev/null +++ b/packages/react-testing-library/src/__mocks__/axios.js @@ -0,0 +1,8 @@ +module.exports = { + get: jest.fn(() => Promise.resolve({data: {}})), +} + +// Note: +// For now we don't need any other method (POST/PUT/PATCH), what we have already works fine. +// We will add more methods only if we need to. +// For reference please read: https://github.com/kentcdodds/react-testing-library/issues/2 diff --git a/packages/react-testing-library/src/__tests__/__snapshots__/fetch.js.snap b/packages/react-testing-library/src/__tests__/__snapshots__/fetch.js.snap new file mode 100644 index 00000000..69e0e57b --- /dev/null +++ b/packages/react-testing-library/src/__tests__/__snapshots__/fetch.js.snap @@ -0,0 +1,12 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Fetch makes an API call and displays the greeting when load-greeting is clicked 1`] = ` +
    + + + hello there + +
    +`; diff --git a/packages/react-testing-library/src/__tests__/bugs.js b/packages/react-testing-library/src/__tests__/bugs.js new file mode 100644 index 00000000..05146cf7 --- /dev/null +++ b/packages/react-testing-library/src/__tests__/bugs.js @@ -0,0 +1,10 @@ +// this is where we'll put bug reproductions/regressions +// to make sure we never see them again + +import React from 'react' +import {render, cleanup} from '../' + +test('cleanup does not error when an element is not a child', () => { + render(
    , {container: document.createElement('div')}) + cleanup() +}) diff --git a/packages/react-testing-library/src/__tests__/debug.js b/packages/react-testing-library/src/__tests__/debug.js new file mode 100644 index 00000000..f7c0a927 --- /dev/null +++ b/packages/react-testing-library/src/__tests__/debug.js @@ -0,0 +1,23 @@ +import React from 'react' +import {render, cleanup} from '../' + +beforeEach(() => { + jest.spyOn(console, 'log').mockImplementation(() => {}) +}) + +afterEach(() => { + cleanup() + console.log.mockRestore() +}) + +test('debug pretty prints the container', () => { + const HelloWorld = () =>

    Hello World

    + const {debug} = render() + debug() + expect(console.log).toHaveBeenCalledTimes(1) + expect(console.log).toHaveBeenCalledWith( + expect.stringContaining('Hello World'), + ) +}) + +/* eslint no-console:0 */ diff --git a/packages/react-testing-library/src/__tests__/end-to-end.js b/packages/react-testing-library/src/__tests__/end-to-end.js new file mode 100644 index 00000000..e8ad2f8b --- /dev/null +++ b/packages/react-testing-library/src/__tests__/end-to-end.js @@ -0,0 +1,42 @@ +import React from 'react' +import {render, wait, cleanup} from '../' + +afterEach(cleanup) + +const fetchAMessage = () => + new Promise(resolve => { + // we are using random timeout here to simulate a real-time example + // of an async operation calling a callback at a non-deterministic time + const randomTimeout = Math.floor(Math.random() * 100) + setTimeout(() => { + resolve({returnedMessage: 'Hello World'}) + }, randomTimeout) + }) + +class ComponentWithLoader extends React.Component { + state = {loading: true} + async componentDidMount() { + const data = await fetchAMessage() + this.setState({data, loading: false}) // eslint-disable-line + } + render() { + if (this.state.loading) { + return
    Loading...
    + } else { + return ( +
    + Loaded this message: {this.state.data.returnedMessage}! +
    + ) + } + } +} + +test('it waits for the data to be loaded', async () => { + const {queryByText, queryByTestId} = render() + + expect(queryByText('Loading...')).toBeTruthy() + + await wait(() => expect(queryByText('Loading...')).toBeNull()) + expect(queryByTestId('message').textContent).toMatch(/Hello World/) +}) diff --git a/packages/react-testing-library/src/__tests__/events.js b/packages/react-testing-library/src/__tests__/events.js new file mode 100644 index 00000000..b8dd487b --- /dev/null +++ b/packages/react-testing-library/src/__tests__/events.js @@ -0,0 +1,165 @@ +import React from 'react' +import {render, cleanup, fireEvent} from '../' + +const eventTypes = [ + { + type: 'Clipboard', + events: ['copy', 'paste'], + elementType: 'input', + }, + { + type: 'Composition', + events: ['compositionEnd', 'compositionStart', 'compositionUpdate'], + elementType: 'input', + }, + { + type: 'Keyboard', + events: ['keyDown', 'keyPress', 'keyUp'], + elementType: 'input', + init: {keyCode: 13}, + }, + { + type: 'Focus', + events: ['focus', 'blur'], + elementType: 'input', + }, + { + type: 'Form', + events: ['focus', 'blur'], + elementType: 'input', + }, + { + type: 'Focus', + events: ['input', 'invalid'], + elementType: 'input', + }, + { + type: 'Focus', + events: ['submit'], + elementType: 'form', + }, + { + type: 'Mouse', + events: [ + 'click', + 'contextMenu', + 'doubleClick', + 'drag', + 'dragEnd', + 'dragEnter', + 'dragExit', + 'dragLeave', + 'dragOver', + 'dragStart', + 'drop', + 'mouseDown', + 'mouseEnter', + 'mouseLeave', + 'mouseMove', + 'mouseOut', + 'mouseOver', + 'mouseUp', + ], + elementType: 'button', + }, + { + type: 'Selection', + events: ['select'], + elementType: 'input', + }, + { + type: 'Touch', + events: ['touchCancel', 'touchEnd', 'touchMove', 'touchStart'], + elementType: 'button', + }, + { + type: 'UI', + events: ['scroll'], + elementType: 'div', + }, + { + type: 'Wheel', + events: ['wheel'], + elementType: 'div', + }, + { + type: 'Media', + events: [ + 'abort', + 'canPlay', + 'canPlayThrough', + 'durationChange', + 'emptied', + 'encrypted', + 'ended', + 'error', + 'loadedData', + 'loadedMetadata', + 'loadStart', + 'pause', + 'play', + 'playing', + 'progress', + 'rateChange', + 'seeked', + 'seeking', + 'stalled', + 'suspend', + 'timeUpdate', + 'volumeChange', + 'waiting', + ], + elementType: 'video', + }, + { + type: 'Image', + events: ['load', 'error'], + elementType: 'img', + }, + { + type: 'Animation', + events: ['animationStart', 'animationEnd', 'animationIteration'], + elementType: 'div', + }, + { + type: 'Transition', + events: ['transitionEnd'], + elementType: 'div', + }, +] + +afterEach(cleanup) + +eventTypes.forEach(({type, events, elementType, init}) => { + describe(`${type} Events`, () => { + events.forEach(eventName => { + const propName = `on${eventName.charAt(0).toUpperCase()}${eventName.slice( + 1, + )}` + + it(`triggers ${propName}`, () => { + const ref = React.createRef() + const spy = jest.fn() + + render( + React.createElement(elementType, { + [propName]: spy, + ref, + }), + ) + + fireEvent[eventName](ref.current, init) + expect(spy).toHaveBeenCalledTimes(1) + }) + }) + }) +}) + +test('onChange works', () => { + const handleChange = jest.fn() + const { + container: {firstChild: input}, + } = render() + fireEvent.change(input, {target: {value: 'a'}}) + expect(handleChange).toHaveBeenCalledTimes(1) +}) diff --git a/packages/react-testing-library/src/__tests__/fetch.js b/packages/react-testing-library/src/__tests__/fetch.js new file mode 100644 index 00000000..c607508c --- /dev/null +++ b/packages/react-testing-library/src/__tests__/fetch.js @@ -0,0 +1,50 @@ +import React from 'react' +import axiosMock from 'axios' +import {render, fireEvent, cleanup, wait} from '../' + +afterEach(cleanup) + +// instead of importing it, we'll define it inline here +// import Fetch from '../fetch' + +class Fetch extends React.Component { + state = {} + componentDidUpdate(prevProps) { + if (this.props.url !== prevProps.url) { + this.fetch() + } + } + fetch = async () => { + const response = await axiosMock.get(this.props.url) + this.setState({data: response.data}) + } + render() { + const {data} = this.state + return ( +
    + + {data ? {data.greeting} : null} +
    + ) + } +} + +test('Fetch makes an API call and displays the greeting when load-greeting is clicked', async () => { + // Arrange + axiosMock.get.mockResolvedValueOnce({data: {greeting: 'hello there'}}) + const url = '/greeting' + const {container, getByText} = render() + + // Act + fireEvent.click(getByText('Fetch')) + + await wait() + + // Assert + expect(axiosMock.get).toHaveBeenCalledTimes(1) + expect(axiosMock.get).toHaveBeenCalledWith(url) + // this assertion is funny because if the textContent were not "hello there" + // then the `getByText` would throw anyway... 🤔 + expect(getByText('hello there').textContent).toBe('hello there') + expect(container.firstChild).toMatchSnapshot() +}) diff --git a/packages/react-testing-library/src/__tests__/forms.js b/packages/react-testing-library/src/__tests__/forms.js new file mode 100644 index 00000000..f3b7ebe9 --- /dev/null +++ b/packages/react-testing-library/src/__tests__/forms.js @@ -0,0 +1,53 @@ +import React from 'react' +import {render, fireEvent, cleanup} from '../' + +afterEach(cleanup) + +function Login({onSubmit}) { + return ( +
    + { + e.preventDefault() + const {username, password} = e.target.elements + onSubmit({ + username: username.value, + password: password.value, + }) + }} + > + + + + + + +
    + ) +} + +test('login form submits', () => { + const fakeUser = {username: 'jackiechan', password: 'hiya! 🥋'} + const handleSubmit = jest.fn() + const {getByLabelText, getByText} = render() + + const usernameNode = getByLabelText(/username/i) + const passwordNode = getByLabelText(/password/i) + const submitButtonNode = getByText(/submit/i) + + // Act + usernameNode.value = fakeUser.username + passwordNode.value = fakeUser.password + fireEvent.click(submitButtonNode) + + // Assert + expect(handleSubmit).toHaveBeenCalledTimes(1) + expect(handleSubmit).toHaveBeenCalledWith(fakeUser) +}) + +/* eslint jsx-a11y/label-has-for:0, jsx-a11y/aria-proptypes:0 */ diff --git a/packages/react-testing-library/src/__tests__/render.js b/packages/react-testing-library/src/__tests__/render.js new file mode 100644 index 00000000..0f9388bf --- /dev/null +++ b/packages/react-testing-library/src/__tests__/render.js @@ -0,0 +1,75 @@ +import 'jest-dom/extend-expect' +import React from 'react' +import ReactDOM from 'react-dom' +import {render, cleanup} from '../' + +afterEach(cleanup) + +test('renders div into document', () => { + const ref = React.createRef() + const {container} = render(
    ) + expect(container.firstChild).toBe(ref.current) +}) + +test('works great with react portals', () => { + class MyPortal extends React.Component { + constructor(...args) { + super(...args) + this.portalNode = document.createElement('div') + this.portalNode.dataset.testid = 'my-portal' + } + componentDidMount() { + document.body.appendChild(this.portalNode) + } + componentWillUnmount() { + this.portalNode.parentNode.removeChild(this.portalNode) + } + render() { + return ReactDOM.createPortal( + , + this.portalNode, + ) + } + } + + function Greet({greeting, subject}) { + return ( +
    + + {greeting} {subject} + +
    + ) + } + + const {unmount, getByTestId, getByText} = render() + expect(getByText('Hello World')).toBeInTheDocument() + const portalNode = getByTestId('my-portal') + expect(portalNode).toBeInTheDocument() + unmount() + expect(portalNode).not.toBeInTheDocument() +}) + +test('returns baseElement which defaults to document.body', () => { + const {baseElement} = render(
    ) + expect(baseElement).toBe(document.body) +}) + +it('cleansup document', () => { + const spy = jest.fn() + + class Test extends React.Component { + componentWillUnmount() { + spy() + } + + render() { + return
    + } + } + + render() + cleanup() + expect(document.body.innerHTML).toBe('') + expect(spy).toHaveBeenCalledTimes(1) +}) diff --git a/packages/react-testing-library/src/__tests__/rerender.js b/packages/react-testing-library/src/__tests__/rerender.js new file mode 100644 index 00000000..79f80875 --- /dev/null +++ b/packages/react-testing-library/src/__tests__/rerender.js @@ -0,0 +1,13 @@ +import React from 'react' +import {render, cleanup} from '../' +import 'jest-dom/extend-expect' + +afterEach(cleanup) + +test('rerender will re-render the element', () => { + const Greeting = props =>
    {props.message}
    + const {container, rerender} = render() + expect(container.firstChild).toHaveTextContent('hi') + rerender() + expect(container.firstChild).toHaveTextContent('hey') +}) diff --git a/packages/react-testing-library/src/__tests__/stopwatch.js b/packages/react-testing-library/src/__tests__/stopwatch.js new file mode 100644 index 00000000..34f58561 --- /dev/null +++ b/packages/react-testing-library/src/__tests__/stopwatch.js @@ -0,0 +1,58 @@ +import React from 'react' +import {render, cleanup, fireEvent} from '../' + +afterEach(cleanup) + +class StopWatch extends React.Component { + state = {lapse: 0, running: false} + handleRunClick = () => { + this.setState(state => { + if (state.running) { + clearInterval(this.timer) + } else { + const startTime = Date.now() - this.state.lapse + this.timer = setInterval(() => { + this.setState({lapse: Date.now() - startTime}) + }) + } + return {running: !state.running} + }) + } + handleClearClick = () => { + clearInterval(this.timer) + this.setState({lapse: 0, running: false}) + } + componentWillUnmount() { + clearInterval(this.timer) + } + render() { + const {lapse, running} = this.state + return ( +
    + {lapse}ms + + +
    + ) + } +} + +const wait = time => new Promise(resolve => setTimeout(resolve, time)) + +test('unmounts a component', async () => { + jest.spyOn(console, 'error').mockImplementation(() => {}) + const {unmount, getByText, container} = render() + fireEvent.click(getByText('Start')) + unmount() + // hey there reader! You don't need to have an assertion like this one + // this is just me making sure that the unmount function works. + // You don't need to do this in your apps. Just rely on the fact that this works. + expect(container.innerHTML).toBe('') + // just wait to see if the interval is cleared or not + // if it's not, then we'll call setState on an unmounted component + // and get an error. + // eslint-disable-next-line no-console + await wait(() => expect(console.error).not.toHaveBeenCalled()) +}) diff --git a/packages/react-testing-library/src/index.js b/packages/react-testing-library/src/index.js new file mode 100644 index 00000000..6c63f002 --- /dev/null +++ b/packages/react-testing-library/src/index.js @@ -0,0 +1,60 @@ +import ReactDOM from 'react-dom' +import {Simulate} from 'react-dom/test-utils' +import {getQueriesForElement, prettyDOM} from 'dom-testing-library' + +const mountedContainers = new Set() + +function render(ui, {container, baseElement = container} = {}) { + if (!container) { + // default to document.body instead of documentElement to avoid output of potentially-large + // head elements (such as JSS style blocks) in debug output + baseElement = document.body + container = document.body.appendChild(document.createElement('div')) + } + + // we'll add it to the mounted containers regardless of whether it's actually + // added to document.body so the cleanup method works regardless of whether + // they're passing us a custom container or not. + mountedContainers.add(container) + + ReactDOM.render(ui, container) + return { + container, + baseElement, + // eslint-disable-next-line no-console + debug: (el = baseElement) => console.log(prettyDOM(el)), + unmount: () => ReactDOM.unmountComponentAtNode(container), + rerender: rerenderUi => { + render(rerenderUi, {container, baseElement}) + // Intentionally do not return anything to avoid unnecessarily complicating the API. + // folks can use all the same utilities we return in the first place that are bound to the container + }, + ...getQueriesForElement(baseElement), + } +} + +function cleanup() { + mountedContainers.forEach(cleanupAtContainer) +} + +// maybe one day we'll expose this (perhaps even as a utility returned by render). +// but let's wait until someone asks for it. +function cleanupAtContainer(container) { + if (container.parentNode === document.body) { + document.body.removeChild(container) + } + ReactDOM.unmountComponentAtNode(container) + mountedContainers.delete(container) +} + +// fallback to synthetic events for React events that the DOM doesn't support +const syntheticEvents = ['select', 'mouseEnter', 'mouseLeave'] +syntheticEvents.forEach(eventName => { + document.addEventListener(eventName.toLowerCase(), e => { + Simulate[eventName](e.target, e) + }) +}) + +// just re-export everything from dom-testing-library +export * from 'dom-testing-library' +export {render, cleanup} diff --git a/packages/react-testing-library/typings/index.d.ts b/packages/react-testing-library/typings/index.d.ts new file mode 100644 index 00000000..796e036b --- /dev/null +++ b/packages/react-testing-library/typings/index.d.ts @@ -0,0 +1,26 @@ +import {getQueriesForElement} from 'dom-testing-library' + +export * from 'dom-testing-library' + +type GetsAndQueries = ReturnType + +export interface RenderResult extends GetsAndQueries { + container: HTMLElement + baseElement: HTMLElement + debug: (baseElement?: HTMLElement) => void + rerender: (ui: React.ReactElement) => void + unmount: () => boolean +} + +/** + * Render into a container which is appended to document.body. It should be used with cleanup. + */ +export function render( + ui: React.ReactElement, + options?: {container: HTMLElement; baseElement?: HTMLElement}, +): RenderResult + +/** + * Unmounts React trees that were mounted with render. + */ +export function cleanup(): void