From 931e177494e42ad90ee8f2a8e9e3acc9a61a43e9 Mon Sep 17 00:00:00 2001 From: Haz Date: Sun, 1 Dec 2019 21:49:28 -0300 Subject: [PATCH 01/10] Create @wordpress/test-utils package --- bin/update-readmes.js | 1 + docs/manifest-devhub.json | 6 + package-lock.json | 215 +++++++++++++++- package.json | 1 + packages/test-utils/.npmrc | 1 + packages/test-utils/CHANGELOG.md | 1 + packages/test-utils/README.md | 112 ++++++++ packages/test-utils/extend-expect.js | 4 + packages/test-utils/package.json | 44 ++++ packages/test-utils/src/act.js | 13 + packages/test-utils/src/blur.js | 34 +++ packages/test-utils/src/click.js | 208 +++++++++++++++ packages/test-utils/src/fire-event.js | 11 + packages/test-utils/src/focus.js | 27 ++ packages/test-utils/src/hover.js | 64 +++++ packages/test-utils/src/index.js | 10 + packages/test-utils/src/press.js | 206 +++++++++++++++ packages/test-utils/src/render.js | 11 + packages/test-utils/src/test/blur.js | 22 ++ packages/test-utils/src/test/click.js | 239 ++++++++++++++++++ packages/test-utils/src/test/focus.js | 20 ++ packages/test-utils/src/test/hover.js | 40 +++ packages/test-utils/src/test/press.js | 170 +++++++++++++ packages/test-utils/src/test/type.js | 57 +++++ packages/test-utils/src/type.js | 63 +++++ .../src/utils/get-active-element.js | 12 + .../src/utils/get-closest-focusable.js | 18 ++ packages/test-utils/src/utils/get-document.js | 9 + .../test-utils/src/utils/get-next-tabbable.js | 24 ++ .../src/utils/get-previous-tabbable.js | 23 ++ .../test-utils/src/utils/is-body-element.js | 7 + packages/test-utils/src/utils/is-focusable.js | 19 ++ .../test-utils/src/utils/mock-client-rects.js | 20 ++ .../src/utils/subscribe-default-prevented.js | 34 +++ packages/test-utils/src/wait.js | 15 ++ test/unit/jest.config.js | 3 + 36 files changed, 1762 insertions(+), 2 deletions(-) create mode 100644 packages/test-utils/.npmrc create mode 100644 packages/test-utils/CHANGELOG.md create mode 100644 packages/test-utils/README.md create mode 100644 packages/test-utils/extend-expect.js create mode 100644 packages/test-utils/package.json create mode 100644 packages/test-utils/src/act.js create mode 100644 packages/test-utils/src/blur.js create mode 100644 packages/test-utils/src/click.js create mode 100644 packages/test-utils/src/fire-event.js create mode 100644 packages/test-utils/src/focus.js create mode 100644 packages/test-utils/src/hover.js create mode 100644 packages/test-utils/src/index.js create mode 100644 packages/test-utils/src/press.js create mode 100644 packages/test-utils/src/render.js create mode 100644 packages/test-utils/src/test/blur.js create mode 100644 packages/test-utils/src/test/click.js create mode 100644 packages/test-utils/src/test/focus.js create mode 100644 packages/test-utils/src/test/hover.js create mode 100644 packages/test-utils/src/test/press.js create mode 100644 packages/test-utils/src/test/type.js create mode 100644 packages/test-utils/src/type.js create mode 100644 packages/test-utils/src/utils/get-active-element.js create mode 100644 packages/test-utils/src/utils/get-closest-focusable.js create mode 100644 packages/test-utils/src/utils/get-document.js create mode 100644 packages/test-utils/src/utils/get-next-tabbable.js create mode 100644 packages/test-utils/src/utils/get-previous-tabbable.js create mode 100644 packages/test-utils/src/utils/is-body-element.js create mode 100644 packages/test-utils/src/utils/is-focusable.js create mode 100644 packages/test-utils/src/utils/mock-client-rects.js create mode 100644 packages/test-utils/src/utils/subscribe-default-prevented.js create mode 100644 packages/test-utils/src/wait.js diff --git a/bin/update-readmes.js b/bin/update-readmes.js index 4fb85e423b5562..9e6c7ce015481a 100755 --- a/bin/update-readmes.js +++ b/bin/update-readmes.js @@ -35,6 +35,7 @@ const packages = [ 'redux-routine', 'rich-text', 'shortcode', + 'test-utils', 'url', 'viewport', 'wordcount', diff --git a/docs/manifest-devhub.json b/docs/manifest-devhub.json index 75573dd5ea1337..cdad4e6e863f24 100644 --- a/docs/manifest-devhub.json +++ b/docs/manifest-devhub.json @@ -1427,6 +1427,12 @@ "markdown_source": "../packages/shortcode/README.md", "parent": "packages" }, + { + "title": "@wordpress/test-utils", + "slug": "packages-test-utils", + "markdown_source": "../packages/test-utils/README.md", + "parent": "packages" + }, { "title": "@wordpress/token-list", "slug": "packages-token-list", diff --git a/package-lock.json b/package-lock.json index 50451a27eb7aed..9c8f3bddad722f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5059,6 +5059,12 @@ "any-observable": "^0.3.0" } }, + "@sheerun/mutationobserver-shim": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@sheerun/mutationobserver-shim/-/mutationobserver-shim-0.3.2.tgz", + "integrity": "sha512-vTCdPp/T/Q3oSqwHmZ5Kpa9oI7iLtGl3RQaA/NyLHikvcrPxACkkKVr/XzkSPJWXHRhKGzVvb0urJsbMlRxi1Q==", + "dev": true + }, "@storybook/addon-a11y": { "version": "5.2.4", "resolved": "https://registry.npmjs.org/@storybook/addon-a11y/-/addon-a11y-5.2.4.tgz", @@ -7064,6 +7070,135 @@ "resolved": "https://registry.npmjs.org/@tannin/postfix/-/postfix-1.0.2.tgz", "integrity": "sha512-Nggtk7/ljfNPpAX8CjxxLkMKuO6u2gH1ozmTvGclWF2pNcxTf6YGghYNYNWZRKrimXGhQ8yZqvAHep7h80K04g==" }, + "@testing-library/dom": { + "version": "6.10.1", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-6.10.1.tgz", + "integrity": "sha512-5BPKxaO+zSJDUbVZBRNf9KrmDkm/EcjjaHSg3F9+031VZyPACKXlwLBjVzZxheunT9m72DoIq7WvyE457/Xweg==", + "dev": true, + "requires": { + "@babel/runtime": "^7.6.2", + "@sheerun/mutationobserver-shim": "^0.3.2", + "@types/testing-library__dom": "^6.0.0", + "aria-query": "3.0.0", + "pretty-format": "^24.9.0", + "wait-for-expect": "^3.0.0" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.7.4.tgz", + "integrity": "sha512-r24eVUUr0QqNZa+qrImUk8fn5SPhHq+IfYvIoIMg0do3GdK9sMdiLKP3GYVVaxpPKORgm8KRKaNTEhAjgIpLMw==", + "dev": true, + "requires": { + "regenerator-runtime": "^0.13.2" + } + }, + "@jest/types": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-24.9.0.tgz", + "integrity": "sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^13.0.0" + } + }, + "@types/yargs": { + "version": "13.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.3.tgz", + "integrity": "sha512-K8/LfZq2duW33XW/tFwEAfnZlqIfVsoyRB3kfXdPXYhl0nfM8mmh7GS0jg7WrX2Dgq/0Ha/pR1PaR+BvmWwjiQ==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "pretty-format": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-24.9.0.tgz", + "integrity": "sha512-00ZMZUiHaJrNfk33guavqgvfJS30sLYf0f8+Srklv0AMPodGGHcoHgksZ3OThYnIvOd+8yMCn0YiEOogjlgsnA==", + "dev": true, + "requires": { + "@jest/types": "^24.9.0", + "ansi-regex": "^4.0.0", + "ansi-styles": "^3.2.0", + "react-is": "^16.8.4" + } + } + } + }, + "@testing-library/jest-dom": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-4.2.4.tgz", + "integrity": "sha512-j31Bn0rQo12fhCWOUWy9fl7wtqkp7In/YP2p5ZFyRuiiB9Qs3g+hS4gAmDWONbAHcRmVooNJ5eOHQDCOmUFXHg==", + "dev": true, + "requires": { + "@babel/runtime": "^7.5.1", + "chalk": "^2.4.1", + "css": "^2.2.3", + "css.escape": "^1.5.1", + "jest-diff": "^24.0.0", + "jest-matcher-utils": "^24.0.0", + "lodash": "^4.17.11", + "pretty-format": "^24.0.0", + "redent": "^3.0.0" + }, + "dependencies": { + "indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true + }, + "redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "requires": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + } + }, + "strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "requires": { + "min-indent": "^1.0.0" + } + } + } + }, + "@testing-library/react": { + "version": "9.3.2", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-9.3.2.tgz", + "integrity": "sha512-J6ftWtm218tOLS175MF9eWCxGp+X+cUXCpkPIin8KAXWtyZbr9CbqJ8M8QNd6spZxJDAGlw+leLG4MJWLlqVgg==", + "dev": true, + "requires": { + "@babel/runtime": "^7.6.0", + "@testing-library/dom": "^6.3.0", + "@types/testing-library__react": "^9.1.0" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.7.4.tgz", + "integrity": "sha512-r24eVUUr0QqNZa+qrImUk8fn5SPhHq+IfYvIoIMg0do3GdK9sMdiLKP3GYVVaxpPKORgm8KRKaNTEhAjgIpLMw==", + "dev": true, + "requires": { + "regenerator-runtime": "^0.13.2" + } + } + } + }, "@types/babel__core": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.3.tgz", @@ -7224,6 +7359,15 @@ "@types/react": "*" } }, + "@types/react-dom": { + "version": "16.9.4", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.4.tgz", + "integrity": "sha512-fya9xteU/n90tda0s+FtN5Ym4tbgxpq/hb/Af24dvs6uYnYn+fspaxw5USlw0R8apDNwxsqumdRoCoKitckQqw==", + "dev": true, + "requires": { + "@types/react": "*" + } + }, "@types/react-syntax-highlighter": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/@types/react-syntax-highlighter/-/react-syntax-highlighter-10.1.0.tgz", @@ -7239,6 +7383,25 @@ "integrity": "sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw==", "dev": true }, + "@types/testing-library__dom": { + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/@types/testing-library__dom/-/testing-library__dom-6.10.0.tgz", + "integrity": "sha512-mL/GMlyQxiZplbUuFNwA0vAI3k3uJNSf6slr5AVve9TXmfLfyefNT0uHHnxwdYuPMxYD5gI/+dgAvc/5opW9JQ==", + "dev": true, + "requires": { + "pretty-format": "^24.3.0" + } + }, + "@types/testing-library__react": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@types/testing-library__react/-/testing-library__react-9.1.2.tgz", + "integrity": "sha512-CYaMqrswQ+cJACy268jsLAw355DZtPZGt3Jwmmotlcu8O/tkoXBI6AeZ84oZBJsIsesozPKzWzmv/0TIU+1E9Q==", + "dev": true, + "requires": { + "@types/react-dom": "*", + "@types/testing-library__dom": "*" + } + }, "@types/unist": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.3.tgz", @@ -8241,6 +8404,16 @@ "memize": "^1.0.5" } }, + "@wordpress/test-utils": { + "version": "file:packages/test-utils", + "dev": true, + "requires": { + "@testing-library/dom": "^6.10.1", + "@testing-library/jest-dom": "^4.2.4", + "@testing-library/react": "^9.3.2", + "@wordpress/dom": "file:packages/dom" + } + }, "@wordpress/token-list": { "version": "file:packages/token-list", "requires": { @@ -12999,6 +13172,26 @@ "randomfill": "^1.0.3" } }, + "css": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/css/-/css-2.2.4.tgz", + "integrity": "sha512-oUnjmWpy0niI3x/mPL8dVEI1l7MnG3+HHyRPHf+YFSbK+svOhXpmSOcDURUh2aOCgl2grzrOPt1nHLuCVFULLw==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "source-map": "^0.6.1", + "source-map-resolve": "^0.5.2", + "urix": "^0.1.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, "css-color-function": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/css-color-function/-/css-color-function-1.3.3.tgz", @@ -13230,6 +13423,12 @@ "integrity": "sha1-lGfQMsOM+u+58teVASUwYvh/ob0=", "dev": true }, + "css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha1-QuJ9T6BK4y+TGktNQZH6nN3ul8s=", + "dev": true + }, "cssesc": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-1.0.1.tgz", @@ -22104,7 +22303,7 @@ "dependencies": { "clone-deep": { "version": "0.2.4", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-0.2.4.tgz", + "resolved": "http://registry.npmjs.org/clone-deep/-/clone-deep-0.2.4.tgz", "integrity": "sha1-TnPdCen7lxzDhnDF3O2cGJZIHMY=", "dev": true, "requires": { @@ -22138,7 +22337,7 @@ "dependencies": { "kind-of": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-2.0.1.tgz", + "resolved": "http://registry.npmjs.org/kind-of/-/kind-of-2.0.1.tgz", "integrity": "sha1-AY7HpM5+OobLkUG+UZ0kyPqpgbU=", "dev": true, "requires": { @@ -23016,6 +23215,12 @@ "dom-walk": "^0.1.0" } }, + "min-indent": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.0.tgz", + "integrity": "sha1-z8RcN+nsDY8KDsPdTvf3w6vjklY=", + "dev": true + }, "mini-css-extract-plugin": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-0.7.0.tgz", @@ -32789,6 +32994,12 @@ "browser-process-hrtime": "^0.1.2" } }, + "wait-for-expect": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/wait-for-expect/-/wait-for-expect-3.0.1.tgz", + "integrity": "sha512-3Ha7lu+zshEG/CeHdcpmQsZnnZpPj/UsG3DuKO8FskjuDbkx3jE3845H+CuwZjA2YWYDfKMU2KhnCaXMLd3wVw==", + "dev": true + }, "wait-on": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-3.3.0.tgz", diff --git a/package.json b/package.json index 74ba34bb8877f7..dc48dbd4c91fc8 100644 --- a/package.json +++ b/package.json @@ -98,6 +98,7 @@ "@wordpress/npm-package-json-lint-config": "file:packages/npm-package-json-lint-config", "@wordpress/postcss-themes": "file:packages/postcss-themes", "@wordpress/scripts": "file:packages/scripts", + "@wordpress/test-utils": "file:packages/test-utils", "babel-loader": "8.0.6", "babel-plugin-emotion": "10.0.23", "babel-plugin-inline-json-import": "0.3.2", diff --git a/packages/test-utils/.npmrc b/packages/test-utils/.npmrc new file mode 100644 index 00000000000000..43c97e719a5a82 --- /dev/null +++ b/packages/test-utils/.npmrc @@ -0,0 +1 @@ +package-lock=false diff --git a/packages/test-utils/CHANGELOG.md b/packages/test-utils/CHANGELOG.md new file mode 100644 index 00000000000000..3d4899c114aec7 --- /dev/null +++ b/packages/test-utils/CHANGELOG.md @@ -0,0 +1 @@ +## master diff --git a/packages/test-utils/README.md b/packages/test-utils/README.md new file mode 100644 index 00000000000000..c6ac6367473d40 --- /dev/null +++ b/packages/test-utils/README.md @@ -0,0 +1,112 @@ +# Test Utils + +Integration test utils for WordPress. + +## Installation + +Install the module + +```bash +npm install @wordpress/test-utils --save-dev +``` + +## API + + + +# **act** + +Simply calls ReactDOMTestUtils.act(cb) If that's not available (older version of react) +then it simply calls the given callback immediately. + +_Related_ + +- +- + +# **blur** + +Blur element. + +_Parameters_ + +- _element_ `[Element]`: + +# **click** + +Click element. + +_Parameters_ + +- _element_ `Element`: +- _options_ `[Object]`: + +# **fireEvent** + +Convenience methods for firing DOM events. + +_Related_ + +- + +# **focus** + +Focus element. + +_Parameters_ + +- _element_ `Element`: + +# **hover** + +Hover element. + +_Parameters_ + +- _element_ `Element`: +- _options_ `[Object]`: + +# **press** + +Press element. + +_Parameters_ + +- _key_ `string`: +- _element_ `[Element]`: +- _options_ `[Object]`: + +# **render** + +Render into a container which is appended to `document.body`. + +_Related_ + +- + +# **type** + +Type on a text field element. + +_Parameters_ + +- _text_ `string`: +- _element_ `[Element]`: +- _options_ `[Object]`: + +# **wait** + +When in need to wait for non-deterministic periods of time you can use `wait` +to wait for your expectations to pass. The `wait` function is a small +wrapper around the [`wait-for-expect`](https://github.com/TheBrainFamily/wait-for-expect) +module. + +_Related_ + +- +- + + + + +

Code is Poetry.

diff --git a/packages/test-utils/extend-expect.js b/packages/test-utils/extend-expect.js new file mode 100644 index 00000000000000..c904e552623097 --- /dev/null +++ b/packages/test-utils/extend-expect.js @@ -0,0 +1,4 @@ +/** + * External dependencies + */ +require( '@testing-library/jest-dom/extend-expect' ); diff --git a/packages/test-utils/package.json b/packages/test-utils/package.json new file mode 100644 index 00000000000000..036c447c01eff2 --- /dev/null +++ b/packages/test-utils/package.json @@ -0,0 +1,44 @@ +{ + "name": "@wordpress/test-utils", + "version": "0.0.1", + "description": "Integration test utils for WordPress.", + "author": "The WordPress Contributors", + "license": "GPL-2.0-or-later", + "keywords": [ + "wordpress", + "test", + "utils" + ], + "homepage": "https://github.com/WordPress/gutenberg/tree/master/packages/test-utils/README.md", + "repository": { + "type": "git", + "url": "https://github.com/WordPress/gutenberg.git", + "directory": "packages/test-utils" + }, + "bugs": { + "url": "https://github.com/WordPress/gutenberg/issues" + }, + "engines": { + "node": ">=8" + }, + "files": [ + "build", + "build-module", + "extend-expect" + ], + "main": "build/index.js", + "module": "build-module/index.js", + "dependencies": { + "@testing-library/dom": "^6.10.1", + "@testing-library/jest-dom": "^4.2.4", + "@testing-library/react": "^9.3.2", + "@wordpress/dom": "file:../dom" + }, + "peerDependencies": { + "react": "^16.8.0", + "react-dom": "^16.8.0" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/packages/test-utils/src/act.js b/packages/test-utils/src/act.js new file mode 100644 index 00000000000000..3343fc952b12dd --- /dev/null +++ b/packages/test-utils/src/act.js @@ -0,0 +1,13 @@ +/** + * External dependencies + */ +import { act } from '@testing-library/react'; + +/** + * Simply calls ReactDOMTestUtils.act(cb) If that's not available (older version of react) + * then it simply calls the given callback immediately. + * + * @see https://testing-library.com/docs/react-testing-library/api#act + * @see https://reactjs.org/docs/test-utils.html#act + */ +export default act; diff --git a/packages/test-utils/src/blur.js b/packages/test-utils/src/blur.js new file mode 100644 index 00000000000000..ead02f37af0154 --- /dev/null +++ b/packages/test-utils/src/blur.js @@ -0,0 +1,34 @@ +/** + * Internal dependencies + */ +import fireEvent from './fire-event'; +import act from './act'; +import getActiveElement from './utils/get-active-element'; +import isBodyElement from './utils/is-body-element'; + +/** + * Blur element. + * + * @param {Element} [element] + */ +export default function blur( element ) { + if ( ! element ) { + element = getActiveElement(); + } + + if ( ! element || isBodyElement( element ) || getActiveElement( element ) !== element ) { + return; + } + + // This is set by `type` so `blur` knows whether to dispatch a change event + if ( element.dirty ) { + fireEvent.change( element ); + element.dirty = false; + } + + act( () => { + element.blur(); + } ); + + fireEvent.focusOut( element ); +} diff --git a/packages/test-utils/src/click.js b/packages/test-utils/src/click.js new file mode 100644 index 00000000000000..1a36cc11641611 --- /dev/null +++ b/packages/test-utils/src/click.js @@ -0,0 +1,208 @@ +/** + * Internal dependencies + */ +import fireEvent from './fire-event'; +import focus from './focus'; +import hover from './hover'; +import blur from './blur'; +import isFocusable from './utils/is-focusable'; +import subscribeDefaultPrevented from './utils/subscribe-default-prevented'; +import getClosestFocusable from './utils/get-closest-focusable'; + +/** + * @param {Element} element + * @return {HTMLLabelElement} Closest label element + */ +function getClosestLabel( element ) { + if ( ! isFocusable( element ) ) { + return element.closest( 'label' ); + } + return null; +} + +/** + * @param {HTMLLabelElement} element + * @return {HTMLInputElement|HTMLTextAreaElement|HTMLSelectElement} Input element + */ +function getInputFromLabel( element ) { + const input = element.htmlFor ? + element.ownerDocument.getElementById( element.htmlFor ) : + element.querySelector( 'input,textarea,select' ); + return input; +} + +/** + * + * @param {HTMLLabelElement} element + * @param {Object} defaultPrevented + * @param {Object} options + */ +function clickLabel( + element, + defaultPrevented, + options +) { + const input = getInputFromLabel( element ); + const isInputDisabled = Boolean( input && input.disabled ); + + if ( input ) { + // JSDOM will automatically "click" input right after we "click" the label. + // Since we need to "focus" it first, we temporarily disable it so it won't + // get automatically clicked. + input.disabled = true; + } + + fireEvent.click( element, options ); + + if ( input ) { + // Now we can revert input disabled state and fire events on it in the + // right order. + input.disabled = isInputDisabled; + if ( ! defaultPrevented.current && isFocusable( input ) ) { + focus( input ); + // Only "click" is fired! Browsers don't go over the whole event stack in + // this case (mousedown, mouseup etc.). + fireEvent.click( input ); + } + } +} + +/** + * @param {HTMLOptionElement} element + * @param {boolean} selected + */ +function setSelected( element, selected ) { + element.setAttribute( 'selected', selected ? 'selected' : '' ); + element.selected = selected; +} + +/** + * @param {HTMLOptionElement} element + * @param {Object} eventOptions + */ +function clickOption( + element, + eventOptions +) { + const select = element.closest( 'select' ); + + if ( ! select ) { + fireEvent.click( element, eventOptions ); + return; + } + + if ( select.multiple ) { + const options = Array.from( select.options ); + + const resetOptions = () => + options.forEach( ( option ) => { + setSelected( option, false ); + } ); + + const selectRange = ( a, b ) => { + const from = Math.min( a, b ); + const to = Math.max( a, b ) + 1; + const selectedOptions = options.slice( from, to ); + selectedOptions.forEach( ( option ) => { + setSelected( option, true ); + } ); + }; + + if ( eventOptions.shiftKey ) { + const elementIndex = options.indexOf( element ); + // https://stackoverflow.com/a/16530782/5513909 + const referenceOption = select.lastOptionSelectedNotByShiftKey; + const referenceOptionIndex = referenceOption ? + options.indexOf( referenceOption ) : + -1; + + resetOptions(); + // Select options between the reference option and the clicked element + selectRange( elementIndex, referenceOptionIndex ); + setSelected( element, true ); + } else { + // Keep track of this option as this will be used later when shift key + // is used. + select.lastOptionSelectedNotByShiftKey = element; + + if ( eventOptions.ctrlKey ) { + // Clicking with ctrlKey will select/deselect the option + setSelected( element, ! element.selected ); + } else { + // Simply clicking an option will select only that option + resetOptions(); + setSelected( element, true ); + } + } + } else { + setSelected( element, true ); + } + + fireEvent.input( select ); + fireEvent.change( select ); + fireEvent.click( element, eventOptions ); +} + +/** + * Click element. + * + * @param {Element} element + * @param {Object} [options] + */ +export default function click( element, options = {} ) { + hover( element, options ); + const { disabled } = element; + + let defaultPrevented = subscribeDefaultPrevented( + element, + 'pointerdown', + 'mousedown' + ); + + fireEvent.pointerDown( element, options ); + + if ( ! disabled ) { + // Mouse events are not called on disabled elements + fireEvent.mouseDown( element, options ); + } + + if ( ! defaultPrevented.current ) { + // Do not enter this if event.preventDefault() has been called on + // pointerdown or mousedown. + if ( isFocusable( element ) ) { + focus( element ); + } else if ( ! disabled ) { + // If the element is not focusable, focus the closest focusable parent + const closestFocusable = getClosestFocusable( element ); + if ( closestFocusable ) { + focus( closestFocusable ); + } else { + // This will automatically set document.body as the activeElement + blur(); + } + } + } + + defaultPrevented = subscribeDefaultPrevented( element, 'click' ); + + fireEvent.pointerUp( element, options ); + + // mouseup and click are not called on disabled elements + if ( disabled ) { + return; + } + + fireEvent.mouseUp( element, options ); + + const label = getClosestLabel( element ); + + if ( label ) { + clickLabel( label, defaultPrevented, options ); + } else if ( element.tagName === 'OPTION' ) { + clickOption( element, options ); + } else { + fireEvent.click( element, options ); + } + + defaultPrevented.unsubscribe(); +} diff --git a/packages/test-utils/src/fire-event.js b/packages/test-utils/src/fire-event.js new file mode 100644 index 00000000000000..821d4475f91e72 --- /dev/null +++ b/packages/test-utils/src/fire-event.js @@ -0,0 +1,11 @@ +/** + * External dependencies + */ +import { fireEvent } from '@testing-library/react'; + +/** + * Convenience methods for firing DOM events. + * + * @see https://testing-library.com/docs/dom-testing-library/api-events + */ +export default fireEvent; diff --git a/packages/test-utils/src/focus.js b/packages/test-utils/src/focus.js new file mode 100644 index 00000000000000..87c10e79bd842c --- /dev/null +++ b/packages/test-utils/src/focus.js @@ -0,0 +1,27 @@ +/** + * Internal dependencies + */ +import fireEvent from './fire-event'; +import act from './act'; +import blur from './blur'; +import isFocusable from './utils/is-focusable'; +import getActiveElement from './utils/get-active-element'; + +/** + * Focus element. + * + * @param {Element} element + */ +export default function focus( element ) { + if ( getActiveElement( element ) === element || ! isFocusable( element ) ) { + return; + } + + blur(); + + act( () => { + element.focus(); + } ); + + fireEvent.focusIn( element ); +} diff --git a/packages/test-utils/src/hover.js b/packages/test-utils/src/hover.js new file mode 100644 index 00000000000000..073e34fa0e736d --- /dev/null +++ b/packages/test-utils/src/hover.js @@ -0,0 +1,64 @@ +/** + * External dependencies + */ +import { fireEvent as domFireEvent } from '@testing-library/dom'; + +/** + * Internal dependencies + */ +import fireEvent from './fire-event'; +import act from './act'; +import getDocument from './utils/get-document'; + +/** + * Hover element. + * + * @param {Element} element + * @param {Object} [options] + */ +export default function hover( element, options ) { + const document = getDocument( element ); + const { lastHovered } = document; + const { disabled } = element; + + if ( lastHovered ) { + fireEvent.pointerMove( lastHovered, options ); + fireEvent.mouseMove( lastHovered, options ); + + const isElementWithinLastHovered = lastHovered.contains( element ); + + fireEvent.pointerOut( lastHovered, options ); + + if ( ! isElementWithinLastHovered ) { + fireEvent.pointerLeave( lastHovered, options ); + } + + fireEvent.mouseOut( lastHovered, options ); + + if ( ! isElementWithinLastHovered ) { + act( () => { + // fireEvent.mouseLeave would be the same as fireEvent.mouseOut + domFireEvent.mouseLeave( lastHovered, options ); + } ); + } + } + + fireEvent.pointerOver( element, options ); + fireEvent.pointerEnter( element, options ); + + if ( ! disabled ) { + fireEvent.mouseOver( element, options ); + act( () => { + // fireEvent.mouseEnter would be the same as fireEvent.mouseOver + domFireEvent.mouseEnter( element, options ); + } ); + } + + fireEvent.pointerMove( element, options ); + + if ( ! disabled ) { + fireEvent.mouseMove( element, options ); + } + + document.lastHovered = element; +} diff --git a/packages/test-utils/src/index.js b/packages/test-utils/src/index.js new file mode 100644 index 00000000000000..9bd9ad6ffa18c4 --- /dev/null +++ b/packages/test-utils/src/index.js @@ -0,0 +1,10 @@ +export { default as act } from './act'; +export { default as blur } from './blur'; +export { default as click } from './click'; +export { default as fireEvent } from './fire-event'; +export { default as focus } from './focus'; +export { default as hover } from './hover'; +export { default as press } from './press'; +export { default as render } from './render'; +export { default as type } from './type'; +export { default as wait } from './wait'; diff --git a/packages/test-utils/src/press.js b/packages/test-utils/src/press.js new file mode 100644 index 00000000000000..e7c17ee18d71b2 --- /dev/null +++ b/packages/test-utils/src/press.js @@ -0,0 +1,206 @@ +/** + * Internal dependencies + */ +import fireEvent from './fire-event'; +import focus from './focus'; +import blur from './blur'; +import getDocument from './utils/get-document'; +import getActiveElement from './utils/get-active-element'; +import isBodyElement from './utils/is-body-element'; +import isFocusable from './utils/is-focusable'; +import getPreviousTabbable from './utils/get-previous-tabbable'; +import getNextTabbable from './utils/get-next-tabbable'; +import subscribeDefaultPrevented from './utils/subscribe-default-prevented'; + +const clickableInputTypes = [ + 'button', + 'color', + 'file', + 'image', + 'reset', + 'submit', +]; + +/** + * @param {HTMLInputElement} element + * @param {Object} options + */ +function submitFormByPressingEnterOn( + element, + options +) { + const { form } = element; + + if ( ! form ) { + return; + } + + const elements = Array.from( form.elements ); + + // When pressing enter on an input, the form is submitted only when there is + // only one of these input types present (or there's a submit button). + const validTypes = [ + 'email', + 'number', + 'password', + 'search', + 'tel', + 'text', + 'url', + ]; + + const validInputs = elements.filter( + ( el ) => el.tagName === 'INPUT' && validTypes.includes( el.type ) + ); + + const submitButton = elements.find( + ( el ) => + ( [ 'INPUT', 'BUTTON' ].includes( el.tagName ) ) && + el.type === 'submit' + ); + + if ( validInputs.length === 1 || submitButton ) { + fireEvent.submit( form, options ); + } +} + +const keyDownMap = { + Tab( element, { shiftKey } ) { + const { body } = getDocument( element ); + const nextElement = shiftKey ? + getPreviousTabbable( body ) : + getNextTabbable( body ); + if ( nextElement ) { + focus( nextElement ); + } + }, + + Enter( element, options ) { + const nonSubmittableTypes = [ ...clickableInputTypes, 'hidden' ]; + + const isClickable = + element.tagName === 'BUTTON' || + ( element.tagName === 'INPUT' && + clickableInputTypes.includes( element.type ) ); + + const isSubmittable = + element.tagName === 'INPUT' && + ! nonSubmittableTypes.includes( element.type ); + + if ( isClickable ) { + fireEvent.click( element, options ); + } else if ( isSubmittable ) { + submitFormByPressingEnterOn( element, options ); + } + }, +}; + +const keyUpMap = { + // Space + ' ': ( element, options ) => { + const spaceableTypes = [ ...clickableInputTypes, 'checkbox', 'radio' ]; + + const isSpaceable = + element.tagName === 'BUTTON' || + ( element.tagName === 'INPUT' && + spaceableTypes.includes( element.type ) ); + + if ( isSpaceable ) { + fireEvent.click( element, options ); + } + }, +}; + +/** + * Press element. + * + * @param {string} key + * @param {Element} [element] + * @param {Object} [options] + */ +export default function press( + key, + element, + options = {} +) { + const document = getDocument( element ); + + // eslint-disable-next-line eqeqeq + if ( ! element ) { + element = getActiveElement() || document.body; + } + + if ( ! element ) { + return; + } + + // We can't press on elements that aren't focusable + if ( ! isFocusable( element ) && ! isBodyElement( element ) ) { + return; + } + + // If element is not focused, we should focus it + if ( getActiveElement( element ) !== element ) { + if ( isBodyElement( element ) ) { + blur(); + } else { + focus( element ); + } + } + + // Track event.preventDefault() calls so we bail out of keydown/keyup effects + const defaultPrevented = subscribeDefaultPrevented( + element, + 'keydown', + 'keyup' + ); + + fireEvent.keyDown( element, { key, ...options } ); + + if ( ! defaultPrevented.current && key in keyDownMap && ! options.metaKey ) { + keyDownMap[ key ]( element, options ); + } + + // If keydown effect changed focus (e.g. Tab), keyup will be triggered on the + // next element. + if ( getActiveElement( element ) !== element ) { + element = getActiveElement( element ); + } + + fireEvent.keyUp( element, { key, ...options } ); + + if ( ! defaultPrevented.current && key in keyUpMap && ! options.metaKey ) { + keyUpMap[ key ]( element, options ); + } + + defaultPrevented.unsubscribe(); +} + +/** + * @callback Press + * @param {Element} [element] + * @param {Object} [options] + */ +/** + * @param {string} key + * @param {Object} [defaultOptions] + * @return {Press} Press function + */ +function createPress( key, defaultOptions = {} ) { + return ( element, options = {} ) => + press( key, element, { ...defaultOptions, ...options } ); +} + +press.Escape = createPress( 'Escape' ); +press.Tab = createPress( 'Tab' ); +press.ShiftTab = createPress( 'Tab', { shiftKey: true } ); +press.Enter = createPress( 'Enter' ); +press.Space = createPress( ' ' ); +press.ArrowUp = createPress( 'ArrowUp' ); +press.ArrowRight = createPress( 'ArrowRight' ); +press.ArrowDown = createPress( 'ArrowDown' ); +press.ArrowLeft = createPress( 'ArrowLeft' ); +press.End = createPress( 'End' ); +press.Home = createPress( 'Home' ); +press.PageUp = createPress( 'PageUp' ); +press.PageDown = createPress( 'PageDown' ); diff --git a/packages/test-utils/src/render.js b/packages/test-utils/src/render.js new file mode 100644 index 00000000000000..e4ccceb244640b --- /dev/null +++ b/packages/test-utils/src/render.js @@ -0,0 +1,11 @@ +/** + * External dependencies + */ +import { render } from '@testing-library/react'; + +/** + * Render into a container which is appended to `document.body`. + * + * @see https://testing-library.com/docs/react-testing-library/api#render + */ +export default render; diff --git a/packages/test-utils/src/test/blur.js b/packages/test-utils/src/test/blur.js new file mode 100644 index 00000000000000..9f343e31b2125d --- /dev/null +++ b/packages/test-utils/src/test/blur.js @@ -0,0 +1,22 @@ +/** + * External dependencies + */ +import * as React from 'react'; + +/** + * Internal dependencies + */ +import render from '../render'; +import blur from '../blur'; + +describe( 'blur', () => { + it( 'should blur focused button', async () => { + // eslint-disable-next-line jsx-a11y/no-autofocus + const { getByText } = render( ); + const button = getByText( 'button' ); + + expect( button ).toHaveFocus(); + blur( button ); + expect( document.body ).toHaveFocus(); + } ); +} ); diff --git a/packages/test-utils/src/test/click.js b/packages/test-utils/src/test/click.js new file mode 100644 index 00000000000000..2345c4f387dce3 --- /dev/null +++ b/packages/test-utils/src/test/click.js @@ -0,0 +1,239 @@ +/** + * External dependencies + */ +import * as React from 'react'; + +/** + * Internal dependencies + */ +import render from '../render'; +import click from '../click'; + +describe( 'click', () => { + it( 'should focus button on click', () => { + const { getByText } = render( ); + const button = getByText( 'button' ); + click( button ); + expect( button ).toHaveFocus(); + } ); + + it( 'should not focus button on click when event.preventDefault() was called on mouse down', () => { + const { getByText } = render( + + ); + const button = getByText( 'button' ); + click( button ); + expect( button ).not.toHaveFocus(); + } ); + + it( 'should not focus disabled button on click', () => { + const onClick = jest.fn(); + const { getByText } = render( + + ); + const button = getByText( 'button' ); + click( button ); + expect( button ).not.toHaveFocus(); + expect( onClick ).not.toHaveBeenCalled(); + } ); + + it( 'should focus closest focusable parent', () => { + const { getByText, baseElement } = render( +
+ parent +
child
+
+ ); + const parent = getByText( 'parent' ); + const child = getByText( 'child' ); + + expect( baseElement ).toHaveFocus(); + click( child ); + expect( parent ).toHaveFocus(); + } ); + + it( 'should not focus focusable parent if child is disabled', () => { + const { getByText, baseElement } = render( +
+ parent + +
+ ); + const child = getByText( 'child' ); + + expect( baseElement ).toHaveFocus(); + click( child ); + expect( baseElement ).toHaveFocus(); + } ); + + it( 'should focus input when clicking on label', () => { + const { getByText, getByLabelText, baseElement } = render( + <> + + { /* eslint-disable-next-line no-restricted-syntax */ } + + { /* eslint-disable-next-line jsx-a11y/label-has-for */ } + + + ); + const label1 = getByText( 'input1' ); + const input1 = getByLabelText( 'input1' ); + const label2 = getByText( 'input2' ); + const input2 = getByLabelText( 'input2' ); + + expect( baseElement ).toHaveFocus(); + click( label1 ); + expect( input1 ).toHaveFocus(); + click( label2 ); + expect( input2 ).toHaveFocus(); + } ); + + it( 'should not focus disabled input when clicking on label', () => { + const { getByText, baseElement } = render( + <> + + { /* eslint-disable-next-line no-restricted-syntax */ } + + { /* eslint-disable-next-line jsx-a11y/label-has-for */ } + + + ); + const label1 = getByText( 'input1' ); + const label2 = getByText( 'input2' ); + + expect( baseElement ).toHaveFocus(); + click( label1 ); + expect( baseElement ).toHaveFocus(); + click( label2 ); + expect( baseElement ).toHaveFocus(); + } ); + + it( 'should check/uncheck checkbox', () => { + const { getByText, getByLabelText } = render( + <> + + + { /* eslint-disable-next-line no-restricted-syntax */ } + + { /* eslint-disable-next-line jsx-a11y/label-has-for */ } + + + ); + const checkbox1 = getByLabelText( 'checkbox1' ); + const label2 = getByText( 'checkbox2' ); + const checkbox2 = getByLabelText( 'checkbox2' ); + const label3 = getByText( 'checkbox3' ); + const checkbox3 = getByLabelText( 'checkbox3' ); + + expect( checkbox1 ).not.toBeChecked(); + click( checkbox1 ); + expect( checkbox1 ).toBeChecked(); + click( checkbox1 ); + expect( checkbox1 ).not.toBeChecked(); + + expect( checkbox2 ).not.toBeChecked(); + click( label2 ); + expect( checkbox2 ).toBeChecked(); + click( label2 ); + expect( checkbox2 ).not.toBeChecked(); + + expect( checkbox3 ).not.toBeChecked(); + click( label3 ); + expect( checkbox3 ).toBeChecked(); + click( label3 ); + expect( checkbox3 ).not.toBeChecked(); + } ); + + it( 'should not check/uncheck disabled checkbox', () => { + const { getByText, getByLabelText } = render( + <> + + + { /* eslint-disable-next-line no-restricted-syntax */ } + + { /* eslint-disable-next-line jsx-a11y/label-has-for */ } + + + ); + const checkbox1 = getByLabelText( 'checkbox1' ); + const label2 = getByText( 'checkbox2' ); + const checkbox2 = getByLabelText( 'checkbox2' ); + const label3 = getByText( 'checkbox3' ); + const checkbox3 = getByLabelText( 'checkbox3' ); + + expect( checkbox1 ).not.toBeChecked(); + click( checkbox1 ); + expect( checkbox1 ).not.toBeChecked(); + + expect( checkbox2 ).not.toBeChecked(); + click( label2 ); + expect( checkbox2 ).not.toBeChecked(); + + expect( checkbox3 ).not.toBeChecked(); + click( label3 ); + expect( checkbox3 ).not.toBeChecked(); + } ); + + it( 'should change select when clicking on options', async () => { + const Test = ( { multiple } ) => { + return ( + + ); + }; + const { getByText, getByLabelText, rerender } = render( ); + const select = getByLabelText( 'select' ); + const option1 = getByText( 'option1' ); + const option2 = getByText( 'option2' ); + const option3 = getByText( 'option3' ); + const option4 = getByText( 'option4' ); + + click( option2 ); + + expect( option2.selected ).toBe( true ); + expect( Array.from( select.selectedOptions ) ).toEqual( [ option2 ] ); + + rerender( ); + + click( option2 ); + click( option4, { shiftKey: true } ); + expect( Array.from( select.selectedOptions ) ).toEqual( [ + option2, + option3, + option4, + ] ); + + click( option3, { ctrlKey: true } ); + click( option1, { ctrlKey: true } ); + expect( Array.from( select.selectedOptions ) ).toEqual( [ + option1, + option2, + option4, + ] ); + + click( option3, { shiftKey: true } ); + expect( Array.from( select.selectedOptions ) ).toEqual( [ + option1, + option2, + option3, + ] ); + } ); +} ); diff --git a/packages/test-utils/src/test/focus.js b/packages/test-utils/src/test/focus.js new file mode 100644 index 00000000000000..219c28facdb7f5 --- /dev/null +++ b/packages/test-utils/src/test/focus.js @@ -0,0 +1,20 @@ +/** + * External dependencies + */ +import * as React from 'react'; + +/** + * Internal dependencies + */ +import render from '../render'; +import focus from '../focus'; + +describe( 'focus', () => { + it( 'should focus button', () => { + const { getByText } = render( ); + const button = getByText( 'button' ); + expect( button ).not.toHaveFocus(); + focus( button ); + expect( button ).toHaveFocus(); + } ); +} ); diff --git a/packages/test-utils/src/test/hover.js b/packages/test-utils/src/test/hover.js new file mode 100644 index 00000000000000..68733651ffbb6e --- /dev/null +++ b/packages/test-utils/src/test/hover.js @@ -0,0 +1,40 @@ +/** + * External dependencies + */ +import * as React from 'react'; + +/** + * Internal dependencies + */ +import render from '../render'; +import hover from '../hover'; + +describe( 'hover', () => { + it( 'should hover button', () => { + const onMouseOut = jest.fn(); + const onMouseOver = jest.fn(); + const { getByText } = render( + <> + { /* eslint-disable-next-line jsx-a11y/mouse-events-have-key-events */ } + + + + ); + const button1 = getByText( 'button1' ); + const button2 = getByText( 'button2' ); + + expect( onMouseOut ).not.toHaveBeenCalled(); + expect( onMouseOver ).not.toHaveBeenCalled(); + + hover( button1 ); + + expect( onMouseOut ).not.toHaveBeenCalled(); + expect( onMouseOver ).toHaveBeenCalled(); + + hover( button2 ); + + expect( onMouseOut ).toHaveBeenCalled(); + } ); +} ); diff --git a/packages/test-utils/src/test/press.js b/packages/test-utils/src/test/press.js new file mode 100644 index 00000000000000..15ec24b1f44654 --- /dev/null +++ b/packages/test-utils/src/test/press.js @@ -0,0 +1,170 @@ +/** + * External dependencies + */ +import * as React from 'react'; + +/** + * Internal dependencies + */ +import render from '../render'; +import press from '../press'; + +describe( 'press', () => { + it( 'should focus element when pressing key on it', () => { + const { getByText } = render(
div
); + const div = getByText( 'div' ); + expect( div ).not.toHaveFocus(); + press( 'a', div ); + expect( div ).toHaveFocus(); + } ); + + it( 'should click button when pressing enter', () => { + const onClick = jest.fn(); + const { getByText } = render( ); + const button = getByText( 'button' ); + expect( onClick ).not.toHaveBeenCalled(); + press.Enter( button ); + expect( onClick ).toHaveBeenCalled(); + } ); + + it( 'should not click button when pressing enter if event.preventDefault() was called on key down', () => { + const onClick = jest.fn(); + const { getByText } = render( + + ); + const button = getByText( 'button' ); + expect( onClick ).not.toHaveBeenCalled(); + press.Enter( button ); + expect( onClick ).not.toHaveBeenCalled(); + } ); + + it( 'should click button when pressing space', () => { + const onClick = jest.fn(); + const { getByText } = render( ); + const button = getByText( 'button' ); + expect( onClick ).not.toHaveBeenCalled(); + press.Space( button ); + expect( onClick ).toHaveBeenCalled(); + } ); + + it( 'should not click button when pressing space if event.preventDefault() was called on key down', () => { + const onClick = jest.fn(); + const { getByText } = render( + + ); + const button = getByText( 'button' ); + expect( onClick ).not.toHaveBeenCalled(); + press.Space( button ); + expect( onClick ).not.toHaveBeenCalled(); + } ); + + it( 'should not click button when pressing space if event.preventDefault() was called on key up', () => { + const onClick = jest.fn(); + const { getByText } = render( + + ); + const button = getByText( 'button' ); + expect( onClick ).not.toHaveBeenCalled(); + press.Space( button ); + expect( onClick ).not.toHaveBeenCalled(); + } ); + + it( 'should submit form when pressing enter on form input', () => { + const onSubmit = jest.fn(); + const { getByLabelText } = render( +
+ form + + +
+ ); + const input = getByLabelText( 'input' ); + expect( onSubmit ).not.toHaveBeenCalled(); + press.Enter( input ); + expect( onSubmit ).toHaveBeenCalled(); + } ); + + it( 'should not submit form when pressing enter on form input with multiple text fields', () => { + const onSubmit = jest.fn(); + const { getByLabelText } = render( +
+ form + + + +
+ ); + const input = getByLabelText( 'input' ); + press.Enter( input ); + expect( onSubmit ).not.toHaveBeenCalled(); + } ); + + it( 'should submit form when pressing enter on form input with multiple text fields with submit button', () => { + const onSubmit = jest.fn(); + const { getByLabelText } = render( +
+ form + + + + +
+ ); + const input = getByLabelText( 'input' ); + expect( onSubmit ).not.toHaveBeenCalled(); + press.Enter( input ); + expect( onSubmit ).toHaveBeenCalled(); + } ); + + it( 'should move focus when pressing tab', () => { + const { getByText } = render( + <> + + span + + + ); + const button1 = getByText( 'button1' ); + const button2 = getByText( 'button2' ); + + expect( button1 ).not.toHaveFocus(); + press.Tab(); + expect( button1 ).toHaveFocus(); + press.Tab(); + expect( button2 ).toHaveFocus(); + press.Tab(); + expect( button1 ).toHaveFocus(); + press.ShiftTab(); + expect( button2 ).toHaveFocus(); + press.ShiftTab(); + expect( button1 ).toHaveFocus(); + } ); + + it( 'should not move focus when pressing tab if event.preventDefault() was called on key down', () => { + const { getByText } = render( + <> + + span + + + ); + const button1 = getByText( 'button1' ); + const button2 = getByText( 'button2' ); + + expect( button1 ).not.toHaveFocus(); + press.Tab(); + expect( button1 ).toHaveFocus(); + press.Tab(); + expect( button2 ).toHaveFocus(); + press.Tab(); + expect( button2 ).toHaveFocus(); + press.ShiftTab(); + expect( button2 ).toHaveFocus(); + } ); +} ); diff --git a/packages/test-utils/src/test/type.js b/packages/test-utils/src/test/type.js new file mode 100644 index 00000000000000..caa42a10ca81c5 --- /dev/null +++ b/packages/test-utils/src/test/type.js @@ -0,0 +1,57 @@ +/** + * External dependencies + */ +import * as React from 'react'; + +/** + * Internal dependencies + */ +import render from '../render'; +import type from '../type'; + +describe( 'type', () => { + it( 'should type on input', () => { + const values = []; + const { getByLabelText } = render( + values.push( event.target.value ) } + /> + ); + const input = getByLabelText( 'input' ); + + type( 'ab cd\b\bef', input ); + + expect( values ).toEqual( [ + 'a', + 'ab', + 'ab ', + 'ab c', + 'ab cd', + 'ab c', + 'ab ', + 'ab e', + 'ab ef', + ] ); + } ); + + it( 'should not type on input if event.preventDefault() was called on key down', () => { + const values = []; + const { getByLabelText } = render( + { + if ( event.key !== 'e' ) { + event.preventDefault(); + } + } } + onChange={ ( event ) => values.push( event.target.value ) } + /> + ); + const input = getByLabelText( 'input' ); + + type( 'ab cd\b\befe', input ); + + expect( values ).toEqual( [ 'e', 'ee' ] ); + } ); +} ); diff --git a/packages/test-utils/src/type.js b/packages/test-utils/src/type.js new file mode 100644 index 00000000000000..7e0b0170271461 --- /dev/null +++ b/packages/test-utils/src/type.js @@ -0,0 +1,63 @@ +/** + * WordPress dependencies + */ +import { isTextField } from '@wordpress/dom'; + +/** + * Internal dependencies + */ +import fireEvent from './fire-event'; +import focus from './focus'; +import getActiveElement from './utils/get-active-element'; +import isFocusable from './utils/is-focusable'; +import subscribeDefaultPrevented from './utils/subscribe-default-prevented'; + +const charMap = { + '\b': 'Backspace', +}; + +const keyMap = { + Backspace( element ) { + return element.value.substr( 0, element.value.length - 1 ); + }, +}; + +/** + * Type on a text field element. + * + * @param {string} text + * @param {Element} [element] + * @param {Object} [options] + */ +export default function type( text, element, options = {} ) { + if ( ! element ) { + element = getActiveElement(); + } + + if ( ! element || ! isFocusable( element ) || ! isTextField( element ) ) { + return; + } + + focus( element ); + + // Set element dirty so blur() can dispatch a change event + element.dirty = true; + + for ( const char of text ) { + const key = char in charMap ? charMap[ char ] : char; + const value = + key in keyMap ? keyMap[ key ]( element, options ) : `${ element.value }${ char }`; + + const defaultPrevented = subscribeDefaultPrevented( element, 'keydown' ); + + fireEvent.keyDown( element, { key, ...options } ); + + if ( ! defaultPrevented.current && ! element.readOnly ) { + fireEvent.input( element, { data: char, target: { value }, ...options } ); + } + + fireEvent.keyUp( element, { key, ...options } ); + + defaultPrevented.unsubscribe(); + } +} diff --git a/packages/test-utils/src/utils/get-active-element.js b/packages/test-utils/src/utils/get-active-element.js new file mode 100644 index 00000000000000..f5ac7ada59e105 --- /dev/null +++ b/packages/test-utils/src/utils/get-active-element.js @@ -0,0 +1,12 @@ +/** + * Internal dependencies + */ +import getDocument from './get-document'; + +/** + * @param {Element} [element] + * @return {Element} Active Element + */ +export default function getActiveElement( element ) { + return getDocument( element ).activeElement; +} diff --git a/packages/test-utils/src/utils/get-closest-focusable.js b/packages/test-utils/src/utils/get-closest-focusable.js new file mode 100644 index 00000000000000..bfd9472187a99a --- /dev/null +++ b/packages/test-utils/src/utils/get-closest-focusable.js @@ -0,0 +1,18 @@ +/** + * Internal dependencies + */ +import isFocusable from './is-focusable'; + +/** + * @param {Element} element + * @return {Element|null} Closest focusable element + */ +export default function getClosestFocusable( element ) { + let container = element; + + do { + container = container.parentElement; + } while ( container && ! isFocusable( container ) ); + + return container; +} diff --git a/packages/test-utils/src/utils/get-document.js b/packages/test-utils/src/utils/get-document.js new file mode 100644 index 00000000000000..cd13fbf02ec91d --- /dev/null +++ b/packages/test-utils/src/utils/get-document.js @@ -0,0 +1,9 @@ +/** + * @param {Element} [element] + * @return {Document} Document + */ +export default function getDocument( element ) { + return element ? + element.ownerDocument || window.document : + window.document; +} diff --git a/packages/test-utils/src/utils/get-next-tabbable.js b/packages/test-utils/src/utils/get-next-tabbable.js new file mode 100644 index 00000000000000..3496b925caeb16 --- /dev/null +++ b/packages/test-utils/src/utils/get-next-tabbable.js @@ -0,0 +1,24 @@ +/** + * WordPress dependencies + */ +import { focus } from '@wordpress/dom'; + +/** + * Internal dependencies + */ +import './mock-client-rects'; +import getDocument from './get-document'; +import getActiveElement from './get-active-element'; + +/** + * @param {Element} element + * @return {Element} Next tabbable element + */ +export default function getNextTabbable( element ) { + const tabbableElements = focus.tabbable.find( getDocument( element ) ); + const currentIndex = tabbableElements.indexOf( getActiveElement( element ) ); + const nextIndex = currentIndex + 1; + return nextIndex >= tabbableElements.length ? + tabbableElements[ 0 ] : + tabbableElements[ nextIndex ]; +} diff --git a/packages/test-utils/src/utils/get-previous-tabbable.js b/packages/test-utils/src/utils/get-previous-tabbable.js new file mode 100644 index 00000000000000..b684a04060e169 --- /dev/null +++ b/packages/test-utils/src/utils/get-previous-tabbable.js @@ -0,0 +1,23 @@ +/** + * WordPress dependencies + */ +import { focus } from '@wordpress/dom'; +/** + * Internal dependencies + */ +import './mock-client-rects'; +import getDocument from './get-document'; +import getActiveElement from './get-active-element'; + +/** + * @param {Element} element + * @return {Element} Previous tabbable element + */ +export default function getPreviousTabbable( element ) { + const tabbableElements = focus.tabbable.find( getDocument( element ) ); + const currentIndex = tabbableElements.indexOf( getActiveElement( element ) ); + const previousIndex = currentIndex - 1; + return previousIndex < 0 ? + tabbableElements[ tabbableElements.length - 1 ] : + tabbableElements[ previousIndex ]; +} diff --git a/packages/test-utils/src/utils/is-body-element.js b/packages/test-utils/src/utils/is-body-element.js new file mode 100644 index 00000000000000..852b544536a379 --- /dev/null +++ b/packages/test-utils/src/utils/is-body-element.js @@ -0,0 +1,7 @@ +/** + * @param {Element} [element] + * @return {boolean} Whether `element` is body + */ +export default function isBodyElement( element ) { + return element && element.tagName === 'BODY'; +} diff --git a/packages/test-utils/src/utils/is-focusable.js b/packages/test-utils/src/utils/is-focusable.js new file mode 100644 index 00000000000000..be6e0dd9b5172f --- /dev/null +++ b/packages/test-utils/src/utils/is-focusable.js @@ -0,0 +1,19 @@ +/** + * WordPress dependencies + */ +import { focus } from '@wordpress/dom'; +/** + * Internal dependencies + */ +import './mock-client-rects'; +import getDocument from './get-document'; + +/** + * @param {Element} element + * @return {boolean} Whether `element` is focusable + */ +export default function isFocusable( element ) { + const document = getDocument( element ); + const focusableElements = focus.focusable.find( document ); + return focusableElements.indexOf( element ) !== -1; +} diff --git a/packages/test-utils/src/utils/mock-client-rects.js b/packages/test-utils/src/utils/mock-client-rects.js new file mode 100644 index 00000000000000..0dfa9ec7580174 --- /dev/null +++ b/packages/test-utils/src/utils/mock-client-rects.js @@ -0,0 +1,20 @@ +function isHidden( element ) { + if ( element.parentElement && isHidden( element.parentElement ) ) { + return true; + } + return ( + ! element.style || + element.style.display === 'none' || + element.style.visibility === 'hidden' || + element.hidden + ); +} + +window.Element.prototype.getClientRects = function() { + if ( isHidden( this ) ) { + return []; + } + return [ + { width: 1, height: 1 }, + ]; +}; diff --git a/packages/test-utils/src/utils/subscribe-default-prevented.js b/packages/test-utils/src/utils/subscribe-default-prevented.js new file mode 100644 index 00000000000000..d95c7df6124c3f --- /dev/null +++ b/packages/test-utils/src/utils/subscribe-default-prevented.js @@ -0,0 +1,34 @@ +/** + * @typedef {Object} DefaultPreventedRef + * @property {boolean} current + * @property {Function} unsubscribe + */ +/** + * + * @param {Element} element + * @param {...string} eventNames + * @return {DefaultPreventedRef} `defaultPrevented` ref object + */ +export default function subscribeDefaultPrevented( element, ...eventNames ) { + const ref = { current: false, unsubscribe: () => {} }; + + const handleEvent = ( event ) => { + const preventDefault = event.preventDefault.bind( event ); + event.preventDefault = () => { + ref.current = true; + preventDefault(); + }; + }; + + eventNames.forEach( ( eventName ) => { + element.addEventListener( eventName, handleEvent ); + } ); + + ref.unsubscribe = () => { + eventNames.forEach( ( eventName ) => { + element.removeEventListener( eventName, handleEvent ); + } ); + }; + + return ref; +} diff --git a/packages/test-utils/src/wait.js b/packages/test-utils/src/wait.js new file mode 100644 index 00000000000000..1ec996a7647e78 --- /dev/null +++ b/packages/test-utils/src/wait.js @@ -0,0 +1,15 @@ +/** + * External dependencies + */ +import { wait } from '@testing-library/react'; + +/** + * When in need to wait for non-deterministic periods of time you can use `wait` + * to wait for your expectations to pass. The `wait` function is a small + * wrapper around the [`wait-for-expect`](https://github.com/TheBrainFamily/wait-for-expect) + * module. + * + * @see https://testing-library.com/docs/dom-testing-library/api-async#wait + * @see https://github.com/TheBrainFamily/wait-for-expect + */ +export default wait; diff --git a/test/unit/jest.config.js b/test/unit/jest.config.js index 4b86746cf90b69..5c7f40050c8e52 100644 --- a/test/unit/jest.config.js +++ b/test/unit/jest.config.js @@ -17,6 +17,9 @@ module.exports = { '/test/unit/config/gutenberg-phase.js', '/test/unit/config/register-context.js', ], + setupFilesAfterEnv: [ + '/packages/test-utils/extend-expect.js', + ], testURL: 'http://localhost', testPathIgnorePatterns: [ '/\.git/', From 3be5a1ad29b4f24b7daaa3811c3774025aac4ac8 Mon Sep 17 00:00:00 2001 From: Haz Date: Sun, 1 Dec 2019 22:29:56 -0300 Subject: [PATCH 02/10] Update jsdocs --- packages/test-utils/src/utils/subscribe-default-prevented.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/test-utils/src/utils/subscribe-default-prevented.js b/packages/test-utils/src/utils/subscribe-default-prevented.js index d95c7df6124c3f..caad0ed6285bf3 100644 --- a/packages/test-utils/src/utils/subscribe-default-prevented.js +++ b/packages/test-utils/src/utils/subscribe-default-prevented.js @@ -4,6 +4,7 @@ * @property {Function} unsubscribe */ /** + * Keep track of event.defaultPrevented state. * * @param {Element} element * @param {...string} eventNames From 1f34b015bcfa3f3b13e33b4ad46cdc46a3e8a2ef Mon Sep 17 00:00:00 2001 From: Haz Date: Sun, 1 Dec 2019 22:30:20 -0300 Subject: [PATCH 03/10] Add @wordpress/test-utils to @wordpress/jest-preset-default --- package-lock.json | 1 + packages/jest-preset-default/jest-preset.json | 3 ++- packages/jest-preset-default/package.json | 1 + test/unit/jest.config.js | 3 --- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9c8f3bddad722f..d15876ba40b783 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8213,6 +8213,7 @@ "dev": true, "requires": { "@wordpress/jest-console": "file:packages/jest-console", + "@wordpress/test-utils": "file:packages/test-utils", "babel-jest": "^24.7.1", "enzyme": "^3.9.0", "enzyme-adapter-react-16": "^1.10.0", diff --git a/packages/jest-preset-default/jest-preset.json b/packages/jest-preset-default/jest-preset.json index 54b62de584fcc9..980653e34e90c9 100644 --- a/packages/jest-preset-default/jest-preset.json +++ b/packages/jest-preset-default/jest-preset.json @@ -9,7 +9,8 @@ "/node_modules/@wordpress/jest-preset-default/scripts/setup-globals.js" ], "setupFilesAfterEnv": [ - "/node_modules/@wordpress/jest-preset-default/scripts/setup-test-framework.js" + "/node_modules/@wordpress/jest-preset-default/scripts/setup-test-framework.js", + "/node_modules/@wordpress/test-utils/extend-expect" ], "snapshotSerializers": [ "/node_modules/enzyme-to-json/serializer.js" diff --git a/packages/jest-preset-default/package.json b/packages/jest-preset-default/package.json index d6e5a30058334e..8a9a7cfe518ecd 100644 --- a/packages/jest-preset-default/package.json +++ b/packages/jest-preset-default/package.json @@ -32,6 +32,7 @@ "main": "index.js", "dependencies": { "@wordpress/jest-console": "file:../jest-console", + "@wordpress/test-utils": "file:../test-utils", "babel-jest": "^24.7.1", "enzyme": "^3.9.0", "enzyme-adapter-react-16": "^1.10.0", diff --git a/test/unit/jest.config.js b/test/unit/jest.config.js index 5c7f40050c8e52..4b86746cf90b69 100644 --- a/test/unit/jest.config.js +++ b/test/unit/jest.config.js @@ -17,9 +17,6 @@ module.exports = { '/test/unit/config/gutenberg-phase.js', '/test/unit/config/register-context.js', ], - setupFilesAfterEnv: [ - '/packages/test-utils/extend-expect.js', - ], testURL: 'http://localhost', testPathIgnorePatterns: [ '/\.git/', From 3ac33a5ef6fea1f3067a7184a940afa507825970 Mon Sep 17 00:00:00 2001 From: Haz Date: Sun, 1 Dec 2019 22:32:04 -0300 Subject: [PATCH 04/10] Update @wordpress/jest-preset-default CHANGELOG.md --- packages/jest-preset-default/CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/jest-preset-default/CHANGELOG.md b/packages/jest-preset-default/CHANGELOG.md index e6c461bfd5b326..85ba03709d5fe9 100644 --- a/packages/jest-preset-default/CHANGELOG.md +++ b/packages/jest-preset-default/CHANGELOG.md @@ -1,3 +1,9 @@ +## master + +### New Features + +- Add `@wordpress/test-utils/extend-expect` to Jest setup ([18855](https://github.com/WordPress/gutenberg/pull/18855)). + ## 5.1.0 (2019-09-03) ### Bug Fixes From 61e6b9da00591a51a0ac6efa1609b401ff2d3604 Mon Sep 17 00:00:00 2001 From: Haz Date: Wed, 4 Dec 2019 08:33:18 -0300 Subject: [PATCH 05/10] Use element.parentElement on isFocusable helper --- packages/test-utils/src/utils/is-focusable.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/test-utils/src/utils/is-focusable.js b/packages/test-utils/src/utils/is-focusable.js index be6e0dd9b5172f..fef9fd90570e4e 100644 --- a/packages/test-utils/src/utils/is-focusable.js +++ b/packages/test-utils/src/utils/is-focusable.js @@ -6,14 +6,15 @@ import { focus } from '@wordpress/dom'; * Internal dependencies */ import './mock-client-rects'; -import getDocument from './get-document'; /** * @param {Element} element * @return {boolean} Whether `element` is focusable */ export default function isFocusable( element ) { - const document = getDocument( element ); - const focusableElements = focus.focusable.find( document ); + if ( ! element.parentElement ) { + return false; + } + const focusableElements = focus.focusable.find( element.parentElement ); return focusableElements.indexOf( element ) !== -1; } From e5b9d4d8b1030244846ad389e2b06935d105adaf Mon Sep 17 00:00:00 2001 From: Haz Date: Wed, 4 Dec 2019 08:41:44 -0300 Subject: [PATCH 06/10] Transform mock-client-rects into a restorable function --- .../test-utils/src/utils/get-next-tabbable.js | 4 +++- .../src/utils/get-previous-tabbable.js | 4 +++- packages/test-utils/src/utils/is-focusable.js | 4 +++- .../test-utils/src/utils/mock-client-rects.js | 24 ++++++++++++------- 4 files changed, 25 insertions(+), 11 deletions(-) diff --git a/packages/test-utils/src/utils/get-next-tabbable.js b/packages/test-utils/src/utils/get-next-tabbable.js index 3496b925caeb16..0b2a896f3f3019 100644 --- a/packages/test-utils/src/utils/get-next-tabbable.js +++ b/packages/test-utils/src/utils/get-next-tabbable.js @@ -6,7 +6,7 @@ import { focus } from '@wordpress/dom'; /** * Internal dependencies */ -import './mock-client-rects'; +import mockClientRects from './mock-client-rects'; import getDocument from './get-document'; import getActiveElement from './get-active-element'; @@ -15,9 +15,11 @@ import getActiveElement from './get-active-element'; * @return {Element} Next tabbable element */ export default function getNextTabbable( element ) { + const restoreClientRects = mockClientRects(); const tabbableElements = focus.tabbable.find( getDocument( element ) ); const currentIndex = tabbableElements.indexOf( getActiveElement( element ) ); const nextIndex = currentIndex + 1; + restoreClientRects(); return nextIndex >= tabbableElements.length ? tabbableElements[ 0 ] : tabbableElements[ nextIndex ]; diff --git a/packages/test-utils/src/utils/get-previous-tabbable.js b/packages/test-utils/src/utils/get-previous-tabbable.js index b684a04060e169..dc3fd5c08c0d45 100644 --- a/packages/test-utils/src/utils/get-previous-tabbable.js +++ b/packages/test-utils/src/utils/get-previous-tabbable.js @@ -5,7 +5,7 @@ import { focus } from '@wordpress/dom'; /** * Internal dependencies */ -import './mock-client-rects'; +import mockClientRects from './mock-client-rects'; import getDocument from './get-document'; import getActiveElement from './get-active-element'; @@ -14,9 +14,11 @@ import getActiveElement from './get-active-element'; * @return {Element} Previous tabbable element */ export default function getPreviousTabbable( element ) { + const restoreClientRects = mockClientRects(); const tabbableElements = focus.tabbable.find( getDocument( element ) ); const currentIndex = tabbableElements.indexOf( getActiveElement( element ) ); const previousIndex = currentIndex - 1; + restoreClientRects(); return previousIndex < 0 ? tabbableElements[ tabbableElements.length - 1 ] : tabbableElements[ previousIndex ]; diff --git a/packages/test-utils/src/utils/is-focusable.js b/packages/test-utils/src/utils/is-focusable.js index fef9fd90570e4e..6f698f887531f5 100644 --- a/packages/test-utils/src/utils/is-focusable.js +++ b/packages/test-utils/src/utils/is-focusable.js @@ -5,7 +5,7 @@ import { focus } from '@wordpress/dom'; /** * Internal dependencies */ -import './mock-client-rects'; +import mockClientRects from './mock-client-rects'; /** * @param {Element} element @@ -15,6 +15,8 @@ export default function isFocusable( element ) { if ( ! element.parentElement ) { return false; } + const restoreClientRects = mockClientRects(); const focusableElements = focus.focusable.find( element.parentElement ); + restoreClientRects(); return focusableElements.indexOf( element ) !== -1; } diff --git a/packages/test-utils/src/utils/mock-client-rects.js b/packages/test-utils/src/utils/mock-client-rects.js index 0dfa9ec7580174..e18c0fbb687da0 100644 --- a/packages/test-utils/src/utils/mock-client-rects.js +++ b/packages/test-utils/src/utils/mock-client-rects.js @@ -10,11 +10,19 @@ function isHidden( element ) { ); } -window.Element.prototype.getClientRects = function() { - if ( isHidden( this ) ) { - return []; - } - return [ - { width: 1, height: 1 }, - ]; -}; +export default function mockClientRects() { + const boundGetClientRects = () => window.Element.prototype.getClientRects(); + + window.Element.prototype.getClientRects = function getClientRects() { + if ( isHidden( this ) ) { + return []; + } + return [ + { width: 1, height: 1 }, + ]; + }; + + return function restoreClientRects() { + window.Element.prototype.getClientRects = boundGetClientRects; + }; +} From b0189798a6de009a5a6304429330927779a6e6d2 Mon Sep 17 00:00:00 2001 From: Haz Date: Mon, 6 Jan 2020 11:54:38 -0300 Subject: [PATCH 07/10] Fix mockClientRects --- package-lock.json | 204 ++++++++++++++++++ .../test-utils/src/utils/get-next-tabbable.js | 4 +- .../src/utils/get-previous-tabbable.js | 4 +- packages/test-utils/src/utils/is-focusable.js | 5 +- .../test-utils/src/utils/mock-client-rects.js | 25 +-- 5 files changed, 219 insertions(+), 23 deletions(-) diff --git a/package-lock.json b/package-lock.json index 701b3116b2582a..f740bdff6977e5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5791,6 +5791,12 @@ "any-observable": "^0.3.0" } }, + "@sheerun/mutationobserver-shim": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@sheerun/mutationobserver-shim/-/mutationobserver-shim-0.3.2.tgz", + "integrity": "sha512-vTCdPp/T/Q3oSqwHmZ5Kpa9oI7iLtGl3RQaA/NyLHikvcrPxACkkKVr/XzkSPJWXHRhKGzVvb0urJsbMlRxi1Q==", + "dev": true + }, "@storybook/addon-a11y": { "version": "5.2.4", "resolved": "https://registry.npmjs.org/@storybook/addon-a11y/-/addon-a11y-5.2.4.tgz", @@ -7796,6 +7802,135 @@ "resolved": "https://registry.npmjs.org/@tannin/postfix/-/postfix-1.0.2.tgz", "integrity": "sha512-Nggtk7/ljfNPpAX8CjxxLkMKuO6u2gH1ozmTvGclWF2pNcxTf6YGghYNYNWZRKrimXGhQ8yZqvAHep7h80K04g==" }, + "@testing-library/dom": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-6.11.0.tgz", + "integrity": "sha512-Pkx9LMIGshyNbfmecjt18rrAp/ayMqGH674jYER0SXj0iG9xZc+zWRjk2Pg9JgPBDvwI//xGrI/oOQkAi4YEew==", + "dev": true, + "requires": { + "@babel/runtime": "^7.6.2", + "@sheerun/mutationobserver-shim": "^0.3.2", + "@types/testing-library__dom": "^6.0.0", + "aria-query": "3.0.0", + "pretty-format": "^24.9.0", + "wait-for-expect": "^3.0.0" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.7.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.7.7.tgz", + "integrity": "sha512-uCnC2JEVAu8AKB5do1WRIsvrdJ0flYx/A/9f/6chdacnEZ7LmavjdsDXr5ksYBegxtuTPR5Va9/+13QF/kFkCA==", + "dev": true, + "requires": { + "regenerator-runtime": "^0.13.2" + } + }, + "@jest/types": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-24.9.0.tgz", + "integrity": "sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^13.0.0" + } + }, + "@types/yargs": { + "version": "13.0.4", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.4.tgz", + "integrity": "sha512-Ke1WmBbIkVM8bpvsNEcGgQM70XcEh/nbpxQhW7FhrsbCsXSY9BmLB1+LHtD7r9zrsOcFlLiF+a/UeJsdfw3C5A==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "pretty-format": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-24.9.0.tgz", + "integrity": "sha512-00ZMZUiHaJrNfk33guavqgvfJS30sLYf0f8+Srklv0AMPodGGHcoHgksZ3OThYnIvOd+8yMCn0YiEOogjlgsnA==", + "dev": true, + "requires": { + "@jest/types": "^24.9.0", + "ansi-regex": "^4.0.0", + "ansi-styles": "^3.2.0", + "react-is": "^16.8.4" + } + } + } + }, + "@testing-library/jest-dom": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-4.2.4.tgz", + "integrity": "sha512-j31Bn0rQo12fhCWOUWy9fl7wtqkp7In/YP2p5ZFyRuiiB9Qs3g+hS4gAmDWONbAHcRmVooNJ5eOHQDCOmUFXHg==", + "dev": true, + "requires": { + "@babel/runtime": "^7.5.1", + "chalk": "^2.4.1", + "css": "^2.2.3", + "css.escape": "^1.5.1", + "jest-diff": "^24.0.0", + "jest-matcher-utils": "^24.0.0", + "lodash": "^4.17.11", + "pretty-format": "^24.0.0", + "redent": "^3.0.0" + }, + "dependencies": { + "indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true + }, + "redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "requires": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + } + }, + "strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "requires": { + "min-indent": "^1.0.0" + } + } + } + }, + "@testing-library/react": { + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-9.4.0.tgz", + "integrity": "sha512-XdhDWkI4GktUPsz0AYyeQ8M9qS/JFie06kcSnUVcpgOwFjAu9vhwR83qBl+lw9yZWkbECjL8Hd+n5hH6C0oWqg==", + "dev": true, + "requires": { + "@babel/runtime": "^7.7.6", + "@testing-library/dom": "^6.11.0", + "@types/testing-library__react": "^9.1.2" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.7.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.7.7.tgz", + "integrity": "sha512-uCnC2JEVAu8AKB5do1WRIsvrdJ0flYx/A/9f/6chdacnEZ7LmavjdsDXr5ksYBegxtuTPR5Va9/+13QF/kFkCA==", + "dev": true, + "requires": { + "regenerator-runtime": "^0.13.2" + } + } + } + }, "@types/babel__core": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.3.tgz", @@ -7956,6 +8091,15 @@ "@types/react": "*" } }, + "@types/react-dom": { + "version": "16.9.4", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.4.tgz", + "integrity": "sha512-fya9xteU/n90tda0s+FtN5Ym4tbgxpq/hb/Af24dvs6uYnYn+fspaxw5USlw0R8apDNwxsqumdRoCoKitckQqw==", + "dev": true, + "requires": { + "@types/react": "*" + } + }, "@types/react-syntax-highlighter": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/@types/react-syntax-highlighter/-/react-syntax-highlighter-10.1.0.tgz", @@ -7977,6 +8121,25 @@ "integrity": "sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw==", "dev": true }, + "@types/testing-library__dom": { + "version": "6.11.1", + "resolved": "https://registry.npmjs.org/@types/testing-library__dom/-/testing-library__dom-6.11.1.tgz", + "integrity": "sha512-ImChHtQqmjwraRLqBC2sgSQFtczeFvBmBcfhTYZn/3KwXbyD07LQykEQ0xJo7QHc1GbVvf7pRyGaIe6PkCdxEw==", + "dev": true, + "requires": { + "pretty-format": "^24.3.0" + } + }, + "@types/testing-library__react": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@types/testing-library__react/-/testing-library__react-9.1.2.tgz", + "integrity": "sha512-CYaMqrswQ+cJACy268jsLAw355DZtPZGt3Jwmmotlcu8O/tkoXBI6AeZ84oZBJsIsesozPKzWzmv/0TIU+1E9Q==", + "dev": true, + "requires": { + "@types/react-dom": "*", + "@types/testing-library__dom": "*" + } + }, "@types/unist": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.3.tgz", @@ -9017,6 +9180,9 @@ "version": "file:packages/test-utils", "dev": true, "requires": { + "@testing-library/dom": "^6.10.1", + "@testing-library/jest-dom": "^4.2.4", + "@testing-library/react": "^9.3.2", "@wordpress/dom": "file:packages/dom" } }, @@ -13769,6 +13935,26 @@ "randomfill": "^1.0.3" } }, + "css": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/css/-/css-2.2.4.tgz", + "integrity": "sha512-oUnjmWpy0niI3x/mPL8dVEI1l7MnG3+HHyRPHf+YFSbK+svOhXpmSOcDURUh2aOCgl2grzrOPt1nHLuCVFULLw==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "source-map": "^0.6.1", + "source-map-resolve": "^0.5.2", + "urix": "^0.1.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, "css-color-function": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/css-color-function/-/css-color-function-1.3.3.tgz", @@ -14000,6 +14186,12 @@ "integrity": "sha1-lGfQMsOM+u+58teVASUwYvh/ob0=", "dev": true }, + "css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha1-QuJ9T6BK4y+TGktNQZH6nN3ul8s=", + "dev": true + }, "cssesc": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-1.0.1.tgz", @@ -23685,6 +23877,12 @@ "dom-walk": "^0.1.0" } }, + "min-indent": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.0.tgz", + "integrity": "sha1-z8RcN+nsDY8KDsPdTvf3w6vjklY=", + "dev": true + }, "mini-css-extract-plugin": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-0.7.0.tgz", @@ -33463,6 +33661,12 @@ "browser-process-hrtime": "^0.1.2" } }, + "wait-for-expect": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/wait-for-expect/-/wait-for-expect-3.0.1.tgz", + "integrity": "sha512-3Ha7lu+zshEG/CeHdcpmQsZnnZpPj/UsG3DuKO8FskjuDbkx3jE3845H+CuwZjA2YWYDfKMU2KhnCaXMLd3wVw==", + "dev": true + }, "wait-on": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-3.3.0.tgz", diff --git a/packages/test-utils/src/utils/get-next-tabbable.js b/packages/test-utils/src/utils/get-next-tabbable.js index 0b2a896f3f3019..6fa520c8df5a9d 100644 --- a/packages/test-utils/src/utils/get-next-tabbable.js +++ b/packages/test-utils/src/utils/get-next-tabbable.js @@ -6,20 +6,18 @@ import { focus } from '@wordpress/dom'; /** * Internal dependencies */ -import mockClientRects from './mock-client-rects'; import getDocument from './get-document'; import getActiveElement from './get-active-element'; +import './mock-client-rects'; /** * @param {Element} element * @return {Element} Next tabbable element */ export default function getNextTabbable( element ) { - const restoreClientRects = mockClientRects(); const tabbableElements = focus.tabbable.find( getDocument( element ) ); const currentIndex = tabbableElements.indexOf( getActiveElement( element ) ); const nextIndex = currentIndex + 1; - restoreClientRects(); return nextIndex >= tabbableElements.length ? tabbableElements[ 0 ] : tabbableElements[ nextIndex ]; diff --git a/packages/test-utils/src/utils/get-previous-tabbable.js b/packages/test-utils/src/utils/get-previous-tabbable.js index dc3fd5c08c0d45..2bd89774dbd33c 100644 --- a/packages/test-utils/src/utils/get-previous-tabbable.js +++ b/packages/test-utils/src/utils/get-previous-tabbable.js @@ -5,20 +5,18 @@ import { focus } from '@wordpress/dom'; /** * Internal dependencies */ -import mockClientRects from './mock-client-rects'; import getDocument from './get-document'; import getActiveElement from './get-active-element'; +import './mock-client-rects'; /** * @param {Element} element * @return {Element} Previous tabbable element */ export default function getPreviousTabbable( element ) { - const restoreClientRects = mockClientRects(); const tabbableElements = focus.tabbable.find( getDocument( element ) ); const currentIndex = tabbableElements.indexOf( getActiveElement( element ) ); const previousIndex = currentIndex - 1; - restoreClientRects(); return previousIndex < 0 ? tabbableElements[ tabbableElements.length - 1 ] : tabbableElements[ previousIndex ]; diff --git a/packages/test-utils/src/utils/is-focusable.js b/packages/test-utils/src/utils/is-focusable.js index 6f698f887531f5..791b7989308311 100644 --- a/packages/test-utils/src/utils/is-focusable.js +++ b/packages/test-utils/src/utils/is-focusable.js @@ -2,10 +2,11 @@ * WordPress dependencies */ import { focus } from '@wordpress/dom'; + /** * Internal dependencies */ -import mockClientRects from './mock-client-rects'; +import './mock-client-rects'; /** * @param {Element} element @@ -15,8 +16,6 @@ export default function isFocusable( element ) { if ( ! element.parentElement ) { return false; } - const restoreClientRects = mockClientRects(); const focusableElements = focus.focusable.find( element.parentElement ); - restoreClientRects(); return focusableElements.indexOf( element ) !== -1; } diff --git a/packages/test-utils/src/utils/mock-client-rects.js b/packages/test-utils/src/utils/mock-client-rects.js index e18c0fbb687da0..e18e15999d1dbe 100644 --- a/packages/test-utils/src/utils/mock-client-rects.js +++ b/packages/test-utils/src/utils/mock-client-rects.js @@ -10,19 +10,16 @@ function isHidden( element ) { ); } -export default function mockClientRects() { - const boundGetClientRects = () => window.Element.prototype.getClientRects(); - - window.Element.prototype.getClientRects = function getClientRects() { - if ( isHidden( this ) ) { - return []; - } - return [ - { width: 1, height: 1 }, - ]; - }; +function getClientRects() { + if ( isHidden( this ) ) { + return []; + } + return [ { width: 1, height: 1 } ]; +} - return function restoreClientRects() { - window.Element.prototype.getClientRects = boundGetClientRects; - }; +if ( + typeof window !== 'undefined' && + window.Element.prototype.getClientRects !== getClientRects +) { + window.Element.prototype.getClientRects = getClientRects; } From 75e4fe55c85a079684260b022da56f4c65f2c995 Mon Sep 17 00:00:00 2001 From: Haz Date: Thu, 6 Feb 2020 08:13:41 -0300 Subject: [PATCH 08/10] Run lerna bootstrap --- package-lock.json | 162 +++++++++++++++++++- packages/jest-preset-default/jest-preset.js | 8 +- 2 files changed, 164 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 06eac2969da4a6..4db8a7d63c6b26 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6690,6 +6690,12 @@ "any-observable": "^0.3.0" } }, + "@sheerun/mutationobserver-shim": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@sheerun/mutationobserver-shim/-/mutationobserver-shim-0.3.2.tgz", + "integrity": "sha512-vTCdPp/T/Q3oSqwHmZ5Kpa9oI7iLtGl3RQaA/NyLHikvcrPxACkkKVr/XzkSPJWXHRhKGzVvb0urJsbMlRxi1Q==", + "dev": true + }, "@storybook/addon-a11y": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/@storybook/addon-a11y/-/addon-a11y-5.3.2.tgz", @@ -9485,6 +9491,115 @@ "resolved": "https://registry.npmjs.org/@tannin/postfix/-/postfix-1.0.2.tgz", "integrity": "sha512-Nggtk7/ljfNPpAX8CjxxLkMKuO6u2gH1ozmTvGclWF2pNcxTf6YGghYNYNWZRKrimXGhQ8yZqvAHep7h80K04g==" }, + "@testing-library/dom": { + "version": "6.12.2", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-6.12.2.tgz", + "integrity": "sha512-KCnvHra5fV+wDxg3wJObGvZFxq7v1DJt829GNFLuRDjKxVNc/B5AdsylNF5PMHFbWMXDsHwM26d2NZcZO9KjbQ==", + "dev": true, + "requires": { + "@babel/runtime": "^7.6.2", + "@sheerun/mutationobserver-shim": "^0.3.2", + "@types/testing-library__dom": "^6.0.0", + "aria-query": "3.0.0", + "pretty-format": "^24.9.0", + "wait-for-expect": "^3.0.0" + }, + "dependencies": { + "@jest/types": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-24.9.0.tgz", + "integrity": "sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^13.0.0" + } + }, + "@types/yargs": { + "version": "13.0.8", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.8.tgz", + "integrity": "sha512-XAvHLwG7UQ+8M4caKIH0ZozIOYay5fQkAgyIXegXT9jPtdIGdhga+sUEdAr1CiG46aB+c64xQEYyEzlwWVTNzA==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "pretty-format": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-24.9.0.tgz", + "integrity": "sha512-00ZMZUiHaJrNfk33guavqgvfJS30sLYf0f8+Srklv0AMPodGGHcoHgksZ3OThYnIvOd+8yMCn0YiEOogjlgsnA==", + "dev": true, + "requires": { + "@jest/types": "^24.9.0", + "ansi-regex": "^4.0.0", + "ansi-styles": "^3.2.0", + "react-is": "^16.8.4" + } + } + } + }, + "@testing-library/jest-dom": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-4.2.4.tgz", + "integrity": "sha512-j31Bn0rQo12fhCWOUWy9fl7wtqkp7In/YP2p5ZFyRuiiB9Qs3g+hS4gAmDWONbAHcRmVooNJ5eOHQDCOmUFXHg==", + "dev": true, + "requires": { + "@babel/runtime": "^7.5.1", + "chalk": "^2.4.1", + "css": "^2.2.3", + "css.escape": "^1.5.1", + "jest-diff": "^24.0.0", + "jest-matcher-utils": "^24.0.0", + "lodash": "^4.17.11", + "pretty-format": "^24.0.0", + "redent": "^3.0.0" + }, + "dependencies": { + "indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true + }, + "redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "requires": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + } + }, + "strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "requires": { + "min-indent": "^1.0.0" + } + } + } + }, + "@testing-library/react": { + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-9.4.0.tgz", + "integrity": "sha512-XdhDWkI4GktUPsz0AYyeQ8M9qS/JFie06kcSnUVcpgOwFjAu9vhwR83qBl+lw9yZWkbECjL8Hd+n5hH6C0oWqg==", + "dev": true, + "requires": { + "@babel/runtime": "^7.7.6", + "@testing-library/dom": "^6.11.0", + "@types/testing-library__react": "^9.1.2" + } + }, "@types/babel-types": { "version": "7.0.7", "resolved": "https://registry.npmjs.org/@types/babel-types/-/babel-types-7.0.7.tgz", @@ -9689,6 +9804,15 @@ "@types/react": "*" } }, + "@types/react-dom": { + "version": "16.9.5", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.5.tgz", + "integrity": "sha512-BX6RQ8s9D+2/gDhxrj8OW+YD4R+8hj7FEM/OJHGNR0KipE1h1mSsf39YeyC81qafkq+N3rU3h3RFbLSwE5VqUg==", + "dev": true, + "requires": { + "@types/react": "*" + } + }, "@types/react-syntax-highlighter": { "version": "11.0.2", "resolved": "https://registry.npmjs.org/@types/react-syntax-highlighter/-/react-syntax-highlighter-11.0.2.tgz", @@ -9719,6 +9843,25 @@ "integrity": "sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw==", "dev": true }, + "@types/testing-library__dom": { + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/@types/testing-library__dom/-/testing-library__dom-6.12.0.tgz", + "integrity": "sha512-PQ/gzABzc53T68RldZ/sJHKCihtP9ofU8XIgOk+H7tlfoCRdg9mqICio5Fo8j3Z8wo+pOfuDsuPprWsn3YtVmA==", + "dev": true, + "requires": { + "pretty-format": "^24.3.0" + } + }, + "@types/testing-library__react": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@types/testing-library__react/-/testing-library__react-9.1.2.tgz", + "integrity": "sha512-CYaMqrswQ+cJACy268jsLAw355DZtPZGt3Jwmmotlcu8O/tkoXBI6AeZ84oZBJsIsesozPKzWzmv/0TIU+1E9Q==", + "dev": true, + "requires": { + "@types/react-dom": "*", + "@types/testing-library__dom": "*" + } + }, "@types/unist": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.3.tgz", @@ -10817,6 +10960,9 @@ "version": "file:packages/test-utils", "dev": true, "requires": { + "@testing-library/dom": "^6.10.1", + "@testing-library/jest-dom": "^4.2.4", + "@testing-library/react": "^9.3.2", "@wordpress/dom": "file:packages/dom" } }, @@ -15910,6 +16056,12 @@ "integrity": "sha1-lGfQMsOM+u+58teVASUwYvh/ob0=", "dev": true }, + "css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha1-QuJ9T6BK4y+TGktNQZH6nN3ul8s=", + "dev": true + }, "cssesc": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-1.0.1.tgz", @@ -19515,7 +19667,7 @@ }, "node-pre-gyp": { "version": "0.12.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.12.0.tgz", "integrity": "sha512-4KghwV8vH5k+g2ylT+sLTjy5wmUOb9vPhnM8NHvRf9dHmnW/CndrFXy2aRPaPST6dugXSdHXfeaHQm77PIz/1A==", "dev": true, "optional": true, @@ -19534,7 +19686,7 @@ }, "nopt": { "version": "4.0.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", "dev": true, "optional": true, @@ -39405,6 +39557,12 @@ "browser-process-hrtime": "^0.1.2" } }, + "wait-for-expect": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/wait-for-expect/-/wait-for-expect-3.0.2.tgz", + "integrity": "sha512-cfS1+DZxuav1aBYbaO/kE06EOS8yRw7qOFoD3XtjTkYvCvh3zUvNST8DXK/nPaeqIzIv3P3kL3lRJn8iwOiSag==", + "dev": true + }, "wait-on": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-3.3.0.tgz", diff --git a/packages/jest-preset-default/jest-preset.js b/packages/jest-preset-default/jest-preset.js index b4e08b82b28f5f..a2992f3215339e 100644 --- a/packages/jest-preset-default/jest-preset.js +++ b/packages/jest-preset-default/jest-preset.js @@ -31,9 +31,9 @@ module.exports = { reporters: 'TRAVIS' in process.env && 'CI' in process.env ? [ - require.resolve( - '@wordpress/jest-preset-default/scripts/travis-fold-passes-reporter.js' - ), - ] + require.resolve( + '@wordpress/jest-preset-default/scripts/travis-fold-passes-reporter.js' + ), + ] : undefined, }; From f1d05fe8ab57c5e6fb7e0ef240df1a283a2281f5 Mon Sep 17 00:00:00 2001 From: Haz Date: Thu, 6 Feb 2020 08:14:46 -0300 Subject: [PATCH 09/10] Update jest-preset-default/jest-preset.js formatting --- packages/jest-preset-default/jest-preset.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/jest-preset-default/jest-preset.js b/packages/jest-preset-default/jest-preset.js index a2992f3215339e..b4e08b82b28f5f 100644 --- a/packages/jest-preset-default/jest-preset.js +++ b/packages/jest-preset-default/jest-preset.js @@ -31,9 +31,9 @@ module.exports = { reporters: 'TRAVIS' in process.env && 'CI' in process.env ? [ - require.resolve( - '@wordpress/jest-preset-default/scripts/travis-fold-passes-reporter.js' - ), - ] + require.resolve( + '@wordpress/jest-preset-default/scripts/travis-fold-passes-reporter.js' + ), + ] : undefined, }; From e317fe47856be6832a6340ae9f2090635b77d190 Mon Sep 17 00:00:00 2001 From: Haz Date: Thu, 6 Feb 2020 08:23:03 -0300 Subject: [PATCH 10/10] Lint code --- packages/test-utils/src/blur.js | 6 ++- packages/test-utils/src/click.js | 23 ++++------ packages/test-utils/src/press.js | 42 +++++++++---------- packages/test-utils/src/test/click.js | 4 +- packages/test-utils/src/test/press.js | 27 +++++++++--- packages/test-utils/src/type.js | 15 +++++-- packages/test-utils/src/utils/get-document.js | 4 +- .../test-utils/src/utils/get-next-tabbable.js | 10 +++-- .../src/utils/get-previous-tabbable.js | 10 +++-- 9 files changed, 81 insertions(+), 60 deletions(-) diff --git a/packages/test-utils/src/blur.js b/packages/test-utils/src/blur.js index ead02f37af0154..7ccf5625cb2204 100644 --- a/packages/test-utils/src/blur.js +++ b/packages/test-utils/src/blur.js @@ -16,7 +16,11 @@ export default function blur( element ) { element = getActiveElement(); } - if ( ! element || isBodyElement( element ) || getActiveElement( element ) !== element ) { + if ( + ! element || + isBodyElement( element ) || + getActiveElement( element ) !== element + ) { return; } diff --git a/packages/test-utils/src/click.js b/packages/test-utils/src/click.js index 1a36cc11641611..dce7967da458c3 100644 --- a/packages/test-utils/src/click.js +++ b/packages/test-utils/src/click.js @@ -25,9 +25,9 @@ function getClosestLabel( element ) { * @return {HTMLInputElement|HTMLTextAreaElement|HTMLSelectElement} Input element */ function getInputFromLabel( element ) { - const input = element.htmlFor ? - element.ownerDocument.getElementById( element.htmlFor ) : - element.querySelector( 'input,textarea,select' ); + const input = element.htmlFor + ? element.ownerDocument.getElementById( element.htmlFor ) + : element.querySelector( 'input,textarea,select' ); return input; } @@ -37,11 +37,7 @@ function getInputFromLabel( element ) { * @param {Object} defaultPrevented * @param {Object} options */ -function clickLabel( - element, - defaultPrevented, - options -) { +function clickLabel( element, defaultPrevented, options ) { const input = getInputFromLabel( element ); const isInputDisabled = Boolean( input && input.disabled ); @@ -80,10 +76,7 @@ function setSelected( element, selected ) { * @param {HTMLOptionElement} element * @param {Object} eventOptions */ -function clickOption( - element, - eventOptions -) { +function clickOption( element, eventOptions ) { const select = element.closest( 'select' ); if ( ! select ) { @@ -112,9 +105,9 @@ function clickOption( const elementIndex = options.indexOf( element ); // https://stackoverflow.com/a/16530782/5513909 const referenceOption = select.lastOptionSelectedNotByShiftKey; - const referenceOptionIndex = referenceOption ? - options.indexOf( referenceOption ) : - -1; + const referenceOptionIndex = referenceOption + ? options.indexOf( referenceOption ) + : -1; resetOptions(); // Select options between the reference option and the clicked element diff --git a/packages/test-utils/src/press.js b/packages/test-utils/src/press.js index e7c17ee18d71b2..d54e695b1bd0ab 100644 --- a/packages/test-utils/src/press.js +++ b/packages/test-utils/src/press.js @@ -25,10 +25,7 @@ const clickableInputTypes = [ * @param {HTMLInputElement} element * @param {Object} options */ -function submitFormByPressingEnterOn( - element, - options -) { +function submitFormByPressingEnterOn( element, options ) { const { form } = element; if ( ! form ) { @@ -55,8 +52,7 @@ function submitFormByPressingEnterOn( const submitButton = elements.find( ( el ) => - ( [ 'INPUT', 'BUTTON' ].includes( el.tagName ) ) && - el.type === 'submit' + [ 'INPUT', 'BUTTON' ].includes( el.tagName ) && el.type === 'submit' ); if ( validInputs.length === 1 || submitButton ) { @@ -67,9 +63,9 @@ function submitFormByPressingEnterOn( const keyDownMap = { Tab( element, { shiftKey } ) { const { body } = getDocument( element ); - const nextElement = shiftKey ? - getPreviousTabbable( body ) : - getNextTabbable( body ); + const nextElement = shiftKey + ? getPreviousTabbable( body ) + : getNextTabbable( body ); if ( nextElement ) { focus( nextElement ); } @@ -79,13 +75,13 @@ const keyDownMap = { const nonSubmittableTypes = [ ...clickableInputTypes, 'hidden' ]; const isClickable = - element.tagName === 'BUTTON' || - ( element.tagName === 'INPUT' && - clickableInputTypes.includes( element.type ) ); + element.tagName === 'BUTTON' || + ( element.tagName === 'INPUT' && + clickableInputTypes.includes( element.type ) ); const isSubmittable = - element.tagName === 'INPUT' && - ! nonSubmittableTypes.includes( element.type ); + element.tagName === 'INPUT' && + ! nonSubmittableTypes.includes( element.type ); if ( isClickable ) { fireEvent.click( element, options ); @@ -101,9 +97,9 @@ const keyUpMap = { const spaceableTypes = [ ...clickableInputTypes, 'checkbox', 'radio' ]; const isSpaceable = - element.tagName === 'BUTTON' || - ( element.tagName === 'INPUT' && - spaceableTypes.includes( element.type ) ); + element.tagName === 'BUTTON' || + ( element.tagName === 'INPUT' && + spaceableTypes.includes( element.type ) ); if ( isSpaceable ) { fireEvent.click( element, options ); @@ -118,11 +114,7 @@ const keyUpMap = { * @param {Element} [element] * @param {Object} [options] */ -export default function press( - key, - element, - options = {} -) { +export default function press( key, element, options = {} ) { const document = getDocument( element ); // eslint-disable-next-line eqeqeq @@ -157,7 +149,11 @@ export default function press( fireEvent.keyDown( element, { key, ...options } ); - if ( ! defaultPrevented.current && key in keyDownMap && ! options.metaKey ) { + if ( + ! defaultPrevented.current && + key in keyDownMap && + ! options.metaKey + ) { keyDownMap[ key ]( element, options ); } diff --git a/packages/test-utils/src/test/click.js b/packages/test-utils/src/test/click.js index 2345c4f387dce3..97c6dbe55e3cfe 100644 --- a/packages/test-utils/src/test/click.js +++ b/packages/test-utils/src/test/click.js @@ -19,7 +19,9 @@ describe( 'click', () => { it( 'should not focus button on click when event.preventDefault() was called on mouse down', () => { const { getByText } = render( - + ); const button = getByText( 'button' ); click( button ); diff --git a/packages/test-utils/src/test/press.js b/packages/test-utils/src/test/press.js index 15ec24b1f44654..b8705c03911be4 100644 --- a/packages/test-utils/src/test/press.js +++ b/packages/test-utils/src/test/press.js @@ -20,7 +20,9 @@ describe( 'press', () => { it( 'should click button when pressing enter', () => { const onClick = jest.fn(); - const { getByText } = render( ); + const { getByText } = render( + + ); const button = getByText( 'button' ); expect( onClick ).not.toHaveBeenCalled(); press.Enter( button ); @@ -30,7 +32,10 @@ describe( 'press', () => { it( 'should not click button when pressing enter if event.preventDefault() was called on key down', () => { const onClick = jest.fn(); const { getByText } = render( - ); @@ -42,7 +47,9 @@ describe( 'press', () => { it( 'should click button when pressing space', () => { const onClick = jest.fn(); - const { getByText } = render( ); + const { getByText } = render( + + ); const button = getByText( 'button' ); expect( onClick ).not.toHaveBeenCalled(); press.Space( button ); @@ -52,7 +59,10 @@ describe( 'press', () => { it( 'should not click button when pressing space if event.preventDefault() was called on key down', () => { const onClick = jest.fn(); const { getByText } = render( - ); @@ -65,7 +75,10 @@ describe( 'press', () => { it( 'should not click button when pressing space if event.preventDefault() was called on key up', () => { const onClick = jest.fn(); const { getByText } = render( - ); @@ -151,7 +164,9 @@ describe( 'press', () => { <> span - + ); const button1 = getByText( 'button1' ); diff --git a/packages/test-utils/src/type.js b/packages/test-utils/src/type.js index 7e0b0170271461..8a09a0ca246da3 100644 --- a/packages/test-utils/src/type.js +++ b/packages/test-utils/src/type.js @@ -46,14 +46,23 @@ export default function type( text, element, options = {} ) { for ( const char of text ) { const key = char in charMap ? charMap[ char ] : char; const value = - key in keyMap ? keyMap[ key ]( element, options ) : `${ element.value }${ char }`; + key in keyMap + ? keyMap[ key ]( element, options ) + : `${ element.value }${ char }`; - const defaultPrevented = subscribeDefaultPrevented( element, 'keydown' ); + const defaultPrevented = subscribeDefaultPrevented( + element, + 'keydown' + ); fireEvent.keyDown( element, { key, ...options } ); if ( ! defaultPrevented.current && ! element.readOnly ) { - fireEvent.input( element, { data: char, target: { value }, ...options } ); + fireEvent.input( element, { + data: char, + target: { value }, + ...options, + } ); } fireEvent.keyUp( element, { key, ...options } ); diff --git a/packages/test-utils/src/utils/get-document.js b/packages/test-utils/src/utils/get-document.js index cd13fbf02ec91d..44de903bfdbdcf 100644 --- a/packages/test-utils/src/utils/get-document.js +++ b/packages/test-utils/src/utils/get-document.js @@ -3,7 +3,5 @@ * @return {Document} Document */ export default function getDocument( element ) { - return element ? - element.ownerDocument || window.document : - window.document; + return element ? element.ownerDocument || window.document : window.document; } diff --git a/packages/test-utils/src/utils/get-next-tabbable.js b/packages/test-utils/src/utils/get-next-tabbable.js index 6fa520c8df5a9d..f77239558a3176 100644 --- a/packages/test-utils/src/utils/get-next-tabbable.js +++ b/packages/test-utils/src/utils/get-next-tabbable.js @@ -16,9 +16,11 @@ import './mock-client-rects'; */ export default function getNextTabbable( element ) { const tabbableElements = focus.tabbable.find( getDocument( element ) ); - const currentIndex = tabbableElements.indexOf( getActiveElement( element ) ); + const currentIndex = tabbableElements.indexOf( + getActiveElement( element ) + ); const nextIndex = currentIndex + 1; - return nextIndex >= tabbableElements.length ? - tabbableElements[ 0 ] : - tabbableElements[ nextIndex ]; + return nextIndex >= tabbableElements.length + ? tabbableElements[ 0 ] + : tabbableElements[ nextIndex ]; } diff --git a/packages/test-utils/src/utils/get-previous-tabbable.js b/packages/test-utils/src/utils/get-previous-tabbable.js index 2bd89774dbd33c..d4d2ee416c0fda 100644 --- a/packages/test-utils/src/utils/get-previous-tabbable.js +++ b/packages/test-utils/src/utils/get-previous-tabbable.js @@ -15,9 +15,11 @@ import './mock-client-rects'; */ export default function getPreviousTabbable( element ) { const tabbableElements = focus.tabbable.find( getDocument( element ) ); - const currentIndex = tabbableElements.indexOf( getActiveElement( element ) ); + const currentIndex = tabbableElements.indexOf( + getActiveElement( element ) + ); const previousIndex = currentIndex - 1; - return previousIndex < 0 ? - tabbableElements[ tabbableElements.length - 1 ] : - tabbableElements[ previousIndex ]; + return previousIndex < 0 + ? tabbableElements[ tabbableElements.length - 1 ] + : tabbableElements[ previousIndex ]; }