diff --git a/.all-contributorsrc b/.all-contributorsrc index 492cac3d..ef6eb129 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -339,6 +339,15 @@ "contributions": [ "code" ] + }, + { + "login": "suddenlyGiovanni", + "name": "Giovanni Ravalico", + "avatar_url": "https://avatars2.githubusercontent.com/u/15946771?v=4", + "profile": "https://suddenlyGiovanni.dev", + "contributions": [ + "ideas" + ] } ], "contributorsPerLine": 7, diff --git a/README.md b/README.md index d81452c1..ee8fd431 100644 --- a/README.md +++ b/README.md @@ -219,6 +219,7 @@ Thanks goes to these wonderful people
Jakub Majorek

💻 +
Giovanni Ravalico

🤔 diff --git a/package-lock.json b/package-lock.json index c865e646..00d80ec2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,9 +5,9 @@ "requires": true, "dependencies": { "@babel/code-frame": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", - "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", + "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", "requires": { "@babel/highlight": "^7.10.4" } @@ -42,11 +42,11 @@ } }, "@babel/generator": { - "version": "7.12.10", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.12.10.tgz", - "integrity": "sha512-6mCdfhWgmqLdtTkhXjnIz0LcdVCd26wS2JXRtj2XY0u5klDsXBREA/pG5NVOuVnF2LUrBGNFtQkIqqTbblg0ww==", + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.12.11.tgz", + "integrity": "sha512-Ggg6WPOJtSi8yYQvLVjG8F/TlpWDlKx0OpS4Kt+xMQPs5OaGYWy+v1A+1TvxI6sAMGZpKWWoAQ1DaeQbImlItA==", "requires": { - "@babel/types": "^7.12.10", + "@babel/types": "^7.12.11", "jsesc": "^2.5.1", "source-map": "^0.5.0" } @@ -70,27 +70,6 @@ "@babel/types": "^7.10.4" } }, - "@babel/helper-builder-react-jsx": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-react-jsx/-/helper-builder-react-jsx-7.10.4.tgz", - "integrity": "sha512-5nPcIZ7+KKDxT1427oBivl9V9YTal7qk0diccnh7RrcgrT/pGFOjgGw1dgryyx1GvHEpXVfoDF6Ak3rTiWh8Rg==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.10.4", - "@babel/types": "^7.10.4" - } - }, - "@babel/helper-builder-react-jsx-experimental": { - "version": "7.12.10", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-react-jsx-experimental/-/helper-builder-react-jsx-experimental-7.12.10.tgz", - "integrity": "sha512-3Kcr2LGpL7CTRDTTYm1bzeor9qZbxbvU2AxsLA6mUG9gYarSfIKMK0UlU+azLWI+s0+BH768bwyaziWB2NOJlQ==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.12.10", - "@babel/helper-module-imports": "^7.12.5", - "@babel/types": "^7.12.10" - } - }, "@babel/helper-compilation-targets": { "version": "7.12.5", "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.12.5.tgz", @@ -147,13 +126,13 @@ } }, "@babel/helper-function-name": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz", - "integrity": "sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ==", + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.12.11.tgz", + "integrity": "sha512-AtQKjtYNolKNi6nNNVLQ27CP6D9oFR6bq/HPYSizlzbp7uC1M59XJe8L+0uXjbIaZaUJF99ruHqVGiKXU/7ybA==", "requires": { - "@babel/helper-get-function-arity": "^7.10.4", - "@babel/template": "^7.10.4", - "@babel/types": "^7.10.4" + "@babel/helper-get-function-arity": "^7.12.10", + "@babel/template": "^7.12.7", + "@babel/types": "^7.12.11" } }, "@babel/helper-get-function-arity": { @@ -230,14 +209,14 @@ } }, "@babel/helper-replace-supers": { - "version": "7.12.5", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.12.5.tgz", - "integrity": "sha512-5YILoed0ZyIpF4gKcpZitEnXEJ9UoDRki1Ey6xz46rxOzfNMAhVIJMoune1hmPVxh40LRv1+oafz7UsWX+vyWA==", + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.12.11.tgz", + "integrity": "sha512-q+w1cqmhL7R0FNzth/PLLp2N+scXEK/L2AHbXUyydxp828F4FEa5WcVoqui9vFRiHDQErj9Zof8azP32uGVTRA==", "requires": { - "@babel/helper-member-expression-to-functions": "^7.12.1", - "@babel/helper-optimise-call-expression": "^7.10.4", - "@babel/traverse": "^7.12.5", - "@babel/types": "^7.12.5" + "@babel/helper-member-expression-to-functions": "^7.12.7", + "@babel/helper-optimise-call-expression": "^7.12.10", + "@babel/traverse": "^7.12.10", + "@babel/types": "^7.12.11" } }, "@babel/helper-simple-access": { @@ -258,22 +237,22 @@ } }, "@babel/helper-split-export-declaration": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz", - "integrity": "sha512-74Vejvp6mHkGE+m+k5vHY93FX2cAtrw1zXrZXRlG4l410Nm9PxfEiVTn1PjDPV5SnmieiueY4AFg2xqhNFuuZg==", + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.11.tgz", + "integrity": "sha512-LsIVN8j48gHgwzfocYUSkO/hjYAOJqlpJEc7tGXcIm4cubjVUf8LGW6eWRyxEu7gA25q02p0rQUWoCI33HNS5g==", "requires": { - "@babel/types": "^7.11.0" + "@babel/types": "^7.12.11" } }, "@babel/helper-validator-identifier": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", - "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==" + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz", + "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==" }, "@babel/helper-validator-option": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.12.1.tgz", - "integrity": "sha512-YpJabsXlJVWP0USHjnC/AQDTLlZERbON577YUVO/wLpqyj6HAtVYnWaQaN0iUN+1/tWn3c+uKKXjRut5115Y2A==", + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.12.11.tgz", + "integrity": "sha512-TBFCyj939mFSdeX7U7DDj32WtzYY7fDcalgq8v3fBZMNOJQNn7nOYzMaUCiPxPYfCup69mtIpqlKgMZLvQ8Xhw==", "dev": true }, "@babel/helper-wrap-function": { @@ -321,14 +300,14 @@ } }, "@babel/parser": { - "version": "7.12.10", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.12.10.tgz", - "integrity": "sha512-PJdRPwyoOqFAWfLytxrWwGrAxghCgh/yTNCYciOz8QgjflA7aZhECPZAa2VUedKg2+QMWkI0L9lynh2SNmNEgA==" + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.12.11.tgz", + "integrity": "sha512-N3UxG+uuF4CMYoNj8AhnbAcJF0PiuJ9KHuy1lQmkYsxTer/MAH9UBNHsBoAX/4s6NvlDD047No8mYVGGzLL4hg==" }, "@babel/plugin-proposal-async-generator-functions": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.12.1.tgz", - "integrity": "sha512-d+/o30tJxFxrA1lhzJqiUcEJdI6jKlNregCv5bASeGf2Q4MXmnwH7viDo7nhx1/ohf09oaH8j1GVYG/e3Yqk6A==", + "version": "7.12.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.12.12.tgz", + "integrity": "sha512-nrz9y0a4xmUrRq51bYkWJIO5SBZyG2ys2qinHsN0zHDHVsUaModrkpyWWWXfGqYQmOL3x9sQIcTNN/pBGpo09A==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.10.4", @@ -658,9 +637,9 @@ } }, "@babel/plugin-transform-block-scoping": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.12.1.tgz", - "integrity": "sha512-zJyAC9sZdE60r1nVQHblcfCj29Dh2Y0DOvlMkcqSo0ckqjiCwNiUezUKw+RjOCwGfpLRwnAeQ2XlLpsnGkvv9w==", + "version": "7.12.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.12.12.tgz", + "integrity": "sha512-VOEPQ/ExOVqbukuP7BYJtI5ZxxsmegTwzZ04j1aF0dkSypGo9XpDHuOrABsJu+ie+penpSJheDJ11x1BEZNiyQ==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.10.4" @@ -886,26 +865,25 @@ } }, "@babel/plugin-transform-react-jsx": { - "version": "7.12.10", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.12.10.tgz", - "integrity": "sha512-MM7/BC8QdHXM7Qc1wdnuk73R4gbuOpfrSUgfV/nODGc86sPY1tgmY2M9E9uAnf2e4DOIp8aKGWqgZfQxnTNGuw==", + "version": "7.12.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.12.12.tgz", + "integrity": "sha512-JDWGuzGNWscYcq8oJVCtSE61a5+XAOos+V0HrxnDieUus4UMnBEosDnY1VJqU5iZ4pA04QY7l0+JvHL1hZEfsw==", "dev": true, "requires": { - "@babel/helper-builder-react-jsx": "^7.10.4", - "@babel/helper-builder-react-jsx-experimental": "^7.12.10", + "@babel/helper-annotate-as-pure": "^7.12.10", + "@babel/helper-module-imports": "^7.12.5", "@babel/helper-plugin-utils": "^7.10.4", - "@babel/plugin-syntax-jsx": "^7.12.1" + "@babel/plugin-syntax-jsx": "^7.12.1", + "@babel/types": "^7.12.12" } }, "@babel/plugin-transform-react-jsx-development": { - "version": "7.12.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.12.7.tgz", - "integrity": "sha512-Rs3ETtMtR3VLXFeYRChle5SsP/P9Jp/6dsewBQfokDSzKJThlsuFcnzLTDRALiUmTC48ej19YD9uN1mupEeEDg==", + "version": "7.12.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.12.12.tgz", + "integrity": "sha512-i1AxnKxHeMxUaWVXQOSIco4tvVvvCxMSfeBMnMM06mpaJt3g+MpxYQQrDfojUQldP1xxraPSJYSMEljoWM/dCg==", "dev": true, "requires": { - "@babel/helper-builder-react-jsx-experimental": "^7.12.4", - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/plugin-syntax-jsx": "^7.12.1" + "@babel/plugin-transform-react-jsx": "^7.12.12" } }, "@babel/plugin-transform-react-jsx-self": { @@ -1043,16 +1021,16 @@ } }, "@babel/preset-env": { - "version": "7.12.10", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.12.10.tgz", - "integrity": "sha512-Gz9hnBT/tGeTE2DBNDkD7BiWRELZt+8lSysHuDwmYXUIvtwZl0zI+D6mZgXZX0u8YBlLS4tmai9ONNY9tjRgRA==", + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.12.11.tgz", + "integrity": "sha512-j8Tb+KKIXKYlDBQyIOy4BLxzv1NUOwlHfZ74rvW+Z0Gp4/cI2IMDPBWAgWceGcE7aep9oL/0K9mlzlMGxA8yNw==", "dev": true, "requires": { "@babel/compat-data": "^7.12.7", "@babel/helper-compilation-targets": "^7.12.5", "@babel/helper-module-imports": "^7.12.5", "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-validator-option": "^7.12.1", + "@babel/helper-validator-option": "^7.12.11", "@babel/plugin-proposal-async-generator-functions": "^7.12.1", "@babel/plugin-proposal-class-properties": "^7.12.1", "@babel/plugin-proposal-dynamic-import": "^7.12.1", @@ -1081,7 +1059,7 @@ "@babel/plugin-transform-arrow-functions": "^7.12.1", "@babel/plugin-transform-async-to-generator": "^7.12.1", "@babel/plugin-transform-block-scoped-functions": "^7.12.1", - "@babel/plugin-transform-block-scoping": "^7.12.1", + "@babel/plugin-transform-block-scoping": "^7.12.11", "@babel/plugin-transform-classes": "^7.12.1", "@babel/plugin-transform-computed-properties": "^7.12.1", "@babel/plugin-transform-destructuring": "^7.12.1", @@ -1111,7 +1089,7 @@ "@babel/plugin-transform-unicode-escapes": "^7.12.1", "@babel/plugin-transform-unicode-regex": "^7.12.1", "@babel/preset-modules": "^0.1.3", - "@babel/types": "^7.12.10", + "@babel/types": "^7.12.11", "core-js-compat": "^3.8.0", "semver": "^5.5.0" } @@ -1180,27 +1158,27 @@ } }, "@babel/traverse": { - "version": "7.12.10", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.12.10.tgz", - "integrity": "sha512-6aEtf0IeRgbYWzta29lePeYSk+YAFIC3kyqESeft8o5CkFlYIMX+EQDDWEiAQ9LHOA3d0oHdgrSsID/CKqXJlg==", - "requires": { - "@babel/code-frame": "^7.10.4", - "@babel/generator": "^7.12.10", - "@babel/helper-function-name": "^7.10.4", - "@babel/helper-split-export-declaration": "^7.11.0", - "@babel/parser": "^7.12.10", - "@babel/types": "^7.12.10", + "version": "7.12.12", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.12.12.tgz", + "integrity": "sha512-s88i0X0lPy45RrLM8b9mz8RPH5FqO9G9p7ti59cToE44xFm1Q+Pjh5Gq4SXBbtb88X7Uy7pexeqRIQDDMNkL0w==", + "requires": { + "@babel/code-frame": "^7.12.11", + "@babel/generator": "^7.12.11", + "@babel/helper-function-name": "^7.12.11", + "@babel/helper-split-export-declaration": "^7.12.11", + "@babel/parser": "^7.12.11", + "@babel/types": "^7.12.12", "debug": "^4.1.0", "globals": "^11.1.0", "lodash": "^4.17.19" } }, "@babel/types": { - "version": "7.12.10", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.12.10.tgz", - "integrity": "sha512-sf6wboJV5mGyip2hIpDSKsr80RszPinEFjsHTalMxZAZkoQ2/2yQzxlcFN52SJqsyPfLtPmenL4g2KB3KJXPDw==", + "version": "7.12.12", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.12.12.tgz", + "integrity": "sha512-lnIX7piTxOH22xE7fDXDbSHg9MM1/6ORnafpJmov5rs0kX5g4BZxeXNJLXsMRiO0U5Rb8/FvMS6xlTnTHvxonQ==", "requires": { - "@babel/helper-validator-identifier": "^7.10.4", + "@babel/helper-validator-identifier": "^7.12.11", "lodash": "^4.17.19", "to-fast-properties": "^2.0.0" } @@ -1315,9 +1293,9 @@ "integrity": "sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA==" }, "@eslint/eslintrc": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.2.2.tgz", - "integrity": "sha512-EfB5OHNYp1F4px/LI/FEnGylop7nOqkQ1LRzCM0KccA2U8tvV8w01KBv37LbO7nW4H+YhKyo2LcJhRwjjV17QQ==", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.3.0.tgz", + "integrity": "sha512-1JTKgrOKAHVivSvOYw+sJOunkBjUOvjqWk1DPja7ZFhIS2mX/4EgTT8M7eTK9jrKhL/FvXXEbQwIs3pg1xp3dg==", "dev": true, "requires": { "ajv": "^6.12.4", @@ -1327,7 +1305,7 @@ "ignore": "^4.0.6", "import-fresh": "^3.2.1", "js-yaml": "^3.13.1", - "lodash": "^4.17.19", + "lodash": "^4.17.20", "minimatch": "^3.0.4", "strip-json-comments": "^3.1.1" }, @@ -1745,18 +1723,20 @@ } }, "@kentcdodds/react-workshop-app": { - "version": "2.20.0", - "resolved": "https://registry.npmjs.org/@kentcdodds/react-workshop-app/-/react-workshop-app-2.20.0.tgz", - "integrity": "sha512-rTVbNGY3L7WMgW/pNKh68yCQeuuY1htF+iOb37o4uxdmuqi2SiwggE3jxUUXZZt4FdWk3eZsm5Wdr4U+x88v+w==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@kentcdodds/react-workshop-app/-/react-workshop-app-3.0.0.tgz", + "integrity": "sha512-5uUQoc8QBxJ6Q3r9U0W5Y4cpcBHpMicOuVXVbPL60CM0O2ul72YMISvvprte0FAPjAN3rBLqsGzHn4LXFxMbdQ==", "requires": { "@babel/runtime": "^7.12.5", "@emotion/core": "^10.0.35", - "@reach/tabs": "^0.11.2", - "@reach/tooltip": "^0.11.2", - "@testing-library/jest-dom": "^5.11.6", - "@testing-library/react": "^11.1.2", + "@reach/tabs": "^0.12.1", + "@reach/tooltip": "^0.12.1", + "@testing-library/jest-dom": "^5.11.9", + "@testing-library/react": "^11.2.3", + "@types/react": "^17.0.0", + "@types/react-dom": "^17.0.0", "chalk": "^4.1.0", - "codegen.macro": "^4.0.0", + "codegen.macro": "^4.1.0", "cross-env": "^7.0.3", "cross-spawn": "^7.0.3", "emotion-theming": "^10.0.27", @@ -1767,11 +1747,12 @@ "inquirer": "^7.3.3", "is-ci": "^2.0.0", "mdx-loader": "^3.0.2", - "msw": "^0.21.3", + "mq-polyfill": "^1.1.8", + "msw": "^0.25.0", "node-match-path": "^0.6.0", "normalize.css": "^8.0.1", "raw-loader": "^4.0.2", - "react-error-boundary": "^3.0.2", + "react-error-boundary": "^3.1.0", "react-icons": "^3.11.0", "react-router-dom": "^5.2.0", "replace-in-file": "^6.1.0" @@ -1826,38 +1807,39 @@ "integrity": "sha512-H1rQc1ZOHANWBvPcW+JpGwr+juXSxM8Q8YCkm3GhZd8REu1fHR3z99CErO1p9pkcfcxZnMdIZdIsXkOHY0NilA==" }, "@nodelib/fs.scandir": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz", - "integrity": "sha512-eGmwYQn3gxo4r7jdQnkrrN6bY478C3P+a/y72IJukF8LjB6ZHeB3c+Ehacj3sYeSmUXGlnA67/PmbM9CVwL7Dw==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz", + "integrity": "sha512-33g3pMJk3bg5nXbL/+CY6I2eJDzZAni49PfJnL5fghPTggPvBd/pFNSgJsdAgWptuFu7qq/ERvOYFlhvsLTCKA==", "dev": true, "requires": { - "@nodelib/fs.stat": "2.0.3", + "@nodelib/fs.stat": "2.0.4", "run-parallel": "^1.1.9" } }, "@nodelib/fs.stat": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz", - "integrity": "sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.4.tgz", + "integrity": "sha512-IYlHJA0clt2+Vg7bccq+TzRdJvv19c2INqBSsoOLp1je7xjtr7J26+WXR72MCdvU9q1qTzIWDfhMf+DRvQJK4Q==", "dev": true }, "@nodelib/fs.walk": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.4.tgz", - "integrity": "sha512-1V9XOY4rDW0rehzbrcqAmHnz8e7SKvX27gh8Gt2WgB0+pdzdiLV83p72kZPU+jvMbS1qU5mauP2iOvO8rhmurQ==", + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.6.tgz", + "integrity": "sha512-8Broas6vTtW4GIXTAHDoE32hnN2M5ykgCpWGbuXHQ15vEMqr23pB76e/GZcYsZCHALv50ktd24qhEyKr6wBtow==", "dev": true, "requires": { - "@nodelib/fs.scandir": "2.1.3", + "@nodelib/fs.scandir": "2.1.4", "fastq": "^1.6.0" } }, "@npmcli/move-file": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.0.1.tgz", - "integrity": "sha512-Uv6h1sT+0DrblvIrolFtbvM1FgWm+/sy4B3pvLp67Zys+thcukzS5ekn7HsZFGpWP4Q3fYJCljbWQE/XivMRLw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.1.tgz", + "integrity": "sha512-LtWTicuF2wp7PNTuyCwABx7nNG+DnzSE8gN0iWxkC6mpgm/iOPu0ZMTkXuCxmJxtWFsDxUaixM9COSNJEMUfuQ==", "dev": true, "requires": { - "mkdirp": "^1.0.4" + "mkdirp": "^1.0.4", + "rimraf": "^3.0.2" }, "dependencies": { "mkdirp": { @@ -1907,20 +1889,20 @@ } }, "@reach/auto-id": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/@reach/auto-id/-/auto-id-0.11.2.tgz", - "integrity": "sha512-YZ21b0Kb88wJ0t7QjSznWOYskARQMnmXY9Y2XZ5RyYcZ2krT4s3+ghghpfaPs6BKcrZDonZCrU65OFDJPa1jAw==", + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/@reach/auto-id/-/auto-id-0.12.1.tgz", + "integrity": "sha512-s8cdY6dF0hEBB/28BbidB2EX6JfEBVIWLP6S2Jg0Xqq2H3xijL+zrsjL40jACwXRkignjuP+CvYsuFuO0+/GRA==", "requires": { - "@reach/utils": "0.11.2", + "@reach/utils": "0.12.1", "tslib": "^2.0.0" } }, "@reach/descendants": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/@reach/descendants/-/descendants-0.11.2.tgz", - "integrity": "sha512-63Wdx32/RyjGRJc4UZKK7F1sIrb6jeGkDwvQH0hv0lRAhEjsiSQ1t2JTYDml3testFP48J0B2xS7JzNeY0zoQw==", + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/@reach/descendants/-/descendants-0.12.1.tgz", + "integrity": "sha512-lvpyQ2EixbN7GvT8LyjZfHWQZz/cKwa/p7E26YQOT8nvxm4ABKRGxaSTwUA7D+MLOX6NtHo2qyZ9wippaXB5sQ==", "requires": { - "@reach/utils": "0.11.2", + "@reach/utils": "0.12.1", "tslib": "^2.0.0" } }, @@ -1930,55 +1912,55 @@ "integrity": "sha512-Ba7HmkFgfQxZqqaeIWWkNK0rEhpxVQHIoVyW1YDSkGsGIXzcaW4deC8B0pZrNSSyLTdIk7y+5olKt5+g0GmFIQ==" }, "@reach/portal": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/@reach/portal/-/portal-0.11.2.tgz", - "integrity": "sha512-/53A/rY5oX2Y7D5TpvsP+V5cSd+4MPY6f21mAmVn4DCVwpkCFOlJ059ZL7ixS85M0Jz48YQnnvBJUqwkxqUG/g==", + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/@reach/portal/-/portal-0.12.1.tgz", + "integrity": "sha512-Lhmtd2Qw1DzNZ2m0GHNzCu+2TYUf6kBREPSQJf44AP6kThRs02p1clntbJcmW/rrpYFYgFNbgf5Lso7NyW9ZXg==", "requires": { - "@reach/utils": "0.11.2", + "@reach/utils": "0.12.1", "tslib": "^2.0.0" } }, "@reach/rect": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/@reach/rect/-/rect-0.11.2.tgz", - "integrity": "sha512-eoUWayAADi1ITtrc+8jN9NsBTUkfpORkOs5bQb4RnR6UA/3zlxo5VPuxWgWAG0BCohZlqsxg7NpvckNAyaiAAQ==", + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/@reach/rect/-/rect-0.12.1.tgz", + "integrity": "sha512-c+EjKc+9ud832MpmYKsxu+2R0/XHyXk47ik/N+DIHsugIgKJn2B34r4r9benqsaHncUO1IQk5rTv40D7x+yR0g==", "requires": { "@reach/observe-rect": "1.2.0", - "@reach/utils": "0.11.2", + "@reach/utils": "0.12.1", "prop-types": "^15.7.2", "tslib": "^2.0.0" } }, "@reach/tabs": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/@reach/tabs/-/tabs-0.11.2.tgz", - "integrity": "sha512-iVDXfmdpJmBavyyiJbm9sUddlelra+x5MaF5Y4QwV7Q+w3t9RLqTePQAOX+2MX0BJgvasX/YItMBO2JumWxGPQ==", + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/@reach/tabs/-/tabs-0.12.1.tgz", + "integrity": "sha512-k9piLoajPhIpM6BlHDSTMaBK9dGYDB9Ri+Ic+o+BqLzO1Lm0mC3ghXeYJcwyTzGA47dOYklI65TW/RxYqtT38g==", "requires": { - "@reach/auto-id": "0.11.2", - "@reach/descendants": "0.11.2", - "@reach/utils": "0.11.2", + "@reach/auto-id": "0.12.1", + "@reach/descendants": "0.12.1", + "@reach/utils": "0.12.1", "prop-types": "^15.7.2", "tslib": "^2.0.0" } }, "@reach/tooltip": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/@reach/tooltip/-/tooltip-0.11.2.tgz", - "integrity": "sha512-aTi3aLkRZMHrNiHt84vnyTWNj84rPdGYkxAfaFpxgkaKpos3XmaOPiR+n/3YzQUoJXISuw8mZezcrDtsSpr3aA==", - "requires": { - "@reach/auto-id": "0.11.2", - "@reach/portal": "0.11.2", - "@reach/rect": "0.11.2", - "@reach/utils": "0.11.2", - "@reach/visually-hidden": "0.11.1", + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/@reach/tooltip/-/tooltip-0.12.1.tgz", + "integrity": "sha512-9qk7WcgAjN/2tFEHN3Oi1m7BrSsL+MBFTFaxTaNqQacXYIKO3jNVFzLxkDVgyFP82h6EMJ5CkAJjOMi4aNyicQ==", + "requires": { + "@reach/auto-id": "0.12.1", + "@reach/portal": "0.12.1", + "@reach/rect": "0.12.1", + "@reach/utils": "0.12.1", + "@reach/visually-hidden": "0.12.0", "prop-types": "^15.7.2", "tslib": "^2.0.0" } }, "@reach/utils": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/@reach/utils/-/utils-0.11.2.tgz", - "integrity": "sha512-fBTolYj+rKTROXmf0zHO0rCWSvw7J0ALmYj5QxW4DmITMOH5uyRuWDWOfqohIGFbOtF/sum50WTB3tvx76d+Aw==", + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/@reach/utils/-/utils-0.12.1.tgz", + "integrity": "sha512-5uH4OgO+GupAzZuf3b6Wv/9uC6NdMBlxS6FSKD6YqSxP4QJ0vjD34RVon6N/RMRORacCLyD/aaZIA7283YgeOg==", "requires": { "@types/warning": "^3.0.0", "tslib": "^2.0.0", @@ -1986,9 +1968,9 @@ } }, "@reach/visually-hidden": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/@reach/visually-hidden/-/visually-hidden-0.11.1.tgz", - "integrity": "sha512-TZZNSttor2jG6ZPGI35s/8G0FNSz49QrJIhAZbnUqHyPf3+jtNqAC0dOWW8Nuk+mApDDDVYd2KZ9P2vnzllNnQ==", + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@reach/visually-hidden/-/visually-hidden-0.12.0.tgz", + "integrity": "sha512-VXtyR36WOS0FHmFQ/BzfswwPyBXICl/YjmTPtx/I0Vy5C6djjhD9kDsE+h/FpHzP3ugTdqdqiPBVElIZTSwOSA==", "requires": { "tslib": "^2.0.0" } @@ -2036,9 +2018,9 @@ } }, "@sinonjs/commons": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.1.tgz", - "integrity": "sha512-892K+kWUUi3cl+LlqEWIDrhvLgdL79tECi8JZUyq6IviKy/DNhuzCRlbHUjxK89f4ypPMMaFnFuR9Ie6DoIMsw==", + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.2.tgz", + "integrity": "sha512-sruwd86RJHdsVf/AtBoijDmUqJp3B6hF/DGC23C+JaegnDHaZyewCjoVGTdg3J0uz3Zs7NnIT05OBOmML72lQw==", "dev": true, "requires": { "type-detect": "4.0.8" @@ -2236,9 +2218,9 @@ } }, "@testing-library/dom": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-7.29.0.tgz", - "integrity": "sha512-0hhuJSmw/zLc6ewR9cVm84TehuTd7tbqBX9pRNSp8znJ9gTmSgesdbiGZtt8R6dL+2rgaPFp9Yjr7IU1HWm49w==", + "version": "7.29.4", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-7.29.4.tgz", + "integrity": "sha512-CtrJRiSYEfbtNGtEsd78mk1n1v2TUbeABlNIcOCJdDfkN5/JTOwQEbbQpoSRxGqzcWPgStMvJ4mNolSuBRv1NA==", "requires": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", @@ -2251,9 +2233,9 @@ } }, "@testing-library/jest-dom": { - "version": "5.11.6", - "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.11.6.tgz", - "integrity": "sha512-cVZyUNRWwUKI0++yepYpYX7uhrP398I+tGz4zOlLVlUYnZS+Svuxv4fwLeCIy7TnBYKXUaOlQr3vopxL8ZfEnA==", + "version": "5.11.9", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.11.9.tgz", + "integrity": "sha512-Mn2gnA9d1wStlAIT2NU8J15LNob0YFBVjs2aEQ3j8rsfRQo+lAs7/ui1i2TGaJjapLmuNPLTsrm+nPjmZDwpcQ==", "requires": { "@babel/runtime": "^7.9.2", "@types/testing-library__jest-dom": "^5.9.1", @@ -2311,20 +2293,20 @@ } }, "@testing-library/react": { - "version": "11.2.2", - "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-11.2.2.tgz", - "integrity": "sha512-jaxm0hwUjv+hzC+UFEywic7buDC9JQ1q3cDsrWVSDAPmLotfA6E6kUHlYm/zOeGCac6g48DR36tFHxl7Zb+N5A==", + "version": "11.2.3", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-11.2.3.tgz", + "integrity": "sha512-BirBUGPkTW28ULuCwIbYo0y2+0aavHczBT6N9r3LrsswEW3pg25l1wgoE7I8QBIy1upXWkwKpYdWY7NYYP0Bxw==", "requires": { "@babel/runtime": "^7.12.5", "@testing-library/dom": "^7.28.1" } }, "@testing-library/user-event": { - "version": "12.5.0", - "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-12.5.0.tgz", - "integrity": "sha512-9uXr4+OwjHVUxzdfYZ2yCnF3xlEzr8cZOdqjGnqD8Qb1NoCJrm7UXxG3RUpL2QqcqZ1eqVuxkFJTCky5Yit+XQ==", + "version": "12.6.2", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-12.6.2.tgz", + "integrity": "sha512-4OsiTSo2vbQm+eOnm1un8b9i2Re4mn+D7d7ET6HXtzYKY7vPe3O01iYKRmSW9vS5mNrQcCLwvRhVq1gWs5YGKA==", "requires": { - "@babel/runtime": "^7.10.2" + "@babel/runtime": "^7.12.5" } }, "@types/anymatch": { @@ -2334,9 +2316,9 @@ "dev": true }, "@types/aria-query": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-4.2.0.tgz", - "integrity": "sha512-iIgQNzCm0v7QMhhe4Jjn9uRh+I6GoPmt03CbEtwx3ao8/EfoQcmgtqH4vQ5Db/lxiIGaWDv6nwvunuh0RyX0+A==" + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-4.2.1.tgz", + "integrity": "sha512-S6oPal772qJZHoRZLFc/XoZW2gFvwXusYUmXPXkgxJLuEk2vOt7jc4Yo6z/vtI0EBkbPBVrJJ0B+prLIKiWqHg==" }, "@types/babel__core": { "version": "7.1.12", @@ -2395,9 +2377,9 @@ } }, "@types/estree": { - "version": "0.0.45", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.45.tgz", - "integrity": "sha512-jnqIUKDUqJbDIUxm0Uj7bnlMnRm1T/eZ9N+AVMqhPgzrba2GhGG5o/jCTwmdPK709nEZsGoMzXEDUjcXHa3W0g==", + "version": "0.0.46", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.46.tgz", + "integrity": "sha512-laIjwTQaD+5DukBZaygQ79K1Z0jb1bPEMRrkXSLjtCcZm+abyp5YbrqpSLzD42FwWW6gK/aS4NYpJ804nG2brg==", "dev": true }, "@types/glob": { @@ -2455,18 +2437,18 @@ } }, "@types/jest": { - "version": "26.0.19", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-26.0.19.tgz", - "integrity": "sha512-jqHoirTG61fee6v6rwbnEuKhpSKih0tuhqeFbCmMmErhtu3BYlOZaXWjffgOstMM4S/3iQD31lI5bGLTrs97yQ==", + "version": "26.0.20", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-26.0.20.tgz", + "integrity": "sha512-9zi2Y+5USJRxd0FsahERhBwlcvFh6D2GLQnY2FH2BzK8J9s9omvNHIbvABwIluXa0fD8XVKMLTO0aOEuUfACAA==", "requires": { "jest-diff": "^26.0.0", "pretty-format": "^26.0.0" } }, "@types/json-schema": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.6.tgz", - "integrity": "sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw==" + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.7.tgz", + "integrity": "sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==" }, "@types/json5": { "version": "0.0.29", @@ -2489,9 +2471,9 @@ "dev": true }, "@types/node": { - "version": "14.14.12", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.12.tgz", - "integrity": "sha512-ASH8OPHMNlkdjrEdmoILmzFfsJICvhBsFfAum4aKZ/9U4B6M6tTmTPh+f3ttWdD74CEGV5XvXWkbyfSdXaTd7g==" + "version": "14.14.22", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.22.tgz", + "integrity": "sha512-g+f/qj/cNcqKkc3tFqlXOYjrmZA+jNBiDzbP3kH+B+otKFqAdPgVTGP1IeKRdMml/aE69as5S4FqtxAbl+LaMw==" }, "@types/normalize-package-data": { "version": "2.4.0", @@ -2510,16 +2492,15 @@ "integrity": "sha512-kUNnecmtkunAoQ3CnjmMkzNU/gtxG8guhi+Fk2U/kOpIKjIMKnXGp4IJCgQJrXSgMsWYimYG4TGjz/UzbGEBTw==" }, "@types/prettier": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.1.5.tgz", - "integrity": "sha512-UEyp8LwZ4Dg30kVU2Q3amHHyTn1jEdhCIE59ANed76GaT1Vp76DD3ZWSAxgCrw6wJ0TqeoBpqmfUHiUDPs//HQ==", + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.1.6.tgz", + "integrity": "sha512-6gOkRe7OIioWAXfnO/2lFiv+SJichKVSys1mSsgyrYHSEjk8Ctv4tSR/Odvnu+HWlH2C8j53dahU03XmQdd5fA==", "dev": true }, "@types/prop-types": { "version": "15.7.3", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz", - "integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==", - "dev": true + "integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==" }, "@types/q": { "version": "1.5.4", @@ -2531,17 +2512,15 @@ "version": "17.0.0", "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.0.tgz", "integrity": "sha512-aj/L7RIMsRlWML3YB6KZiXB3fV2t41+5RBGYF8z+tAKU43Px8C3cYUZsDvf1/+Bm4FK21QWBrDutu8ZJ/70qOw==", - "dev": true, "requires": { "@types/prop-types": "*", "csstype": "^3.0.2" }, "dependencies": { "csstype": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.5.tgz", - "integrity": "sha512-uVDi8LpBUKQj6sdxNaTetL6FpeCqTjOvAQuQUa/qAqq8oOd4ivkbhgnqayl0dnPal8Tb/yB1tF+gOvCBiicaiQ==", - "dev": true + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.6.tgz", + "integrity": "sha512-+ZAmfyWMT7TiIlzdqJgjMb7S4f1beorDbWbsocyK4RaiqA5RTX3K14bnBWmmA9QEM0gRdsjyyrEmcyga8Zsxmw==" } } }, @@ -2549,7 +2528,6 @@ "version": "17.0.0", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.0.tgz", "integrity": "sha512-lUqY7OlkF/RbNtD5nIq7ot8NquXrdFrjSOR6+w9a9RFQevGi1oZO1dcJbXMeONAPKtZ2UrZOEJ5UOCVsxbLk/g==", - "dev": true, "requires": { "@types/react": "*" } @@ -2617,9 +2595,9 @@ "integrity": "sha1-DSUBJorY+ZYrdA04fEZU9fjiPlI=" }, "@types/webpack": { - "version": "4.41.25", - "resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.25.tgz", - "integrity": "sha512-cr6kZ+4m9lp86ytQc1jPOJXgINQyz3kLLunZ57jznW+WIAL0JqZbGubQk4GlD42MuQL5JGOABrxdpqqWeovlVQ==", + "version": "4.41.26", + "resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.26.tgz", + "integrity": "sha512-7ZyTfxjCRwexh+EJFwRUM+CDB2XvgHl4vfuqf1ZKrgGvcS5BrNvPQqJh3tsZ0P6h6Aa1qClVHaJZszLPzpqHeA==", "dev": true, "requires": { "@types/anymatch": "*", @@ -2658,28 +2636,29 @@ } }, "@types/yargs": { - "version": "15.0.11", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.11.tgz", - "integrity": "sha512-jfcNBxHFYJ4nPIacsi3woz1+kvUO6s1CyeEhtnDHBjHUMNj5UlW2GynmnSgiJJEdNg9yW5C8lfoNRZrHGv5EqA==", + "version": "15.0.13", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.13.tgz", + "integrity": "sha512-kQ5JNTrbDv3Rp5X2n/iUu37IJBDU2gsZ5R/g1/KHOOEc5IKfUFjXT6DENPGduh08I/pamwtEq4oul7gUqKTQDQ==", "requires": { "@types/yargs-parser": "*" } }, "@types/yargs-parser": { - "version": "15.0.0", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-15.0.0.tgz", - "integrity": "sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw==" + "version": "20.2.0", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-20.2.0.tgz", + "integrity": "sha512-37RSHht+gzzgYeobbG+KWryeAW8J33Nhr69cjTqSYymXVZEN9NbRYWoYlRtDhHKPVT1FyNKwaTPC1NynKZpzRA==" }, "@typescript-eslint/eslint-plugin": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.9.1.tgz", - "integrity": "sha512-QRLDSvIPeI1pz5tVuurD+cStNR4sle4avtHhxA+2uyixWGFjKzJ+EaFVRW6dA/jOgjV5DTAjOxboQkRDE8cRlQ==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.14.1.tgz", + "integrity": "sha512-5JriGbYhtqMS1kRcZTQxndz1lKMwwEXKbwZbkUZNnp6MJX0+OVXnG0kOlBZP4LUAxEyzu3cs+EXd/97MJXsGfw==", "dev": true, "requires": { - "@typescript-eslint/experimental-utils": "4.9.1", - "@typescript-eslint/scope-manager": "4.9.1", + "@typescript-eslint/experimental-utils": "4.14.1", + "@typescript-eslint/scope-manager": "4.14.1", "debug": "^4.1.1", "functional-red-black-tree": "^1.0.1", + "lodash": "^4.17.15", "regexpp": "^3.0.0", "semver": "^7.3.2", "tsutils": "^3.17.1" @@ -2697,55 +2676,55 @@ } }, "@typescript-eslint/experimental-utils": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.9.1.tgz", - "integrity": "sha512-c3k/xJqk0exLFs+cWSJxIjqLYwdHCuLWhnpnikmPQD2+NGAx9KjLYlBDcSI81EArh9FDYSL6dslAUSwILeWOxg==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.14.1.tgz", + "integrity": "sha512-2CuHWOJwvpw0LofbyG5gvYjEyoJeSvVH2PnfUQSn0KQr4v8Dql2pr43ohmx4fdPQ/eVoTSFjTi/bsGEXl/zUUQ==", "dev": true, "requires": { "@types/json-schema": "^7.0.3", - "@typescript-eslint/scope-manager": "4.9.1", - "@typescript-eslint/types": "4.9.1", - "@typescript-eslint/typescript-estree": "4.9.1", + "@typescript-eslint/scope-manager": "4.14.1", + "@typescript-eslint/types": "4.14.1", + "@typescript-eslint/typescript-estree": "4.14.1", "eslint-scope": "^5.0.0", "eslint-utils": "^2.0.0" } }, "@typescript-eslint/parser": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.9.1.tgz", - "integrity": "sha512-Gv2VpqiomvQ2v4UL+dXlQcZ8zCX4eTkoIW+1aGVWT6yTO+6jbxsw7yQl2z2pPl/4B9qa5JXeIbhJpONKjXIy3g==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.14.1.tgz", + "integrity": "sha512-mL3+gU18g9JPsHZuKMZ8Z0Ss9YP1S5xYZ7n68Z98GnPq02pYNQuRXL85b9GYhl6jpdvUc45Km7hAl71vybjUmw==", "dev": true, "requires": { - "@typescript-eslint/scope-manager": "4.9.1", - "@typescript-eslint/types": "4.9.1", - "@typescript-eslint/typescript-estree": "4.9.1", + "@typescript-eslint/scope-manager": "4.14.1", + "@typescript-eslint/types": "4.14.1", + "@typescript-eslint/typescript-estree": "4.14.1", "debug": "^4.1.1" } }, "@typescript-eslint/scope-manager": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.9.1.tgz", - "integrity": "sha512-sa4L9yUfD/1sg9Kl8OxPxvpUcqxKXRjBeZxBuZSSV1v13hjfEJkn84n0An2hN8oLQ1PmEl2uA6FkI07idXeFgQ==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.14.1.tgz", + "integrity": "sha512-F4bjJcSqXqHnC9JGUlnqSa3fC2YH5zTtmACS1Hk+WX/nFB0guuynVK5ev35D4XZbdKjulXBAQMyRr216kmxghw==", "dev": true, "requires": { - "@typescript-eslint/types": "4.9.1", - "@typescript-eslint/visitor-keys": "4.9.1" + "@typescript-eslint/types": "4.14.1", + "@typescript-eslint/visitor-keys": "4.14.1" } }, "@typescript-eslint/types": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.9.1.tgz", - "integrity": "sha512-fjkT+tXR13ks6Le7JiEdagnwEFc49IkOyys7ueWQ4O8k4quKPwPJudrwlVOJCUQhXo45PrfIvIarcrEjFTNwUA==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.14.1.tgz", + "integrity": "sha512-SkhzHdI/AllAgQSxXM89XwS1Tkic7csPdndUuTKabEwRcEfR8uQ/iPA3Dgio1rqsV3jtqZhY0QQni8rLswJM2w==", "dev": true }, "@typescript-eslint/typescript-estree": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.9.1.tgz", - "integrity": "sha512-bzP8vqwX6Vgmvs81bPtCkLtM/Skh36NE6unu6tsDeU/ZFoYthlTXbBmpIrvosgiDKlWTfb2ZpPELHH89aQjeQw==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.14.1.tgz", + "integrity": "sha512-M8+7MbzKC1PvJIA8kR2sSBnex8bsR5auatLCnVlNTJczmJgqRn8M+sAlQfkEq7M4IY3WmaNJ+LJjPVRrREVSHQ==", "dev": true, "requires": { - "@typescript-eslint/types": "4.9.1", - "@typescript-eslint/visitor-keys": "4.9.1", + "@typescript-eslint/types": "4.14.1", + "@typescript-eslint/visitor-keys": "4.14.1", "debug": "^4.1.1", "globby": "^11.0.1", "is-glob": "^4.0.1", @@ -2766,12 +2745,12 @@ } }, "@typescript-eslint/visitor-keys": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.9.1.tgz", - "integrity": "sha512-9gspzc6UqLQHd7lXQS7oWs+hrYggspv/rk6zzEMhCbYwPE/sF7oxo7GAjkS35Tdlt7wguIG+ViWCPtVZHz/ybQ==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.14.1.tgz", + "integrity": "sha512-TAblbDXOI7bd0C/9PE1G+AFo7R5uc+ty1ArDoxmrC1ah61Hn6shURKy7gLdRb1qKJmjHkqu5Oq+e4Kt0jwf1IA==", "dev": true, "requires": { - "@typescript-eslint/types": "4.9.1", + "@typescript-eslint/types": "4.14.1", "eslint-visitor-keys": "^2.0.0" } }, @@ -3309,9 +3288,9 @@ "dev": true }, "astral-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", - "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", "dev": true }, "async": { @@ -3480,13 +3459,37 @@ } }, "babel-plugin-codegen": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/babel-plugin-codegen/-/babel-plugin-codegen-4.0.1.tgz", - "integrity": "sha512-cehTkKAGgENw+ftEngX/zVfFqMxFFItsatwmLHfxVLJCnkJvREQhsCb5/WNrNdP7L3tbwGgd5JQt9dFth+p24g==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/babel-plugin-codegen/-/babel-plugin-codegen-4.1.4.tgz", + "integrity": "sha512-WWqjrCgi/+bOA9Vnx0k6tbuyDVzJaMFcBlzBpw02r9yrW8W4qWu+ObZE8lbM1g57IX+tHHS4WbO0zW40sDLGPA==", "requires": { - "@babel/runtime": "^7.9.2", - "babel-plugin-macros": "^2.8.0", + "@babel/runtime": "^7.12.5", + "babel-plugin-macros": "^3.0.1", "require-from-string": "^2.0.2" + }, + "dependencies": { + "babel-plugin-macros": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.0.1.tgz", + "integrity": "sha512-CKt4+Oy9k2wiN+hT1uZzOw7d8zb1anbQpf7KLwaaXRCi/4pzKdFKHf7v5mvoPmjkmxshh7eKZQuRop06r5WP4w==", + "requires": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + } + }, + "cosmiconfig": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.0.tgz", + "integrity": "sha512-pondGvTuVYDk++upghXJabWzL6Kxu6f26ljFw64Swq9v6sQPUL3EUlVDV56diOjpCayKihL6hVe8exIACU4XcA==", + "requires": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + } + } } }, "babel-plugin-dynamic-import-node": { @@ -3592,9 +3595,9 @@ "dev": true }, "babel-preset-current-node-syntax": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.0.tgz", - "integrity": "sha512-mGkvkpocWJes1CmMKtgGUwCeeq0pOhALyymozzDWYomHTbDLwueDYG6p4TK1YOeYHCzBzYPsWkgTto10JubI1Q==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", + "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", "dev": true, "requires": { "@babel/plugin-syntax-async-generators": "^7.8.4", @@ -3923,9 +3926,9 @@ "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==" }, "binary-extensions": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz", - "integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==" + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==" }, "bindings": { "version": "1.5.0", @@ -4127,16 +4130,16 @@ } }, "browserslist": { - "version": "4.16.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.0.tgz", - "integrity": "sha512-/j6k8R0p3nxOC6kx5JGAxsnhc9ixaWJfYc+TNTzxg6+ARaESAvQGV7h0uNOB4t+pLQJZWzcrMxXOxjgsCj3dqQ==", + "version": "4.16.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.1.tgz", + "integrity": "sha512-UXhDrwqsNcpTYJBTZsbGATDxZbiVDsx6UjpmRUmtnP10pr8wAYr5LgFoEFw9ixriQH2mv/NX2SfGzE/o8GndLA==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30001165", + "caniuse-lite": "^1.0.30001173", "colorette": "^1.2.1", - "electron-to-chromium": "^1.3.621", + "electron-to-chromium": "^1.3.634", "escalade": "^3.1.1", - "node-releases": "^1.1.67" + "node-releases": "^1.1.69" } }, "bser": { @@ -4186,9 +4189,9 @@ "dev": true }, "builtin-modules": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.1.0.tgz", - "integrity": "sha512-k0KL0aWZuBt2lrxrcASWDfwOLMnodeQjodT/1SxEQAXsHANgo6ZC/VEaSEHCXt7aSTZ4/4H5LKa+tBXmW7Vtvw==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.2.0.tgz", + "integrity": "sha512-lGzLKcioL90C7wMczpkY0n/oART3MbBa8R9OFGE1rJxoVI86u4WAGfEk8Wjv10eKSyTHVGkSo3bvBylCEtk7LA==", "dev": true }, "builtin-status-codes": { @@ -4254,13 +4257,13 @@ } }, "call-bind": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.0.tgz", - "integrity": "sha512-AEXsYIyyDY3MCzbwdhzG3Jx1R0J2wetQyUynn6dYHAO+bg8l1k7jwZtRv4ryryFs7EP+NDlikJlVe59jr0cM2w==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", "dev": true, "requires": { "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.0" + "get-intrinsic": "^1.0.2" } }, "caller-callsite": { @@ -4326,9 +4329,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001165", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001165.tgz", - "integrity": "sha512-8cEsSMwXfx7lWSUMA2s08z9dIgsnR5NAqjXP23stdsU3AUWkCr/rr4s4OFtHXn5XXr6+7kam3QFVoYyXNPdJPA==", + "version": "1.0.30001181", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001181.tgz", + "integrity": "sha512-m5ul/ARCX50JB8BSNM+oiPmQrR5UmngaQ3QThTTp5HcIIQGP/nPBs82BYLE+tigzm3VW+F4BJIhUyaVtEweelQ==", "dev": true }, "capture-exit": { @@ -4460,13 +4463,13 @@ "dev": true }, "chokidar": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.3.tgz", - "integrity": "sha512-DtM3g7juCXQxFVSNPNByEC2+NImtBuxQQvWlHunpJIS5Ocr0lG306cC7FCi7cEA0fzmybPUIl4txBIobk1gGOQ==", + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", + "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==", "requires": { "anymatch": "~3.1.1", "braces": "~3.0.2", - "fsevents": "~2.1.2", + "fsevents": "~2.3.1", "glob-parent": "~5.1.0", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", @@ -4640,11 +4643,11 @@ } }, "codegen.macro": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/codegen.macro/-/codegen.macro-4.0.0.tgz", - "integrity": "sha512-7K7BXVLmsO4shE/+2KhzCjP7uPbqwbkY4o5EJaJouncVY7IkZozpnbUex8xjWB9AzEV9aWpnXe0+oLbEuPj9Ew==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/codegen.macro/-/codegen.macro-4.1.0.tgz", + "integrity": "sha512-T0A8vdX9SkhT00cd7nWQwlg8CT2qQwp1o2+ZD7Wqx38ABK0kJvhk/IJTjNgnE1W35dE98+OzLr1zluVREz5bPg==", "requires": { - "babel-plugin-codegen": "^4.0.0" + "babel-plugin-codegen": "^4.1.0" } }, "collapse-white-space": { @@ -4955,18 +4958,18 @@ "dev": true }, "core-js": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.8.1.tgz", - "integrity": "sha512-9Id2xHY1W7m8hCl8NkhQn5CufmF/WuR30BTRewvCXc1aZd3kMECwNZ69ndLbekKfakw9Rf2Xyc+QR6E7Gg+obg==", + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.8.3.tgz", + "integrity": "sha512-KPYXeVZYemC2TkNEkX/01I+7yd+nX3KddKwZ1Ww7SKWdI2wQprSgLmrTddT8nw92AjEklTsPBoSdQBhbI1bQ6Q==", "dev": true }, "core-js-compat": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.8.1.tgz", - "integrity": "sha512-a16TLmy9NVD1rkjUGbwuyWkiDoN0FDpAwrfLONvHFQx0D9k7J9y0srwMT8QP/Z6HE3MIFaVynEeYwZwPX1o5RQ==", + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.8.3.tgz", + "integrity": "sha512-1sCb0wBXnBIL16pfFG1Gkvei6UzvKyTNYpiC41yrdjEv0UoJoq9E/abTMzyYJ6JpTkAj15dLjbqifIzEBDVvog==", "dev": true, "requires": { - "browserslist": "^4.15.0", + "browserslist": "^4.16.1", "semver": "7.0.0" }, "dependencies": { @@ -4979,9 +4982,9 @@ } }, "core-js-pure": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.8.1.tgz", - "integrity": "sha512-Se+LaxqXlVXGvmexKGPvnUIYC1jwXu1H6Pkyb3uBM5d8/NELMYCHs/4/roD7721NxrTLyv7e5nXd5/QLBO+10g==" + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.8.3.tgz", + "integrity": "sha512-V5qQZVAr9K0xu7jXg1M7qTEwuxUgqr7dUOezGaNa7i+Xn9oXAU/d1fzqD9ObuwpVQOaorO5s70ckyi1woP9lVA==" }, "core-util-is": { "version": "1.0.2", @@ -6021,9 +6024,9 @@ "dev": true }, "electron-to-chromium": { - "version": "1.3.622", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.622.tgz", - "integrity": "sha512-AJT0Fm1W0uZlMVVkkJrcCVvczDuF8tPm3bwzQf5WO8AaASB2hwTRP7B8pU5rqjireH+ib6am8+hH5/QkXzzYKw==", + "version": "1.3.649", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.649.tgz", + "integrity": "sha512-ojGDupQ3UMkvPWcTICe4JYe17+o9OLiFMPoduoR72Zp2ILt1mRVeqnxBEd6s/ptekrnsFU+0A4lStfBe/wyG/A==", "dev": true }, "elliptic": { @@ -6096,9 +6099,9 @@ } }, "enhanced-resolve": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.3.0.tgz", - "integrity": "sha512-3e87LvavsdxyoCfGusJnrZ5G8SLPOFeHSNpZI/ATL9a5leXo2k0w6MKnbqhdBad9qTobSfB20Ld7UmgoNbAZkQ==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.5.0.tgz", + "integrity": "sha512-Nv9m36S/vxpsI+Hc4/ZGRs0n9mXqSWGGq49zxb/cJfPAQMbUtttJAlNPS4AQzaBdw/pKskw5bMbekT/Y7W/Wlg==", "dev": true, "requires": { "graceful-fs": "^4.1.2", @@ -6158,15 +6161,15 @@ } }, "entities": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", - "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", "dev": true }, "errno": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", - "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==", + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", + "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", "dev": true, "requires": { "prr": "~1.0.1" @@ -6190,23 +6193,25 @@ } }, "es-abstract": { - "version": "1.18.0-next.1", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.1.tgz", - "integrity": "sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA==", + "version": "1.18.0-next.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.2.tgz", + "integrity": "sha512-Ih4ZMFHEtZupnUh6497zEL4y2+w8+1ljnCyaTa+adcoafI1GOvMwFlDjBLfWR7y9VLfrjRJe9ocuHY1PSR9jjw==", "dev": true, "requires": { + "call-bind": "^1.0.2", "es-to-primitive": "^1.2.1", "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2", "has": "^1.0.3", "has-symbols": "^1.0.1", "is-callable": "^1.2.2", - "is-negative-zero": "^2.0.0", + "is-negative-zero": "^2.0.1", "is-regex": "^1.1.1", - "object-inspect": "^1.8.0", + "object-inspect": "^1.9.0", "object-keys": "^1.1.1", - "object.assign": "^4.1.1", - "string.prototype.trimend": "^1.0.1", - "string.prototype.trimstart": "^1.0.1" + "object.assign": "^4.1.2", + "string.prototype.trimend": "^1.0.3", + "string.prototype.trimstart": "^1.0.3" } }, "es-to-primitive": { @@ -6330,13 +6335,13 @@ } }, "eslint": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.15.0.tgz", - "integrity": "sha512-Vr64xFDT8w30wFll643e7cGrIkPEU50yIiI36OdSIDoSGguIeaLzBo0vpGvzo9RECUqq7htURfwEtKqwytkqzA==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.18.0.tgz", + "integrity": "sha512-fbgTiE8BfUJZuBeq2Yi7J3RB3WGUQ9PNuNbmgi6jt9Iv8qrkxfy19Ds3OpL1Pm7zg3BtTVhvcUZbIRQ0wmSjAQ==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", - "@eslint/eslintrc": "^0.2.2", + "@eslint/eslintrc": "^0.3.0", "ajv": "^6.10.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", @@ -6360,7 +6365,7 @@ "js-yaml": "^3.13.1", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", - "lodash": "^4.17.19", + "lodash": "^4.17.20", "minimatch": "^3.0.4", "natural-compare": "^1.4.0", "optionator": "^0.9.1", @@ -6369,7 +6374,7 @@ "semver": "^7.2.1", "strip-ansi": "^6.0.0", "strip-json-comments": "^3.1.0", - "table": "^5.2.3", + "table": "^6.0.4", "text-table": "^0.2.0", "v8-compile-cache": "^2.0.3" }, @@ -6620,17 +6625,17 @@ }, "dependencies": { "emoji-regex": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.0.tgz", - "integrity": "sha512-DNc3KFPK18bPdElMJnf/Pkv5TXhxFU3YFDEuGLDRtPmV4rkmCjBkCSEp22u6rBHdSN9Vlp/GK7k98prmE1Jgug==", + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.1.tgz", + "integrity": "sha512-117l1H6U4X3Krn+MrzYrL57d5H7siRHWraBs7s+LjRuFK7Fe7hJqnJ0skWlinqsycVLU5YAo6L8CsEYQ0V5prg==", "dev": true } } }, "eslint-plugin-react": { - "version": "7.21.5", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.21.5.tgz", - "integrity": "sha512-8MaEggC2et0wSF6bUeywF7qQ46ER81irOdWS4QWxnnlAEsnzeBevk1sWh7fhpCghPpXb+8Ks7hvaft6L/xsR6g==", + "version": "7.22.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.22.0.tgz", + "integrity": "sha512-p30tuX3VS+NWv9nQot9xIGAHBXR0+xJVaZriEsHoJrASGCJZDJ8JLNM0YqKqI0AKm6Uxaa1VUHoNEibxRCMQHA==", "dev": true, "requires": { "array-includes": "^3.1.1", @@ -6767,9 +6772,9 @@ "dev": true }, "eslint-webpack-plugin": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/eslint-webpack-plugin/-/eslint-webpack-plugin-2.4.1.tgz", - "integrity": "sha512-cj8iPWZKuAiVD8MMgTSunyMCAvxQxp5mxoPHZl1UMGkApFXaXJHdCFcCR+oZEJbBNhReNa5SjESIn34uqUbBtg==", + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/eslint-webpack-plugin/-/eslint-webpack-plugin-2.4.3.tgz", + "integrity": "sha512-+15ifHFkGn0gB7lQBe+xgyKcjelxv9xlTutGHEPYBUUj+1Rjrjq3+1REJLJpyAHgpQTatpqkRY1z8gQuyn3Aww==", "dev": true, "requires": { "@types/eslint": "^7.2.4", @@ -7243,9 +7248,9 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "fast-glob": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.4.tgz", - "integrity": "sha512-kr/Oo6PX51265qeuCYsyGypiO5uJFgBS0jksyG7FUeCyQzNwYnzrNIMR1NXfkZXsMYXYLRAHgISHBz8gQcxKHQ==", + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.5.tgz", + "integrity": "sha512-2DtFcgT68wiTTiwZ2hNdJfcHNke9XOfnwmBRWXhmeKM8rF0TGwmC/Qto3S7RoZKp5cilZbxzO5iTNTQsJ+EeDg==", "dev": true, "requires": { "@nodelib/fs.stat": "^2.0.2", @@ -7268,9 +7273,9 @@ "dev": true }, "fastq": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.9.0.tgz", - "integrity": "sha512-i7FVWL8HhVY+CTkwFxkN2mk3h+787ixS5S63eb78diVRc1MCssarHq3W5cj0av7YDSwmaV928RNag+U1etRQ7w==", + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.10.1.tgz", + "integrity": "sha512-AWuv6Ery3pM+dY7LYS8YIaCiQvUaos9OB1RyNgaOWnaX+Tik7Onvcsf8x8c+YtDeT0maYLniBip2hox5KtEXXA==", "dev": true, "requires": { "reusify": "^1.0.4" @@ -7470,12 +7475,12 @@ } }, "find-versions": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/find-versions/-/find-versions-3.2.0.tgz", - "integrity": "sha512-P8WRou2S+oe222TOCHitLy8zj+SIsVJh52VP4lvXkaFVnOFFdoWv1H1Jjvel1aI6NCFOAaeAVm8qrI0odiLcww==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/find-versions/-/find-versions-4.0.0.tgz", + "integrity": "sha512-wgpWy002tA+wgmO27buH/9KzyEOQnKsG/R0yrcjPT9BOFm0zRBVQbZ95nRGXWMywS8YR5knRbpohio0bcJABxQ==", "dev": true, "requires": { - "semver-regex": "^2.0.0" + "semver-regex": "^3.1.2" } }, "flat-cache": { @@ -7489,9 +7494,9 @@ } }, "flatted": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.1.0.tgz", - "integrity": "sha512-tW+UkmtNg/jv9CSofAKvgVcO7c2URjhTdW1ZTkcAritblu8tajiYy7YisnIflEwtKssCtOxpnBRoCB7iap0/TA==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.1.1.tgz", + "integrity": "sha512-zAoAQiudy+r5SvnSw3KJy5os/oRJYHzrzja/tBDqrZtNhUw8bt6y8OBzMWcjWr+8liV8Eb6yOhw8WZ7VFZ5ZzA==", "dev": true }, "flatten": { @@ -7548,9 +7553,9 @@ "integrity": "sha512-Rwix9pBtC1Nuy5wysTmKy+UjbDJpIfg8eHjw0rjZ1mX4GNLz1Bmd16uDpI3Gk1i70Fgcs8Csg2lPm8HULFg9DQ==" }, "follow-redirects": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.0.tgz", - "integrity": "sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.2.tgz", + "integrity": "sha512-6mPTgLxYm3r6Bkkg0vNM0HTjfGrOEtsfbhagQvbxDEsEkpNhw582upBaoRZylzen6krEmxXJgt9Ju6HiI4O7BA==", "dev": true }, "for-in": { @@ -7798,15 +7803,15 @@ } }, "fs-extra": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.1.tgz", - "integrity": "sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ==", + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", "dev": true, "requires": { "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", - "universalify": "^1.0.0" + "universalify": "^2.0.0" } }, "fs-minipass": { @@ -7868,9 +7873,9 @@ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, "fsevents": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", - "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.1.tgz", + "integrity": "sha512-YR47Eg4hChJGAB1O3yEAOkGO+rlzutoICGqGo9EZ4lKWokzZRSyIW1QmTzqjtw8MJdj9srP869CuWw/hyzSiBw==", "optional": true }, "function-bind": { @@ -7895,9 +7900,9 @@ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" }, "get-intrinsic": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.0.1.tgz", - "integrity": "sha512-ZnWP+AmS1VUaLgTRy47+zKtjTxz+0xMpx3I52i+aalBK1QP19ggLF3Db89KJX7kjfOfP2eoa01qc++GwPgufPg==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.0.tgz", + "integrity": "sha512-M11rgtQp5GZMZzDL7jLTNxbDfurpzuau5uqRWDPvlHjfvg3TdScAZo96GLvhMjImrmR8uAt0FS2RLoMrfWGKlg==", "dev": true, "requires": { "function-bind": "^1.1.1", @@ -8014,9 +8019,9 @@ "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==" }, "globby": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.1.tgz", - "integrity": "sha512-iH9RmgwCmUJHi2z5o2l3eTtGBtXek1OYlHrbcxOYugyHLmAsZrPj43OtHThd62Buh/Vv6VyCBD2bdyWcGNQqoQ==", + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.2.tgz", + "integrity": "sha512-2ZThXDvvV8fYFRVIxnrMQBipZQDr7MxKAmQK1vujaj9/7eF0efG7BPUKJ7jP7G5SLF37xKDXvO4S/KKLj/Z0og==", "dev": true, "requires": { "array-union": "^2.1.0", @@ -8043,9 +8048,9 @@ "dev": true }, "graphql": { - "version": "15.4.0", - "resolved": "https://registry.npmjs.org/graphql/-/graphql-15.4.0.tgz", - "integrity": "sha512-EB3zgGchcabbsU9cFe1j+yxdzKQKAbGUWRb13DsrsMN1yyfmmIq+2+L5MqVWcDCE4V89R5AyUOi7sMOGxdsYtA==" + "version": "15.5.0", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-15.5.0.tgz", + "integrity": "sha512-OmaM7y0kaK31NKG31q4YbD2beNYa6jBBKtMFT6gLYJljHLJr42IqJ8KX08u3Li/0ifzTU5HjmoOOrwa5BRLeDA==" }, "gray-matter": { "version": "4.0.2", @@ -8437,9 +8442,9 @@ } }, "html-entities": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.3.1.tgz", - "integrity": "sha512-rhE/4Z3hIhzHAUKbW8jVcCyuT5oJCXXqhN/6mXXVCpzTmvJnoH2HL/bt3EZ6p55jbFJBeAe1ZNpL5BugLujxNA==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.4.0.tgz", + "integrity": "sha512-8nxjcBcd8wovbeKx7h3wTji4e6+rhaVuPNpMqwWgnHh+N9ToqsCs6XztWRBPQ+UtzsoMAdKZtUENoVzU/EMtZA==", "dev": true }, "html-escaper": { @@ -8792,18 +8797,18 @@ "dev": true }, "husky": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/husky/-/husky-4.3.5.tgz", - "integrity": "sha512-E5S/1HMoDDaqsH8kDF5zeKEQbYqe3wL9zJDyqyYqc8I4vHBtAoxkDBGXox0lZ9RI+k5GyB728vZdmnM4bYap+g==", + "version": "4.3.8", + "resolved": "https://registry.npmjs.org/husky/-/husky-4.3.8.tgz", + "integrity": "sha512-LCqqsB0PzJQ/AlCgfrfzRe3e3+NvmefAdKQhRYpxS4u6clblBoDdzzvHi8fmxKRzvMxPY/1WZWzomPZww0Anow==", "dev": true, "requires": { "chalk": "^4.0.0", "ci-info": "^2.0.0", "compare-versions": "^3.6.0", "cosmiconfig": "^7.0.0", - "find-versions": "^3.2.0", + "find-versions": "^4.0.0", "opencollective-postinstall": "^2.0.2", - "pkg-dir": "^4.2.0", + "pkg-dir": "^5.0.0", "please-upgrade-node": "^3.2.0", "slash": "^3.0.0", "which-pm-runs": "^1.0.0" @@ -8884,9 +8889,9 @@ } }, "import-fresh": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.2.tgz", - "integrity": "sha512-cTPNrlvJT6twpYy+YmKUKrTSjWFs3bjYjAhCwm+z4EOCubZxAuO+hHpRN64TqjEaYSHs7tJAE0w1CKMGmsG/lw==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", "requires": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -8917,6 +8922,17 @@ "requires": { "pkg-dir": "^4.2.0", "resolve-cwd": "^3.0.0" + }, + "dependencies": { + "pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "requires": { + "find-up": "^4.0.0" + } + } } }, "imurmurhash": { @@ -8957,9 +8973,9 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "ini": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.7.tgz", - "integrity": "sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ==", + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", "dev": true }, "inline-style-parser": { @@ -8998,35 +9014,14 @@ } }, "internal-slot": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.2.tgz", - "integrity": "sha512-2cQNfwhAfJIkU4KZPkDI+Gj5yNNnbqi40W9Gge6dfnk4TocEVm00B3bdiL+JINrbGJil2TeHvM4rETGzk/f/0g==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", + "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", "dev": true, "requires": { - "es-abstract": "^1.17.0-next.1", + "get-intrinsic": "^1.1.0", "has": "^1.0.3", - "side-channel": "^1.0.2" - }, - "dependencies": { - "es-abstract": { - "version": "1.17.7", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz", - "integrity": "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==", - "dev": true, - "requires": { - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1", - "is-callable": "^1.2.2", - "is-regex": "^1.1.1", - "object-inspect": "^1.8.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.1", - "string.prototype.trimend": "^1.0.1", - "string.prototype.trimstart": "^1.0.1" - } - } + "side-channel": "^1.0.4" } }, "ip": { @@ -10650,14 +10645,6 @@ "requires": { "graceful-fs": "^4.1.6", "universalify": "^2.0.0" - }, - "dependencies": { - "universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "dev": true - } } }, "jsprim": { @@ -10673,13 +10660,13 @@ } }, "jsx-ast-utils": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.1.0.tgz", - "integrity": "sha512-d4/UOjg+mxAWxCiF0c5UTSwyqbchkbqCvK87aBovhnh8GtysTjWmgC63tY0cJx/HzGgm9qnA147jVBdpOiQ2RA==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.2.0.tgz", + "integrity": "sha512-EIsmt3O3ljsU6sot/J4E1zDRxfBNrhjyf/OKjlydwgEimQuznlM4Wv7U+ueONJMyEn1WRE0K8dhi3dVAXYT24Q==", "dev": true, "requires": { - "array-includes": "^3.1.1", - "object.assign": "^4.1.1" + "array-includes": "^3.1.2", + "object.assign": "^4.1.2" } }, "killable": { @@ -11371,18 +11358,18 @@ "dev": true }, "mime-db": { - "version": "1.44.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", - "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==", + "version": "1.45.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.45.0.tgz", + "integrity": "sha512-CkqLUxUk15hofLoLyljJSrukZi8mAtgd+yE5uO4tqRZsdsAJKv0O+rFMhVDRJgozy+yG6md5KwuXhD4ocIoP+w==", "dev": true }, "mime-types": { - "version": "2.1.27", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", - "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", + "version": "2.1.28", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.28.tgz", + "integrity": "sha512-0TO2yJ5YHYr7M2zzT7gDU1tbwHxEUWBCLt0lscSNpcdAfFyJOVEpRYNS7EXVcTLNj/25QO8gulHC5JtTzSE2UQ==", "dev": true, "requires": { - "mime-db": "1.44.0" + "mime-db": "1.45.0" } }, "mimic-fn": { @@ -11584,29 +11571,23 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "msw": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/msw/-/msw-0.21.3.tgz", - "integrity": "sha512-voPc/EJsjarvi454vSEuozZQQqLG4AUHT6qQL5Ah47lq7sGCpc7icByeUlfvEj5+MvaugN0c7JwXyCa2rxu8cA==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/msw/-/msw-0.25.0.tgz", + "integrity": "sha512-KWW9Qwf4ecw7lgf7KM0gRfFLvfmUPw0ljCvii2IluhObZggoN/dBMFe5YJf1utnW7q9qHBc4oMNLrd8zFE6BfQ==", "requires": { "@open-draft/until": "^1.0.3", "@types/cookie": "^0.4.0", "chalk": "^4.1.0", "chokidar": "^3.4.2", "cookie": "^0.4.1", - "graphql": "^15.3.0", + "graphql": "^15.4.0", "headers-utils": "^1.2.0", "node-fetch": "^2.6.1", - "node-match-path": "^0.4.4", - "node-request-interceptor": "^0.5.1", + "node-match-path": "^0.6.0", + "node-request-interceptor": "^0.6.3", "statuses": "^2.0.0", - "yargs": "^16.0.3" - }, - "dependencies": { - "node-match-path": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/node-match-path/-/node-match-path-0.4.4.tgz", - "integrity": "sha512-pBq9gp7TG0r0VXuy/oeZmQsjBSnYQo7G886Ly/B3azRwZuEtHCY155dzmfoKWcDPGgyfIGD8WKVC7h3+6y7yTg==" - } + "strict-event-emitter": "^0.1.0", + "yargs": "^16.2.0" } }, "multicast-dns": { @@ -11827,9 +11808,9 @@ } }, "node-match-path": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/node-match-path/-/node-match-path-0.6.0.tgz", - "integrity": "sha512-mld1LbiLaufULAYFPAWgNEG4P0ccL49otlL/nbF5VBQLATuzfS1BGYV1rjRMsxbc0vcnasikFqGHoKDFMQylMw==" + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/node-match-path/-/node-match-path-0.6.1.tgz", + "integrity": "sha512-5KjUxG0fUqiIB4HXVatTYGNXf0fD/ZIC88P2w5izFrk6jz45hgavoVuIERsTqb+lMPMuvvL3rxvFWwpnJzN/wQ==" }, "node-modules-regexp": { "version": "1.0.0", @@ -11838,9 +11819,9 @@ "dev": true }, "node-notifier": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-8.0.0.tgz", - "integrity": "sha512-46z7DUmcjoYdaWyXouuFNNfUo6eFa94t23c53c+lG/9Cvauk4a98rAUp9672X5dxGdQmLpPzTxzu8f/OeEPaFA==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-8.0.1.tgz", + "integrity": "sha512-BvEXF+UmsnAfYfoapKM9nGxnP+Wn7P91YfXmrKnfcYCx6VBeoN5Ez5Ogck6I8Bi5k4RlpqRYaw75pAwzX9OphA==", "dev": true, "optional": true, "requires": { @@ -11865,19 +11846,20 @@ } }, "node-releases": { - "version": "1.1.67", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.67.tgz", - "integrity": "sha512-V5QF9noGFl3EymEwUYzO+3NTDpGfQB4ve6Qfnzf3UNydMhjQRVPR1DZTuvWiLzaFJYw2fmDwAfnRNEVb64hSIg==", + "version": "1.1.70", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.70.tgz", + "integrity": "sha512-Slf2s69+2/uAD79pVVQo8uSiC34+g8GWY8UH2Qtqv34ZfhYrxpYpfzs9Js9d6O0mbDmALuxaTlplnBTnSELcrw==", "dev": true }, "node-request-interceptor": { - "version": "0.5.9", - "resolved": "https://registry.npmjs.org/node-request-interceptor/-/node-request-interceptor-0.5.9.tgz", - "integrity": "sha512-M1a3aulCW/kqajDn/w+qBX86G4So7utJGlrODAjQ1piz/kR8ZaDfd/wrJnsuPtUM12F0YxsnXG8qRKFkIEIxsw==", + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/node-request-interceptor/-/node-request-interceptor-0.6.3.tgz", + "integrity": "sha512-8I2V7H2Ch0NvW7qWcjmS0/9Lhr0T6x7RD6PDirhvWEkUQvy83x8BA4haYMr09r/rig7hcgYSjYh6cd4U7G1vLA==", "requires": { "@open-draft/until": "^1.0.3", "debug": "^4.3.0", - "headers-utils": "^1.2.0" + "headers-utils": "^1.2.0", + "strict-event-emitter": "^0.1.0" } }, "normalize-package-data": { @@ -12216,9 +12198,9 @@ } }, "open": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/open/-/open-7.3.0.tgz", - "integrity": "sha512-mgLwQIx2F/ye9SmbrUkurZCnkoXyXyu9EbHtJZrICjVAJfyMArdHp3KkixGdZx1ZHFPNIwl0DDM1dFFqXbTLZw==", + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/open/-/open-7.3.1.tgz", + "integrity": "sha512-f2wt9DCBKKjlFbjzGb8MOAW8LH8F0mrs1zc7KTjAJ9PZNQbfenzWbNP1VZJvw6ICMG9r14Ah6yfwPn7T7i646A==", "dev": true, "requires": { "is-docker": "^2.0.0", @@ -12435,9 +12417,9 @@ } }, "parse-json": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.1.0.tgz", - "integrity": "sha512-+mi/lmVVNKFNVyLXV31ERiy2CY5E1/F6QtJFEzoChPRwwngMNXRDQ9GJ5WdE2Z2P4AujsOi0/+2qHID68KwfIQ==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", "requires": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", @@ -12596,12 +12578,51 @@ } }, "pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-5.0.0.tgz", + "integrity": "sha512-NPE8TDbzl/3YQYY7CSS228s3g2ollTFnc+Qi3tqmqJp9Vg2ovUpixcJEo2HJScN2Ez+kEaal6y70c0ehqJBJeA==", "dev": true, "requires": { - "find-up": "^4.0.0" + "find-up": "^5.0.0" + }, + "dependencies": { + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "requires": { + "p-limit": "^3.0.2" + } + } } }, "pkg-up": { @@ -13704,9 +13725,9 @@ }, "dependencies": { "postcss": { - "version": "8.2.1", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.2.1.tgz", - "integrity": "sha512-RhsqOOAQzTgh1UB/IZdca7F9WDb7SUCR2Vnv1x7DbvuuggQIpoDwjK+q0rzoPffhYvWNKX5JSwS4so4K3UC6vA==", + "version": "8.2.4", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.2.4.tgz", + "integrity": "sha512-kRFftRoExRVXZlwUuay9iC824qmXPcQQVzAjbCCgjpXnkdMCJYBu2gTwAaFBzv8ewND6O8xFb3aELmEkh9zTzg==", "dev": true, "requires": { "colorette": "^1.2.1", @@ -13733,9 +13754,9 @@ } }, "postcss-selector-not": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-selector-not/-/postcss-selector-not-4.0.0.tgz", - "integrity": "sha512-W+bkBZRhqJaYN8XAnbbZPLWMvZD1wKTu0UxtFKdhtGjWYmxhkUneoeOhRJKdAE5V7ZTlnbHfCR+6bNwK9e1dTQ==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-selector-not/-/postcss-selector-not-4.0.1.tgz", + "integrity": "sha512-YolvBgInEK5/79C+bdFMyzqTg6pkYqDbzZIST/PDMqa/o3qtXenD05apBG2jLgT0/BQ77d4U2UK12jWpilqMAQ==", "dev": true, "requires": { "balanced-match": "^1.0.0", @@ -13821,9 +13842,9 @@ "dev": true }, "pretty-bytes": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.4.1.tgz", - "integrity": "sha512-s1Iam6Gwz3JI5Hweaz4GoCD1WUNUIyzePFy5+Js2hjwGVt2Z79wNN+ZKOZ2vB6C+Xs6njyB84Z1IthQg8d9LxA==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.5.0.tgz", + "integrity": "sha512-p+T744ZyjjiaFlMUZZv6YPC5JrkNj8maRmPaQCWFJFplUAzpIUTRaTcS+7wmZtUoFXHtESJb23ISliaWyz3SHA==", "dev": true }, "pretty-error": { @@ -13876,9 +13897,9 @@ } }, "prismjs": { - "version": "1.22.0", - "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.22.0.tgz", - "integrity": "sha512-lLJ/Wt9yy0AiSYBf212kK3mM5L8ycwlyTlSxHBAneXLR0nzFMlZ5y7riFPF3E33zXOF2IH95xdY5jIyZbM9z/w==", + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.23.0.tgz", + "integrity": "sha512-c29LVsqOaLbBHuIbsTxaKENh1N2EQBOHaWv7gkHN4dgRbxSREqDnDbtFJYdpPauS4YCplMSNCABQ6Eeor69bAA==", "requires": { "clipboard": "^2.0.0" } @@ -14196,6 +14217,15 @@ "text-table": "0.2.0" }, "dependencies": { + "@babel/code-frame": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", + "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", + "dev": true, + "requires": { + "@babel/highlight": "^7.10.4" + } + }, "browserslist": { "version": "4.14.2", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.14.2.tgz", @@ -14233,6 +14263,20 @@ "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", "dev": true }, + "globby": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.1.tgz", + "integrity": "sha512-iH9RmgwCmUJHi2z5o2l3eTtGBtXek1OYlHrbcxOYugyHLmAsZrPj43OtHThd62Buh/Vv6VyCBD2bdyWcGNQqoQ==", + "dev": true, + "requires": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.1.1", + "ignore": "^5.1.4", + "merge2": "^1.3.0", + "slash": "^3.0.0" + } + }, "loader-utils": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", @@ -14593,9 +14637,9 @@ } }, "reading-time": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/reading-time/-/reading-time-1.2.1.tgz", - "integrity": "sha512-a27kU9sCJzx4JklmKFMXgMoCWlhBi75DFTbU7+M3rjh5SXRWyacYt02il3muYaA+SUQ7tg5gMQn7GC8zOxTt/g==" + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/reading-time/-/reading-time-1.3.0.tgz", + "integrity": "sha512-RJ8J5O6UvrclfZpcPSPuKusrdRfoY7uXXoYOOdeswZNtSkQaewT3919yz6RyloDBR+iwcUyz5zGOUjhgvfuv3g==" }, "recursive-readdir": { "version": "2.2.2", @@ -14682,34 +14726,13 @@ "dev": true }, "regexp.prototype.flags": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.0.tgz", - "integrity": "sha512-2+Q0C5g951OlYlJz6yu5/M33IcsESLlLfsyIaLJaG4FA2r4yP8MvVMJUUP/fVBkSpbbbZlS5gynbEWLipiiXiQ==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.1.tgz", + "integrity": "sha512-JiBdRBq91WlY7uRJ0ds7R+dU02i6LKi8r3BuQhNXn+kmeLN+EfHhfjqMRis1zJxnlu88hq/4dx0P2OP3APRTOA==", "dev": true, "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1" - }, - "dependencies": { - "es-abstract": { - "version": "1.17.7", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz", - "integrity": "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==", - "dev": true, - "requires": { - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1", - "is-callable": "^1.2.2", - "is-regex": "^1.1.1", - "object-inspect": "^1.8.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.1", - "string.prototype.trimend": "^1.0.1", - "string.prototype.trimstart": "^1.0.1" - } - } + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" } }, "regexpp": { @@ -14739,9 +14762,9 @@ "dev": true }, "regjsparser": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.4.tgz", - "integrity": "sha512-64O87/dPDgfk8/RQqC4gkZoGyyWFIEUTTh80CU6CWuK5vkCGyekIx+oKcEIYtP/RAxSQltCZHCNu/mdd7fqlJw==", + "version": "0.6.7", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.7.tgz", + "integrity": "sha512-ib77G0uxsA2ovgiYbCVGx4Pv3PSttAx2vIwidqQzbL2U5S4Q+j00HdSAneSBuyVcMvEnTXMjiGgB+DlXozVhpQ==", "dev": true, "requires": { "jsesc": "~0.5.0" @@ -14869,14 +14892,14 @@ "dev": true }, "renderkid": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-2.0.4.tgz", - "integrity": "sha512-K2eXrSOJdq+HuKzlcjOlGoOarUu5SDguDEhE7+Ah4zuOWL40j8A/oHvLlLob9PSTNvVnBd+/q0Er1QfpEuem5g==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-2.0.5.tgz", + "integrity": "sha512-ccqoLg+HLOHq1vdfYNm4TBeaCDIi1FLt3wGojTDSvdewUv65oTmI3cnT2E4hRjl1gzKZIPK+KZrXzlUYKnR+vQ==", "dev": true, "requires": { - "css-select": "^1.1.0", + "css-select": "^2.0.2", "dom-converter": "^0.2", - "htmlparser2": "^3.3.0", + "htmlparser2": "^3.10.1", "lodash": "^4.17.20", "strip-ansi": "^3.0.0" }, @@ -14887,34 +14910,6 @@ "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", "dev": true }, - "css-select": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", - "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", - "dev": true, - "requires": { - "boolbase": "~1.0.0", - "css-what": "2.1", - "domutils": "1.5.1", - "nth-check": "~1.0.1" - } - }, - "css-what": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz", - "integrity": "sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==", - "dev": true - }, - "domutils": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", - "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", - "dev": true, - "requires": { - "dom-serializer": "0", - "domelementtype": "1" - } - }, "strip-ansi": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", @@ -15798,9 +15793,9 @@ "dev": true }, "semver-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-2.0.0.tgz", - "integrity": "sha512-mUdIBBvdn0PLOeP3TEkMH7HHeUP3GjsXCwKarjv/kGmUFOYg1VqEemKhoQpWMu6X2I8kHeuVdGibLGkVK+/5Qw==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-3.1.2.tgz", + "integrity": "sha512-bXWyL6EAKOJa81XG1OZ/Yyuq+oT0b2YLlxx7c+mrdYPaPbnj6WgVULXhinMIeZGufuUBu/eVRqXEhiv4imfwxA==", "dev": true }, "send": { @@ -16022,13 +16017,14 @@ "optional": true }, "side-channel": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.3.tgz", - "integrity": "sha512-A6+ByhlLkksFoUepsGxfj5x1gTSrs+OydsRptUxeNCabQpCFUvcwIczgOigI8vhY/OJCnPnyE9rGiwgvr9cS1g==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", "dev": true, "requires": { - "es-abstract": "^1.18.0-next.0", - "object-inspect": "^1.8.0" + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" } }, "signal-exit": { @@ -16066,20 +16062,38 @@ "dev": true }, "slice-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", - "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", "dev": true, "requires": { - "ansi-styles": "^3.2.0", - "astral-regex": "^1.0.0", - "is-fullwidth-code-point": "^2.0.0" + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" }, "dependencies": { - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true } } @@ -16461,9 +16475,9 @@ } }, "ssri": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.0.tgz", - "integrity": "sha512-aq/pz989nxVYwn16Tsbj1TqFpD5LLrQxHf5zaHuieFV+R0Bbr4y8qUsOA45hXT/N4/9UNXTarBjnjVmjSOVaAA==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz", + "integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==", "dev": true, "requires": { "minipass": "^3.1.1" @@ -16525,9 +16539,9 @@ } }, "statuses": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.0.tgz", - "integrity": "sha512-w9jNUUQdpuVoYqXxnyOakhckBbOxRaoYqJscyIBYCS5ixyCnO7nQn7zBZvP9zf5QOPZcz2DLUpE3KsNPbJBOFA==" + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" }, "stealthy-require": { "version": "1.1.1", @@ -16638,6 +16652,11 @@ "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", "dev": true }, + "strict-event-emitter": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.1.0.tgz", + "integrity": "sha512-8hSYfU+WKLdNcHVXJ0VxRXiPESalzRe7w1l8dg9+/22Ry+iZQUoQuoJ27R30GMD1TiyYINWsIEGY05WrskhSKw==" + }, "strict-uri-encode": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", @@ -16964,54 +16983,34 @@ "dev": true }, "table": { - "version": "5.4.6", - "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", - "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==", + "version": "6.0.7", + "resolved": "https://registry.npmjs.org/table/-/table-6.0.7.tgz", + "integrity": "sha512-rxZevLGTUzWna/qBLObOe16kB2RTnnbhciwgPbMMlazz1yZGVEgnZK762xyVdVznhqxrfCeBMmMkgOOaPwjH7g==", "dev": true, "requires": { - "ajv": "^6.10.2", - "lodash": "^4.17.14", - "slice-ansi": "^2.1.0", - "string-width": "^3.0.0" + "ajv": "^7.0.2", + "lodash": "^4.17.20", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.0" }, "dependencies": { - "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 - }, - "emoji-regex": { + "ajv": { "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-7.0.3.tgz", + "integrity": "sha512-R50QRlXSxqXcQP5SvKUrw8VZeypvo12i2IX0EeR5PiZ7bEKeHWgzgo264LDadUsCU42lTJVhFikTqJwNeH34gQ==", "dev": true, "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" } }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true } } }, @@ -17022,9 +17021,9 @@ "dev": true }, "tar": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.0.5.tgz", - "integrity": "sha512-0b4HOimQHj9nXNEAA7zWwMM91Zhhba3pspja6sQbgTpynOJf+bkjBnfybNYzbpLbnwXnbyB4LOREvlyXLkCHSg==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.0.tgz", + "integrity": "sha512-DUCttfhsnLCjwoDoFcI+B2iJgYa93vBnDUATYEeRx6sntCTdN01VnqsIuTlALXla/LWooNg0yEGeB+Y8WdFxGA==", "dev": true, "requires": { "chownr": "^2.0.0", @@ -17155,6 +17154,15 @@ "yocto-queue": "^0.1.0" } }, + "pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "requires": { + "find-up": "^4.0.0" + } + }, "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", @@ -17488,14 +17496,14 @@ } }, "tslib": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz", - "integrity": "sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ==" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz", + "integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==" }, "tsutils": { - "version": "3.17.1", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.17.1.tgz", - "integrity": "sha512-kzeQ5B8H3w60nFY2g8cJIuH7JDpsALXySGtwGJ0p2LSjLgay3NdIpqq5SoOBe46bKDW2iq25irHCr8wjomUS2g==", + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.20.0.tgz", + "integrity": "sha512-RYbuQuvkhuqVeXweWT3tJLKOEJ/UUw9GjNEZGWdrLLlM+611o1gwLHBpxoFJKKl25fLprp2eVthtKs5JOrNeXg==", "dev": true, "requires": { "tslib": "^1.8.1" @@ -17581,6 +17589,12 @@ "is-typedarray": "^1.0.0" } }, + "typescript": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.1.3.tgz", + "integrity": "sha512-B3ZIOf1IKeH2ixgHhj6la6xdwR9QrLC5d1VKeCSY4tvkqhF2eqd9O7txNlS0PO3GrBAFIdr3L1ndNwteUbZLYg==", + "dev": true + }, "typographic-apostrophes": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/typographic-apostrophes/-/typographic-apostrophes-1.1.1.tgz", @@ -17809,9 +17823,9 @@ } }, "universalify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-1.0.0.tgz", - "integrity": "sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", "dev": true }, "unpipe": { @@ -17892,9 +17906,9 @@ } }, "uri-js": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.0.tgz", - "integrity": "sha512-B0yRTzYdUCCn9n+F4+Gh4yIDtMQcaJsmYBDsTSG8g/OejKBodLQ2IHfN3bM7jUsRXndopT7OIXWdYqc1fjmV6g==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "requires": { "punycode": "^2.1.0" } @@ -18045,9 +18059,9 @@ "dev": true }, "v8-to-istanbul": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-7.0.0.tgz", - "integrity": "sha512-fLL2rFuQpMtm9r8hrAV2apXX/WqHJ6+IC4/eQVdMDGBUgH/YMV4Gv3duk3kjmyg6uiQWBAA9nJwue4iJUOkHeA==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-7.1.0.tgz", + "integrity": "sha512-uXUVqNUCLa0AH1vuVxzi+MI4RfxEOKt9pBgKwHbgH7st8Kv2P1m+jvWNnektzBh5QShF3ODgKmUFCf38LnVz1g==", "dev": true, "requires": { "@types/istanbul-lib-coverage": "^2.0.1", @@ -18789,9 +18803,9 @@ } }, "webpack-dev-middleware": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-3.7.2.tgz", - "integrity": "sha512-1xC42LxbYoqLNAhV6YzTYacicgMZQTqRd27Sim9wn5hJrX3I5nxYy1SxSd4+gjUFsz1dQFj+yEe6zEVmSkeJjw==", + "version": "3.7.3", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-3.7.3.tgz", + "integrity": "sha512-djelc/zGiz9nZj/U7PTBi2ViorGJXEWo/3ltkPbDyxCXhhEXkW0ce99falaok4TPj+AsxLiXJR0EBOb0zh9fKQ==", "dev": true, "requires": { "memory-fs": "^0.4.1", @@ -18802,9 +18816,9 @@ }, "dependencies": { "mime": { - "version": "2.4.6", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.6.tgz", - "integrity": "sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.5.0.tgz", + "integrity": "sha512-ft3WayFSFUVBuJj7BMLKAQcSlItKtfjsKDDsii3rqFDAZ7t11zRe8ASw/GlmivGwVUYtwkQrxiGGpL6gFvB0ag==", "dev": true } } @@ -19805,9 +19819,9 @@ } }, "ws": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.1.tgz", - "integrity": "sha512-pTsP8UAfhy3sk1lSk/O/s4tjD0CRwvMnzvwr4OKGX7ZvqZtUyx4KIJB5JWbkykPoc55tixMGgTNoh3k4FkNGFQ==", + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.2.tgz", + "integrity": "sha512-T4tewALS3+qsrpGI/8dqNMLIVdq/g/85U98HPMa6F0m6xTbvhXU6RCQLqPH3+SlomNV/LdY6RXEbBpMH6EOJnA==", "dev": true }, "x-is-string": { diff --git a/package.json b/package.json index 848ad8fb..18441e65 100644 --- a/package.json +++ b/package.json @@ -13,11 +13,11 @@ "npm": ">=6" }, "dependencies": { - "@kentcdodds/react-workshop-app": "^2.20.0", - "@testing-library/react": "^11.2.2", - "@testing-library/user-event": "^12.5.0", + "@kentcdodds/react-workshop-app": "^3.0.0", + "@testing-library/react": "^11.2.3", + "@testing-library/user-event": "^12.6.2", "chalk": "^4.1.0", - "codegen.macro": "^4.0.0", + "codegen.macro": "^4.1.0", "mq-polyfill": "^1.1.8", "react": "^17.0.1", "react-dom": "^17.0.1", @@ -26,10 +26,11 @@ "devDependencies": { "@types/react": "^17.0.0", "@types/react-dom": "^17.0.0", - "husky": "^4.3.5", + "husky": "^4.3.8", "npm-run-all": "^4.1.5", "prettier": "^2.2.1", - "react-scripts": "^4.0.1" + "react-scripts": "^4.0.1", + "typescript": "^4.1.3" }, "scripts": { "start": "react-scripts start", @@ -53,7 +54,14 @@ ] }, "eslintConfig": { - "extends": "react-app" + "extends": [ + "react-app", + "react-app/jest", + "plugin:jsx-a11y/recommended" + ], + "plugins": [ + "jsx-a11y" + ] }, "babel": { "presets": [ diff --git a/public/mockServiceWorker.js b/public/mockServiceWorker.js index d8703e53..0de6bf7b 100644 --- a/public/mockServiceWorker.js +++ b/public/mockServiceWorker.js @@ -7,7 +7,7 @@ /* eslint-disable */ /* tslint:disable */ -const INTEGRITY_CHECKSUM = 'd1e0e502f550d40a34bee90822e4bf98' +const INTEGRITY_CHECKSUM = '7a54d6f8bbbda3fb393dcd9176d1fd19' const bypassHeaderName = 'x-msw-bypass' let clients = {} @@ -74,11 +74,23 @@ self.addEventListener('message', async function (event) { } }) -self.addEventListener('fetch', async function (event) { +self.addEventListener('fetch', function (event) { const { clientId, request } = event + const requestId = uuidv4() const requestClone = request.clone() const getOriginalResponse = () => fetch(requestClone) + // Bypass navigation requests. + if (request.mode === 'navigate') { + return + } + + // Bypass mocking if the current client isn't present in the internal clients map + // (i.e. has the mocking disabled). + if (!clients[clientId]) { + return + } + // Opening the DevTools triggers the "only-if-cached" request // that cannot be handled by the worker. Bypass such requests. if (request.cache === 'only-if-cached' && request.mode !== 'same-origin') { @@ -89,20 +101,15 @@ self.addEventListener('fetch', async function (event) { new Promise(async (resolve, reject) => { const client = await event.target.clients.get(clientId) - if ( - // Bypass mocking when no clients active - !client || - // Bypass mocking if the current client has mocking disabled - !clients[clientId] || - // Bypass mocking for navigation requests - request.mode === 'navigate' - ) { + // Bypass mocking when the request client is not active. + if (!client) { return resolve(getOriginalResponse()) } // Bypass requests with the explicit bypass header if (requestClone.headers.get(bypassHeaderName) === 'true') { const modifiedHeaders = serializeHeaders(requestClone.headers) + // Remove the bypass header to comply with the CORS preflight check delete modifiedHeaders[bypassHeaderName] @@ -119,6 +126,7 @@ self.addEventListener('fetch', async function (event) { const rawClientMessage = await sendToClient(client, { type: 'REQUEST', payload: { + id: requestId, url: request.url, method: request.method, headers: reqHeaders, @@ -180,14 +188,34 @@ If you wish to mock an error response, please refer to this guide: https://mswjs return resolve(createResponse(clientMessage)) } } - }).catch((error) => { - console.error( - '[MSW] Failed to mock a "%s" request to "%s": %s', - request.method, - request.url, - error, - ) - }), + }) + .then(async (response) => { + const client = await event.target.clients.get(clientId) + const clonedResponse = response.clone() + + sendToClient(client, { + type: 'RESPONSE', + payload: { + requestId, + ok: clonedResponse.ok, + status: clonedResponse.status, + statusText: clonedResponse.statusText, + body: await clonedResponse.text(), + headers: serializeHeaders(clonedResponse.headers), + redirected: clonedResponse.redirected, + }, + }) + + return response + }) + .catch((error) => { + console.error( + '[MSW] Failed to mock a "%s" request to "%s": %s', + request.method, + request.url, + error, + ) + }), ) }) @@ -233,3 +261,11 @@ function ensureKeys(keys, obj) { return acc }, {}) } + +function uuidv4() { + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { + const r = (Math.random() * 16) | 0 + const v = c == 'x' ? r : (r & 0x3) | 0x8 + return v.toString(16) + }) +} diff --git a/src/__tests__/01.js b/src/__tests__/01.tsx similarity index 85% rename from src/__tests__/01.js rename to src/__tests__/01.tsx index 623fa83d..dfca97b9 100644 --- a/src/__tests__/01.js +++ b/src/__tests__/01.tsx @@ -2,7 +2,8 @@ import * as React from 'react' import {alfredTip} from '@kentcdodds/react-workshop-app/test-utils' import {render} from '@testing-library/react' import userEvent from '@testing-library/user-event' -import App from '../final/01' +import App from '../final/01.js' +// import App from '../final-ts/01' // import App from '../exercise/01' test('clicking the button increments the count with useReducer', () => { @@ -10,6 +11,9 @@ test('clicking the button increments the count with useReducer', () => { const {container} = render() const button = container.querySelector('button') + if (!button) { + throw new Error('button node is null') + } userEvent.click(button) expect(button).toHaveTextContent('1') userEvent.click(button) diff --git a/src/__tests__/02.extra-3.js b/src/__tests__/02.extra-3.tsx similarity index 83% rename from src/__tests__/02.extra-3.js rename to src/__tests__/02.extra-3.tsx index ef89295b..4f564915 100644 --- a/src/__tests__/02.extra-3.js +++ b/src/__tests__/02.extra-3.tsx @@ -3,16 +3,20 @@ import {alfredTip} from '@kentcdodds/react-workshop-app/test-utils' import {render, screen} from '@testing-library/react' import userEvent from '@testing-library/user-event' import App from '../final/02.extra-3' +// import App from '../final-ts/02.extra-3' // import App from '../exercise/02' +let fetchSpy: jest.SpyInstance +let consoleErrorSpy: jest.SpyInstance + beforeEach(() => { - jest.spyOn(window, 'fetch') - jest.spyOn(console, 'error') + fetchSpy = jest.spyOn(window, 'fetch') + consoleErrorSpy = jest.spyOn(console, 'error') }) afterEach(() => { - window.fetch.mockRestore() - console.error.mockRestore() + fetchSpy.mockRestore() + consoleErrorSpy.mockRestore() }) test('displays the pokemon', async () => { @@ -34,7 +38,7 @@ test('displays the pokemon', async () => { await screen.findByRole('heading', {name: /ditto/i}) // verify that when props remain the same a request is not made - window.fetch.mockClear() + fetchSpy.mockClear() userEvent.click(submit) @@ -46,7 +50,7 @@ test('displays the pokemon', async () => { ) // verify error handling - console.error.mockImplementation(() => {}) + consoleErrorSpy.mockImplementation(() => {}) userEvent.clear(input) userEvent.type(input, 'george') @@ -56,7 +60,7 @@ test('displays the pokemon', async () => { ) expect(console.error).toHaveBeenCalledTimes(2) - console.error.mockReset() + consoleErrorSpy.mockReset() userEvent.type(input, 'mew') userEvent.click(submit) diff --git a/src/__tests__/02.js b/src/__tests__/02.tsx similarity index 78% rename from src/__tests__/02.js rename to src/__tests__/02.tsx index 9c1b290e..4b88b9ad 100644 --- a/src/__tests__/02.js +++ b/src/__tests__/02.tsx @@ -2,17 +2,21 @@ import * as React from 'react' import {alfredTip} from '@kentcdodds/react-workshop-app/test-utils' import {render, screen} from '@testing-library/react' import userEvent from '@testing-library/user-event' -import App from '../final/02' +// import App from '../final/02' +import App from '../final-ts/02' // import App from '../exercise/02' +let spyFetch: jest.SpyInstance +let spyConsoleError: jest.SpyInstance + beforeEach(() => { - jest.spyOn(window, 'fetch') - jest.spyOn(console, 'error') + spyFetch = jest.spyOn(window, 'fetch') + spyConsoleError = jest.spyOn(console, 'error') }) afterEach(() => { - window.fetch.mockRestore() - console.error.mockRestore() + spyFetch.mockRestore() + spyConsoleError.mockRestore() }) test('displays the pokemon', async () => { @@ -34,7 +38,7 @@ test('displays the pokemon', async () => { await screen.findByRole('heading', {name: /ditto/i}) // verify that when props remain the same a request is not made - window.fetch.mockClear() + spyFetch.mockClear() userEvent.click(submit) @@ -46,7 +50,7 @@ test('displays the pokemon', async () => { ) // verify error handling - console.error.mockImplementation(() => {}) + spyConsoleError.mockImplementation(() => {}) userEvent.clear(input) userEvent.type(input, 'george') @@ -56,5 +60,5 @@ test('displays the pokemon', async () => { ) expect(console.error).toHaveBeenCalledTimes(2) - console.error.mockReset() + spyConsoleError.mockReset() }) diff --git a/src/__tests__/03.extra-2.js b/src/__tests__/03.extra-2.tsx similarity index 81% rename from src/__tests__/03.extra-2.js rename to src/__tests__/03.extra-2.tsx index 45281b4c..7d35aef9 100644 --- a/src/__tests__/03.extra-2.js +++ b/src/__tests__/03.extra-2.tsx @@ -3,16 +3,20 @@ import {alfredTip} from '@kentcdodds/react-workshop-app/test-utils' import {render, screen} from '@testing-library/react' import userEvent from '@testing-library/user-event' import App from '../final/03.extra-2' +// import App from '../final-ts/03.extra-2' // import App from '../exercise/03.extra-2' +let fetchSpy: jest.SpyInstance +let consoleErrorSpy: jest.SpyInstance + beforeEach(() => { - jest.spyOn(window, 'fetch') - jest.spyOn(console, 'error') + fetchSpy = jest.spyOn(window, 'fetch') + consoleErrorSpy = jest.spyOn(console, 'error') }) afterEach(() => { - window.fetch.mockRestore() - console.error.mockRestore() + fetchSpy.mockRestore() + consoleErrorSpy.mockRestore() }) test('displays the pokemon', async () => { @@ -34,7 +38,7 @@ test('displays the pokemon', async () => { await screen.findByRole('heading', {name: /ditto/i}) // verify that when props remain the same a request is not made - window.fetch.mockClear() + fetchSpy.mockClear() userEvent.click(submit) @@ -46,7 +50,7 @@ test('displays the pokemon', async () => { ) // verify error handling - console.error.mockImplementation(() => {}) + consoleErrorSpy.mockImplementation(() => {}) userEvent.clear(input) userEvent.type(input, 'george') @@ -56,8 +60,8 @@ test('displays the pokemon', async () => { ) expect(console.error).toHaveBeenCalledTimes(2) - console.error.mockReset() - window.fetch.mockClear() + consoleErrorSpy.mockReset() + fetchSpy.mockClear() // use the cached value userEvent.click(screen.getByRole('button', {name: /ditto/i})) diff --git a/src/__tests__/03.js b/src/__tests__/03.tsx similarity index 94% rename from src/__tests__/03.js rename to src/__tests__/03.tsx index d79c9f1e..c44dc591 100644 --- a/src/__tests__/03.js +++ b/src/__tests__/03.tsx @@ -2,6 +2,7 @@ import * as React from 'react' import {render, screen} from '@testing-library/react' import userEvent from '@testing-library/user-event' import App from '../final/03' +// import App from '../final-ts/03' // import App from '../exercise/03' test('clicking the button increments the count', () => { diff --git a/src/__tests__/04.js b/src/__tests__/04.tsx similarity index 94% rename from src/__tests__/04.js rename to src/__tests__/04.tsx index e97fc4ec..d1add309 100644 --- a/src/__tests__/04.js +++ b/src/__tests__/04.tsx @@ -2,6 +2,7 @@ import * as React from 'react' import {render} from '@testing-library/react' import userEvent from '@testing-library/user-event' import App from '../final/04' +// import App from '../final-ts/04' // import App from '../exercise/04' test('adds and removes children from the log', () => { @@ -24,12 +25,12 @@ test('scrolls to the bottom', () => { const scrollTopSetter = jest.fn() Object.defineProperties(log, { scrollHeight: { - get() { + get(): number { return 100 }, }, scrollTop: { - get() { + get(): number { return 0 }, set: scrollTopSetter, diff --git a/src/__tests__/05.js b/src/__tests__/05.tsx similarity index 97% rename from src/__tests__/05.js rename to src/__tests__/05.tsx index 02bd4326..85e9aaeb 100644 --- a/src/__tests__/05.js +++ b/src/__tests__/05.tsx @@ -2,6 +2,7 @@ import * as React from 'react' import {render} from '@testing-library/react' import userEvent from '@testing-library/user-event' import App from '../final/05' +// import App from '../final-ts/05' // import App from '../exercise/05' test('adds and removes children from the log', () => { diff --git a/src/__tests__/06.js b/src/__tests__/06.tsx similarity index 98% rename from src/__tests__/06.js rename to src/__tests__/06.tsx index 8ccdf5bd..6d68b9ae 100644 --- a/src/__tests__/06.js +++ b/src/__tests__/06.tsx @@ -3,6 +3,7 @@ import * as React from 'react' import {alfredTip} from '@kentcdodds/react-workshop-app/test-utils' import {render, act} from '@testing-library/react' import App from '../final/06' +// import App from '../final-ts/06' // import App from '../exercise/06' beforeAll(() => { diff --git a/src/exercise/03.extra-2.js b/src/exercise/03.extra-2.js index 29d9cd56..9d491a4d 100644 --- a/src/exercise/03.extra-2.js +++ b/src/exercise/03.extra-2.js @@ -40,8 +40,8 @@ function PokemonInfo({pokemonName}) { const [cache, dispatch] = React.useReducer(pokemonCacheReducer, {}) // 🐨 get the cache and dispatch from useContext with PokemonCacheContext - const {data: pokemon, status, error, run, setData} = useAsync() - + const {state, run, setData} = useAsync() + const {data: pokemon, status, error} = state React.useEffect(() => { if (!pokemonName) { return diff --git a/src/final-ts/01.extra-1.tsx b/src/final-ts/01.extra-1.tsx new file mode 100644 index 00000000..81b71c33 --- /dev/null +++ b/src/final-ts/01.extra-1.tsx @@ -0,0 +1,35 @@ +// useReducer: simple Counter +// 💯 accept the step as the action +// http://localhost:3000/isolated/final-ts/01.extra-1.tsx + +import * as React from 'react' + +type State = number + + +type Reducer = (state: State, action: Action) => State + +// React also export the same type definition at `React.Reducer` and it has the +// exact same api +const countReducer: Reducer = (count, change) => count + change + +//#region Counter +interface CounterProps { + initialCount?: number + step?: number +} +const Counter: React.VFC = ({initialCount = 0, step = 1}) => { + const [count, changeCount] = React.useReducer(countReducer, initialCount) + const increment: React.MouseEventHandler = () => + changeCount(step) + return +} +//#endregion Counter + +//#region Usage +const Usage: React.VFC = () => { + return +} +//#endregion Usage + +export default Usage diff --git a/src/final-ts/01.extra-2.tsx b/src/final-ts/01.extra-2.tsx new file mode 100644 index 00000000..d99abd7a --- /dev/null +++ b/src/final-ts/01.extra-2.tsx @@ -0,0 +1,39 @@ +// useReducer: simple Counter +// 💯 simulate setState with an object +// http://localhost:3000/isolated/final-ts/01.extra-2.tsx + +import * as React from 'react' + +//#region reducer +interface State { + count: number +} + +const countReducer: React.Reducer = (state, action) => ({ + ...state, + ...action, +}) +//#endregion reducer + +//#region Counter +interface CounterProps { + initialCount?: number + step?: number +} +const Counter: React.VFC = ({initialCount = 0, step = 1}) => { + const initialState: State = {count: initialCount} + const [state, setState] = React.useReducer(countReducer, initialState) + const {count} = state + const increment: React.MouseEventHandler = () => + setState({count: count + step}) + return +} +//#endregion Counter + +//#region App +const App: React.VFC = () => { + return +} +//#endregion App + +export default App diff --git a/src/final-ts/01.extra-3.tsx b/src/final-ts/01.extra-3.tsx new file mode 100644 index 00000000..ea4fab5a --- /dev/null +++ b/src/final-ts/01.extra-3.tsx @@ -0,0 +1,40 @@ +// useReducer: simple Counter +// 💯 simulate setState with an object OR function +// http://localhost:3000/isolated/final-ts/01.extra-3.tsx + +import * as React from 'react' + +//#region reducer +interface State { + count: number +} +type Action = State | ((prevState: State) => State) + +const countReducer: React.Reducer = (state, action) => ({ + ...state, + ...(typeof action === 'function' ? action(state) : action), +}) +//#endregion reducer + +//#region Counter +interface CounterProps { + initialCount?: number + step?: number +} +const Counter: React.VFC = ({initialCount = 0, step = 1}) => { + const initialState: State = {count: initialCount} + const [state, setState] = React.useReducer(countReducer, initialState) + const {count} = state + const increment: React.MouseEventHandler = () => + setState(currentState => ({count: currentState.count + step})) + return +} +//#endregion Counter + +//#region App +const App: React.VFC = () => { + return +} +//#endregion App + +export default App diff --git a/src/final-ts/01.extra-4.tsx b/src/final-ts/01.extra-4.tsx new file mode 100644 index 00000000..81792561 --- /dev/null +++ b/src/final-ts/01.extra-4.tsx @@ -0,0 +1,73 @@ +// useReducer: simple Counter +// 💯 traditional dispatch object with a type and switch statement +// http://localhost:3000/isolated/final-ts/01.extra-4.tsx + +import * as React from 'react' + +//#region reducer + +interface State { + count: number +} + +/** + * an utility type for building Action types. + * Definitely overkill for this small example, but quite common in the real world + */ +interface ActionType { + type: Type +} + +interface IncrementAction extends ActionType<'increment'> { + step: number +} + +interface DecrementAction extends ActionType<'decrement'> { + step: number +} + +/** + * Action type is defined as the discriminated union type of all the custom + * action that you have defined. + * This ensure that you can't dispatch a misshaped Action by accident. + */ +type Action = IncrementAction | DecrementAction + +const countReducer: React.Reducer = (state, action) => { + const {type, step} = action + switch (type) { + case 'increment': { + return { + ...state, + count: state.count + step, + } + } + default: { + throw new Error(`Unsupported action type: ${action.type}`) + } + } +} +//#endregion reducer + +//#region Counter +interface CounterProps { + initialCount?: number + step?: number +} +const Counter: React.VFC = ({initialCount = 0, step = 1}) => { + const initialState: State = {count: initialCount} + const [state, dispatch] = React.useReducer(countReducer, initialState) + const {count} = state + const increment: React.MouseEventHandler = () => + dispatch({type: 'increment', step}) + return +} +//#endregion Counter + +//#region App +const App: React.VFC = () => { + return +} +//#endregion App + +export default App diff --git a/src/final-ts/01.tsx b/src/final-ts/01.tsx new file mode 100644 index 00000000..d0e28950 --- /dev/null +++ b/src/final-ts/01.tsx @@ -0,0 +1,31 @@ +// useReducer: simple Counter +// http://localhost:3000/isolated/final-ts/01.tsx + +import * as React from 'react' + +type State = number + +const countReducer = (state: State, newState: State): State => newState + +//#region Counter +interface CounterProps { + initialCount?: number + step?: number +} +const Counter: React.VFC = ({initialCount = 0, step = 1}) => { + const [count, setCount] = React.useReducer(countReducer, initialCount) + + const increment: React.MouseEventHandler = () => + setCount(count + step) + + return +} +//#endregion Counter + +//#region App +const App: React.VFC = () => { + return +} +//#endregion App + +export default App diff --git a/src/final-ts/02.extra-1.tsx b/src/final-ts/02.extra-1.tsx new file mode 100644 index 00000000..52484626 --- /dev/null +++ b/src/final-ts/02.extra-1.tsx @@ -0,0 +1,202 @@ +// useCallback: custom hooks +// 💯 use useCallback to empower the user to customize memoization +// http://localhost:3000/isolated/final-ts/02.extra-1.tsx + +import * as React from 'react' +import { + fetchPokemon, + PokemonForm, + PokemonDataView, + PokemonInfoFallback, + PokemonErrorBoundary, +} from '../pokemon' + +//#region useAsync: a generic custom hook that would live in a separate module +export enum Status { + IDLE = 'idle', + PENDING = 'pending', + RESOLVED = 'resolved', + REJECTED = 'rejected', +} +interface FiniteState< + Status extends 'idle' | 'pending' | 'resolved' | 'rejected', + Data extends unknown = null, + Err extends null | Error = null +> { + status: Status + data: Data + error: Err +} + +type State = + | FiniteState<'idle'> + | FiniteState<'pending'> + | FiniteState<'resolved', Data> + | FiniteState<'rejected', null, Error> + +interface ActionType< + Type extends 'idle' | 'pending' | 'resolved' | 'rejected' +> { + type: Type +} + +interface ActionIdle extends ActionType {} +interface ActionPending extends ActionType {} + +interface ActionResolved extends ActionType { + data: Data +} + +interface ActionRejected extends ActionType { + error: Error +} + +type Action = + | ActionIdle + | ActionPending + | ActionResolved + | ActionRejected + +type AsyncReducer = React.Reducer, Action> +function asyncReducer( + state: State, + action: Action, +): State { + switch (action.type) { + case Status.IDLE: + return { + status: Status.IDLE, + data: null, + error: null, + } + + case Status.PENDING: + return { + status: Status.PENDING, + data: null, + error: null, + } + + case Status.RESOLVED: + return { + status: Status.RESOLVED, + data: action.data, + error: null, + } + + case Status.REJECTED: + return { + status: Status.REJECTED, + data: null, + error: action.error, + } + + default: { + // @ts-ignore: exhaustive fallthrough checks: Property 'type' does not exist on type 'never'. + throw new Error(`Unhandled action type: ${action.type}`) + } + } +} + +export function useAsync( + asyncCallback: () => void | Promise, + initialState: State, +): State { + const [state, dispatch] = React.useReducer>(asyncReducer, { + // @ts-expect-error: 'status' is specified more than once, so this usage will be overwritten. + status: Status.IDLE, + data: null, + error: null, + ...initialState, + }) + + React.useEffect(() => { + const promise = asyncCallback() + if (!promise) return + dispatch({type: Status.PENDING}) + promise.then( + data => dispatch({type: Status.RESOLVED, data}), + error => dispatch({type: Status.REJECTED, error}), + ) + }, [asyncCallback]) + + return state +} +//#endregion useAsync + +//#region PokemonInfo +const PokemonInfo: React.VFC<{pokemonName?: string}> = ({pokemonName}) => { + const asyncCallback = React.useCallback(() => { + if (!pokemonName) return + return fetchPokemon(pokemonName) + }, [pokemonName]) + + const state = useAsync(asyncCallback, { + status: pokemonName ? 'pending' : 'idle', + error: null, + data: null, + }) + + switch (state.status) { + case 'idle': + return <>Submit a pokemon + + case 'pending': + return + + case 'rejected': + throw state.error + + case 'resolved': + return + + default: + throw new Error('This should be impossible') + } +} + +//#endregion PokemonInfo + +function App(): JSX.Element { + const [pokemonName, setPokemonName] = React.useState('') + + function handleSubmit(newPokemonName: string): void { + setPokemonName(newPokemonName) + } + + function handleReset(): void { + setPokemonName('') + } + + return ( +
+ +
+
+ + + +
+
+ ) +} + +function AppWithUnmountCheckbox(): JSX.Element { + const [mountApp, setMountApp] = React.useState(true) + return ( +
+ +
+ {mountApp ? : null} +
+ ) +} + +export default AppWithUnmountCheckbox diff --git a/src/final-ts/02.extra-2.tsx b/src/final-ts/02.extra-2.tsx new file mode 100644 index 00000000..dfa35cb2 --- /dev/null +++ b/src/final-ts/02.extra-2.tsx @@ -0,0 +1,200 @@ +// useCallback: custom hooks +// 💯 return a memoized `run` function from useAsync +// http://localhost:3000/isolated/final-ts/02.extra-2.tsx + +import * as React from 'react' +import { + fetchPokemon, + PokemonForm, + PokemonDataView, + PokemonInfoFallback, + PokemonErrorBoundary, + IPokemon, +} from '../pokemon' + +//#region useAsync: a generic custom hook that would live in a separate module +export enum Status { + IDLE = 'idle', + PENDING = 'pending', + RESOLVED = 'resolved', + REJECTED = 'rejected', +} +interface FiniteState< + Status extends 'idle' | 'pending' | 'resolved' | 'rejected', + Data extends unknown = null, + Err extends null | Error = null +> { + status: Status + data: Data + error: Err +} + +type State = + | FiniteState<'idle'> + | FiniteState<'pending'> + | FiniteState<'resolved', Data> + | FiniteState<'rejected', null, Error> + +interface ActionType< + Type extends 'idle' | 'pending' | 'resolved' | 'rejected' +> { + type: Type +} + +interface ActionIdle extends ActionType {} +interface ActionPending extends ActionType {} + +interface ActionResolved extends ActionType { + data: Data +} + +interface ActionRejected extends ActionType { + error: Error +} + +type Action = + | ActionIdle + | ActionPending + | ActionResolved + | ActionRejected + +type AsyncReducer = React.Reducer, Action> +function asyncReducer( + state: State, + action: Action, +): State { + switch (action.type) { + case Status.IDLE: + return { + status: Status.IDLE, + data: null, + error: null, + } + + case Status.PENDING: + return { + status: Status.PENDING, + data: null, + error: null, + } + + case Status.RESOLVED: + return { + status: Status.RESOLVED, + data: action.data, + error: null, + } + + case Status.REJECTED: + return { + status: Status.REJECTED, + data: null, + error: action.error, + } + + default: { + // @ts-ignore: exhaustive fallthrough checks: Property 'type' does not exist on type 'never'. + throw new Error(`Unhandled action type: ${action.type}`) + } + } +} + +export function useAsync( + initialState: State, +): {state: State; run: (promise: Promise) => void} { + const [state, dispatch] = React.useReducer>(asyncReducer, { + // @ts-expect-error: 'status' is specified more than once, so this usage will be overwritten. + status: Status.IDLE, + data: null, + error: null, + ...initialState, + }) + + const run = React.useCallback((promise: Promise) => { + dispatch({type: Status.PENDING}) + promise.then( + data => dispatch({type: Status.RESOLVED, data}), + error => dispatch({type: Status.REJECTED, error}), + ) + }, []) + + return {state, run} +} +//#endregion useAsync + +//#region PokemonInfo +const PokemonInfo: React.VFC<{pokemonName?: string}> = ({pokemonName}) => { + const {state, run} = useAsync({ + status: pokemonName ? 'pending' : 'idle', + error: null, + data: null, + }) + + React.useEffect(() => { + if (!pokemonName) return + run(fetchPokemon(pokemonName)) + }, [pokemonName, run]) + + switch (state.status) { + case 'idle': + return <>Submit a pokemon + + case 'pending': + return + + case 'rejected': + throw state.error + + case 'resolved': + return + + default: + throw new Error('This should be impossible') + } +} + +//#endregion PokemonInfo + +function App(): JSX.Element { + const [pokemonName, setPokemonName] = React.useState('') + + function handleSubmit(newPokemonName: string): void { + setPokemonName(newPokemonName) + } + + function handleReset(): void { + setPokemonName('') + } + + return ( +
+ +
+
+ + + +
+
+ ) +} + +function AppWithUnmountCheckbox(): JSX.Element { + const [mountApp, setMountApp] = React.useState(true) + return ( +
+ +
+ {mountApp ? : null} +
+ ) +} + +export default AppWithUnmountCheckbox diff --git a/src/final-ts/02.extra-3.tsx b/src/final-ts/02.extra-3.tsx new file mode 100644 index 00000000..f146bda4 --- /dev/null +++ b/src/final-ts/02.extra-3.tsx @@ -0,0 +1,248 @@ +// useCallback: custom hooks +// 💯 make safeDispatch with useCallback, useRef, and useEffect +// http://localhost:3000/isolated/final-ts/02.extra-3.tsx + +import * as React from 'react' +import { + fetchPokemon, + PokemonForm, + PokemonDataView, + PokemonInfoFallback, + PokemonErrorBoundary, + IPokemon, +} from '../pokemon' + +//#region useAsync: a generic custom hook that would live in a separate module +export enum Status { + IDLE = 'idle', + PENDING = 'pending', + RESOLVED = 'resolved', + REJECTED = 'rejected', +} +interface FiniteState< + Status extends 'idle' | 'pending' | 'resolved' | 'rejected', + Data extends unknown = null, + Err extends null | Error = null +> { + status: Status + data: Data + error: Err +} + +type State = + | FiniteState<'idle'> + | FiniteState<'pending'> + | FiniteState<'resolved', Data> + | FiniteState<'rejected', null, Error> + +interface ActionType< + Type extends 'idle' | 'pending' | 'resolved' | 'rejected' +> { + type: Type +} + +interface ActionIdle extends ActionType {} +interface ActionPending extends ActionType {} + +interface ActionResolved extends ActionType { + data: Data +} + +interface ActionRejected extends ActionType { + error: Error +} + +type Action = + | ActionIdle + | ActionPending + | ActionResolved + | ActionRejected + +function useSafeDispatch void>( + dispatch: Dispatch, +): Dispatch { + const mountedRef = React.useRef(false) + + // to make this even more generic you should use the useLayoutEffect hook to + // make sure that you are correctly setting the mountedRef.current immediately + // after React updates the DOM. Even though this effect does not interact + // with the dom another side effect inside a useLayoutEffect which does + // interact with the dom may depend on the value being set + React.useEffect(() => { + mountedRef.current = true + return () => { + mountedRef.current = false + } + }, []) + + // eslint react-hooks/exhaustive-deps rule unfortunately is not abel to parse + // through this typescript type casting syntax here, and therefore is raising + // a warning. Do not worry, everything is still fine and dandy! + // eslint-disable-next-line + return React.useCallback( + ((...args) => { + if (mountedRef.current) { + dispatch(...args) + } + }) as Dispatch, + [dispatch], + ) +} + +type AsyncReducer = React.Reducer, Action> +function asyncReducer( + state: State, + action: Action, +): State { + switch (action.type) { + case Status.IDLE: + return { + status: Status.IDLE, + data: null, + error: null, + } + + case Status.PENDING: + return { + status: Status.PENDING, + data: null, + error: null, + } + + case Status.RESOLVED: + return { + status: Status.RESOLVED, + data: action.data, + error: null, + } + + case Status.REJECTED: + return { + status: Status.REJECTED, + data: null, + error: action.error, + } + + default: { + // @ts-ignore: exhaustive fallthrough checks: Property 'type' does not exist on type 'never'. + throw new Error(`Unhandled action type: ${action.type}`) + } + } +} + +function useAsync( + initialState: State, +): { + state: State + run: (promise: Promise) => void +} { + const [state, unsafeDispatch] = React.useReducer>( + asyncReducer, + { + // @ts-expect-error: 'status' is specified more than once, so this usage will be overwritten. + status: Status.IDLE, + data: null, + error: null, + ...initialState, + }, + ) + + const dispatch = useSafeDispatch(unsafeDispatch) + + const run = React.useCallback( + (promise: Promise): void => { + dispatch({type: Status.PENDING}) + promise.then( + data => { + dispatch({type: Status.RESOLVED, data}) + }, + error => { + dispatch({type: Status.REJECTED, error}) + }, + ) + }, + [dispatch], + ) + + return {state, run} +} + +//#region PokemonInfo +const PokemonInfo: React.VFC<{pokemonName?: string}> = ({pokemonName}) => { + const {state, run} = useAsync({ + status: pokemonName ? 'pending' : 'idle', + data: null, + error: null, + }) + + React.useEffect(() => { + if (!pokemonName) { + return + } + run(fetchPokemon(pokemonName)) + }, [pokemonName, run]) + + switch (state.status) { + case 'idle': + return <>Submit a pokemon + + case 'pending': + return + + case 'rejected': + throw state.error + + case 'resolved': + return + + default: + throw new Error('This should be impossible') + } +} +//#endregion useAsync + +//#endregion PokemonInfo + +function App(): JSX.Element { + const [pokemonName, setPokemonName] = React.useState('') + + function handleSubmit(newPokemonName: string): void { + setPokemonName(newPokemonName) + } + + function handleReset(): void { + setPokemonName('') + } + + return ( +
+ +
+
+ + + +
+
+ ) +} + +function AppWithUnmountCheckbox(): JSX.Element { + const [mountApp, setMountApp] = React.useState(true) + return ( +
+ +
+ {mountApp ? : null} +
+ ) +} + +export default AppWithUnmountCheckbox diff --git a/src/final-ts/02.tsx b/src/final-ts/02.tsx new file mode 100644 index 00000000..d2b98331 --- /dev/null +++ b/src/final-ts/02.tsx @@ -0,0 +1,203 @@ +// useCallback: custom hooks +// http://localhost:3000/isolated/final-ts/02.tsx + +import * as React from 'react' +import { + fetchPokemon, + PokemonForm, + PokemonDataView, + PokemonInfoFallback, + PokemonErrorBoundary, +} from '../pokemon' + + +//#region useAsync: a generic custom hook that would live in a separate module +export enum Status { + IDLE = 'idle', + PENDING = 'pending', + RESOLVED = 'resolved', + REJECTED = 'rejected', +} +interface FiniteState< + Status extends 'idle' | 'pending' | 'resolved' | 'rejected', + Data extends unknown = null, + Err extends null | Error = null +> { + status: Status + data: Data + error: Err +} + +type State = + | FiniteState<'idle'> + | FiniteState<'pending'> + | FiniteState<'resolved', Data> + | FiniteState<'rejected', null, Error> + +interface ActionType< + Type extends 'idle' | 'pending' | 'resolved' | 'rejected' +> { + type: Type +} + +interface ActionIdle extends ActionType {} +interface ActionPending extends ActionType {} + +interface ActionResolved extends ActionType { + data: Data +} + +interface ActionRejected extends ActionType { + error: Error +} + +type Action = + | ActionIdle + | ActionPending + | ActionResolved + | ActionRejected + +type AsyncReducer = React.Reducer, Action> +function asyncReducer( + state: State, + action: Action, +): State { + switch (action.type) { + case Status.IDLE: + return { + status: Status.IDLE, + data: null, + error: null, + } + + case Status.PENDING: + return { + status: Status.PENDING, + data: null, + error: null, + } + + case Status.RESOLVED: + return { + status: Status.RESOLVED, + data: action.data, + error: null, + } + + case Status.REJECTED: + return { + status: Status.REJECTED, + data: null, + error: action.error, + } + + default: { + // @ts-ignore: exhaustive fallthrough checks: Property 'type' does not exist on type 'never'. + throw new Error(`Unhandled action type: ${action.type}`) + } + } +} + +export function useAsync( + asyncCallback: () => void | Promise, + initialState: State, + dependencies: ReadonlyArray, +): State { + const [state, dispatch] = React.useReducer>(asyncReducer, { + // @ts-expect-error: 'status' is specified more than once, so this usage will be overwritten. + status: Status.IDLE, + data: null, + error: null, + ...initialState, + }) + + React.useEffect(() => { + const promise = asyncCallback() + if (!promise) return + dispatch({type: Status.PENDING}) + promise.then( + data => dispatch({type: Status.RESOLVED, data}), + error => dispatch({type: Status.REJECTED, error}), + ) + // too bad the eslint plugin can't statically analyze this :-( + // eslint-disable-next-line react-hooks/exhaustive-deps + }, dependencies) + + return state +} +//#endregion useAsync + +//#region PokemonInfo +const PokemonInfo: React.VFC<{pokemonName?: string}> = ({pokemonName}) => { + const state = useAsync( + () => { + if (!pokemonName) return + return fetchPokemon(pokemonName) + }, + {status: pokemonName ? 'pending' : 'idle', error: null, data: null}, + [pokemonName], + ) + + switch (state.status) { + case 'idle': + return <>Submit a pokemon + + case 'pending': + return + + case 'rejected': + throw state.error + + case 'resolved': + return + + default: + throw new Error('This should be impossible') + } +} + +//#endregion PokemonInfo + +function App(): JSX.Element { + const [pokemonName, setPokemonName] = React.useState('') + + function handleSubmit(newPokemonName: string): void { + setPokemonName(newPokemonName) + } + + function handleReset(): void { + setPokemonName('') + } + + return ( +
+ +
+
+ + + +
+
+ ) +} + +function AppWithUnmountCheckbox(): JSX.Element { + const [mountApp, setMountApp] = React.useState(true) + return ( +
+ +
+ {mountApp ? : null} +
+ ) +} + +export default AppWithUnmountCheckbox diff --git a/src/final-ts/03.extra-1.tsx b/src/final-ts/03.extra-1.tsx new file mode 100644 index 00000000..85a9c621 --- /dev/null +++ b/src/final-ts/03.extra-1.tsx @@ -0,0 +1,56 @@ +// useContext: simple Counter +// 💯 create a consumer hook +// http://localhost:3000/isolated/final-ts/03.extra-1.tsx + +import * as React from 'react' + +type CounterContextInterface = readonly [ + number, + React.Dispatch>, +] + +// in JS calling React.createContext() with no argument doesn't raise any issue, +// in Typescript land thought, the compiler will yell at you! +// In fact, said function is defined as such: +// ``` function createContext(defaultValue: T ): Context ``` +// where the defaultValue argument is required. if you want to read morea about it +// this is the issue discussion: https://github.com/DefinitelyTyped/DefinitelyTyped/pull/24509#issuecomment-382213106 +// The possible options are listed in theses examples: https://react-typescript-cheatsheet.netlify.app/docs/basic/getting-started/context#extended-example +const CountContext = React.createContext(undefined!) + +const CountProvider: React.FC = props => { + const value = React.useState(0) + return +} + +function useCount(): CounterContextInterface { + const context = React.useContext(CountContext) + if (!context) { + throw new Error('useCount must be used within a CountProvider') + } + return context +} + +function CountDisplay(): JSX.Element { + const [count] = useCount() + return
{`The current count is ${count}`}
+} + +function Counter(): JSX.Element { + const [, setCount] = useCount() + const increment = () => setCount(c => c + 1) + return +} + +function App(): JSX.Element { + return ( +
+ + + + +
+ ) +} + +export default App diff --git a/src/final-ts/03.extra-2.tsx b/src/final-ts/03.extra-2.tsx new file mode 100644 index 00000000..ca158a47 --- /dev/null +++ b/src/final-ts/03.extra-2.tsx @@ -0,0 +1,181 @@ +// useContext: Caching response data in context +// 💯 caching in a context provider (final) +// http://localhost:3000/isolated/final-ts/03.extra-2.tsx + +// you can edit this here and look at the isolated page or you can copy/paste +// this in the regular exercise file. + +import * as React from 'react' +import {useAsync} from '../utils' +import { + fetchPokemon, + PokemonForm, + PokemonDataView, + PokemonInfoFallback, + PokemonErrorBoundary, + IPokemon, +} from '../pokemon' + +type PokemonCacheContextInterface = readonly [ + cache: Record, + dispatch: React.Dispatch, +] +const PokemonCacheContext = React.createContext( + undefined!, +) + +type PokemonName = string +type State = Record +type Action = { + type: 'ADD_POKEMON' + pokemonData: IPokemon + pokemonName: PokemonName +} +const pokemonCacheReducer: React.Reducer = (state, action) => { + switch (action.type) { + case 'ADD_POKEMON': { + return {...state, [action.pokemonName]: action.pokemonData} + } + default: { + throw new Error(`Unhandled action type: ${action.type}`) + } + } +} + +function PokemonCacheProvider(props: { + children?: React.ReactNode +}): JSX.Element { + const [cache, dispatch] = React.useReducer(pokemonCacheReducer, {}) + return ( + + ) +} + +function usePokemonCache(): PokemonCacheContextInterface { + const context = React.useContext(PokemonCacheContext) + if (!context) { + throw new Error( + 'usePokemonCache must be used within a PokemonCacheProvider', + ) + } + return context +} + +interface PokemonInfoProps { + pokemonName: string +} +function PokemonInfo({pokemonName}: PokemonInfoProps): JSX.Element { + const [cache, dispatch] = usePokemonCache() + + const {state, run, setData} = useAsync({ + status: pokemonName ? 'pending' : 'idle', + error: null, + data: null, + }) + + React.useEffect(() => { + if (!pokemonName) { + return + } else if (cache[pokemonName]) { + setData(cache[pokemonName]) + } else { + run( + fetchPokemon(pokemonName).then(pokemonData => { + dispatch({type: 'ADD_POKEMON', pokemonName, pokemonData}) + return pokemonData + }), + ) + } + }, [cache, dispatch, pokemonName, run, setData]) + + switch (state.status) { + case 'idle': + return <>Submit a pokemon + + case 'pending': + return + + case 'rejected': + throw state.error + + case 'resolved': + return + + default: + throw new Error('This should be impossible') + } +} + +interface PreviousPokemonProps { + onSelect: (pokemonName: string) => void +} +function PreviousPokemon({onSelect}: PreviousPokemonProps): JSX.Element { + const [cache] = usePokemonCache() + return ( +
+ Previous Pokemon +
    + {Object.keys(cache).map(pokemonName => ( +
  • + +
  • + ))} +
+
+ ) +} + +interface PokemonSectionProps { + onSelect: (pokemonName: string) => void + pokemonName: string +} +function PokemonSection({ + onSelect, + pokemonName, +}: PokemonSectionProps): JSX.Element { + return ( + +
+ +
+ onSelect('')} + resetKeys={[pokemonName]} + > + + +
+
+
+ ) +} + +function App(): JSX.Element { + const [pokemonName, setPokemonName] = React.useState('') + + function handleSubmit(newPokemonName: string): void { + setPokemonName(newPokemonName) + } + + function handleSelect(newPokemonName: string): void { + setPokemonName(newPokemonName) + } + + return ( +
+ +
+ +
+ ) +} + +export default App diff --git a/src/final-ts/03.tsx b/src/final-ts/03.tsx new file mode 100644 index 00000000..98802fdc --- /dev/null +++ b/src/final-ts/03.tsx @@ -0,0 +1,52 @@ +// useContext: simple Counter +// http://localhost:3000/isolated/final-ts/03.tsx + +import * as React from 'react' + +type CounterContextInterface = readonly [ + number, + React.Dispatch>, +] + +// in JS calling React.createContext() with no argument doesn't raise any issue, +// in Typescript land thought, the compiler will yell at you! +// In fact, said function is defined as such: +// ``` function createContext(defaultValue: T ): Context ``` +// where the defaultValue argument is required. if you want to read morea about it +// this is the issue discussion: https://github.com/DefinitelyTyped/DefinitelyTyped/pull/24509#issuecomment-382213106 +// The possible options are listed in theses examples: https://react-typescript-cheatsheet.netlify.app/docs/basic/getting-started/context#extended-example +const CountContext = React.createContext( + ([] as unknown) as CounterContextInterface, +) + +const CountProvider: React.FC = props => { + const [count, setCount] = React.useState(0) + const value = [count, setCount] as const + // could also do it like this: + // const value = React.useState(0) + return +} + +function CountDisplay(): JSX.Element { + const [count] = React.useContext(CountContext) + return
{`The current count is ${count}`}
+} + +function Counter(): JSX.Element { + const [, setCount] = React.useContext(CountContext) + const increment = () => setCount(c => c + 1) + return +} + +function App(): JSX.Element { + return ( +
+ + + + +
+ ) +} + +export default App diff --git a/src/final-ts/04.tsx b/src/final-ts/04.tsx new file mode 100644 index 00000000..c3823cb7 --- /dev/null +++ b/src/final-ts/04.tsx @@ -0,0 +1,108 @@ +// useLayoutEffect: auto-scrolling textarea +// http://localhost:3000/isolated/final/04.js + +import * as React from 'react' +interface Message { + id: number + author: string + content: string +} + +interface MessagesDisplayProps { + messages: Message[] +} +function MessagesDisplay({messages}: MessagesDisplayProps): JSX.Element { + const containerRef = React.useRef(null!) + React.useLayoutEffect(() => { + containerRef.current.scrollTop = containerRef.current.scrollHeight + }) + + return ( +
+ {messages.map((message, index, array) => ( +
+ {message.author}: {message.content} + {array.length - 1 === index ? null :
} +
+ ))} +
+ ) +} + +// this is to simulate major computation/big rendering tree/etc. +function sleep(time: number = 0): void { + const wakeUpTime = Date.now() + time + while (Date.now() < wakeUpTime) {} +} + +function SlooooowSibling(): React.ReactElement | null { + // try this with useLayoutEffect as well to see + // how it impacts interactivity of the page before updates. + React.useEffect(() => { + // increase this number to see a more stark difference + sleep(300) + }) + return null +} + +function App(): JSX.Element { + const [messages, setMessages] = React.useState( + allMessages.slice(0, 8), + ) + const addMessage = (): void => + messages.length < allMessages.length + ? setMessages(allMessages.slice(0, messages.length + 1)) + : undefined + const removeMessage = (): void => + messages.length > 0 + ? setMessages(allMessages.slice(0, messages.length - 1)) + : undefined + + return ( +
+
+ + +
+
+ + +
+ ) +} + +export default App + +const allMessages: Message[] = [ + `Leia: Aren't you a little short to be a stormtrooper?`, + `Luke: What? Oh... the uniform. I'm Luke Skywalker. I'm here to rescue you.`, + `Leia: You're who?`, + `Luke: I'm here to rescue you. I've got your R2 unit. I'm here with Ben Kenobi.`, + `Leia: Ben Kenobi is here! Where is he?`, + `Luke: Come on!`, + `Luke: Will you forget it? I already tried it. It's magnetically sealed!`, + `Leia: Put that thing away! You're going to get us all killed.`, + `Han: Absolutely, Your Worship. Look, I had everything under control until you led us down here. You know, it's not going to take them long to figure out what happened to us.`, + `Leia: It could be worse...`, + `Han: It's worse.`, + `Luke: There's something alive in here!`, + `Han: That's your imagination.`, + `Luke: Something just moves past my leg! Look! Did you see that?`, + `Han: What?`, + `Luke: Help!`, + `Han: Luke! Luke! Luke!`, + `Leia: Luke!`, + `Leia: Luke, Luke, grab a hold of this.`, + `Luke: Blast it, will you! My gun's jammed.`, + `Han: Where?`, + `Luke: Anywhere! Oh!!`, + `Han: Luke! Luke!`, + `Leia: Grab him!`, + `Leia: What happened?`, + `Luke: I don't know, it just let go of me and disappeared...`, + `Han: I've got a very bad feeling about this.`, + `Luke: The walls are moving!`, + `Leia: Don't just stand there. Try to brace it with something.`, + `Luke: Wait a minute!`, + `Luke: Threepio! Come in Threepio! Threepio! Where could he be?`, +].map((m, i) => ({id: i, author: m.split(': ')[0], content: m.split(': ')[1]})) diff --git a/src/final-ts/05.tsx b/src/final-ts/05.tsx new file mode 100644 index 00000000..15c93d75 --- /dev/null +++ b/src/final-ts/05.tsx @@ -0,0 +1,117 @@ +// useImperativeHandle: scroll to top/bottom +// http://localhost:3000/isolated/final-ts/05.tsx + +import * as React from 'react' + +interface Message { + id: number + author: string + content: string +} + +interface MessagesDisplayProps { + messages: Message[] +} +interface MessagesDisplayRef { + scrollToTop: () => void + scrollToBottom: () => void +} +const MessagesDisplay = React.forwardRef< + MessagesDisplayRef, + MessagesDisplayProps +>(function MessagesDisplay({messages}, ref) { + const containerRef = React.useRef(null!) + React.useLayoutEffect(() => { + scrollToBottom() + }) + function scrollToTop() { + containerRef.current.scrollTop = 0 + } + function scrollToBottom() { + containerRef.current.scrollTop = containerRef.current.scrollHeight + } + React.useImperativeHandle(ref, () => ({ + scrollToTop, + scrollToBottom, + })) + + return ( +
+ {messages.map((message, index, array) => ( +
+ {message.author}: {message.content} + {array.length - 1 === index ? null :
} +
+ ))} +
+ ) +}) + +function App(): JSX.Element { + const messageDisplayRef = React.useRef(null) + const [messages, setMessages] = React.useState(allMessages.slice(0, 8)) + const addMessage = (): void => + messages.length < allMessages.length + ? setMessages(allMessages.slice(0, messages.length + 1)) + : undefined + const removeMessage = (): void => + messages.length > 0 + ? setMessages(allMessages.slice(0, messages.length - 1)) + : undefined + + const scrollToTop = (): void => messageDisplayRef.current?.scrollToTop() + const scrollToBottom = (): void => messageDisplayRef.current?.scrollToBottom() + + return ( +
+
+ + +
+
+
+ +
+ +
+ +
+
+ ) +} + +export default App + +const allMessages: Message[] = [ + `Leia: Aren't you a little short to be a stormtrooper?`, + `Luke: What? Oh... the uniform. I'm Luke Skywalker. I'm here to rescue you.`, + `Leia: You're who?`, + `Luke: I'm here to rescue you. I've got your R2 unit. I'm here with Ben Kenobi.`, + `Leia: Ben Kenobi is here! Where is he?`, + `Luke: Come on!`, + `Luke: Will you forget it? I already tried it. It's magnetically sealed!`, + `Leia: Put that thing away! You're going to get us all killed.`, + `Han: Absolutely, Your Worship. Look, I had everything under control until you led us down here. You know, it's not going to take them long to figure out what happened to us.`, + `Leia: It could be worse...`, + `Han: It's worse.`, + `Luke: There's something alive in here!`, + `Han: That's your imagination.`, + `Luke: Something just moves past my leg! Look! Did you see that?`, + `Han: What?`, + `Luke: Help!`, + `Han: Luke! Luke! Luke!`, + `Leia: Luke!`, + `Leia: Luke, Luke, grab a hold of this.`, + `Luke: Blast it, will you! My gun's jammed.`, + `Han: Where?`, + `Luke: Anywhere! Oh!!`, + `Han: Luke! Luke!`, + `Leia: Grab him!`, + `Leia: What happened?`, + `Luke: I don't know, it just let go of me and disappeared...`, + `Han: I've got a very bad feeling about this.`, + `Luke: The walls are moving!`, + `Leia: Don't just stand there. Try to brace it with something.`, + `Luke: Wait a minute!`, + `Luke: Threepio! Come in Threepio! Threepio! Where could he be?`, +].map((m, i) => ({id: i, author: m.split(': ')[0], content: m.split(': ')[1]})) diff --git a/src/final-ts/06.extra-1.tsx b/src/final-ts/06.extra-1.tsx new file mode 100644 index 00000000..d705dd59 --- /dev/null +++ b/src/final-ts/06.extra-1.tsx @@ -0,0 +1,60 @@ +// useDebugValue: useMedia +// 💯 use the format function +// http://localhost:3000/isolated/final-ts/06.extra-1.tsx + +import * as React from 'react' + +const formatDebugValue = ({ + query, + state, +}: { + query: string + state: boolean +}): string => `\`${query}\` => ${state}` + +function useMedia(query: string, initialState: boolean = false): boolean { + const [state, setState] = React.useState(initialState) + React.useDebugValue({query, state}, formatDebugValue) + + React.useEffect(() => { + let mounted = true + const mql = window.matchMedia(query) + function onChange() { + if (!mounted) { + return + } + setState(Boolean(mql.matches)) + } + + mql.addListener(onChange) + setState(mql.matches) + + return () => { + mounted = false + mql.removeListener(onChange) + } + }, [query]) + + return state +} + +function Box(): JSX.Element { + const isBig = useMedia('(min-width: 1000px)') + const isMedium = useMedia('(max-width: 999px) and (min-width: 700px)') + const isSmall = useMedia('(max-width: 699px)') + const color = isBig + ? 'green' + : isMedium + ? 'yellow' + : isSmall + ? 'red' + : 'inherit' + + return
+} + +function App(): JSX.Element { + return +} + +export default App diff --git a/src/final-ts/06.tsx b/src/final-ts/06.tsx new file mode 100644 index 00000000..88f4d146 --- /dev/null +++ b/src/final-ts/06.tsx @@ -0,0 +1,51 @@ +// useDebugValue: useMedia +// http://localhost:3000/isolated/final-ts/06.tsx + +import * as React from 'react' + +function useMedia(query: string, initialState: boolean = false): boolean { + const [state, setState] = React.useState(initialState) + React.useDebugValue(`\`${query}\` => ${state}`) + + React.useEffect(() => { + let mounted = true + const mql: MediaQueryList = window.matchMedia(query) + function onChange(): void { + if (!mounted) { + return + } + setState(Boolean(mql.matches)) + } + + mql.addListener(onChange) + setState(mql.matches) + + return () => { + mounted = false + mql.removeListener(onChange) + } + }, [query]) + + return state +} + +function Box(): JSX.Element { + const isBig = useMedia('(min-width: 1000px)') + const isMedium = useMedia('(max-width: 999px) and (min-width: 700px)') + const isSmall = useMedia('(max-width: 699px)') + const color = isBig + ? 'green' + : isMedium + ? 'yellow' + : isSmall + ? 'red' + : 'inherit' + + return
+} + +function App(): JSX.Element { + return +} + +export default App diff --git a/src/final/03.extra-2.js b/src/final/03.extra-2.js index af9e84a0..e06cbaf3 100644 --- a/src/final/03.extra-2.js +++ b/src/final/03.extra-2.js @@ -46,10 +46,12 @@ function usePokemonCache() { function PokemonInfo({pokemonName}) { const [cache, dispatch] = usePokemonCache() - const {data: pokemon, status, error, run, setData} = useAsync({ + const {state, run, setData} = useAsync({ status: pokemonName ? 'pending' : 'idle', }) + const {data: pokemon, status, error} = state + React.useEffect(() => { if (!pokemonName) { return diff --git a/src/pokemon.js b/src/pokemon.tsx similarity index 65% rename from src/pokemon.js rename to src/pokemon.tsx index b37f6ee0..74d595a6 100644 --- a/src/pokemon.js +++ b/src/pokemon.tsx @@ -1,13 +1,52 @@ import * as React from 'react' -import {ErrorBoundary} from 'react-error-boundary' +import {ErrorBoundary, FallbackProps} from 'react-error-boundary' -const formatDate = date => +const formatDate = (date: Date): string => `${date.getHours()}:${String(date.getMinutes()).padStart(2, '0')} ${String( date.getSeconds(), ).padStart(2, '0')}.${String(date.getMilliseconds()).padStart(3, '0')}` +//#region Pokemon interface + +interface FetchedAt { + fetchedAt: string +} + +/** Represents a Pokémon */ +export interface Pokemon { + /** The ID of an object */ + id: string + /** The identifier of this Pokémon */ + number?: null | string + /** The name of this Pokémon */ + name?: null | string + image?: null | string + + attacks: null | PokemonAttack +} + +/** Represents a Pokémon's attack types */ +interface PokemonAttack { + /** The fast attacks of this Pokémon */ + fast?: null | Array + /** The special attacks of this Pokémon */ + special?: null | Array +} + +/** Represents a Pokémon's attack type */ +interface Attack { + /** The name of this Pokémon attack */ + name?: null | string + /** The type of this Pokémon attack */ + type?: null | string + /** The damage of this Pokémon attack */ + damage?: null | number +} +export type IPokemon = Pokemon & FetchedAt +//#endregion + // the delay argument is for faking things out a bit -function fetchPokemon(name, delay = 1500) { +function fetchPokemon(name: string, delay: number = 1500): Promise { const pokemonQuery = ` query PokemonInfo($name: String) { pokemon(name: $name) { @@ -32,7 +71,7 @@ function fetchPokemon(name, delay = 1500) { method: 'POST', headers: { 'content-type': 'application/json;charset=UTF-8', - delay: delay, + delay: String(delay), }, body: JSON.stringify({ query: pokemonQuery, @@ -51,24 +90,25 @@ function fetchPokemon(name, delay = 1500) { } } else { // handle the graphql errors - const error = { - message: data?.errors?.map(e => e.message).join('\n'), + const error: Partial = { + message: data?.errors?.map((e: Error) => e.message).join('\n'), } return Promise.reject(error) } }) } -function PokemonInfoFallback({name}) { +function PokemonInfoFallback({name}: {name: string}): JSX.Element { const initialName = React.useRef(name).current - const fallbackPokemonData = { + const fallbackPokemonData: IPokemon = { + id: '', name: initialName, number: 'XXX', image: '/img/pokemon/fallback-pokemon.jpg', attacks: { special: [ - {name: 'Loading Attack 1', type: 'Type', damage: 'XX'}, - {name: 'Loading Attack 2', type: 'Type', damage: 'XX'}, + {name: 'Loading Attack 1', type: 'Type', damage: null}, + {name: 'Loading Attack 2', type: 'Type', damage: null}, ], }, fetchedAt: 'loading...', @@ -76,11 +116,11 @@ function PokemonInfoFallback({name}) { return } -function PokemonDataView({pokemon}) { +function PokemonDataView({pokemon}: {pokemon: IPokemon}): JSX.Element { return (
- {pokemon.name} + {pokemon.name

@@ -90,11 +130,11 @@ function PokemonDataView({pokemon}) {

    - {pokemon.attacks.special.map(attack => ( -
  • - :{' '} + {pokemon.attacks?.special?.map((attack, idx) => ( +
  • + :{' '} - {attack.damage} ({attack.type}) + {attack?.damage} ({attack?.type})
  • ))} @@ -105,11 +145,16 @@ function PokemonDataView({pokemon}) { ) } +interface PokemonFormProps { + pokemonName: string + initialPokemonName?: string + onSubmit: (pokemonName: string) => void +} function PokemonForm({ pokemonName: externalPokemonName, initialPokemonName = externalPokemonName || '', onSubmit, -}) { +}: PokemonFormProps): JSX.Element { const [pokemonName, setPokemonName] = React.useState(initialPokemonName) // this is generally not a great idea. We're synchronizing state when it is @@ -124,16 +169,16 @@ function PokemonForm({ } }, [externalPokemonName]) - function handleChange(e) { + function handleChange(e: React.ChangeEvent): void { setPokemonName(e.target.value) } - function handleSubmit(e) { + function handleSubmit(e: React.FormEvent): void { e.preventDefault() onSubmit(pokemonName) } - function handleSelect(newPokemonName) { + function handleSelect(newPokemonName: string): void { setPokemonName(newPokemonName) onSubmit(newPokemonName) } @@ -184,7 +229,10 @@ function PokemonForm({ ) } -function ErrorFallback({error, resetErrorBoundary}) { +function ErrorFallback({ + error, + resetErrorBoundary, +}: FallbackProps): JSX.Element { return (
    There was an error:{' '} @@ -194,7 +242,9 @@ function ErrorFallback({error, resetErrorBoundary}) { ) } -function PokemonErrorBoundary(props) { +function PokemonErrorBoundary( + props: Omit, 'FallbackComponent'>, +): JSX.Element { return } diff --git a/src/react-app-env.d.ts b/src/react-app-env.d.ts new file mode 100644 index 00000000..6431bc5f --- /dev/null +++ b/src/react-app-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/src/setupTests.js b/src/setupTests.js deleted file mode 100644 index d9dd7e1a..00000000 --- a/src/setupTests.js +++ /dev/null @@ -1 +0,0 @@ -import '@kentcdodds/react-workshop-app/setup-tests' diff --git a/src/setupTests.ts b/src/setupTests.ts new file mode 100644 index 00000000..9ccf8935 --- /dev/null +++ b/src/setupTests.ts @@ -0,0 +1,8 @@ +// jest.config.ts +import type {Config} from '@jest/types' + +import '@kentcdodds/react-workshop-app/setup-tests' + +// Sync object +const config: Config.InitialOptions = {} +export default config diff --git a/src/types.d.ts b/src/types.d.ts new file mode 100644 index 00000000..3f69993a --- /dev/null +++ b/src/types.d.ts @@ -0,0 +1,14 @@ +declare module '@kentcdodds/react-workshop-app/test-utils' { + declare function alfredTip( + shouldThrow: unknown | ((...args: unknown[]) => unknown), + tip: string, + ): void + + export {alfredTip} +} + +declare module 'mq-polyfill' { + declare function matchMediaPolyfill(window: Window): void + + export default matchMediaPolyfill +} diff --git a/src/utils.js b/src/utils.js deleted file mode 100644 index 2ff73970..00000000 --- a/src/utils.js +++ /dev/null @@ -1,80 +0,0 @@ -import * as React from 'react' - -function useSafeDispatch(dispatch) { - const mounted = React.useRef(false) - - React.useLayoutEffect(() => { - mounted.current = true - return () => (mounted.current = false) - }, []) - - return React.useCallback( - (...args) => (mounted.current ? dispatch(...args) : void 0), - [dispatch], - ) -} - -function asyncReducer(state, action) { - switch (action.type) { - case 'pending': { - return {status: 'pending', data: null, error: null} - } - case 'resolved': { - return {status: 'resolved', data: action.data, error: null} - } - case 'rejected': { - return {status: 'rejected', data: null, error: action.error} - } - default: { - throw new Error(`Unhandled action type: ${action.type}`) - } - } -} - -function useAsync(initialState) { - const [state, unsafeDispatch] = React.useReducer(asyncReducer, { - status: 'idle', - data: null, - error: null, - ...initialState, - }) - - const dispatch = useSafeDispatch(unsafeDispatch) - - const {data, error, status} = state - - const run = React.useCallback( - promise => { - dispatch({type: 'pending'}) - promise.then( - data => { - dispatch({type: 'resolved', data}) - }, - error => { - dispatch({type: 'rejected', error}) - }, - ) - }, - [dispatch], - ) - - const setData = React.useCallback( - data => dispatch({type: 'resolved', data}), - [dispatch], - ) - const setError = React.useCallback( - error => dispatch({type: 'rejected', error}), - [dispatch], - ) - - return { - setData, - setError, - error, - status, - data, - run, - } -} - -export {useAsync} diff --git a/src/utils.tsx b/src/utils.tsx new file mode 100644 index 00000000..75f353cd --- /dev/null +++ b/src/utils.tsx @@ -0,0 +1,166 @@ +import * as React from 'react' + +export enum Status { + IDLE = 'idle', + PENDING = 'pending', + RESOLVED = 'resolved', + REJECTED = 'rejected', +} + +interface FiniteState< + Status extends 'idle' | 'pending' | 'resolved' | 'rejected', + Data extends unknown = null, + Err extends null | Error = null +> { + status: Status + data: Data + error: Err +} + +interface ActionType< + Type extends 'idle' | 'pending' | 'resolved' | 'rejected' +> { + type: Type +} + +interface ActionIdle extends ActionType {} +interface ActionPending extends ActionType {} + +interface ActionResolved extends ActionType { + data: Data +} + +interface ActionRejected extends ActionType { + error: Error +} + +type State = + | FiniteState<'idle'> + | FiniteState<'pending'> + | FiniteState<'resolved', Data> + | FiniteState<'rejected', null, Error> + +type Action = + | ActionIdle + | ActionPending + | ActionResolved + | ActionRejected + +function useSafeDispatch void>( + dispatch: Dispatch, +): Dispatch { + const mounted = React.useRef(false) + + React.useLayoutEffect(() => { + mounted.current = true + return () => { + mounted.current = false + } + }, []) + + // eslint-disable-next-line react-hooks/exhaustive-deps + return React.useCallback( + ((...args) => { + if (mounted.current) { + dispatch(...args) + } + }) as Dispatch, + [dispatch], + ) +} + +type AsyncReducer = React.Reducer, Action> +function asyncReducer( + state: State, + action: Action, +): State { + switch (action.type) { + case Status.IDLE: + return { + status: Status.IDLE, + data: null, + error: null, + } + + case Status.PENDING: + return { + status: Status.PENDING, + data: null, + error: null, + } + + case Status.RESOLVED: + return { + status: Status.RESOLVED, + data: action.data, + error: null, + } + + case Status.REJECTED: + return { + status: Status.REJECTED, + data: null, + error: action.error, + } + + default: { + // @ts-ignore: exhaustive fallthrough checks: Property 'type' does not exist on type 'never'. + throw new Error(`Unhandled action type: ${action.type}`) + } + } +} + +function useAsync( + initialState?: State, +): Readonly<{ + setData: (data: Data) => void + setError: (error: Error) => void + state: State + run: (promise: Promise) => void +}> { + const [state, unsafeDispatch] = React.useReducer>( + asyncReducer, + { + status: Status.IDLE, + data: null, + error: null, + ...initialState, + }, + ) + + const dispatch = useSafeDispatch(unsafeDispatch) + + const run = React.useCallback( + (promise: Promise) => { + dispatch({type: Status.PENDING}) + promise.then( + data => { + dispatch({type: Status.RESOLVED, data}) + }, + error => { + dispatch({type: Status.REJECTED, error}) + }, + ) + }, + [dispatch], + ) + + const setData = React.useCallback( + (data: Data) => dispatch({type: Status.RESOLVED, data}), + [dispatch], + ) + + const setError = React.useCallback( + (error: Error) => dispatch({type: Status.REJECTED, error}), + [dispatch], + ) + + return { + setData, + setError, + state, + run, + } as const +} + +export {useAsync} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000..10ee6a4c --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "ESNext", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "noFallthroughCasesInSwitch": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx" + }, + "include": ["src", "./src/types.d.ts"] +}