From 343a93b984e9e52ecb86ef52f032a6319f6ac787 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 22 Aug 2020 08:06:41 +0200 Subject: [PATCH 01/59] Installed TS and updated linter --- .eslintrc | 81 +- package-lock.json | 2182 ++++++++++++++++++++--- package.json | 15 +- src/servers/helpers/ImportServersBtn.js | 2 +- src/utils/services/ColorGenerator.js | 2 +- src/visits/helpers/LineChartCard.js | 4 +- tsconfig.json | 35 + 7 files changed, 1983 insertions(+), 338 deletions(-) create mode 100644 tsconfig.json diff --git a/.eslintrc b/.eslintrc index bf19fb0c..e4f13c20 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,45 +1,58 @@ { - "extends": [ - "adidas-env/browser", - "adidas-env/module", - "adidas-env/node", - "adidas-es6", - "adidas-babel", - "adidas-react" - ], "plugins": ["jest"], "env": { "jest/globals": true }, + "parserOptions": { + "tsconfigRootDir": ".", + "createDefaultProgram": true + }, "globals": { "process": true, "setImmediate": true }, - "settings": { - "react": { - "version": "16.3" + "overrides": [ + { + "files": ["**/*.js"], + "extends": [ + "adidas-env/browser", + "adidas-env/module", + "adidas-env/node", + "adidas-es9", + "adidas-babel", + "adidas-react" + ], + "settings": { + "react": { + "version": "detect" + } + }, + "rules": { + "comma-dangle": ["error", "always-multiline"], + "no-invalid-this": "off", + "no-inline-comments": "off", + "no-console": "warn", + "template-curly-spacing": ["error", "never"], + "no-warning-comments": "off", + "no-undefined": "off", + "indent": ["error", 2, {"SwitchCase": 1}], + "no-empty-function": "off", + "lines-around-comment": "off", + "no-magic-numbers": "off", + "react/no-array-index-key": "off", + "react/no-did-update-set-state": "off", + "react/jsx-curly-spacing": ["error", "never"], + "react/jsx-indent-props": ["error", 2], + "react/jsx-first-prop-new-line": ["error", "multiline-multiprop"], + "react/jsx-closing-bracket-location": ["error", "tag-aligned"], + "react/jsx-filename-extension": ["error", {"extensions": [".js", ".jsx"]}] + } + }, + { + "files": ["**/*.ts", "**/*.tsx"], + "extends": [ + "@shlinkio/js-coding-standard" + ] } - }, - "rules": { - "comma-dangle": ["error", "always-multiline"], - "no-invalid-this": "off", - "no-console": "warn", - "template-curly-spacing": ["error", "never"], - "no-warning-comments": "off", - "no-magic-numbers": "off", - "no-undefined": "off", - "no-inline-comments": "off", - "lines-around-comment": "off", - "indent": ["error", 2, { - "SwitchCase": 1 - } - ], - "react/jsx-curly-spacing": ["error", "never"], - "react/jsx-indent-props": ["error", 2], - "react/jsx-first-prop-new-line": ["error", "multiline-multiprop"], - "react/jsx-closing-bracket-location": ["error", "tag-aligned"], - "react/no-array-index-key": "off", - "react/no-did-update-set-state": "off", - "react/display-name": "off" - } + ] } diff --git a/package-lock.json b/package-lock.json index 1adab9b6..703cd958 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1024,6 +1024,24 @@ "regenerator-runtime": "^0.12.0" } }, + "@babel/runtime-corejs3": { + "version": "7.11.2", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.11.2.tgz", + "integrity": "sha512-qh5IR+8VgFz83VBa6OkaET6uN/mJOhHONuy3m1sgF0CV6mXdPSEBdA7e1eUbVvyNtANjMbg22JUv71BaDXLY6A==", + "dev": true, + "requires": { + "core-js-pure": "^3.0.0", + "regenerator-runtime": "^0.13.4" + }, + "dependencies": { + "regenerator-runtime": { + "version": "0.13.7", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", + "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==", + "dev": true + } + } + }, "@babel/template": { "version": "7.6.0", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.6.0.tgz", @@ -1468,6 +1486,24 @@ "integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==", "dev": true }, + "@shlinkio/eslint-config-js-coding-standard": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@shlinkio/eslint-config-js-coding-standard/-/eslint-config-js-coding-standard-1.1.0.tgz", + "integrity": "sha512-/Dx+xGYWkjEGx/4GdUXtrHX10hTkYxl08WFqMvssz9pCbam7P5P/w1ymL/o8A46vL/bu9J37wvdbigVSMIo79Q==", + "dev": true, + "requires": { + "babel-eslint": "^10.1.0", + "eslint-config-adidas-babel": "^1.2.0", + "eslint-config-adidas-env": "^1.2.0", + "eslint-config-adidas-react": "^1.2.0", + "eslint-config-adidas-typescript": "^1.3.1", + "eslint-plugin-import": "^2.22.0", + "eslint-plugin-jest": "^23.20.0", + "eslint-plugin-jsx-a11y": "^6.3.1", + "eslint-plugin-promise": "^4.2.1", + "eslint-plugin-react": "^7.20.6" + } + }, "@stryker-mutator/api": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/@stryker-mutator/api/-/api-3.2.4.tgz", @@ -1992,6 +2028,12 @@ "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", "dev": true }, + "@types/eslint-visitor-keys": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", + "integrity": "sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag==", + "dev": true + }, "@types/events": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz", @@ -2034,10 +2076,141 @@ "@types/istanbul-lib-report": "*" } }, + "@types/jest": { + "version": "26.0.10", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-26.0.10.tgz", + "integrity": "sha512-i2m0oyh8w/Lum7wWK/YOZJakYF8Mx08UaKA1CtbmFeDquVhAEdA7znacsVSf2hJ1OQ/OfVMGN90pw/AtzF8s/Q==", + "dev": true, + "requires": { + "jest-diff": "^25.2.1", + "pretty-format": "^25.2.1" + }, + "dependencies": { + "@jest/types": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.5.0.tgz", + "integrity": "sha512-OXD0RgQ86Tu3MazKo8bnrkDRaDXXMGUqd+kTtLtK1Zb7CRzQcaSRPPPV37SvYTdevXEBVxe0HXylEjs8ibkmCw==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^15.0.0", + "chalk": "^3.0.0" + } + }, + "@types/yargs": { + "version": "15.0.5", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.5.tgz", + "integrity": "sha512-Dk/IDOPtOgubt/IaevIUbTgV7doaKkoorvOyYM2CMwuDyP89bekI7H4xLIwunNYiK9jhCkmc6pUrJk3cj2AB9w==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "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" + } + }, + "diff-sequences": { + "version": "25.2.6", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-25.2.6.tgz", + "integrity": "sha512-Hq8o7+6GaZeoFjtpgvRBUknSXNeJiCx7V9Fr94ZMljNiCr9n9L8H8aJqgWOQiDDGdyn29fRNcDdRVJ5fdyihfg==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "jest-diff": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-25.5.0.tgz", + "integrity": "sha512-z1kygetuPiREYdNIumRpAHY6RXiGmp70YHptjdaxTWGmA085W3iCnXNx0DhflK3vwrKmrRWyY1wUpkPMVxMK7A==", + "dev": true, + "requires": { + "chalk": "^3.0.0", + "diff-sequences": "^25.2.6", + "jest-get-type": "^25.2.6", + "pretty-format": "^25.5.0" + } + }, + "jest-get-type": { + "version": "25.2.6", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-25.2.6.tgz", + "integrity": "sha512-DxjtyzOHjObRM+sM1knti6or+eOgcGU4xVSb2HNP1TqO4ahsT+rqZg+nyqHWJSvWgKC5cG3QjGFBqxLghiF/Ig==", + "dev": true + }, + "pretty-format": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-25.5.0.tgz", + "integrity": "sha512-kbo/kq2LQ/A/is0PQwsEHM7Ca6//bGPPvU6UnsdDRSKTWxT/ru/xb88v4BJf6a69H+uTytOEsTusT9ksd/1iWQ==", + "dev": true, + "requires": { + "@jest/types": "^25.5.0", + "ansi-regex": "^5.0.0", + "ansi-styles": "^4.0.0", + "react-is": "^16.12.0" + } + }, + "react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, "@types/json-schema": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.3.tgz", - "integrity": "sha512-Il2DtDVRGDcqjDtE+rF8iqg1CArehSK84HZJCT7AMITlyXRBpuPhqGLDQMowraqqu1coEaimg4ZOqggt6L6L+A==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.5.tgz", + "integrity": "sha512-7+2BITlgjgDhH0vvwZU/HZJVyk+2XUlvxXe8dFMedNX/aMkaOq++rMAFXc0tM7ij15QaWlbdQASBR9dihi+bDQ==", + "dev": true + }, + "@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", "dev": true }, "@types/minimatch": { @@ -2106,31 +2279,115 @@ "integrity": "sha512-gCubfBUZ6KxzoibJ+SCUc/57Ms1jz5NjHe4+dI2krNmU5zCPAphyLJYyTOg06ueIyfj+SaCUqmzun7ImlxDcKg==", "dev": true }, + "@typescript-eslint/eslint-plugin": { + "version": "2.34.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.34.0.tgz", + "integrity": "sha512-4zY3Z88rEE99+CNvTbXSyovv2z9PNOVffTWD2W8QF5s2prBQtwN2zadqERcrHpcR7O/+KMI3fcTAmUUhK/iQcQ==", + "dev": true, + "requires": { + "@typescript-eslint/experimental-utils": "2.34.0", + "functional-red-black-tree": "^1.0.1", + "regexpp": "^3.0.0", + "tsutils": "^3.17.1" + }, + "dependencies": { + "regexpp": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz", + "integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==", + "dev": true + } + } + }, "@typescript-eslint/experimental-utils": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-1.13.0.tgz", - "integrity": "sha512-zmpS6SyqG4ZF64ffaJ6uah6tWWWgZ8m+c54XXgwFtUv0jNz8aJAVx8chMCvnk7yl6xwn8d+d96+tWp7fXzTuDg==", + "version": "2.34.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-2.34.0.tgz", + "integrity": "sha512-eS6FTkq+wuMJ+sgtuNTtcqavWXqsflWcfBnlYhg/nS4aZ1leewkXGbvBhaapn1q6qf4M71bsR1tez5JTRMuqwA==", "dev": true, "requires": { "@types/json-schema": "^7.0.3", - "@typescript-eslint/typescript-estree": "1.13.0", - "eslint-scope": "^4.0.0" + "@typescript-eslint/typescript-estree": "2.34.0", + "eslint-scope": "^5.0.0", + "eslint-utils": "^2.0.0" + }, + "dependencies": { + "eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + } + } + } + }, + "@typescript-eslint/parser": { + "version": "2.34.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-2.34.0.tgz", + "integrity": "sha512-03ilO0ucSD0EPTw2X4PntSIRFtDPWjrVq7C3/Z3VQHRC7+13YB55rcJI3Jt+YgeHbjUdJPcPa7b23rXCBokuyA==", + "dev": true, + "requires": { + "@types/eslint-visitor-keys": "^1.0.0", + "@typescript-eslint/experimental-utils": "2.34.0", + "@typescript-eslint/typescript-estree": "2.34.0", + "eslint-visitor-keys": "^1.1.0" } }, "@typescript-eslint/typescript-estree": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-1.13.0.tgz", - "integrity": "sha512-b5rCmd2e6DCC6tCTN9GSUAuxdYwCM/k/2wdjHGrIRGPSJotWMCe/dGpi66u42bhuh8q3QBzqM4TMA1GUUCJvdw==", + "version": "2.34.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-2.34.0.tgz", + "integrity": "sha512-OMAr+nJWKdlVM9LOqCqh3pQQPwxHAN7Du8DR6dmwCrAmxtiXQnhHJ6tBNtf+cggqfo51SG/FCwnKhXCIM7hnVg==", "dev": true, "requires": { - "lodash.unescape": "4.0.1", - "semver": "5.5.0" + "debug": "^4.1.1", + "eslint-visitor-keys": "^1.1.0", + "glob": "^7.1.6", + "is-glob": "^4.0.1", + "lodash": "^4.17.15", + "semver": "^7.3.2", + "tsutils": "^3.17.1" }, "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "lodash": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", + "dev": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, "semver": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", - "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", "dev": true } } @@ -2368,9 +2625,9 @@ } }, "acorn-jsx": { - "version": "5.0.1", - "resolved": "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.0.1.tgz", - "integrity": "sha1-MqBk/ZJUKSFqCbFBECv90YX65A4=", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.2.0.tgz", + "integrity": "sha512-HiUX/+K2YpkpJ+SzBffkM/AQ2YE03S0U1kjTLVpoJdhZMOWy8qvXVN9JdLqv2QsaQ6MPYQIuNmwD8zOiYUofLQ==", "dev": true }, "acorn-walk": { @@ -2561,19 +2818,28 @@ } }, "aria-query": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-3.0.0.tgz", - "integrity": "sha1-ZbP8wcoRVajJrmTW7uKX8V1RM8w=", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-4.2.2.tgz", + "integrity": "sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==", "dev": true, "requires": { - "ast-types-flow": "0.0.7", - "commander": "^2.11.0" + "@babel/runtime": "^7.10.2", + "@babel/runtime-corejs3": "^7.10.2" }, "dependencies": { - "commander": { - "version": "2.20.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.1.tgz", - "integrity": "sha512-cCuLsMhJeWQ/ZpsFTbE765kvVfoeSddc4nU3up4fV+fDBcfUXnbITJ+JzhkdjzOqhURjZgujxaioam4RM9yGUg==", + "@babel/runtime": { + "version": "7.11.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", + "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", + "dev": true, + "requires": { + "regenerator-runtime": "^0.13.4" + } + }, + "regenerator-runtime": { + "version": "0.13.7", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", + "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==", "dev": true } } @@ -2620,13 +2886,73 @@ "dev": true }, "array-includes": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.0.3.tgz", - "integrity": "sha1-GEtI9i2S10UrsxsyMWXH+L0CJm0=", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.1.tgz", + "integrity": "sha512-c2VXaCHl7zPsvpkFsw4nxvFie4fh1ur9bpcgsVkIjqn0H/Xwdg+7fv3n2r/isyS8EBj5b06M9kHyZuIr4El6WQ==", "dev": true, "requires": { - "define-properties": "^1.1.2", - "es-abstract": "^1.7.0" + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0", + "is-string": "^1.0.5" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.6", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz", + "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==", + "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.0", + "is-regex": "^1.1.0", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "has-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", + "dev": true + }, + "is-callable": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.0.tgz", + "integrity": "sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw==", + "dev": true + }, + "is-regex": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", + "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", + "dev": true, + "requires": { + "has-symbols": "^1.0.1" + } + }, + "object-inspect": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", + "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==", + "dev": true + } } }, "array-map": { @@ -2838,6 +3164,76 @@ } } }, + "array.prototype.flatmap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.2.3.tgz", + "integrity": "sha512-OOEk+lkePcg+ODXIpvuU9PAryCikCJyo7GlDG1upleEpQRx6mzL9puEBkozQ5iAx20KV0l3DbyQwqciJtqe5Pg==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1", + "function-bind": "^1.1.1" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.6", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz", + "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==", + "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.0", + "is-regex": "^1.1.0", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "has-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", + "dev": true + }, + "is-callable": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.0.tgz", + "integrity": "sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw==", + "dev": true + }, + "is-regex": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", + "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", + "dev": true, + "requires": { + "has-symbols": "^1.0.1" + } + }, + "object-inspect": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", + "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==", + "dev": true + } + } + }, "arrify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", @@ -2983,6 +3379,12 @@ "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==", "dev": true }, + "axe-core": { + "version": "3.5.5", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-3.5.5.tgz", + "integrity": "sha512-5P0QZ6J5xGikH780pghEdbEKijCTrruK9KxtPZCFWUpef0f6GipO+xEZ5GKCb020mmqgbiNO6TcA55CriL784Q==", + "dev": true + }, "axios": { "version": "0.19.0", "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.0.tgz", @@ -3016,13 +3418,10 @@ } }, "axobject-query": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.0.2.tgz", - "integrity": "sha512-MCeek8ZH7hKyO1rWUbKNQBbl4l2eY0ntk7OGi+q0RlafrCnfPxC06WZA+uebCfmYp4mNU9jRBP1AhGyf8+W3ww==", - "dev": true, - "requires": { - "ast-types-flow": "0.0.7" - } + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz", + "integrity": "sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA==", + "dev": true }, "babel-code-frame": { "version": "6.26.0", @@ -3075,17 +3474,170 @@ "dev": true }, "babel-eslint": { - "version": "10.0.3", - "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.0.3.tgz", - "integrity": "sha512-z3U7eMY6r/3f3/JB9mTsLjyxrv0Yb1zb8PCWCLpguxfCzBIZUwy23R1t/XKewP+8mEN2Ck8Dtr4q20z6ce6SoA==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.1.0.tgz", + "integrity": "sha512-ifWaTHQ0ce+448CYop8AdrQiBsGrnC+bMgfyKFdi6EsPLTAWG+QfyDeM6OH+FmWnKvEq5NnBMLvlBUPKQZoDSg==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", - "@babel/parser": "^7.0.0", - "@babel/traverse": "^7.0.0", - "@babel/types": "^7.0.0", + "@babel/parser": "^7.7.0", + "@babel/traverse": "^7.7.0", + "@babel/types": "^7.7.0", "eslint-visitor-keys": "^1.0.0", "resolve": "^1.12.0" + }, + "dependencies": { + "@babel/generator": { + "version": "7.11.4", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.11.4.tgz", + "integrity": "sha512-Rn26vueFx0eOoz7iifCN2UHT6rGtnkSGWSoDRIy8jZN3B91PzeSULbswfLoOWuTuAcNwpG/mxy+uCTDnZ9Mp1g==", + "dev": true, + "requires": { + "@babel/types": "^7.11.0", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + } + }, + "@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==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz", + "integrity": "sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A==", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@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==", + "dev": true, + "requires": { + "@babel/types": "^7.11.0" + } + }, + "@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==", + "dev": true + }, + "@babel/highlight": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", + "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.11.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.11.4.tgz", + "integrity": "sha512-MggwidiH+E9j5Sh8pbrX5sJvMcsqS5o+7iB42M9/k0CD63MjYbdP4nhSh7uB5wnv2/RVzTZFTxzF/kIa5mrCqA==", + "dev": true + }, + "@babel/template": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.4.tgz", + "integrity": "sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/parser": "^7.10.4", + "@babel/types": "^7.10.4" + }, + "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" + } + } + } + }, + "@babel/traverse": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.11.0.tgz", + "integrity": "sha512-ZB2V+LskoWKNpMq6E5UUCrjtDUh5IOTAyIl0dTjIEoXum/iKWkoIEKIRDnUucO6f+2FzNkE0oD4RLKoPIufDtg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.11.0", + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.11.0", + "@babel/parser": "^7.11.0", + "@babel/types": "^7.11.0", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.19" + }, + "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" + } + } + } + }, + "@babel/types": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.0.tgz", + "integrity": "sha512-O53yME4ZZI0jO1EVGtF1ePGl0LHirG4P1ibcD80XyzZcKhcMFeCXmh4Xb1ifGBIV233Qg12x4rBfQgA+tmOukA==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "lodash": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", + "dev": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } } }, "babel-extract-comments": { @@ -3744,6 +4296,15 @@ "node-releases": "^1.1.29" } }, + "bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "requires": { + "fast-json-stable-stringify": "2.x" + } + }, "bser": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.0.tgz", @@ -4216,12 +4777,6 @@ "safe-buffer": "^5.0.1" } }, - "circular-json": { - "version": "0.3.3", - "resolved": "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.3.tgz", - "integrity": "sha1-gVyZ6oT2gJUp0vRXkb34JxE1LWY=", - "dev": true - }, "class-utils": { "version": "0.3.6", "resolved": "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz", @@ -4679,6 +5234,12 @@ } } }, + "core-js-pure": { + "version": "3.6.5", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.6.5.tgz", + "integrity": "sha512-lacdXOimsiD0QyNf9BC/mxivNJ/ybBGJXQFKzRekp1WTHoVUWsUHEn+2T8GJAzzIhyOuXA+gOxCVN3l+5PLPUA==", + "dev": true + }, "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", @@ -5133,9 +5694,9 @@ "dev": true }, "damerau-levenshtein": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.5.tgz", - "integrity": "sha512-CBCRqFnpu715iPmw1KrdOrzRqbdFwQTwAWyyyYS42+iAgHCuXZ+/TdMgQkUENPomxEz9z1BEzuQU2Xw0kUuAgA==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.6.tgz", + "integrity": "sha512-JVrozIeElnj3QzfUIt8tB8YMluBJom4Vw9qTPpjGYQ9fYlB3D/rb6OordUxf3xeFB35LKWs0xqcO5U6ySvBtug==", "dev": true }, "dashdash": { @@ -5505,12 +6066,13 @@ } }, "doctrine": { - "version": "2.1.0", - "resolved": "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha1-XNAfwQFiG0LEzX9dGmYkNxbT850=", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", + "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=", "dev": true, "requires": { - "esutils": "^2.0.2" + "esutils": "^2.0.2", + "isarray": "^1.0.0" } }, "dom-converter": { @@ -6198,192 +6760,352 @@ } }, "eslint": { - "version": "5.12.0", - "resolved": "https://registry.yarnpkg.com/eslint/-/eslint-5.12.0.tgz", - "integrity": "sha1-+rO5CPYMUmcfsU6ZakULlsdDyFk=", + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.8.0.tgz", + "integrity": "sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", - "ajv": "^6.5.3", + "ajv": "^6.10.0", "chalk": "^2.1.0", "cross-spawn": "^6.0.5", "debug": "^4.0.1", - "doctrine": "^2.1.0", - "eslint-scope": "^4.0.0", - "eslint-utils": "^1.3.1", - "eslint-visitor-keys": "^1.0.0", - "espree": "^5.0.0", + "doctrine": "^3.0.0", + "eslint-scope": "^5.0.0", + "eslint-utils": "^1.4.3", + "eslint-visitor-keys": "^1.1.0", + "espree": "^6.1.2", "esquery": "^1.0.1", "esutils": "^2.0.2", - "file-entry-cache": "^2.0.0", + "file-entry-cache": "^5.0.1", "functional-red-black-tree": "^1.0.1", - "glob": "^7.1.2", - "globals": "^11.7.0", + "glob-parent": "^5.0.0", + "globals": "^12.1.0", "ignore": "^4.0.6", "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", - "inquirer": "^6.1.0", - "js-yaml": "^3.12.0", + "inquirer": "^7.0.0", + "is-glob": "^4.0.0", + "js-yaml": "^3.13.1", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.3.0", - "lodash": "^4.17.5", + "lodash": "^4.17.14", "minimatch": "^3.0.4", "mkdirp": "^0.5.1", "natural-compare": "^1.4.0", - "optionator": "^0.8.2", - "path-is-inside": "^1.0.2", - "pluralize": "^7.0.0", + "optionator": "^0.8.3", "progress": "^2.0.0", "regexpp": "^2.0.1", - "semver": "^5.5.1", - "strip-ansi": "^4.0.0", - "strip-json-comments": "^2.0.1", - "table": "^5.0.2", - "text-table": "^0.2.0" + "semver": "^6.1.2", + "strip-ansi": "^5.2.0", + "strip-json-comments": "^3.0.1", + "table": "^5.2.3", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" }, "dependencies": { + "ajv": { + "version": "6.12.4", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.4.tgz", + "integrity": "sha512-eienB2c9qVQs2KWexhkrdMLVDoIQCz5KSeLxwg9Lzk4DOfBtIK9PQwwufcsn1jjGuf9WZmqPMbGxOzfcuphJCQ==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", "dev": true }, "debug": { "version": "4.1.1", - "resolved": "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz", - "integrity": "sha1-O3ImAlUQnGtYnO4FDx1RYTlmR5E=", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", "dev": true, "requires": { "ms": "^2.1.1" } }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "eslint-scope": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.0.tgz", + "integrity": "sha512-iiGRvtxWqgtx5m8EyQUJihBloE4EnYeGE/bz1wSPwJE6tZuJUtHlhqDM4Xj2ukE8Dyy1+HCZ4hE0fzIVMzb58w==", + "dev": true, + "requires": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + }, + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "glob-parent": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", + "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "globals": { + "version": "12.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", + "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", + "dev": true, + "requires": { + "type-fest": "^0.8.1" + } + }, "ignore": { "version": "4.0.6", - "resolved": "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha1-dQ49tYYgh7RzfrrIIH/9HvJ7Jfw=", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", "dev": true }, "import-fresh": { - "version": "3.0.0", - "resolved": "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.0.0.tgz", - "integrity": "sha1-o9iX9CDKsOZxI2iX91vBS0iFw5A=", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz", + "integrity": "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==", "dev": true, "requires": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, - "inquirer": { - "version": "6.2.1", - "resolved": "https://registry.yarnpkg.com/inquirer/-/inquirer-6.2.1.tgz", - "integrity": "sha1-mUP8SIIWG9sLDJJ2dpx1sy2/zVI=", + "js-yaml": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", + "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", "dev": true, "requires": { - "ansi-escapes": "^3.0.0", - "chalk": "^2.0.0", - "cli-cursor": "^2.1.0", - "cli-width": "^2.0.0", - "external-editor": "^3.0.0", - "figures": "^2.0.0", - "lodash": "^4.17.10", - "mute-stream": "0.0.7", - "run-async": "^2.2.0", - "rxjs": "^6.1.0", - "string-width": "^2.1.0", - "strip-ansi": "^5.0.0", - "through": "^2.3.6" - }, - "dependencies": { - "ansi-regex": { - "version": "4.0.0", - "resolved": "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.0.0.tgz", - "integrity": "sha1-cN55Ht8CFATD/WFaqJEYrgQy5ak=", - "dev": true - }, - "strip-ansi": { - "version": "5.0.0", - "resolved": "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.0.0.tgz", - "integrity": "sha1-949otdCGbCCyybjGG1KYUI3IdW8=", - "dev": true, - "requires": { - "ansi-regex": "^4.0.0" - } - } + "argparse": "^1.0.7", + "esprima": "^4.0.0" } }, - "ms": { - "version": "2.1.1", - "resolved": "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz", - "integrity": "sha1-MKWGTrPrsKZvLr5tcnrwagnYbgo=", + "lodash": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", "dev": true }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "dev": true, + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + } + }, "resolve-from": { "version": "4.0.0", - "resolved": "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha1-SrzYUq0y3Xuqv+m0DgCjbbXzkuY=", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "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==", "dev": true, "requires": { - "ansi-regex": "^3.0.0" + "ansi-styles": "^3.2.0", + "astral-regex": "^1.0.0", + "is-fullwidth-code-point": "^2.0.0" } + }, + "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==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "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" + } + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "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==", + "dev": true, + "requires": { + "ajv": "^6.10.2", + "lodash": "^4.17.14", + "slice-ansi": "^2.1.0", + "string-width": "^3.0.0" + } + }, + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true } } }, "eslint-config-adidas-babel": { - "version": "1.1.0", - "resolved": "https://registry.yarnpkg.com/eslint-config-adidas-babel/-/eslint-config-adidas-babel-1.1.0.tgz", - "integrity": "sha1-EWAF6DChXUHbi+O51+fKWPF4RyE=", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/eslint-config-adidas-babel/-/eslint-config-adidas-babel-1.2.0.tgz", + "integrity": "sha512-erdxFE43GHDvNv4GDAIH7uObsC9VDRl7SvbhsMjbcyOBIoiPQspl7VSiG92sH14ISuztjlNvPSG11OQvgea0YA==", "dev": true }, "eslint-config-adidas-env": { - "version": "1.1.0", - "resolved": "https://registry.yarnpkg.com/eslint-config-adidas-env/-/eslint-config-adidas-env-1.1.0.tgz", - "integrity": "sha1-u2yLfrRC76/khUdVSq/zgTPzO84=", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/eslint-config-adidas-env/-/eslint-config-adidas-env-1.2.0.tgz", + "integrity": "sha512-J70Vurpbl9WBA85nfgGXTbTG3IYWdSuL+hJ49QYS4kbuubf85XNKuQhB0YbsJocDgmP1BDI4J/Qoj+YlTKGtww==", "dev": true }, "eslint-config-adidas-es5": { - "version": "1.1.1", - "resolved": "https://registry.yarnpkg.com/eslint-config-adidas-es5/-/eslint-config-adidas-es5-1.1.1.tgz", - "integrity": "sha1-G5tKQRRClse6ZEOsxhVfvI05/e8=", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/eslint-config-adidas-es5/-/eslint-config-adidas-es5-1.2.0.tgz", + "integrity": "sha512-Rms7sSWp2B4QPrR5NQNbMTFi08hlCHcAYI5QtveFHLh8w1NQwr/zBrsB+hjaTeXBYLX5b5i4A7u/Mdo33s4L2A==", "dev": true }, "eslint-config-adidas-es6": { - "version": "1.2.0", - "resolved": "https://registry.yarnpkg.com/eslint-config-adidas-es6/-/eslint-config-adidas-es6-1.2.0.tgz", - "integrity": "sha1-94r+PjCKcOfYO4ySYkx3a90RqEU=", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-config-adidas-es6/-/eslint-config-adidas-es6-1.3.0.tgz", + "integrity": "sha512-We/z8kBj6KaqosFMw/JMhC8rysnAtMwWCpKtcTtQWdbb24/iNkXxSoZOrFRVqaLBKTgWRrilcUgvLm7/zAVVNQ==", "dev": true, "requires": { - "eslint-config-adidas-es5": "^1.1" + "eslint-config-adidas-es5": "^1.2" + } + }, + "eslint-config-adidas-es7": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/eslint-config-adidas-es7/-/eslint-config-adidas-es7-1.2.0.tgz", + "integrity": "sha512-cS+QrKQpuwI2i43/CMPoU5okb/58NWQKUxfAs+g2IwhUM7t5eweKhFKiv2HF8kW2pGmNarkq951ioLLYLJ/BaA==", + "dev": true, + "requires": { + "eslint-config-adidas-es6": "^1.3" + } + }, + "eslint-config-adidas-es8": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/eslint-config-adidas-es8/-/eslint-config-adidas-es8-1.2.0.tgz", + "integrity": "sha512-Hg4Fds3hFkY1b0aNIdZ5mE3mUmFtVQ+rbbvFCPpyI6pyjyF+aHWadmIhRvF2NYwV6/vjDKBO2Kcbv/thvLH/tQ==", + "dev": true, + "requires": { + "eslint-config-adidas-es7": "^1.2" + } + }, + "eslint-config-adidas-es9": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/eslint-config-adidas-es9/-/eslint-config-adidas-es9-1.2.0.tgz", + "integrity": "sha512-og0xMtYQaSGN9coztOuptxaCfc7I+913EsVFcX/Twr3/fHmf9179hzeyX8Vqvl4zBgYCK7eEkmt2QUmZxy96aw==", + "dev": true, + "requires": { + "eslint-config-adidas-es8": "^1.2" } }, "eslint-config-adidas-jsx": { - "version": "1.1.0", - "resolved": "https://registry.yarnpkg.com/eslint-config-adidas-jsx/-/eslint-config-adidas-jsx-1.1.0.tgz", - "integrity": "sha1-oN2c1RqPIGtpBuFMRCwEf+iUzfM=", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/eslint-config-adidas-jsx/-/eslint-config-adidas-jsx-1.2.0.tgz", + "integrity": "sha512-ciNPL8WHdXYwSDKHZUpNbrWVZTQzHxWgKJ2xusqc8T+zwH4/Q37DIZLo/IClKNQXyGkc+TBPgh4+3HTNGOyBvw==", "dev": true }, "eslint-config-adidas-react": { - "version": "1.1.1", - "resolved": "https://registry.yarnpkg.com/eslint-config-adidas-react/-/eslint-config-adidas-react-1.1.1.tgz", - "integrity": "sha1-RVcCT9J1UIYmEHI05OXzbGUwyu0=", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/eslint-config-adidas-react/-/eslint-config-adidas-react-1.2.0.tgz", + "integrity": "sha512-oyV/qeFDM+a5bPbr3M/mk4JLt8HopObJYKigJsIsoBRvDyvr0ssuBGIBYjNN8AH6X2Qe8RRwGPEw0y0yhLfeaQ==", "dev": true, "requires": { "eslint-config-adidas-jsx": "^1.1" } }, + "eslint-config-adidas-typescript": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/eslint-config-adidas-typescript/-/eslint-config-adidas-typescript-1.3.1.tgz", + "integrity": "sha512-cXz0yslN3+h9KjHRdGWk1yg2Ytv3lLA97wfC5vQDjwZwEQE0v9c6wmnU9NihMtZxD1yh8UlDgfGLpACpeDeFNg==", + "dev": true, + "requires": { + "@typescript-eslint/eslint-plugin": "^2.9", + "@typescript-eslint/parser": "^2.9", + "eslint-config-adidas-es9": "^1.2" + } + }, "eslint-import-resolver-node": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.2.tgz", - "integrity": "sha512-sfmTqJfPSizWu4aymbPr4Iidp5yKm8yDkHp+Ir3YiTHiiDfxh69mOUsmiqW6RZ9zRXFaF64GtYmN7e+8GHBv6Q==", + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.4.tgz", + "integrity": "sha512-ogtf+5AB/O+nM6DIeBUNr2fuT7ot9Qg/1harBfBtaP13ekEWFQEEMP94BCB7zaNW3gyY+8SHYF00rnqYwXKWOA==", "dev": true, "requires": { "debug": "^2.6.9", - "resolve": "^1.5.0" + "resolve": "^1.13.1" + }, + "dependencies": { + "resolve": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", + "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + } } }, "eslint-loader": { @@ -6430,12 +7152,12 @@ } }, "eslint-module-utils": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.4.1.tgz", - "integrity": "sha512-H6DOj+ejw7Tesdgbfs4jeS4YMFrT8uI8xwd1gtQqXssaR0EQ26L+2O/w6wkYFy2MymON0fTwHmXBvvfLNZVZEw==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.6.0.tgz", + "integrity": "sha512-6j9xxegbqe8/kZY8cYpcp0xhbK0EgJlg3g9mib3/miLaExuuwc3n5UEfSnU6hWMbT0FAYVvDbL9RrRgpUeQIvA==", "dev": true, "requires": { - "debug": "^2.6.8", + "debug": "^2.6.9", "pkg-dir": "^2.0.0" }, "dependencies": { @@ -6494,32 +7216,54 @@ } }, "eslint-plugin-import": { - "version": "2.18.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.18.2.tgz", - "integrity": "sha512-5ohpsHAiUBRNaBWAF08izwUGlbrJoJJ+W9/TBwsGoR1MnlgfwMIKrFeSjWbt6moabiXW9xNvtFz+97KHRfI4HQ==", + "version": "2.22.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.22.0.tgz", + "integrity": "sha512-66Fpf1Ln6aIS5Gr/55ts19eUuoDhAbZgnr6UxK5hbDx6l/QgQgx61AePq+BV4PP2uXQFClgMVzep5zZ94qqsxg==", "dev": true, "requires": { - "array-includes": "^3.0.3", + "array-includes": "^3.1.1", + "array.prototype.flat": "^1.2.3", "contains-path": "^0.1.0", "debug": "^2.6.9", "doctrine": "1.5.0", - "eslint-import-resolver-node": "^0.3.2", - "eslint-module-utils": "^2.4.0", + "eslint-import-resolver-node": "^0.3.3", + "eslint-module-utils": "^2.6.0", "has": "^1.0.3", "minimatch": "^3.0.4", - "object.values": "^1.1.0", + "object.values": "^1.1.1", "read-pkg-up": "^2.0.0", - "resolve": "^1.11.0" + "resolve": "^1.17.0", + "tsconfig-paths": "^3.9.0" }, "dependencies": { - "doctrine": { - "version": "1.5.0", - "resolved": "https://registry.yarnpkg.com/doctrine/-/doctrine-1.5.0.tgz", - "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=", + "es-abstract": { + "version": "1.17.6", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz", + "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==", "dev": true, "requires": { - "esutils": "^2.0.2", - "isarray": "^1.0.0" + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.0", + "is-regex": "^1.1.0", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" } }, "find-up": { @@ -6531,9 +7275,30 @@ "locate-path": "^2.0.0" } }, + "has-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", + "dev": true + }, + "is-callable": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.0.tgz", + "integrity": "sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw==", + "dev": true + }, + "is-regex": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", + "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", + "dev": true, + "requires": { + "has-symbols": "^1.0.1" + } + }, "load-json-file": { "version": "2.0.0", - "resolved": "https://registry.yarnpkg.com/load-json-file/-/load-json-file-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", "dev": true, "requires": { @@ -6553,6 +7318,24 @@ "path-exists": "^3.0.0" } }, + "object-inspect": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", + "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==", + "dev": true + }, + "object.values": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.1.tgz", + "integrity": "sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1", + "function-bind": "^1.1.1", + "has": "^1.0.3" + } + }, "p-limit": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", @@ -6579,7 +7362,7 @@ }, "parse-json": { "version": "2.2.0", - "resolved": "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", "dev": true, "requires": { @@ -6588,7 +7371,7 @@ }, "path-type": { "version": "2.0.0", - "resolved": "https://registry.yarnpkg.com/path-type/-/path-type-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", "dev": true, "requires": { @@ -6597,13 +7380,13 @@ }, "pify": { "version": "2.3.0", - "resolved": "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", "dev": true }, "read-pkg": { "version": "2.0.0", - "resolved": "https://registry.yarnpkg.com/read-pkg/-/read-pkg-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", "dev": true, "requires": { @@ -6614,61 +7397,72 @@ }, "read-pkg-up": { "version": "2.0.0", - "resolved": "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", "dev": true, "requires": { "find-up": "^2.0.0", "read-pkg": "^2.0.0" } + }, + "resolve": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", + "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } } } }, "eslint-plugin-jest": { - "version": "22.17.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-22.17.0.tgz", - "integrity": "sha512-WT4DP4RoGBhIQjv+5D0FM20fAdAUstfYAf/mkufLNTojsfgzc5/IYW22cIg/Q4QBavAZsROQlqppiWDpFZDS8Q==", + "version": "23.20.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-23.20.0.tgz", + "integrity": "sha512-+6BGQt85OREevBDWCvhqj1yYA4+BFK4XnRZSGJionuEYmcglMZYLNNBBemwzbqUAckURaHdJSBcjHPyrtypZOw==", "dev": true, "requires": { - "@typescript-eslint/experimental-utils": "^1.13.0" + "@typescript-eslint/experimental-utils": "^2.5.0" } }, "eslint-plugin-jsx-a11y": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.2.3.tgz", - "integrity": "sha512-CawzfGt9w83tyuVekn0GDPU9ytYtxyxyFZ3aSWROmnRRFQFT2BiPJd7jvRdzNDi6oLWaS2asMeYSNMjWTV4eNg==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.3.1.tgz", + "integrity": "sha512-i1S+P+c3HOlBJzMFORRbC58tHa65Kbo8b52/TwCwSKLohwvpfT5rm2GjGWzOHTEuq4xxf2aRlHHTtmExDQOP+g==", "dev": true, "requires": { - "@babel/runtime": "^7.4.5", - "aria-query": "^3.0.0", - "array-includes": "^3.0.3", + "@babel/runtime": "^7.10.2", + "aria-query": "^4.2.2", + "array-includes": "^3.1.1", "ast-types-flow": "^0.0.7", - "axobject-query": "^2.0.2", - "damerau-levenshtein": "^1.0.4", - "emoji-regex": "^7.0.2", + "axe-core": "^3.5.4", + "axobject-query": "^2.1.2", + "damerau-levenshtein": "^1.0.6", + "emoji-regex": "^9.0.0", "has": "^1.0.3", - "jsx-ast-utils": "^2.2.1" + "jsx-ast-utils": "^2.4.1", + "language-tags": "^1.0.5" }, "dependencies": { "@babel/runtime": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.6.2.tgz", - "integrity": "sha512-EXxN64agfUqqIGeEjI5dL5z0Sw0ZwWo1mLTi4mQowCZ42O59b7DRpZAnTC6OqdF28wMBMFKNb/4uFGrVaigSpg==", + "version": "7.11.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", + "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", "dev": true, "requires": { - "regenerator-runtime": "^0.13.2" + "regenerator-runtime": "^0.13.4" } }, "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.0.0.tgz", + "integrity": "sha512-6p1NII1Vm62wni/VR/cUMauVQoxmLVb9csqQlvLz+hO2gk8U2UYDfXHQSUYIBKmZwAKz867IDqG7B+u0mj+M6w==", "dev": true }, "regenerator-runtime": { - "version": "0.13.3", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz", - "integrity": "sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw==", + "version": "0.13.7", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", + "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==", "dev": true } } @@ -6680,26 +7474,128 @@ "dev": true }, "eslint-plugin-react": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.16.0.tgz", - "integrity": "sha512-GacBAATewhhptbK3/vTP09CbFrgUJmBSaaRcWdbQLFvUZy9yVcQxigBNHGPU/KE2AyHpzj3AWXpxoMTsIDiHug==", + "version": "7.20.6", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.20.6.tgz", + "integrity": "sha512-kidMTE5HAEBSLu23CUDvj8dc3LdBU0ri1scwHBZjI41oDv4tjsWZKU7MQccFzH1QYPYhsnTF2ovh7JlcIcmxgg==", "dev": true, "requires": { - "array-includes": "^3.0.3", + "array-includes": "^3.1.1", + "array.prototype.flatmap": "^1.2.3", "doctrine": "^2.1.0", "has": "^1.0.3", - "jsx-ast-utils": "^2.2.1", - "object.entries": "^1.1.0", - "object.fromentries": "^2.0.0", - "object.values": "^1.1.0", + "jsx-ast-utils": "^2.4.1", + "object.entries": "^1.1.2", + "object.fromentries": "^2.0.2", + "object.values": "^1.1.1", "prop-types": "^15.7.2", - "resolve": "^1.12.0" + "resolve": "^1.17.0", + "string.prototype.matchall": "^4.0.2" + }, + "dependencies": { + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "es-abstract": { + "version": "1.17.6", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz", + "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==", + "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.0", + "is-regex": "^1.1.0", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "has-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", + "dev": true + }, + "is-callable": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.0.tgz", + "integrity": "sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw==", + "dev": true + }, + "is-regex": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", + "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", + "dev": true, + "requires": { + "has-symbols": "^1.0.1" + } + }, + "object-inspect": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", + "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==", + "dev": true + }, + "object.entries": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.2.tgz", + "integrity": "sha512-BQdB9qKmb/HyNdMNWVr7O3+z5MUIx3aiegEIJqjMBbBf0YT9RRxTJSim4mzFqtyr7PDAHigq0N9dO0m0tRakQA==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5", + "has": "^1.0.3" + } + }, + "object.values": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.1.tgz", + "integrity": "sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1", + "function-bind": "^1.1.1", + "has": "^1.0.3" + } + }, + "resolve": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", + "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + } } }, "eslint-scope": { - "version": "4.0.0", - "resolved": "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.0.tgz", - "integrity": "sha1-UL8wcekzi83EMzF5Sgy1M/ATYXI=", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.0.tgz", + "integrity": "sha512-iiGRvtxWqgtx5m8EyQUJihBloE4EnYeGE/bz1wSPwJE6tZuJUtHlhqDM4Xj2ukE8Dyy1+HCZ4hE0fzIVMzb58w==", "dev": true, "requires": { "esrecurse": "^4.1.0", @@ -6707,26 +7603,51 @@ } }, "eslint-utils": { - "version": "1.3.1", - "resolved": "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.3.1.tgz", - "integrity": "sha1-moUbqJ7nxGA0b5fPiTnHKYgn5RI=", - "dev": true + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz", + "integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + } + } }, "eslint-visitor-keys": { - "version": "1.0.0", - "resolved": "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", - "integrity": "sha1-PzGA+y4pEBdxastMnW1bXDSmqB0=", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", "dev": true }, "espree": { - "version": "5.0.0", - "resolved": "https://registry.yarnpkg.com/espree/-/espree-5.0.0.tgz", - "integrity": "sha1-/H+YS2Kzag9UOxP7nNe59Kf1tlw=", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-6.2.1.tgz", + "integrity": "sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==", "dev": true, "requires": { - "acorn": "^6.0.2", - "acorn-jsx": "^5.0.0", - "eslint-visitor-keys": "^1.0.0" + "acorn": "^7.1.1", + "acorn-jsx": "^5.2.0", + "eslint-visitor-keys": "^1.1.0" + }, + "dependencies": { + "acorn": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.0.tgz", + "integrity": "sha512-+G7P8jJmCHr+S+cLfQxygbWhXy+8YTVGzAkpEbcLo2mLoL7tij/VG41QSHACSf5QgYRhMZYHuNc6drJaO0Da+w==", + "dev": true + }, + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + } } }, "esprima": { @@ -6736,12 +7657,20 @@ "dev": true }, "esquery": { - "version": "1.0.1", - "resolved": "https://registry.yarnpkg.com/esquery/-/esquery-1.0.1.tgz", - "integrity": "sha1-QGxRZYsfWZGl+bYrHcJbAOPlxwg=", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.3.1.tgz", + "integrity": "sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ==", "dev": true, "requires": { - "estraverse": "^4.0.0" + "estraverse": "^5.1.0" + }, + "dependencies": { + "estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "dev": true + } } }, "esrecurse": { @@ -7099,13 +8028,12 @@ } }, "file-entry-cache": { - "version": "2.0.0", - "resolved": "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-2.0.0.tgz", - "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", + "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", "dev": true, "requires": { - "flat-cache": "^1.2.1", - "object-assign": "^4.0.1" + "flat-cache": "^2.0.1" } }, "file-loader": { @@ -7226,15 +8154,14 @@ } }, "flat-cache": { - "version": "1.3.4", - "resolved": "https://registry.yarnpkg.com/flat-cache/-/flat-cache-1.3.4.tgz", - "integrity": "sha1-LC73dSXMKSkAff/6HdMUqpyd7m8=", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", + "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", "dev": true, "requires": { - "circular-json": "^0.3.1", - "graceful-fs": "^4.1.2", - "rimraf": "~2.6.2", - "write": "^0.2.1" + "flatted": "^2.0.0", + "rimraf": "2.6.3", + "write": "1.0.3" } }, "flatted": { @@ -9151,6 +10078,76 @@ "ipaddr.js": "^1.9.0" } }, + "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==", + "dev": true, + "requires": { + "es-abstract": "^1.17.0-next.1", + "has": "^1.0.3", + "side-channel": "^1.0.2" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.6", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz", + "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==", + "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.0", + "is-regex": "^1.1.0", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "has-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", + "dev": true + }, + "is-callable": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.0.tgz", + "integrity": "sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw==", + "dev": true + }, + "is-regex": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", + "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", + "dev": true, + "requires": { + "has-symbols": "^1.0.1" + } + }, + "object-inspect": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", + "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==", + "dev": true + } + } + }, "invariant": { "version": "2.2.4", "resolved": "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz", @@ -10492,12 +11489,12 @@ } }, "jsx-ast-utils": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.2.1.tgz", - "integrity": "sha512-v3FxCcAf20DayI+uxnCuw795+oOIkVu6EnJ1+kSzhqqTZHNkTZ7B66ZgLp4oLJ/gbA64cI0B7WRoHZMSRdyVRQ==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.4.1.tgz", + "integrity": "sha512-z1xSldJ6imESSzOjd3NNkieVJKRlKYSOtMG8SFyCj2FIrvSaSuli/WjpBkEzCBoR9bYYYFgqJw61Xhu7Lcgk+w==", "dev": true, "requires": { - "array-includes": "^3.0.3", + "array-includes": "^3.1.1", "object.assign": "^4.1.0" } }, @@ -10533,6 +11530,21 @@ "integrity": "sha512-bEZlJzXo5V/ApNNa5z375mJC6Nrz4vG43UgcSCrg2OHC+yuB6j0iDSrY7RQ/+PRofFB03wNIIt9iXIVLr4wc7w==", "dev": true }, + "language-subtag-registry": { + "version": "0.3.20", + "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.20.tgz", + "integrity": "sha512-KPMwROklF4tEx283Xw0pNKtfTj1gZ4UByp4EsIFWLgBavJltF4TiYPc39k06zSTsLzxTVXXDSpbwaQXaFB4Qeg==", + "dev": true + }, + "language-tags": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.5.tgz", + "integrity": "sha1-0yHbxNowuovzAk4ED6XBRmH5GTo=", + "dev": true, + "requires": { + "language-subtag-registry": "~0.3.2" + } + }, "last-call-webpack-plugin": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/last-call-webpack-plugin/-/last-call-webpack-plugin-3.0.0.tgz", @@ -10821,12 +11833,6 @@ "resolved": "https://registry.npmjs.org/lodash.tonumber/-/lodash.tonumber-4.0.3.tgz", "integrity": "sha1-C5azGzVnJ5Prf1pj7nkfG56QJdk=" }, - "lodash.unescape": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/lodash.unescape/-/lodash.unescape-4.0.1.tgz", - "integrity": "sha1-vyJJiGzlFM2hEvrpIYzcBlIR/Jw=", - "dev": true - }, "lodash.uniq": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", @@ -10941,6 +11947,12 @@ "pify": "^3.0.0" } }, + "make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, "makeerror": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.11.tgz", @@ -11977,15 +12989,74 @@ } }, "object.fromentries": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.1.tgz", - "integrity": "sha512-PUQv8Hbg3j2QX0IQYv3iAGCbGcu4yY4KQ92/dhA4sFSixBmSmp13UpDLs6jGK8rBtbmhNNIK99LD2k293jpiGA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.2.tgz", + "integrity": "sha512-r3ZiBH7MQppDJVLx6fhD618GKNG40CZYH9wgwdhKxBDDbQgjeWGGd4AtkZad84d291YxvWe7bJGuE65Anh0dxQ==", "dev": true, "requires": { "define-properties": "^1.1.3", - "es-abstract": "^1.15.0", + "es-abstract": "^1.17.0-next.1", "function-bind": "^1.1.1", "has": "^1.0.3" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.6", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz", + "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==", + "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.0", + "is-regex": "^1.1.0", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "has-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", + "dev": true + }, + "is-callable": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.0.tgz", + "integrity": "sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw==", + "dev": true + }, + "is-regex": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", + "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", + "dev": true, + "requires": { + "has-symbols": "^1.0.1" + } + }, + "object-inspect": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", + "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==", + "dev": true + } } }, "object.getownpropertydescriptors": { @@ -12298,18 +13369,18 @@ } }, "parent-module": { - "version": "1.0.0", - "resolved": "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.0.tgz", - "integrity": "sha1-3yUL3FOR9KCF+1idrXYfWta4ZbU=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, "requires": { "callsites": "^3.0.0" }, "dependencies": { "callsites": { - "version": "3.0.0", - "resolved": "https://registry.yarnpkg.com/callsites/-/callsites-3.0.0.tgz", - "integrity": "sha1-+361abcq16RYEvk/2UMKPkELPdM=", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true } } @@ -12458,6 +13529,12 @@ "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", "dev": true }, + "picomatch": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", + "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", + "dev": true + }, "pify": { "version": "3.0.0", "resolved": "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz", @@ -12551,12 +13628,6 @@ } } }, - "pluralize": { - "version": "7.0.0", - "resolved": "https://registry.yarnpkg.com/pluralize/-/pluralize-7.0.0.tgz", - "integrity": "sha1-KYuJ34uTsCIdv0Ia0rGx6iP8Z3c=", - "dev": true - }, "pn": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/pn/-/pn-1.1.0.tgz", @@ -14810,8 +15881,8 @@ }, "regexpp": { "version": "2.0.1", - "resolved": "https://registry.yarnpkg.com/regexpp/-/regexpp-2.0.1.tgz", - "integrity": "sha1-jRnTHPYySCtYkEn4KB+T28uk0H8=", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", + "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", "dev": true }, "regexpu-core": { @@ -15226,15 +16297,6 @@ "aproba": "^1.1.1" } }, - "rxjs": { - "version": "6.3.3", - "resolved": "https://registry.yarnpkg.com/rxjs/-/rxjs-6.3.3.tgz", - "integrity": "sha1-PGp/pCDoRKgTkPsRWKnsYU9LrVU=", - "dev": true, - "requires": { - "tslib": "^1.9.0" - } - }, "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz", @@ -15853,6 +16915,75 @@ "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==", "dev": true }, + "side-channel": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.2.tgz", + "integrity": "sha512-7rL9YlPHg7Ancea1S96Pa8/QWb4BtXL/TZvS6B8XFetGBeuhAsfmUspK6DokBeZ64+Kj9TCNRD/30pVz1BvQNA==", + "dev": true, + "requires": { + "es-abstract": "^1.17.0-next.1", + "object-inspect": "^1.7.0" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.6", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz", + "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==", + "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.0", + "is-regex": "^1.1.0", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "has-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", + "dev": true + }, + "is-callable": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.0.tgz", + "integrity": "sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw==", + "dev": true + }, + "is-regex": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", + "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", + "dev": true, + "requires": { + "has-symbols": "^1.0.1" + } + }, + "object-inspect": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", + "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==", + "dev": true + } + } + }, "signal-exit": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", @@ -16411,6 +17542,89 @@ } } }, + "string.prototype.matchall": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.2.tgz", + "integrity": "sha512-N/jp6O5fMf9os0JU3E72Qhf590RSRZU/ungsL/qJUYVTNv7hTG0P/dbPjxINVN9jpscu3nzYwKESU3P3RY5tOg==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0", + "has-symbols": "^1.0.1", + "internal-slot": "^1.0.2", + "regexp.prototype.flags": "^1.3.0", + "side-channel": "^1.0.2" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.6", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz", + "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==", + "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.0", + "is-regex": "^1.1.0", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "has-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", + "dev": true + }, + "is-callable": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.0.tgz", + "integrity": "sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw==", + "dev": true + }, + "is-regex": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", + "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", + "dev": true, + "requires": { + "has-symbols": "^1.0.1" + } + }, + "object-inspect": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", + "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==", + "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==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1" + } + } + } + }, "string.prototype.trim": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.1.tgz", @@ -16501,6 +17715,75 @@ } } }, + "string.prototype.trimend": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz", + "integrity": "sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.6", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz", + "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==", + "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.0", + "is-regex": "^1.1.0", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "has-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", + "dev": true + }, + "is-callable": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.0.tgz", + "integrity": "sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw==", + "dev": true + }, + "is-regex": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", + "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", + "dev": true, + "requires": { + "has-symbols": "^1.0.1" + } + }, + "object-inspect": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", + "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==", + "dev": true + } + } + }, "string.prototype.trimleft": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.0.tgz", @@ -16521,6 +17804,75 @@ "function-bind": "^1.1.1" } }, + "string.prototype.trimstart": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz", + "integrity": "sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.6", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz", + "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==", + "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.0", + "is-regex": "^1.1.0", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "has-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", + "dev": true + }, + "is-callable": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.0.tgz", + "integrity": "sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw==", + "dev": true + }, + "is-regex": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", + "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", + "dev": true, + "requires": { + "has-symbols": "^1.0.1" + } + }, + "object-inspect": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", + "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==", + "dev": true + } + } + }, "string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -17803,18 +19155,252 @@ "integrity": "sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA==", "dev": true }, + "ts-jest": { + "version": "26.2.0", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-26.2.0.tgz", + "integrity": "sha512-9+y2qwzXdAImgLSYLXAb/Rhq9+K4rbt0417b8ai987V60g2uoNWBBmMkYgutI7D8Zhu+IbCSHbBtrHxB9d7xyA==", + "dev": true, + "requires": { + "@types/jest": "26.x", + "bs-logger": "0.x", + "buffer-from": "1.x", + "fast-json-stable-stringify": "2.x", + "jest-util": "26.x", + "json5": "2.x", + "lodash.memoize": "4.x", + "make-error": "1.x", + "mkdirp": "1.x", + "semver": "7.x", + "yargs-parser": "18.x" + }, + "dependencies": { + "@jest/types": { + "version": "26.3.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.3.0.tgz", + "integrity": "sha512-BDPG23U0qDeAvU4f99haztXwdAg3hz4El95LkAM+tHAqqhiVzRpEGHHU8EDxT/AnxOrA65YjLBwDahdJ9pTLJQ==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^15.0.0", + "chalk": "^4.0.0" + } + }, + "@types/istanbul-reports": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", + "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", + "dev": true, + "requires": { + "@types/istanbul-lib-report": "*" + } + }, + "@types/yargs": { + "version": "15.0.5", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.5.tgz", + "integrity": "sha512-Dk/IDOPtOgubt/IaevIUbTgV7doaKkoorvOyYM2CMwuDyP89bekI7H4xLIwunNYiK9jhCkmc6pUrJk3cj2AB9w==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "dev": true + }, + "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" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "is-ci": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", + "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", + "dev": true, + "requires": { + "ci-info": "^2.0.0" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "jest-util": { + "version": "26.3.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-26.3.0.tgz", + "integrity": "sha512-4zpn6bwV0+AMFN0IYhH/wnzIQzRaYVrz1A8sYnRnj4UXDXbOVtWmlaZkO9mipFqZ13okIfN87aDoJWB7VH6hcw==", + "dev": true, + "requires": { + "@jest/types": "^26.3.0", + "@types/node": "*", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", + "is-ci": "^2.0.0", + "micromatch": "^4.0.2" + } + }, + "micromatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", + "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", + "dev": true, + "requires": { + "braces": "^3.0.1", + "picomatch": "^2.0.5" + } + }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true + }, + "semver": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } + }, "ts-pnp": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/ts-pnp/-/ts-pnp-1.1.4.tgz", "integrity": "sha512-1J/vefLC+BWSo+qe8OnJQfWTYRS6ingxjwqmHMqaMxXMj7kFtKLgAaYW3JeX3mktjgUL+etlU8/B4VUAUI9QGw==", "dev": true }, + "tsconfig-paths": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz", + "integrity": "sha512-dRcuzokWhajtZWkQsDVKbWyY+jgcLC5sqJhg2PSgf4ZkH2aHPvaOY8YWGhmjb68b5qqTfasSsDO9k7RUiEmZAw==", + "dev": true, + "requires": { + "@types/json5": "^0.0.29", + "json5": "^1.0.1", + "minimist": "^1.2.0", + "strip-bom": "^3.0.0" + }, + "dependencies": { + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + } + } + }, "tslib": { "version": "1.9.3", "resolved": "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz", "integrity": "sha1-1+TdeSRdhUKMTX5IIqeZF5VMooY=", "dev": true }, + "tsutils": { + "version": "3.17.1", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.17.1.tgz", + "integrity": "sha512-kzeQ5B8H3w60nFY2g8cJIuH7JDpsALXySGtwGJ0p2LSjLgay3NdIpqq5SoOBe46bKDW2iq25irHCr8wjomUS2g==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + } + }, "tty-browserify": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", @@ -17903,6 +19489,12 @@ "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", "dev": true }, + "typescript": { + "version": "3.9.7", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.7.tgz", + "integrity": "sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw==", + "dev": true + }, "uglify-es": { "version": "3.3.9", "resolved": "https://registry.yarnpkg.com/uglify-es/-/uglify-es-3.3.9.tgz", @@ -18353,6 +19945,12 @@ "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz", "integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==" }, + "v8-compile-cache": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz", + "integrity": "sha512-8OQ9CL+VWyt3JStj7HX7/ciTL2V3Rl1Wf5OL+SNTm0yK1KvtReVulksyeRnCANHHuUxHlQig+JJDlUhBt1NQDQ==", + "dev": true + }, "validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", @@ -19167,6 +20765,12 @@ "string-width": "^2.1.1" } }, + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true + }, "wordwrap": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", @@ -19442,9 +21046,9 @@ "dev": true }, "write": { - "version": "0.2.1", - "resolved": "https://registry.yarnpkg.com/write/-/write-0.2.1.tgz", - "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", + "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", "dev": true, "requires": { "mkdirp": "^0.5.1" diff --git a/package.json b/package.json index 7267a0b3..46d11128 100644 --- a/package.json +++ b/package.json @@ -68,6 +68,7 @@ }, "devDependencies": { "@babel/core": "^7.6.2", + "@shlinkio/eslint-config-js-coding-standard": "~1.1.0", "@stryker-mutator/core": "^3.2.4", "@stryker-mutator/javascript-mutator": "^3.2.4", "@stryker-mutator/jest-runner": "^3.2.4", @@ -75,7 +76,6 @@ "adm-zip": "^0.4.13", "autoprefixer": "^9.6.3", "babel-core": "7.0.0-bridge.0", - "babel-eslint": "^10.0.3", "babel-jest": "^24.9.0", "babel-loader": "^8.0.6", "babel-plugin-named-asset-import": "^0.3.4", @@ -89,17 +89,8 @@ "dotenv-expand": "^5.1.0", "enzyme": "^3.11.0", "enzyme-adapter-react-16": "^1.15.2", - "eslint": "^5.11.1", - "eslint-config-adidas-babel": "^1.1.0", - "eslint-config-adidas-env": "^1.1.0", - "eslint-config-adidas-es6": "^1.2.0", - "eslint-config-adidas-react": "^1.1.1", + "eslint": "^6.8.0", "eslint-loader": "^3.0.2", - "eslint-plugin-import": "^2.18.2", - "eslint-plugin-jest": "^22.17.0", - "eslint-plugin-jsx-a11y": "^6.2.3", - "eslint-plugin-promise": "^4.2.1", - "eslint-plugin-react": "^7.16.0", "file-loader": "^4.2.0", "fork-ts-checker-webpack-plugin-alt": "^0.4.14", "fs-extra": "^8.1.0", @@ -134,6 +125,8 @@ "stylelint-scss": "^3.11.1", "sw-precache-webpack-plugin": "^0.11.5", "terser-webpack-plugin": "^2.1.2", + "ts-jest": "^26.0.0", + "typescript": "^3.9.3", "url-loader": "^2.2.0", "webpack": "^4.41.0", "webpack-dev-server": "^3.8.2", diff --git a/src/servers/helpers/ImportServersBtn.js b/src/servers/helpers/ImportServersBtn.js index 65a47b86..7bde4df7 100644 --- a/src/servers/helpers/ImportServersBtn.js +++ b/src/servers/helpers/ImportServersBtn.js @@ -10,7 +10,7 @@ const propTypes = { // FIXME Replace with typescript: (ServersImporter) const ImportServersBtn = ({ importServersFromFile }) => { - const ImportServersBtnComp = ({ createServers, fileRef, onImport = () => {} }) => { + const ImportServersBtnComp = ({ createServers, fileRef, onImport = () => '' }) => { const ref = fileRef || useRef(); const onChange = ({ target }) => importServersFromFile(target.files[0]) diff --git a/src/utils/services/ColorGenerator.js b/src/utils/services/ColorGenerator.js index 85c80278..1a15f2a4 100644 --- a/src/utils/services/ColorGenerator.js +++ b/src/utils/services/ColorGenerator.js @@ -33,7 +33,7 @@ export default class ColorGenerator { this.storage.set('colors', this.colors); return color; - } + }; } export const colorGeneratorType = PropTypes.shape({ diff --git a/src/visits/helpers/LineChartCard.js b/src/visits/helpers/LineChartCard.js index bd4a1808..264d3dde 100644 --- a/src/visits/helpers/LineChartCard.js +++ b/src/visits/helpers/LineChartCard.js @@ -106,14 +106,14 @@ const generateDataset = (stats, label, color) => ({ const LineChartCard = ({ title, visits, highlightedVisits, highlightedLabel = 'Selected' }) => { const [ step, setStep ] = useState( - visits.length > 0 ? determineInitialStep(visits[visits.length - 1].date) : 'monthly' + visits.length > 0 ? determineInitialStep(visits[visits.length - 1].date) : 'monthly', ); const [ skipNoVisits, toggleSkipNoVisits ] = useToggle(true); const groupedVisitsWithGaps = useMemo(() => groupVisitsByStep(step, reverse(visits)), [ step, visits ]); const [ labels, groupedVisits ] = useMemo( () => generateLabelsAndGroupedVisits(visits, groupedVisitsWithGaps, step, skipNoVisits), - [ visits, step, skipNoVisits ] + [ visits, step, skipNoVisits ], ); const groupedHighlighted = useMemo( () => fillTheGaps(groupVisitsByStep(step, reverse(highlightedVisits)), labels), diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000..d9994741 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,35 @@ +{ + "compilerOptions": { + "allowJs": true, + "allowSyntheticDefaultImports": true, + "jsx": "preserve", + "lib": [ + "dom", + "es2019" + ], + "module": "esnext", + "moduleResolution": "node", + "noEmit": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "preserveConstEnums": true, + "removeComments": false, + "skipLibCheck": true, + "sourceMap": true, + "strict": true, + "target": "esnext", + "forceConsistentCasingInFileNames": true, + "esModuleInterop": true, + "resolveJsonModule": true, + "isolatedModules": true + }, + "exclude": [ + "node_modules", + "**/*.spec.*" + ], + "include": [ + "*.d.ts", + "**/*.ts", + "**/*.tsx" + ] +} From a91f1b3bd4fd14f6375152005f1166b113898aba Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 22 Aug 2020 08:10:31 +0200 Subject: [PATCH 02/59] Fixed coding styles --- .eslintrc | 6 ++- config/env.js | 6 +-- config/webpack.config.js | 14 +++--- scripts/build.js | 14 +++--- scripts/start.js | 12 ++--- src/App.js | 50 ++++++++++--------- src/common/MenuLayout.js | 4 +- src/common/services/provideServices.js | 2 +- src/container/index.js | 2 +- src/container/store.js | 2 +- src/index.js | 2 +- src/registerServiceWorker.js | 8 +-- src/servers/reducers/selectedServer.js | 4 +- src/servers/reducers/servers.js | 2 +- src/short-urls/SearchBar.js | 4 +- src/short-urls/helpers/ShortUrlsRow.js | 2 +- src/short-urls/reducers/shortUrlsList.js | 8 +-- src/short-urls/services/provideServices.js | 8 +-- src/tags/TagsList.js | 2 +- src/tags/reducers/tagEdit.js | 2 +- src/tags/services/provideServices.js | 4 +- src/visits/VisitsStats.js | 2 +- src/visits/VisitsTable.js | 4 +- src/visits/helpers/LineChartCard.js | 2 +- src/visits/helpers/SortableBarGraph.js | 8 +-- src/visits/services/VisitsParser.js | 2 +- src/visits/services/provideServices.js | 4 +- test/common/ErrorHandler.test.js | 2 +- test/common/NotFound.test.js | 2 +- test/servers/CreateServer.test.js | 2 +- test/servers/DeleteServerModal.test.js | 4 +- test/servers/EditServer.test.js | 2 +- test/servers/ServersDropdown.test.js | 2 +- test/servers/helpers/ForServerVersion.test.js | 2 +- test/servers/helpers/ImportServersBtn.test.js | 2 +- test/servers/helpers/ServerError.test.js | 2 +- test/short-urls/CreateShortUrl.test.js | 2 +- test/short-urls/SearchBar.test.js | 4 +- test/short-urls/ShortUrlsList.test.js | 2 +- .../helpers/DeleteShortUrlModal.test.js | 2 +- test/short-urls/helpers/EditMetaModal.test.js | 2 +- .../helpers/EditShortUrlModal.test.js | 2 +- test/short-urls/helpers/EditTagsModal.test.js | 2 +- .../helpers/ShortUrlVisitsCount.test.js | 2 +- test/short-urls/helpers/ShortUrlsRow.test.js | 2 +- .../helpers/ShortUrlsRowMenu.test.js | 4 +- .../reducers/shortUrlDeletion.test.js | 2 +- test/short-urls/reducers/shortUrlTags.test.js | 2 +- test/tags/TagsList.test.js | 2 +- .../helpers/DeleteTagConfirmModal.test.js | 2 +- test/utils/services/ShlinkApiClient.test.js | 2 +- test/visits/ShortUrlVisits.test.js | 2 +- test/visits/ShortUrlVisitsHeader.test.js | 2 +- test/visits/TagVisits.test.js | 2 +- test/visits/TagVisitsHeader.test.js | 2 +- test/visits/VisitsHeader.test.js | 4 +- test/visits/VisitsStats.test.js | 2 +- test/visits/VisitsTable.test.js | 4 +- test/visits/helpers/SortableBarGraph.test.js | 4 +- test/visits/reducers/visitCreation.test.js | 2 +- 60 files changed, 133 insertions(+), 125 deletions(-) diff --git a/.eslintrc b/.eslintrc index e4f13c20..dcf40fae 100644 --- a/.eslintrc +++ b/.eslintrc @@ -41,6 +41,7 @@ "no-magic-numbers": "off", "react/no-array-index-key": "off", "react/no-did-update-set-state": "off", + "react/display-name": "off", "react/jsx-curly-spacing": ["error", "never"], "react/jsx-indent-props": ["error", 2], "react/jsx-first-prop-new-line": ["error", "multiline-multiprop"], @@ -52,7 +53,10 @@ "files": ["**/*.ts", "**/*.tsx"], "extends": [ "@shlinkio/js-coding-standard" - ] + ], + "rules": { + "react/display-name": "off" + } } ] } diff --git a/config/env.js b/config/env.js index f04c21ad..b57375d1 100644 --- a/config/env.js +++ b/config/env.js @@ -10,7 +10,7 @@ const { NODE_ENV } = process.env; if (!NODE_ENV) { throw new Error( - 'The NODE_ENV environment variable is required but was not specified.' + 'The NODE_ENV environment variable is required but was not specified.', ); } @@ -36,7 +36,7 @@ dotenvFiles.forEach((dotenvFile) => { require('dotenv-expand')( require('dotenv').config({ path: dotenvFile, - }) + }), ); } }); @@ -82,7 +82,7 @@ function getClientEnvironment(publicUrl) { // This should only be used as an escape hatch. Normally you would put // images into the `src` and `import` them in code to get their paths. PUBLIC_URL: publicUrl, - } + }, ); // Stringify all values so we can feed into Webpack DefinePlugin diff --git a/config/webpack.config.js b/config/webpack.config.js index babf3b02..5134e5a2 100644 --- a/config/webpack.config.js +++ b/config/webpack.config.js @@ -75,7 +75,7 @@ module.exports = (webpackEnv) => { loader: MiniCssExtractPlugin.loader, options: Object.assign( {}, - shouldUseRelativeAssetPaths ? { publicPath: '../../' } : undefined + shouldUseRelativeAssetPaths ? { publicPath: '../../' } : undefined, ), }, { @@ -281,7 +281,7 @@ module.exports = (webpackEnv) => { modules: [ 'node_modules' ].concat( // It is guaranteed to exist because we tweak it in `env.js` - process.env.NODE_PATH.split(path.delimiter).filter(Boolean) + process.env.NODE_PATH.split(path.delimiter).filter(Boolean), ), // These are the reasonable defaults supported by the Node ecosystem. @@ -372,7 +372,7 @@ module.exports = (webpackEnv) => { loader: require.resolve('babel-loader'), options: { customize: require.resolve( - 'babel-preset-react-app/webpack-overrides' + 'babel-preset-react-app/webpack-overrides', ), plugins: [ @@ -470,7 +470,7 @@ module.exports = (webpackEnv) => { importLoaders: 2, sourceMap: isEnvProduction && shouldUseSourceMap, }, - 'sass-loader' + 'sass-loader', ), // Don't consider CSS imports dead code even if the @@ -491,7 +491,7 @@ module.exports = (webpackEnv) => { modules: true, getLocalIdent: getCSSModuleLocalIdent, }, - 'sass-loader' + 'sass-loader', ), }, @@ -544,8 +544,8 @@ module.exports = (webpackEnv) => { minifyURLs: true, }, } - : undefined - ) + : undefined, + ), ), // Inlines the webpack runtime script. This script is too small to warrant diff --git a/scripts/build.js b/scripts/build.js index f76af2d4..b8854055 100644 --- a/scripts/build.js +++ b/scripts/build.js @@ -75,12 +75,12 @@ checkBrowsers(paths.appPath, isInteractive) console.log( `\nSearch for the ${ chalk.underline(chalk.yellow('keywords')) - } to learn more about each warning.` + } to learn more about each warning.`, ); console.log( `To ignore, add ${ chalk.cyan('// eslint-disable-next-line') - } to the line before.\n` + } to the line before.\n`, ); } else { console.log(chalk.green('Compiled successfully.\n')); @@ -93,7 +93,7 @@ checkBrowsers(paths.appPath, isInteractive) previousFileSizes, paths.appBuild, WARN_AFTER_BUNDLE_GZIP_SIZE, - WARN_AFTER_CHUNK_GZIP_SIZE + WARN_AFTER_CHUNK_GZIP_SIZE, ); console.log(); }, @@ -101,7 +101,7 @@ checkBrowsers(paths.appPath, isInteractive) console.log(chalk.red('Failed to compile.\n')); printBuildError(err); process.exit(1); - } + }, ) .then(() => hasVersion && !withoutDist && zipDist(version)) .catch((err) => { @@ -133,7 +133,7 @@ function build(previousFileSizes) { }); } else { messages = formatWebpackMessages( - stats.toJson({ all: false, warnings: true, errors: true }) + stats.toJson({ all: false, warnings: true, errors: true }), ); } if (messages.errors.length) { @@ -154,8 +154,8 @@ function build(previousFileSizes) { console.log( chalk.yellow( '\nTreating warnings as errors because process.env.CI = true.\n' + - 'Most CI servers set it automatically.\n' - ) + 'Most CI servers set it automatically.\n', + ), ); return reject(new Error(messages.warnings.join('\n\n'))); diff --git a/scripts/start.js b/scripts/start.js index 73842edc..68a4a08c 100644 --- a/scripts/start.js +++ b/scripts/start.js @@ -49,15 +49,15 @@ if (process.env.HOST) { console.log( chalk.cyan( `Attempting to bind to HOST environment variable: ${chalk.yellow( - chalk.bold(process.env.HOST) - )}` - ) + chalk.bold(process.env.HOST), + )}`, + ), ); console.log( - 'If this was unintentional, check that you haven\'t mistakenly set it in your shell.' + 'If this was unintentional, check that you haven\'t mistakenly set it in your shell.', ); console.log( - `Learn more here: ${chalk.yellow('http://bit.ly/CRA-advanced-config')}` + `Learn more here: ${chalk.yellow('http://bit.ly/CRA-advanced-config')}`, ); console.log(); } @@ -91,7 +91,7 @@ checkBrowsers(paths.appPath, isInteractive) // Serve webpack assets generated by the compiler over a web server. const serverConfig = createDevServerConfig( proxyConfig, - urls.lanUrlForConfig + urls.lanUrlForConfig, ); const devServer = new WebpackDevServer(compiler, serverConfig); diff --git a/src/App.js b/src/App.js index 5ed710e8..3a8716eb 100644 --- a/src/App.js +++ b/src/App.js @@ -9,32 +9,36 @@ const propTypes = { servers: PropTypes.object, }; -const App = (MainHeader, Home, MenuLayout, CreateServer, EditServer, Settings) => ({ fetchServers, servers }) => { - // On first load, try to fetch the remote servers if the list is empty - useEffect(() => { - if (Object.keys(servers).length === 0) { - fetchServers(); - } - }, []); +const App = (MainHeader, Home, MenuLayout, CreateServer, EditServer, Settings) => { + const AppComp = ({ fetchServers, servers }) => { + // On first load, try to fetch the remote servers if the list is empty + useEffect(() => { + if (Object.keys(servers).length === 0) { + fetchServers(); + } + }, []); - return ( -
- + return ( +
+ -
- - - - - - - - +
+ + + + + + + + +
-
- ); + ); + }; + + AppComp.propTypes = propTypes; + + return AppComp; }; -App.propTypes = propTypes; - export default App; diff --git a/src/common/MenuLayout.js b/src/common/MenuLayout.js index 428eeb1a..f890223d 100644 --- a/src/common/MenuLayout.js +++ b/src/common/MenuLayout.js @@ -26,7 +26,7 @@ const MenuLayout = ( ShortUrlVisits, TagVisits, ShlinkVersions, - ServerError + ServerError, ) => { const MenuLayoutComp = ({ match, location, selectedServer }) => { const [ sidebarVisible, toggleSidebar, showSidebar, hideSidebar ] = useToggle(); @@ -44,7 +44,7 @@ const MenuLayout = ( }); const swipeMenuIfNoModalExists = (callback) => (e) => { const swippedOnVisitsTable = e.event.path.some( - ({ classList }) => classList && classList.contains('visits-table') + ({ classList }) => classList && classList.contains('visits-table'), ); if (swippedOnVisitsTable || document.querySelector('.modal')) { diff --git a/src/common/services/provideServices.js b/src/common/services/provideServices.js index 1d4b288b..87454117 100644 --- a/src/common/services/provideServices.js +++ b/src/common/services/provideServices.js @@ -29,7 +29,7 @@ const provideServices = (bottle, connect, withRouter) => { 'ShortUrlVisits', 'TagVisits', 'ShlinkVersions', - 'ServerError' + 'ServerError', ); bottle.decorator('MenuLayout', connect([ 'selectedServer', 'shortUrlsListParams' ], [ 'selectServer' ])); bottle.decorator('MenuLayout', withRouter); diff --git a/src/container/index.js b/src/container/index.js index 51309435..38ec382f 100644 --- a/src/container/index.js +++ b/src/container/index.js @@ -25,7 +25,7 @@ const mapActionService = (map, actionName) => ({ const connect = (propsFromState, actionServiceNames = []) => reduxConnect( propsFromState ? pick(propsFromState) : null, - actionServiceNames.reduce(mapActionService, {}) + actionServiceNames.reduce(mapActionService, {}), ); bottle.serviceFactory('App', App, 'MainHeader', 'Home', 'MenuLayout', 'CreateServer', 'EditServer', 'Settings'); diff --git a/src/container/store.js b/src/container/store.js index 5066521a..754f3569 100644 --- a/src/container/store.js +++ b/src/container/store.js @@ -15,7 +15,7 @@ const localStorageConfig = { }; const store = createStore(reducers, load(localStorageConfig), composeEnhancers( - applyMiddleware(save(localStorageConfig), ReduxThunk) + applyMiddleware(save(localStorageConfig), ReduxThunk), )); export default store; diff --git a/src/index.js b/src/index.js index 5b6446f9..333d5115 100644 --- a/src/index.js +++ b/src/index.js @@ -28,6 +28,6 @@ render( , - document.getElementById('root') + document.getElementById('root'), ); registerServiceWorker(); diff --git a/src/registerServiceWorker.js b/src/registerServiceWorker.js index 2d6cd0ce..023439b2 100644 --- a/src/registerServiceWorker.js +++ b/src/registerServiceWorker.js @@ -18,8 +18,8 @@ const isLocalhost = Boolean( // 127.0.0.1/8 is considered localhost for IPv4. window.location.hostname.match( - /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ - ) + /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/, + ), ); export default function register() { @@ -46,7 +46,7 @@ export default function register() { return navigator.serviceWorker.ready.then(() => { console.log( 'This web app is being served cache-first by a service ' + - 'worker. To learn more, visit https://goo.gl/SC7cgQ' + 'worker. To learn more, visit https://goo.gl/SC7cgQ', ); }); } @@ -110,7 +110,7 @@ function checkValidServiceWorker(swUrl) { }) .catch(() => { console.log( - 'No internet connection found. App is running in offline mode.' + 'No internet connection found. App is running in offline mode.', ); }); } diff --git a/src/servers/reducers/selectedServer.js b/src/servers/reducers/selectedServer.js index 835df2a7..07ca284d 100644 --- a/src/servers/reducers/selectedServer.js +++ b/src/servers/reducers/selectedServer.js @@ -15,7 +15,7 @@ export const LATEST_VERSION_CONSTRAINT = 'latest'; const initialState = null; const versionToSemVer = pipe( (version) => version === LATEST_VERSION_CONSTRAINT ? MAX_FALLBACK_VERSION : version, - toSemVer(MIN_FALLBACK_VERSION) + toSemVer(MIN_FALLBACK_VERSION), ); const getServerVersion = memoizeWith(identity, (serverId, health) => health().then(({ version }) => ({ @@ -27,7 +27,7 @@ export const resetSelectedServer = createAction(RESET_SELECTED_SERVER); export const selectServer = (buildShlinkApiClient, loadMercureInfo) => (serverId) => async ( dispatch, - getState + getState, ) => { dispatch(resetSelectedServer()); dispatch(resetShortUrlParams()); diff --git a/src/servers/reducers/servers.js b/src/servers/reducers/servers.js index 61c60ac4..3173d1d6 100644 --- a/src/servers/reducers/servers.js +++ b/src/servers/reducers/servers.js @@ -27,7 +27,7 @@ const serversListToMap = reduce((acc, server) => assoc(server.id, server, acc), export const createServers = pipe( map(assocId), serversListToMap, - (newServers) => ({ type: CREATE_SERVERS, newServers }) + (newServers) => ({ type: CREATE_SERVERS, newServers }), ); export const editServer = (serverId, serverData) => ({ type: EDIT_SERVER, serverId, serverData }); diff --git a/src/short-urls/SearchBar.js b/src/short-urls/SearchBar.js index 2bda7245..53232f4e 100644 --- a/src/short-urls/SearchBar.js +++ b/src/short-urls/SearchBar.js @@ -23,7 +23,7 @@ const SearchBar = (colorGenerator, ForServerVersion) => { const selectedTags = shortUrlsListParams.tags || []; const setDate = (dateName) => pipe( formatDate(), - (date) => listShortUrls({ ...shortUrlsListParams, [dateName]: date }) + (date) => listShortUrls({ ...shortUrlsListParams, [dateName]: date }), ); return ( @@ -63,7 +63,7 @@ const SearchBar = (colorGenerator, ForServerVersion) => { { ...shortUrlsListParams, tags: selectedTags.filter((selectedTag) => selectedTag !== tag), - } + }, )} /> ))} diff --git a/src/short-urls/helpers/ShortUrlsRow.js b/src/short-urls/helpers/ShortUrlsRow.js index b6788da2..f2762a9f 100644 --- a/src/short-urls/helpers/ShortUrlsRow.js +++ b/src/short-urls/helpers/ShortUrlsRow.js @@ -23,7 +23,7 @@ const propTypes = { const ShortUrlsRow = ( ShortUrlsRowMenu, colorGenerator, - useStateFlagTimeout + useStateFlagTimeout, ) => { const ShortUrlsRowComp = ({ shortUrl, selectedServer, refreshList, shortUrlsListParams }) => { const [ copiedToClipboard, setCopiedToClipboard ] = useStateFlagTimeout(); diff --git a/src/short-urls/reducers/shortUrlsList.js b/src/short-urls/reducers/shortUrlsList.js index 9953e4e2..90d2b1c8 100644 --- a/src/short-urls/reducers/shortUrlsList.js +++ b/src/short-urls/reducers/shortUrlsList.js @@ -33,9 +33,9 @@ const initialState = { const setPropFromActionOnMatchingShortUrl = (prop) => (state, { shortCode, domain, [prop]: propValue }) => assocPath( [ 'shortUrls', 'data' ], state.shortUrls.data.map( - (shortUrl) => shortUrlMatches(shortUrl, shortCode, domain) ? assoc(prop, propValue, shortUrl) : shortUrl + (shortUrl) => shortUrlMatches(shortUrl, shortCode, domain) ? assoc(prop, propValue, shortUrl) : shortUrl, ), - state + state, ); export default handleActions({ @@ -55,9 +55,9 @@ export default handleActions({ state.shortUrls && state.shortUrls.data && state.shortUrls.data.map( (shortUrl) => shortUrlMatches(shortUrl, shortCode, domain) ? assoc('visitsCount', visitsCount, shortUrl) - : shortUrl + : shortUrl, ), - state + state, ), }, initialState); diff --git a/src/short-urls/services/provideServices.js b/src/short-urls/services/provideServices.js index 46254639..ad1f71fa 100644 --- a/src/short-urls/services/provideServices.js +++ b/src/short-urls/services/provideServices.js @@ -23,7 +23,7 @@ const provideServices = (bottle, connect) => { // Components bottle.serviceFactory('ShortUrls', ShortUrls, 'SearchBar', 'ShortUrlsList'); bottle.decorator('ShortUrls', reduxConnect( - (state) => assoc('shortUrlsList', state.shortUrlsList.shortUrls, state.shortUrlsList) + (state) => assoc('shortUrlsList', state.shortUrlsList.shortUrls, state.shortUrlsList), )); bottle.serviceFactory('SearchBar', SearchBar, 'ColorGenerator', 'ForServerVersion'); @@ -32,7 +32,7 @@ const provideServices = (bottle, connect) => { bottle.serviceFactory('ShortUrlsList', ShortUrlsList, 'ShortUrlsRow'); bottle.decorator('ShortUrlsList', connect( [ 'selectedServer', 'shortUrlsListParams', 'mercureInfo' ], - [ 'listShortUrls', 'resetShortUrlParams', 'createNewVisit', 'loadMercureInfo' ] + [ 'listShortUrls', 'resetShortUrlParams', 'createNewVisit', 'loadMercureInfo' ], )); bottle.serviceFactory('ShortUrlsRow', ShortUrlsRow, 'ShortUrlsRowMenu', 'ColorGenerator', 'useStateFlagTimeout'); @@ -44,14 +44,14 @@ const provideServices = (bottle, connect) => { 'EditTagsModal', 'EditMetaModal', 'EditShortUrlModal', - 'ForServerVersion' + 'ForServerVersion', ); bottle.serviceFactory('CreateShortUrlResult', CreateShortUrlResult, 'useStateFlagTimeout'); bottle.serviceFactory('CreateShortUrl', CreateShortUrl, 'TagsSelector', 'CreateShortUrlResult', 'ForServerVersion'); bottle.decorator( 'CreateShortUrl', - connect([ 'shortUrlCreationResult', 'selectedServer' ], [ 'createShortUrl', 'resetCreateShortUrl' ]) + connect([ 'shortUrlCreationResult', 'selectedServer' ], [ 'createShortUrl', 'resetCreateShortUrl' ]), ); bottle.serviceFactory('DeleteShortUrlModal', () => DeleteShortUrlModal); diff --git a/src/tags/TagsList.js b/src/tags/TagsList.js index 9e6e9492..a923bf1b 100644 --- a/src/tags/TagsList.js +++ b/src/tags/TagsList.js @@ -23,7 +23,7 @@ const propTypes = { const TagsList = (TagCard) => { const TagListComp = ( - { filterTags, forceListTags, tagsList, selectedServer, createNewVisit, loadMercureInfo, mercureInfo } + { filterTags, forceListTags, tagsList, selectedServer, createNewVisit, loadMercureInfo, mercureInfo }, ) => { const [ displayedTag, setDisplayedTag ] = useState(); diff --git a/src/tags/reducers/tagEdit.js b/src/tags/reducers/tagEdit.js index 095d87a0..137f36df 100644 --- a/src/tags/reducers/tagEdit.js +++ b/src/tags/reducers/tagEdit.js @@ -28,7 +28,7 @@ export default handleActions({ export const editTag = (buildShlinkApiClient, colorGenerator) => (oldName, newName, color) => async ( dispatch, - getState + getState, ) => { dispatch({ type: EDIT_TAG_START }); const { editTag } = buildShlinkApiClient(getState); diff --git a/src/tags/services/provideServices.js b/src/tags/services/provideServices.js index f1bc835d..66204c16 100644 --- a/src/tags/services/provideServices.js +++ b/src/tags/services/provideServices.js @@ -18,7 +18,7 @@ const provideServices = (bottle, connect) => { 'DeleteTagConfirmModal', 'EditTagModal', 'ForServerVersion', - 'ColorGenerator' + 'ColorGenerator', ); bottle.serviceFactory('DeleteTagConfirmModal', () => DeleteTagConfirmModal); @@ -30,7 +30,7 @@ const provideServices = (bottle, connect) => { bottle.serviceFactory('TagsList', TagsList, 'TagCard'); bottle.decorator('TagsList', connect( [ 'tagsList', 'selectedServer', 'mercureInfo' ], - [ 'forceListTags', 'filterTags', 'createNewVisit', 'loadMercureInfo' ] + [ 'forceListTags', 'filterTags', 'createNewVisit', 'loadMercureInfo' ], )); // Actions diff --git a/src/visits/VisitsStats.js b/src/visits/VisitsStats.js index c30b8aa2..dc4d539d 100644 --- a/src/visits/VisitsStats.js +++ b/src/visits/VisitsStats.js @@ -68,7 +68,7 @@ const VisitsStats = ({ processStatsFromVisits, normalizeVisits }, OpenMapModalBt const normalizedVisits = useMemo(() => normalizeVisits(visits), [ visits ]); const { os, browsers, referrers, countries, cities, citiesForMap } = useMemo( () => processStatsFromVisits(normalizedVisits), - [ normalizedVisits ] + [ normalizedVisits ], ); const mapLocations = values(citiesForMap); diff --git a/src/visits/VisitsTable.js b/src/visits/VisitsTable.js index 346143ac..0beb7635 100644 --- a/src/visits/VisitsTable.js +++ b/src/visits/VisitsTable.js @@ -96,7 +96,7 @@ const VisitsTable = ({ 'visits-table__sticky': isSticky, })} onClick={() => setSelectedVisits( - selectedVisits.length < resultSet.total ? resultSet.visitsGroups.flat() : [] + selectedVisits.length < resultSet.total ? resultSet.visitsGroups.flat() : [], )} > 0 })} /> @@ -149,7 +149,7 @@ const VisitsTable = ({ style={{ cursor: 'pointer' }} className={classNames({ 'table-primary': isSelected })} onClick={() => setSelectedVisits( - isSelected ? selectedVisits.filter((v) => v !== visit) : [ ...selectedVisits, visit ] + isSelected ? selectedVisits.filter((v) => v !== visit) : [ ...selectedVisits, visit ], )} > diff --git a/src/visits/helpers/LineChartCard.js b/src/visits/helpers/LineChartCard.js index 264d3dde..3daeb969 100644 --- a/src/visits/helpers/LineChartCard.js +++ b/src/visits/helpers/LineChartCard.js @@ -117,7 +117,7 @@ const LineChartCard = ({ title, visits, highlightedVisits, highlightedLabel = 'S ); const groupedHighlighted = useMemo( () => fillTheGaps(groupVisitsByStep(step, reverse(highlightedVisits)), labels), - [ highlightedVisits, step, labels ] + [ highlightedVisits, step, labels ], ); const data = { diff --git a/src/visits/helpers/SortableBarGraph.js b/src/visits/helpers/SortableBarGraph.js index efaa03f0..7ff4c123 100644 --- a/src/visits/helpers/SortableBarGraph.js +++ b/src/visits/helpers/SortableBarGraph.js @@ -44,9 +44,9 @@ const SortableBarGraph = ({ const sortedPairs = !order.orderField ? pairs : sortBy( pipe( prop(order.orderField === head(keys(sortingItems)) ? 0 : 1), - toLowerIfString + toLowerIfString, ), - pairs + pairs, ); return !order.orderDir || order.orderDir === 'ASC' ? sortedPairs : reverse(sortedPairs); @@ -56,7 +56,7 @@ const SortableBarGraph = ({ const sortedKeys = sortedPairs.map(pickKeyFromPair); // The highlighted stats have to be ordered based on the regular stats, not on its own values const sortedHighlightedPairs = highlightedStats && toPairs( - { ...zipObj(sortedKeys, sortedKeys.map(() => 0)), ...highlightedStats } + { ...zipObj(sortedKeys, sortedKeys.map(() => 0)), ...highlightedStats }, ); if (sortedPairs.length <= itemsPerPage) { @@ -94,7 +94,7 @@ const SortableBarGraph = ({ const { currentPageStats, currentPageHighlightedStats, pagination, max } = determineStats( stats, highlightedStats && keys(highlightedStats).length > 0 ? highlightedStats : undefined, - sortingItems + sortingItems, ); const activeCities = keys(currentPageStats); const computeTitle = () => ( diff --git a/src/visits/services/VisitsParser.js b/src/visits/services/VisitsParser.js index 5ab88f71..83f984bb 100644 --- a/src/visits/services/VisitsParser.js +++ b/src/visits/services/VisitsParser.js @@ -56,7 +56,7 @@ export const processStatsFromVisits = (normalizedVisits) => return stats; }, - { os: {}, browsers: {}, referrers: {}, countries: {}, cities: {}, citiesForMap: {} } + { os: {}, browsers: {}, referrers: {}, countries: {}, cities: {}, citiesForMap: {} }, ); export const normalizeVisits = map(({ userAgent, date, referer, visitLocation }) => { diff --git a/src/visits/services/provideServices.js b/src/visits/services/provideServices.js index 18da7c20..cc7062f5 100644 --- a/src/visits/services/provideServices.js +++ b/src/visits/services/provideServices.js @@ -17,12 +17,12 @@ const provideServices = (bottle, connect) => { bottle.serviceFactory('ShortUrlVisits', ShortUrlVisits, 'VisitsStats'); bottle.decorator('ShortUrlVisits', connect( [ 'shortUrlVisits', 'shortUrlDetail', 'mercureInfo' ], - [ 'getShortUrlVisits', 'getShortUrlDetail', 'cancelGetShortUrlVisits', 'createNewVisit', 'loadMercureInfo' ] + [ 'getShortUrlVisits', 'getShortUrlDetail', 'cancelGetShortUrlVisits', 'createNewVisit', 'loadMercureInfo' ], )); bottle.serviceFactory('TagVisits', TagVisits, 'VisitsStats', 'ColorGenerator'); bottle.decorator('TagVisits', connect( [ 'tagVisits', 'mercureInfo' ], - [ 'getTagVisits', 'cancelGetTagVisits', 'createNewVisit', 'loadMercureInfo' ] + [ 'getTagVisits', 'cancelGetTagVisits', 'createNewVisit', 'loadMercureInfo' ], )); // Services diff --git a/test/common/ErrorHandler.test.js b/test/common/ErrorHandler.test.js index 721d7a0d..a98437d0 100644 --- a/test/common/ErrorHandler.test.js +++ b/test/common/ErrorHandler.test.js @@ -30,7 +30,7 @@ describe('', () => { expect(wrapper.text()).toContain('Oops! This is awkward :S'); expect(wrapper.text()).toContain( - 'It seems that something went wrong. Try refreshing the page or just click this button.' + 'It seems that something went wrong. Try refreshing the page or just click this button.', ); expect(wrapper.find(Button)).toHaveLength(1); }); diff --git a/test/common/NotFound.test.js b/test/common/NotFound.test.js index 00d3739b..036db8a6 100644 --- a/test/common/NotFound.test.js +++ b/test/common/NotFound.test.js @@ -24,7 +24,7 @@ describe('', () => { const { content } = createWrapper(); expect(content).toContain( - 'Use your browser\'s back button to navigate to the page you have previously come from, or just press this button.' + 'Use your browser\'s back button to navigate to the page you have previously come from, or just press this button.', ); }); diff --git a/test/servers/CreateServer.test.js b/test/servers/CreateServer.test.js index cab51a60..98863b2f 100644 --- a/test/servers/CreateServer.test.js +++ b/test/servers/CreateServer.test.js @@ -15,7 +15,7 @@ describe('', () => { const CreateServer = createServerConstruct(ImportServersBtn, () => [ serversImported, () => '' ]); wrapper = shallow( - + , ); return wrapper; diff --git a/test/servers/DeleteServerModal.test.js b/test/servers/DeleteServerModal.test.js index 6aca966b..1114fd8f 100644 --- a/test/servers/DeleteServerModal.test.js +++ b/test/servers/DeleteServerModal.test.js @@ -22,7 +22,7 @@ describe('', () => { isOpen={true} deleteServer={deleteServerMock} history={historyMock} - /> + />, ); }); afterEach(() => wrapper.unmount()); @@ -38,7 +38,7 @@ describe('', () => { const modalBody = wrapper.find(ModalBody); expect(modalBody.find('p').first().text()).toEqual( - `Are you sure you want to remove ${serverName}?` + `Are you sure you want to remove ${serverName}?`, ); }); diff --git a/test/servers/EditServer.test.js b/test/servers/EditServer.test.js index 36d81d2c..366dd24f 100644 --- a/test/servers/EditServer.test.js +++ b/test/servers/EditServer.test.js @@ -28,7 +28,7 @@ describe('', () => { match={match} selectedServer={selectedServer} selectServer={jest.fn()} - /> + />, ); }); diff --git a/test/servers/ServersDropdown.test.js b/test/servers/ServersDropdown.test.js index d90dbba2..f2e6bd51 100644 --- a/test/servers/ServersDropdown.test.js +++ b/test/servers/ServersDropdown.test.js @@ -37,7 +37,7 @@ describe('', () => { it('shows a message when no servers exist yet', () => { wrapped = shallow( - + , ); const item = wrapped.find(DropdownItem); diff --git a/test/servers/helpers/ForServerVersion.test.js b/test/servers/helpers/ForServerVersion.test.js index 43f486bd..171d2feb 100644 --- a/test/servers/helpers/ForServerVersion.test.js +++ b/test/servers/helpers/ForServerVersion.test.js @@ -9,7 +9,7 @@ describe('', () => { wrapped = mount( Hello - + , ); return wrapped; diff --git a/test/servers/helpers/ImportServersBtn.test.js b/test/servers/helpers/ImportServersBtn.test.js index d4c68186..fc203d28 100644 --- a/test/servers/helpers/ImportServersBtn.test.js +++ b/test/servers/helpers/ImportServersBtn.test.js @@ -23,7 +23,7 @@ describe('', () => { const ImportServersBtn = importServersBtnConstruct(serversImporterMock); wrapper = shallow( - + , ); }); afterEach(() => wrapper.unmount()); diff --git a/test/servers/helpers/ServerError.test.js b/test/servers/helpers/ServerError.test.js index 01811564..0847afe2 100644 --- a/test/servers/helpers/ServerError.test.js +++ b/test/servers/helpers/ServerError.test.js @@ -33,7 +33,7 @@ describe('', () => { wrapper = shallow( - + , ); const wrapperText = wrapper.html(); const textPairs = Object.entries(textsToFind); diff --git a/test/short-urls/CreateShortUrl.test.js b/test/short-urls/CreateShortUrl.test.js index ae47aa10..e9ca10cb 100644 --- a/test/short-urls/CreateShortUrl.test.js +++ b/test/short-urls/CreateShortUrl.test.js @@ -17,7 +17,7 @@ describe('', () => { const CreateShortUrl = createShortUrlsCreator(TagsSelector, () => '', () => ''); wrapper = shallow( - + , ); }); afterEach(() => { diff --git a/test/short-urls/SearchBar.test.js b/test/short-urls/SearchBar.test.js index 95b9b1d6..efbdddc1 100644 --- a/test/short-urls/SearchBar.test.js +++ b/test/short-urls/SearchBar.test.js @@ -52,7 +52,7 @@ describe('', () => { it('updates short URLs list when a tag is removed', () => { wrapper = shallow( - + , ); const tag = wrapper.find(Tag).first(); @@ -63,7 +63,7 @@ describe('', () => { it.each([ 'startDateChange', 'endDateChange' ])('updates short URLs list when date range changes', (event) => { wrapper = shallow( - + , ); const dateRange = wrapper.find(DateRangeRow); diff --git a/test/short-urls/ShortUrlsList.test.js b/test/short-urls/ShortUrlsList.test.js index 050238b9..65246833 100644 --- a/test/short-urls/ShortUrlsList.test.js +++ b/test/short-urls/ShortUrlsList.test.js @@ -37,7 +37,7 @@ describe('', () => { ] } mercureInfo={{ loading: true }} - /> + />, ); }); diff --git a/test/short-urls/helpers/DeleteShortUrlModal.test.js b/test/short-urls/helpers/DeleteShortUrlModal.test.js index 1cb4dbb1..c950d642 100644 --- a/test/short-urls/helpers/DeleteShortUrlModal.test.js +++ b/test/short-urls/helpers/DeleteShortUrlModal.test.js @@ -20,7 +20,7 @@ describe('', () => { toggle={identity} deleteShortUrl={deleteShortUrl} resetDeleteShortUrl={identity} - /> + />, ); return wrapper; diff --git a/test/short-urls/helpers/EditMetaModal.test.js b/test/short-urls/helpers/EditMetaModal.test.js index e44be7bd..fb20f7ac 100644 --- a/test/short-urls/helpers/EditMetaModal.test.js +++ b/test/short-urls/helpers/EditMetaModal.test.js @@ -17,7 +17,7 @@ describe('', () => { toggle={toggle} editShortUrlMeta={editShortUrlMeta} resetShortUrlMeta={resetShortUrlMeta} - /> + />, ); return wrapper; diff --git a/test/short-urls/helpers/EditShortUrlModal.test.js b/test/short-urls/helpers/EditShortUrlModal.test.js index 976cde14..a7a4ab15 100644 --- a/test/short-urls/helpers/EditShortUrlModal.test.js +++ b/test/short-urls/helpers/EditShortUrlModal.test.js @@ -15,7 +15,7 @@ describe('', () => { shortUrlEdition={shortUrlEdition} toggle={toggle} editShortUrl={editShortUrl} - /> + />, ); return wrapper; diff --git a/test/short-urls/helpers/EditTagsModal.test.js b/test/short-urls/helpers/EditTagsModal.test.js index bb9866c2..ceef068b 100644 --- a/test/short-urls/helpers/EditTagsModal.test.js +++ b/test/short-urls/helpers/EditTagsModal.test.js @@ -26,7 +26,7 @@ describe('', () => { toggle={toggle} editShortUrlTags={editShortUrlTags} resetShortUrlsTags={resetShortUrlsTags} - /> + />, ); return wrapper; diff --git a/test/short-urls/helpers/ShortUrlVisitsCount.test.js b/test/short-urls/helpers/ShortUrlVisitsCount.test.js index f12d3fe3..c7f34659 100644 --- a/test/short-urls/helpers/ShortUrlVisitsCount.test.js +++ b/test/short-urls/helpers/ShortUrlVisitsCount.test.js @@ -21,7 +21,7 @@ describe('', () => { const maxVisitsTooltip = wrapper.find(UncontrolledTooltip); expect(wrapper.html()).toEqual( - `${visitsCount}` + `${visitsCount}`, ); expect(maxVisitsHelper).toHaveLength(0); expect(maxVisitsTooltip).toHaveLength(0); diff --git a/test/short-urls/helpers/ShortUrlsRow.test.js b/test/short-urls/helpers/ShortUrlsRow.test.js index 4c0d022c..38367d45 100644 --- a/test/short-urls/helpers/ShortUrlsRow.test.js +++ b/test/short-urls/helpers/ShortUrlsRow.test.js @@ -34,7 +34,7 @@ describe('', () => { const ShortUrlsRow = createShortUrlsRow(ShortUrlsRowMenu, colorGenerator, useStateFlagTimeout); wrapper = shallow( - + , ); }); afterEach(() => wrapper.unmount()); diff --git a/test/short-urls/helpers/ShortUrlsRowMenu.test.js b/test/short-urls/helpers/ShortUrlsRowMenu.test.js index aecd2d84..28e1c5c5 100644 --- a/test/short-urls/helpers/ShortUrlsRowMenu.test.js +++ b/test/short-urls/helpers/ShortUrlsRowMenu.test.js @@ -23,7 +23,7 @@ describe('', () => { EditTagsModal, EditMetaModal, EditShortUrlModal, - () => '' + () => '', ); wrapper = shallow( @@ -31,7 +31,7 @@ describe('', () => { selectedServer={selectedServer} shortUrl={shortUrl} onCopyToClipboard={onCopyToClipboard} - /> + />, ); return wrapper; diff --git a/test/short-urls/reducers/shortUrlDeletion.test.js b/test/short-urls/reducers/shortUrlDeletion.test.js index ad5b2649..c8437c25 100644 --- a/test/short-urls/reducers/shortUrlDeletion.test.js +++ b/test/short-urls/reducers/shortUrlDeletion.test.js @@ -60,7 +60,7 @@ describe('shortUrlDeletionReducer', () => { }); it.each( - [[ undefined ], [ null ], [ 'example.com' ]] + [[ undefined ], [ null ], [ 'example.com' ]], )('dispatches proper actions if API client request succeeds', async (domain) => { const apiClientMock = { deleteShortUrl: jest.fn(() => ''), diff --git a/test/short-urls/reducers/shortUrlTags.test.js b/test/short-urls/reducers/shortUrlTags.test.js index 98bd13a7..9be9f5ef 100644 --- a/test/short-urls/reducers/shortUrlTags.test.js +++ b/test/short-urls/reducers/shortUrlTags.test.js @@ -70,7 +70,7 @@ describe('shortUrlTagsReducer', () => { expect(dispatch).toHaveBeenNthCalledWith(1, { type: EDIT_SHORT_URL_TAGS_START }); expect(dispatch).toHaveBeenNthCalledWith( 2, - { type: SHORT_URL_TAGS_EDITED, tags: normalizedTags, shortCode, domain } + { type: SHORT_URL_TAGS_EDITED, tags: normalizedTags, shortCode, domain }, ); }); diff --git a/test/tags/TagsList.test.js b/test/tags/TagsList.test.js index c3bd3393..cd20409e 100644 --- a/test/tags/TagsList.test.js +++ b/test/tags/TagsList.test.js @@ -15,7 +15,7 @@ describe('', () => { const TagsList = createTagsList(TagCard); wrapper = shallow( - + , ); return wrapper; diff --git a/test/tags/helpers/DeleteTagConfirmModal.test.js b/test/tags/helpers/DeleteTagConfirmModal.test.js index aa45d921..a13881a1 100644 --- a/test/tags/helpers/DeleteTagConfirmModal.test.js +++ b/test/tags/helpers/DeleteTagConfirmModal.test.js @@ -17,7 +17,7 @@ describe('', () => { deleteTag={deleteTag} tagDeleted={tagDeleted} tagDelete={tagDelete} - /> + />, ); return wrapper; diff --git a/test/utils/services/ShlinkApiClient.test.js b/test/utils/services/ShlinkApiClient.test.js index c1889f5d..55a4fd3b 100644 --- a/test/utils/services/ShlinkApiClient.test.js +++ b/test/utils/services/ShlinkApiClient.test.js @@ -42,7 +42,7 @@ describe('ShlinkApiClient', () => { const { createShortUrl } = new ShlinkApiClient(axiosSpy); await createShortUrl( - { foo: 'bar', empty: undefined, anotherEmpty: null } + { foo: 'bar', empty: undefined, anotherEmpty: null }, ); expect(axiosSpy).toHaveBeenCalledWith(expect.objectContaining({ data: { foo: 'bar' } })); diff --git a/test/visits/ShortUrlVisits.test.js b/test/visits/ShortUrlVisits.test.js index 38297826..89310a55 100644 --- a/test/visits/ShortUrlVisits.test.js +++ b/test/visits/ShortUrlVisits.test.js @@ -30,7 +30,7 @@ describe('', () => { shortUrlDetail={{}} cancelGetShortUrlVisits={identity} matchMedia={() => ({ matches: false })} - /> + />, ); }); diff --git a/test/visits/ShortUrlVisitsHeader.test.js b/test/visits/ShortUrlVisitsHeader.test.js index 763efc57..fef9c2ca 100644 --- a/test/visits/ShortUrlVisitsHeader.test.js +++ b/test/visits/ShortUrlVisitsHeader.test.js @@ -21,7 +21,7 @@ describe('', () => { beforeEach(() => { wrapper = shallow( - + , ); }); afterEach(() => wrapper.unmount()); diff --git a/test/visits/TagVisits.test.js b/test/visits/TagVisits.test.js index 96d3368f..023b5caf 100644 --- a/test/visits/TagVisits.test.js +++ b/test/visits/TagVisits.test.js @@ -25,7 +25,7 @@ describe('', () => { history={history} tagVisits={{ loading: true, visits: [] }} cancelGetTagVisits={identity} - /> + />, ); }); diff --git a/test/visits/TagVisitsHeader.test.js b/test/visits/TagVisitsHeader.test.js index d35d3b50..15a8defc 100644 --- a/test/visits/TagVisitsHeader.test.js +++ b/test/visits/TagVisitsHeader.test.js @@ -13,7 +13,7 @@ describe('', () => { beforeEach(() => { wrapper = shallow( - + , ); }); afterEach(() => wrapper.unmount()); diff --git a/test/visits/VisitsHeader.test.js b/test/visits/VisitsHeader.test.js index ade980e8..a23cf5ee 100644 --- a/test/visits/VisitsHeader.test.js +++ b/test/visits/VisitsHeader.test.js @@ -10,7 +10,7 @@ describe('', () => { beforeEach(() => { wrapper = shallow( - + , ); }); @@ -21,7 +21,7 @@ describe('', () => { const visitsBadge = wrapper.find('.badge'); expect(visitsBadge.html()).toContain( - `Visits: ${visits.length}` + `Visits: ${visits.length}`, ); }); diff --git a/test/visits/VisitsStats.test.js b/test/visits/VisitsStats.test.js index c3872512..b4670d22 100644 --- a/test/visits/VisitsStats.test.js +++ b/test/visits/VisitsStats.test.js @@ -24,7 +24,7 @@ describe('', () => { visitsInfo={visitsInfo} cancelGetVisits={identity} matchMedia={() => ({ matches: false })} - /> + />, ); return wrapper; diff --git a/test/visits/VisitsTable.test.js b/test/visits/VisitsTable.test.js index 0cf9e26b..fdee519d 100644 --- a/test/visits/VisitsTable.test.js +++ b/test/visits/VisitsTable.test.js @@ -16,7 +16,7 @@ describe('', () => { selectedVisits={selectedVisits} setSelectedVisits={setSelectedVisits} matchMedia={matchMedia} - /> + />, ); return wrapper; @@ -64,7 +64,7 @@ describe('', () => { }); it.each( - rangeOf(20, (value) => [ value ]) + rangeOf(20, (value) => [ value ]), )('does not render footer when there is only one page to render', (visitsCount) => { const wrapper = createWrapper(rangeOf(visitsCount, () => ({ browser: '', date: '', referer: '' }))); const tr = wrapper.find('tbody').find('tr'); diff --git a/test/visits/helpers/SortableBarGraph.test.js b/test/visits/helpers/SortableBarGraph.test.js index 12f1d09a..4ea06650 100644 --- a/test/visits/helpers/SortableBarGraph.test.js +++ b/test/visits/helpers/SortableBarGraph.test.js @@ -24,7 +24,7 @@ describe('', () => { stats={{ ...stats, ...extraStats }} sortingItems={sortingItems} withPagination={withPagination} - /> + />, ); return wrapper; @@ -108,7 +108,7 @@ describe('', () => { )} /> - + , ).find(SortableBarGraph); const header = wrapper.renderProp('extraHeaderContent')(); diff --git a/test/visits/reducers/visitCreation.test.js b/test/visits/reducers/visitCreation.test.js index e010255e..7f8dbe3b 100644 --- a/test/visits/reducers/visitCreation.test.js +++ b/test/visits/reducers/visitCreation.test.js @@ -4,7 +4,7 @@ describe('visitCreationReducer', () => { describe('createNewVisit', () => { it('just returns the action with proper type', () => expect(createNewVisit({ shortUrl: {}, visit: {} })).toEqual( - { type: CREATE_VISIT, shortUrl: {}, visit: {} } + { type: CREATE_VISIT, shortUrl: {}, visit: {} }, )); }); }); From 72de9d4ff891480b22ad6b0d80f83b58fde90959 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 22 Aug 2020 08:47:19 +0200 Subject: [PATCH 03/59] Added first Typescript files --- .eslintrc | 12 +++ jest.config.js | 2 +- package-lock.json | 114 +++++++++++++++++++++++++-- package.json | 4 + shlink-web-client.d.ts | 5 ++ src/container/{store.js => store.ts} | 9 +-- src/{index.js => index.tsx} | 2 +- src/reducers/{index.js => index.ts} | 0 8 files changed, 135 insertions(+), 13 deletions(-) create mode 100644 shlink-web-client.d.ts rename src/container/{store.js => store.ts} (59%) rename src/{index.js => index.tsx} (100%) rename src/reducers/{index.js => index.ts} (100%) diff --git a/.eslintrc b/.eslintrc index dcf40fae..f50c9716 100644 --- a/.eslintrc +++ b/.eslintrc @@ -28,6 +28,12 @@ } }, "rules": { + "max-len": ["error", { + "code": 120, + "ignoreStrings": true, + "ignoreTemplateLiterals": true + }], + "no-mixed-operators": "off", "comma-dangle": ["error", "always-multiline"], "no-invalid-this": "off", "no-inline-comments": "off", @@ -55,6 +61,12 @@ "@shlinkio/js-coding-standard" ], "rules": { + "max-len": ["error", { + "code": 120, + "ignoreStrings": true, + "ignoreTemplateLiterals": true + }], + "no-mixed-operators": "off", "react/display-name": "off" } } diff --git a/jest.config.js b/jest.config.js index 3c0762c6..f698a928 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,7 +1,7 @@ module.exports = { coverageDirectory: '/coverage', collectCoverageFrom: [ - 'src/**/*.js', + 'src/**/*.{js,jsx,ts,tsx}', '!src/registerServiceWorker.js', '!src/index.js', '!src/reducers/index.js', diff --git a/package-lock.json b/package-lock.json index 703cd958..31584ce3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2051,6 +2051,33 @@ "@types/node": "*" } }, + "@types/history": { + "version": "4.7.7", + "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.7.tgz", + "integrity": "sha512-2xtoL22/3Mv6a70i4+4RB7VgbDDORoWwjcqeNysojZA0R7NK17RbY5Gof/2QiFfJgX+KkWghbwJ+d/2SB8Ndzg==", + "dev": true + }, + "@types/hoist-non-react-statics": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", + "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==", + "dev": true, + "requires": { + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0" + }, + "dependencies": { + "hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "dev": true, + "requires": { + "react-is": "^16.7.0" + } + } + } + }, "@types/istanbul-lib-coverage": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz", @@ -2225,12 +2252,81 @@ "integrity": "sha512-Otxmr2rrZLKRYIybtdG/sgeO+tHY20GxeDjcGmUnmmlCWyEnv2a2x1ZXBo3BTec4OiTXMQCiazB8NMBf0iRlFw==", "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 + }, "@types/q": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.2.tgz", "integrity": "sha512-ce5d3q03Ex0sy4R14722Rmt6MT07Ua+k4FwDfdcToYJcMKNtRVQvJ6JCAPdAmAnbRb6CsX6aYb9m96NGod9uTw==", "dev": true }, + "@types/react": { + "version": "16.9.46", + "resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.46.tgz", + "integrity": "sha512-dbHzO3aAq1lB3jRQuNpuZ/mnu+CdD3H0WVaaBQA8LTT3S33xhVBUj232T8M3tAhSWJs/D/UqORYUlJNl/8VQZg==", + "dev": true, + "requires": { + "@types/prop-types": "*", + "csstype": "^3.0.2" + } + }, + "@types/react-dom": { + "version": "16.9.8", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.8.tgz", + "integrity": "sha512-ykkPQ+5nFknnlU6lDd947WbQ6TE3NNzbQAkInC2EKY1qeYdTKp7onFusmYZb+ityzx2YviqT6BXSu+LyWWJwcA==", + "dev": true, + "requires": { + "@types/react": "*" + } + }, + "@types/react-redux": { + "version": "7.1.9", + "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.9.tgz", + "integrity": "sha512-mpC0jqxhP4mhmOl3P4ipRsgTgbNofMRXJb08Ms6gekViLj61v1hOZEKWDCyWsdONr6EjEA6ZHXC446wdywDe0w==", + "dev": true, + "requires": { + "@types/hoist-non-react-statics": "^3.3.0", + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0", + "redux": "^4.0.0" + }, + "dependencies": { + "hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "dev": true, + "requires": { + "react-is": "^16.7.0" + } + } + } + }, + "@types/react-router": { + "version": "5.1.8", + "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.8.tgz", + "integrity": "sha512-HzOyJb+wFmyEhyfp4D4NYrumi+LQgQL/68HvJO+q6XtuHSDvw6Aqov7sCAhjbNq3bUPgPqbdvjXC5HeB2oEAPg==", + "dev": true, + "requires": { + "@types/history": "*", + "@types/react": "*" + } + }, + "@types/react-router-dom": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.1.5.tgz", + "integrity": "sha512-ArBM4B1g3BWLGbaGvwBGO75GNFbLDUthrDojV2vHLih/Tq8M+tgvY1DSwkuNrPSwdp/GUL93WSEpTZs8nVyJLw==", + "dev": true, + "requires": { + "@types/history": "*", + "@types/react": "*", + "@types/react-router": "*" + } + }, "@types/stack-utils": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz", @@ -5673,6 +5769,12 @@ "cssom": "0.3.x" } }, + "csstype": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.2.tgz", + "integrity": "sha512-ofovWglpqoqbfLNOTBNZLSbMuGrblAf1efvvArGKOZMBrIoJeu5UsAipQolkijtyQx5MtAzT/J9IHj/CEY1mJw==", + "dev": true + }, "csvjson": { "version": "5.1.0", "resolved": "https://registry.yarnpkg.com/csvjson/-/csvjson-5.1.0.tgz", @@ -7298,7 +7400,7 @@ }, "load-json-file": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", + "resolved": "https://registry.yarnpkg.com/load-json-file/-/load-json-file-2.0.0.tgz", "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", "dev": true, "requires": { @@ -7362,7 +7464,7 @@ }, "parse-json": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "resolved": "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz", "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", "dev": true, "requires": { @@ -7371,7 +7473,7 @@ }, "path-type": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", + "resolved": "https://registry.yarnpkg.com/path-type/-/path-type-2.0.0.tgz", "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", "dev": true, "requires": { @@ -7380,13 +7482,13 @@ }, "pify": { "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "resolved": "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz", "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", "dev": true }, "read-pkg": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", + "resolved": "https://registry.yarnpkg.com/read-pkg/-/read-pkg-2.0.0.tgz", "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", "dev": true, "requires": { @@ -7397,7 +7499,7 @@ }, "read-pkg-up": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", + "resolved": "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-2.0.0.tgz", "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", "dev": true, "requires": { diff --git a/package.json b/package.json index 46d11128..50cdcf77 100644 --- a/package.json +++ b/package.json @@ -73,6 +73,10 @@ "@stryker-mutator/javascript-mutator": "^3.2.4", "@stryker-mutator/jest-runner": "^3.2.4", "@svgr/webpack": "^4.3.3", + "@types/react": "^16.9.46", + "@types/react-dom": "^16.9.8", + "@types/react-redux": "^7.1.9", + "@types/react-router-dom": "^5.1.5", "adm-zip": "^0.4.13", "autoprefixer": "^9.6.3", "babel-core": "7.0.0-bridge.0", diff --git a/shlink-web-client.d.ts b/shlink-web-client.d.ts new file mode 100644 index 00000000..bbe9db86 --- /dev/null +++ b/shlink-web-client.d.ts @@ -0,0 +1,5 @@ +export declare global { + interface Window { + __REDUX_DEVTOOLS_EXTENSION_COMPOSE__?: Function; + } +} diff --git a/src/container/store.js b/src/container/store.ts similarity index 59% rename from src/container/store.js rename to src/container/store.ts index 754f3569..70ea604e 100644 --- a/src/container/store.js +++ b/src/container/store.ts @@ -1,13 +1,12 @@ import ReduxThunk from 'redux-thunk'; import { applyMiddleware, compose, createStore } from 'redux'; -import { save, load } from 'redux-localstorage-simple'; +import { save, load, RLSOptions } from 'redux-localstorage-simple'; import reducers from '../reducers'; -const composeEnhancers = process.env.NODE_ENV !== 'production' && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ - ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ - : compose; +const isProduction = process.env.NODE_ENV !== 'production'; +const composeEnhancers: Function = !isProduction && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; -const localStorageConfig = { +const localStorageConfig: RLSOptions = { states: [ 'settings', 'servers' ], namespace: 'shlink', namespaceSeparator: '.', diff --git a/src/index.js b/src/index.tsx similarity index 100% rename from src/index.js rename to src/index.tsx index 333d5115..822c2988 100644 --- a/src/index.js +++ b/src/index.tsx @@ -1,4 +1,3 @@ -import 'bootstrap/dist/css/bootstrap.min.css'; import React from 'react'; import { render } from 'react-dom'; import { Provider } from 'react-redux'; @@ -8,6 +7,7 @@ import registerServiceWorker from './registerServiceWorker'; import container from './container'; import store from './container/store'; import { fixLeafletIcons } from './utils/utils'; +import 'bootstrap/dist/css/bootstrap.min.css'; import 'react-datepicker/dist/react-datepicker.css'; import 'leaflet/dist/leaflet.css'; import './common/react-tagsinput.scss'; diff --git a/src/reducers/index.js b/src/reducers/index.ts similarity index 100% rename from src/reducers/index.js rename to src/reducers/index.ts From 524b0a74c6fe39b9fb7667524109006d5055dc05 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 22 Aug 2020 09:15:05 +0200 Subject: [PATCH 04/59] Migrated first component and test to typescript --- jest.config.js | 4 +-- package-lock.json | 34 +++++++++++++++++++ package.json | 3 ++ src/App.js | 44 ------------------------- src/App.tsx | 39 ++++++++++++++++++++++ src/container/{index.js => index.ts} | 10 +++--- src/visits/helpers/OpenMapModalBtn.scss | 2 +- test/{App.test.js => App.test.tsx} | 11 ++++--- 8 files changed, 91 insertions(+), 56 deletions(-) delete mode 100644 src/App.js create mode 100644 src/App.tsx rename src/container/{index.js => index.ts} (81%) rename test/{App.test.js => App.test.tsx} (69%) diff --git a/jest.config.js b/jest.config.js index f698a928..0387f86d 100644 --- a/jest.config.js +++ b/jest.config.js @@ -17,9 +17,9 @@ module.exports = { testEnvironment: 'jsdom', testURL: 'http://localhost', transform: { - '^.+\\.(js|jsx|mjs)$': '/node_modules/babel-jest', + '^.+\\.(ts|tsx|js|jsx|mjs)$': '/node_modules/babel-jest', '^.+\\.css$': '/config/jest/cssTransform.js', - '^(?!.*\\.(js|jsx|mjs|css|json)$)': '/config/jest/fileTransform.js', + '^(?!.*\\.(ts|tsx|js|jsx|mjs|css|json)$)': '/config/jest/fileTransform.js', }, transformIgnorePatterns: [ '[/\\\\]node_modules[/\\\\].+\\.(js|jsx|ts|tsx)$', diff --git a/package-lock.json b/package-lock.json index 31584ce3..63d74a88 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2022,12 +2022,31 @@ "@babel/types": "^7.3.0" } }, + "@types/cheerio": { + "version": "0.22.21", + "resolved": "https://registry.npmjs.org/@types/cheerio/-/cheerio-0.22.21.tgz", + "integrity": "sha512-aGI3DfswwqgKPiEOTaiHV2ZPC9KEhprpgEbJnv0fZl3SGX0cGgEva1126dGrMC6AJM6v/aihlUgJn9M5DbDZ/Q==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/color-name": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", "dev": true }, + "@types/enzyme": { + "version": "3.10.5", + "resolved": "https://registry.npmjs.org/@types/enzyme/-/enzyme-3.10.5.tgz", + "integrity": "sha512-R+phe509UuUYy9Tk0YlSbipRpfVtIzb/9BHn5pTEtjJTF5LXvUjrIQcZvNyANNEyFrd2YGs196PniNT1fgvOQA==", + "dev": true, + "requires": { + "@types/cheerio": "*", + "@types/react": "*" + } + }, "@types/eslint-visitor-keys": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", @@ -2264,6 +2283,15 @@ "integrity": "sha512-ce5d3q03Ex0sy4R14722Rmt6MT07Ua+k4FwDfdcToYJcMKNtRVQvJ6JCAPdAmAnbRb6CsX6aYb9m96NGod9uTw==", "dev": true }, + "@types/ramda": { + "version": "0.27.14", + "resolved": "https://registry.npmjs.org/@types/ramda/-/ramda-0.27.14.tgz", + "integrity": "sha512-vbw/VAtEJeSJ6Z61QT+epirlnBeJiJIO7ndK1BJ0fKswnfbiTNga/jBG6R3OnBaFYx+UJv6Iv7ZfWDFSsSzNqA==", + "dev": true, + "requires": { + "ts-toolbelt": "^6.3.3" + } + }, "@types/react": { "version": "16.9.46", "resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.46.tgz", @@ -19465,6 +19493,12 @@ "integrity": "sha512-1J/vefLC+BWSo+qe8OnJQfWTYRS6ingxjwqmHMqaMxXMj7kFtKLgAaYW3JeX3mktjgUL+etlU8/B4VUAUI9QGw==", "dev": true }, + "ts-toolbelt": { + "version": "6.15.5", + "resolved": "https://registry.npmjs.org/ts-toolbelt/-/ts-toolbelt-6.15.5.tgz", + "integrity": "sha512-FZIXf1ksVyLcfr7M317jbB67XFJhOO1YqdTcuGaq9q5jLUoTikukZ+98TPjKiP2jC5CgmYdWWYs0s2nLSU0/1A==", + "dev": true + }, "tsconfig-paths": { "version": "3.9.0", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz", diff --git a/package.json b/package.json index 50cdcf77..99762489 100644 --- a/package.json +++ b/package.json @@ -73,6 +73,9 @@ "@stryker-mutator/javascript-mutator": "^3.2.4", "@stryker-mutator/jest-runner": "^3.2.4", "@svgr/webpack": "^4.3.3", + "@types/enzyme": "^3.10.5", + "@types/jest": "^26.0.10", + "@types/ramda": "^0.27.14", "@types/react": "^16.9.46", "@types/react-dom": "^16.9.8", "@types/react-redux": "^7.1.9", diff --git a/src/App.js b/src/App.js deleted file mode 100644 index 3a8716eb..00000000 --- a/src/App.js +++ /dev/null @@ -1,44 +0,0 @@ -import React, { useEffect } from 'react'; -import PropTypes from 'prop-types'; -import { Route, Switch } from 'react-router-dom'; -import NotFound from './common/NotFound'; -import './App.scss'; - -const propTypes = { - fetchServers: PropTypes.func, - servers: PropTypes.object, -}; - -const App = (MainHeader, Home, MenuLayout, CreateServer, EditServer, Settings) => { - const AppComp = ({ fetchServers, servers }) => { - // On first load, try to fetch the remote servers if the list is empty - useEffect(() => { - if (Object.keys(servers).length === 0) { - fetchServers(); - } - }, []); - - return ( -
- - -
- - - - - - - - -
-
- ); - }; - - AppComp.propTypes = propTypes; - - return AppComp; -}; - -export default App; diff --git a/src/App.tsx b/src/App.tsx new file mode 100644 index 00000000..81311fd2 --- /dev/null +++ b/src/App.tsx @@ -0,0 +1,39 @@ +import React, { useEffect, FC } from 'react'; +import { Route, Switch } from 'react-router-dom'; +import NotFound from './common/NotFound'; +import './App.scss'; + +interface AppProps { + fetchServers: Function; + servers: Record; +} + +const App = (MainHeader: FC, Home: FC, MenuLayout: FC, CreateServer: FC, EditServer: FC, Settings: FC) => ( + { fetchServers, servers }: AppProps, +) => { + // On first load, try to fetch the remote servers if the list is empty + useEffect(() => { + if (Object.keys(servers).length === 0) { + fetchServers(); + } + }, []); + + return ( +
+ + +
+ + + + + + + + +
+
+ ); +}; + +export default App; diff --git a/src/container/index.js b/src/container/index.ts similarity index 81% rename from src/container/index.js rename to src/container/index.ts index 38ec382f..ae20ae63 100644 --- a/src/container/index.js +++ b/src/container/index.ts @@ -1,4 +1,4 @@ -import Bottle from 'bottlejs'; +import Bottle, { IContainer } from 'bottlejs'; import { withRouter } from 'react-router-dom'; import { connect as reduxConnect } from 'react-redux'; import { pick } from 'ramda'; @@ -12,17 +12,19 @@ import provideUtilsServices from '../utils/services/provideServices'; import provideMercureServices from '../mercure/services/provideServices'; import provideSettingsServices from '../settings/services/provideServices'; +type ActionMap = Record; + const bottle = new Bottle(); const { container } = bottle; -const lazyService = (container, serviceName) => (...args) => container[serviceName](...args); -const mapActionService = (map, actionName) => ({ +const lazyService = (container: IContainer, serviceName: string) => (...args: any[]) => container[serviceName](...args); +const mapActionService = (map: ActionMap, actionName: string): ActionMap => ({ ...map, // Wrap actual action service in a function so that it is lazily created the first time it is called [actionName]: lazyService(container, actionName), }); -const connect = (propsFromState, actionServiceNames = []) => +const connect = (propsFromState: string[], actionServiceNames: string[] = []) => reduxConnect( propsFromState ? pick(propsFromState) : null, actionServiceNames.reduce(mapActionService, {}), diff --git a/src/visits/helpers/OpenMapModalBtn.scss b/src/visits/helpers/OpenMapModalBtn.scss index 1da946fe..007802ae 100644 --- a/src/visits/helpers/OpenMapModalBtn.scss +++ b/src/visits/helpers/OpenMapModalBtn.scss @@ -1,4 +1,4 @@ -.open-map-modal-btn__btn { +.open-map-modal-btn__btn.open-map-modal-btn__btn { padding: 0; margin-right: 1rem; } diff --git a/test/App.test.js b/test/App.test.tsx similarity index 69% rename from test/App.test.js rename to test/App.test.tsx index ef67971c..fe50f068 100644 --- a/test/App.test.js +++ b/test/App.test.tsx @@ -1,17 +1,18 @@ import React from 'react'; -import { shallow } from 'enzyme'; +import { shallow, ShallowWrapper } from 'enzyme'; import { Route } from 'react-router-dom'; import { identity } from 'ramda'; import appFactory from '../src/App'; describe('', () => { - let wrapper; - const MainHeader = () => ''; + let wrapper: ShallowWrapper; + const MainHeader = () => null; + const DummyComponent = () => null; beforeEach(() => { - const App = appFactory(MainHeader, identity, identity, identity, identity); + const App = appFactory(MainHeader, DummyComponent, DummyComponent, DummyComponent, DummyComponent, DummyComponent); - wrapper = shallow(); + wrapper = shallow(); }); afterEach(() => wrapper.unmount()); From d65a6ba97075d60f7968190e7d67727087ff1808 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 22 Aug 2020 09:48:55 +0200 Subject: [PATCH 05/59] Migrated to Typescript a file which is imported in JS files --- package-lock.json | 9 +++++++++ package.json | 1 + src/utils/helpers/date.js | 3 --- src/utils/helpers/date.ts | 13 +++++++++++++ test/utils/helpers/date.test.js | 30 ++++++++++++++++++++++++++++++ 5 files changed, 53 insertions(+), 3 deletions(-) delete mode 100644 src/utils/helpers/date.js create mode 100644 src/utils/helpers/date.ts create mode 100644 test/utils/helpers/date.test.js diff --git a/package-lock.json b/package-lock.json index 63d74a88..d361ba85 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2265,6 +2265,15 @@ "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==", "dev": true }, + "@types/moment": { + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/@types/moment/-/moment-2.13.0.tgz", + "integrity": "sha1-YE69GJvDvDShVIaJQE5hoqSqyJY=", + "dev": true, + "requires": { + "moment": "*" + } + }, "@types/node": { "version": "12.7.11", "resolved": "https://registry.npmjs.org/@types/node/-/node-12.7.11.tgz", diff --git a/package.json b/package.json index 99762489..2859341e 100644 --- a/package.json +++ b/package.json @@ -75,6 +75,7 @@ "@svgr/webpack": "^4.3.3", "@types/enzyme": "^3.10.5", "@types/jest": "^26.0.10", + "@types/moment": "^2.13.0", "@types/ramda": "^0.27.14", "@types/react": "^16.9.46", "@types/react-dom": "^16.9.8", diff --git a/src/utils/helpers/date.js b/src/utils/helpers/date.js deleted file mode 100644 index db4f5fda..00000000 --- a/src/utils/helpers/date.js +++ /dev/null @@ -1,3 +0,0 @@ -export const formatDate = (format = 'YYYY-MM-DD') => (date) => date && date.format ? date.format(format) : date; - -export const formatIsoDate = (date) => date && date.format ? date.format() : date; diff --git a/src/utils/helpers/date.ts b/src/utils/helpers/date.ts new file mode 100644 index 00000000..f8da9a77 --- /dev/null +++ b/src/utils/helpers/date.ts @@ -0,0 +1,13 @@ +import * as moment from 'moment'; + +type MomentOrString = moment.Moment | string; +type NullableDate = MomentOrString | null; + +const isMomentObject = (date: moment.Moment | string): date is moment.Moment => typeof (date as moment.Moment).format === 'function'; + +const formatDateFromFormat = (date?: NullableDate, format?: string): NullableDate | undefined => + !date || !isMomentObject(date) ? date : date.format(format); + +export const formatDate = (format = 'YYYY-MM-DD') => (date?: NullableDate) => formatDateFromFormat(date, format); + +export const formatIsoDate = (date: NullableDate) => formatDateFromFormat(date, undefined); diff --git a/test/utils/helpers/date.test.js b/test/utils/helpers/date.test.js new file mode 100644 index 00000000..950de3ef --- /dev/null +++ b/test/utils/helpers/date.test.js @@ -0,0 +1,30 @@ +import moment from 'moment'; +import { formatDate, formatIsoDate } from '../../../src/utils/helpers/date'; + +describe('date', () => { + describe('formatDate', () => { + it.each([ + [ moment('2020-03-05 10:00:10'), 'DD/MM/YYYY', '05/03/2020' ], + [ moment('2020-03-05 10:00:10'), 'YYYY-MM', '2020-03' ], + [ moment('2020-03-05 10:00:10'), undefined, '2020-03-05' ], + [ '2020-03-05 10:00:10', 'DD-MM-YYYY', '2020-03-05 10:00:10' ], + [ '2020-03-05 10:00:10', undefined, '2020-03-05 10:00:10' ], + [ undefined, undefined, undefined ], + [ null, undefined, null ], + ])('formats date as expected', (date, format, expected) => { + expect(formatDate(format)(date)).toEqual(expected); + }); + }); + + describe('formatIsoDate', () => { + it.each([ + [ moment('2020-03-05 10:00:10'), moment('2020-03-05 10:00:10').format() ], + [ '2020-03-05 10:00:10', '2020-03-05 10:00:10' ], + [ 'foo', 'foo' ], + [ undefined, undefined ], + [ null, null ], + ])('formats date as expected', (date, expected) => { + expect(formatIsoDate(date)).toEqual(expected); + }); + }); +}); From eefea0c37b5d4bba181861a717a8119c73da0f47 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 22 Aug 2020 11:00:11 +0200 Subject: [PATCH 06/59] Added babel plugins to support latest TS functionalities --- csvjson.d.ts | 5 + package-lock.json | 4967 +++++++++++++---- package.json | 13 +- src/servers/data/index.ts | 15 + src/servers/services/ServersImporter.js | 23 - src/servers/services/ServersImporter.ts | 26 + src/servers/services/provideServices.js | 3 +- test/servers/services/ServersImporter.test.js | 46 - test/servers/services/ServersImporter.test.ts | 39 + 9 files changed, 4014 insertions(+), 1123 deletions(-) create mode 100644 csvjson.d.ts create mode 100644 src/servers/data/index.ts delete mode 100644 src/servers/services/ServersImporter.js create mode 100644 src/servers/services/ServersImporter.ts delete mode 100644 test/servers/services/ServersImporter.test.js create mode 100644 test/servers/services/ServersImporter.test.ts diff --git a/csvjson.d.ts b/csvjson.d.ts new file mode 100644 index 00000000..dbe9abde --- /dev/null +++ b/csvjson.d.ts @@ -0,0 +1,5 @@ +declare module 'csvjson' { + export declare class CsvJson { + public toObject(content: string): T[]; + } +} diff --git a/package-lock.json b/package-lock.json index d361ba85..3c9c4b58 100644 --- a/package-lock.json +++ b/package-lock.json @@ -317,6 +317,40 @@ "@babel/types": "^7.0.0" } }, + "@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.11.0.tgz", + "integrity": "sha512-0XIdiQln4Elglgjbwo9wuJpL/K7AGCY26kmEt0+pRP0TAj4jjyNq1MjoRvikrTVqKcx4Gysxt4cXvVFXP/JO2Q==", + "dev": true, + "requires": { + "@babel/types": "^7.11.0" + }, + "dependencies": { + "@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==", + "dev": true + }, + "@babel/types": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.0.tgz", + "integrity": "sha512-O53yME4ZZI0jO1EVGtF1ePGl0LHirG4P1ibcD80XyzZcKhcMFeCXmh4Xb1ifGBIV233Qg12x4rBfQgA+tmOukA==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + }, + "lodash": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", + "dev": true + } + } + }, "@babel/helper-split-export-declaration": { "version": "7.4.4", "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.4.4.tgz", @@ -424,6 +458,24 @@ "@babel/plugin-syntax-json-strings": "^7.2.0" } }, + "@babel/plugin-proposal-nullish-coalescing-operator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.10.4.tgz", + "integrity": "sha512-wq5n1M3ZUlHl9sqT2ok1T2/MTt6AXE0e1Lz4WzWBr95LsAZ5qDXe4KnFuauYyEyLiohvXFMdbsOTMyLZs91Zlw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0" + }, + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz", + "integrity": "sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==", + "dev": true + } + } + }, "@babel/plugin-proposal-object-rest-spread": { "version": "7.6.2", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.6.2.tgz", @@ -444,6 +496,25 @@ "@babel/plugin-syntax-optional-catch-binding": "^7.2.0" } }, + "@babel/plugin-proposal-optional-chaining": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.11.0.tgz", + "integrity": "sha512-v9fZIu3Y8562RRwhm1BbMRxtqZNFmFA2EG+pT2diuU8PT3H6T/KXoZ54KgYisfOFZHV6PfvAiBIZ9Rcz+/JCxA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-skip-transparent-expression-wrappers": "^7.11.0", + "@babel/plugin-syntax-optional-chaining": "^7.8.0" + }, + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz", + "integrity": "sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==", + "dev": true + } + } + }, "@babel/plugin-proposal-unicode-property-regex": { "version": "7.6.2", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.6.2.tgz", @@ -464,6 +535,40 @@ "@babel/helper-plugin-utils": "^7.0.0" } }, + "@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz", + "integrity": "sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==", + "dev": true + } + } + }, + "@babel/plugin-syntax-class-properties": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.10.4.tgz", + "integrity": "sha512-GCSBF7iUle6rNugfURwNmCGG3Z/2+opxAMLs1nND4bhEG5PuxTIggDBoeYYSujAlLtsupzOHYJQgPS3pivwXIA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz", + "integrity": "sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==", + "dev": true + } + } + }, "@babel/plugin-syntax-decorators": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.2.0.tgz", @@ -491,6 +596,23 @@ "@babel/helper-plugin-utils": "^7.0.0" } }, + "@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz", + "integrity": "sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==", + "dev": true + } + } + }, "@babel/plugin-syntax-json-strings": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.2.0.tgz", @@ -509,6 +631,57 @@ "@babel/helper-plugin-utils": "^7.0.0" } }, + "@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz", + "integrity": "sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==", + "dev": true + } + } + }, + "@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz", + "integrity": "sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==", + "dev": true + } + } + }, + "@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz", + "integrity": "sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==", + "dev": true + } + } + }, "@babel/plugin-syntax-object-rest-spread": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.2.0.tgz", @@ -527,6 +700,23 @@ "@babel/helper-plugin-utils": "^7.0.0" } }, + "@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz", + "integrity": "sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==", + "dev": true + } + } + }, "@babel/plugin-syntax-typescript": { "version": "7.3.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.3.3.tgz", @@ -1121,10 +1311,16 @@ } } }, + "@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true + }, "@cnakazawa/watch": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@cnakazawa/watch/-/watch-1.0.3.tgz", - "integrity": "sha512-r5160ogAvGyHsal38Kux7YYtodEKOj89RGb28ht1jh3SJb08VwRwAKKJL0bGb04Zd/3r9FL3BFIc3bBidYffCA==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@cnakazawa/watch/-/watch-1.0.4.tgz", + "integrity": "sha512-v9kIhKwjeZThiWrLmj0y17CWoyddASLj9O2yvbZkbvw/N3rWOYy9zkV66ursAoVr0mV15bL8g0c4QZUE6cdDoQ==", "dev": true, "requires": { "exec-sh": "^0.3.2", @@ -1223,171 +1419,810 @@ "resolved": "https://registry.npmjs.org/@icons/material/-/material-0.2.4.tgz", "integrity": "sha512-QPcGmICAPbGLGb6F/yNf/KzKqvFx8z5qx3D1yFqVAjoFmXK35EgyW+cJ57Te3CNsmzblwtzakLGFqHPqrfb4Tw==" }, - "@jest/console": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-24.9.0.tgz", - "integrity": "sha512-Zuj6b8TnKXi3q4ymac8EQfc3ea/uhLeCGThFqXeC8H9/raaH8ARPUTdId+XyGd03Z4In0/VjD2OYFcBF09fNLQ==", + "@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", "dev": true, "requires": { - "@jest/source-map": "^24.9.0", - "chalk": "^2.0.1", - "slash": "^2.0.0" + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" }, "dependencies": { - "slash": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", - "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "js-yaml": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", + "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "dev": true } } }, - "@jest/core": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-24.9.0.tgz", - "integrity": "sha512-Fogg3s4wlAr1VX7q+rhV9RVnUv5tD7VuWfYy1+whMiWUrvl7U3QJSJyWcDio9Lq2prqYsZaeTv2Rz24pWGkJ2A==", + "@istanbuljs/schema": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.2.tgz", + "integrity": "sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw==", + "dev": true + }, + "@jest/console": { + "version": "26.3.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-26.3.0.tgz", + "integrity": "sha512-/5Pn6sJev0nPUcAdpJHMVIsA8sKizL2ZkcKPE5+dJrCccks7tcM7c9wbgHudBJbxXLoTbqsHkG1Dofoem4F09w==", "dev": true, "requires": { - "@jest/console": "^24.7.1", - "@jest/reporters": "^24.9.0", - "@jest/test-result": "^24.9.0", - "@jest/transform": "^24.9.0", - "@jest/types": "^24.9.0", - "ansi-escapes": "^3.0.0", - "chalk": "^2.0.1", + "@jest/types": "^26.3.0", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^26.3.0", + "jest-util": "^26.3.0", + "slash": "^3.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "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" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "@jest/core": { + "version": "26.4.1", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-26.4.1.tgz", + "integrity": "sha512-EFziH1tJC5N8xb8OjUcQgyWdezJh6+zBX5p+9S7HR1jzBVeG8jCE/Edp7yqxW/cToLG/QKj8qrpox+HV9Qw1rw==", + "dev": true, + "requires": { + "@jest/console": "^26.3.0", + "@jest/reporters": "^26.4.1", + "@jest/test-result": "^26.3.0", + "@jest/transform": "^26.3.0", + "@jest/types": "^26.3.0", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", "exit": "^0.1.2", - "graceful-fs": "^4.1.15", - "jest-changed-files": "^24.9.0", - "jest-config": "^24.9.0", - "jest-haste-map": "^24.9.0", - "jest-message-util": "^24.9.0", - "jest-regex-util": "^24.3.0", - "jest-resolve": "^24.9.0", - "jest-resolve-dependencies": "^24.9.0", - "jest-runner": "^24.9.0", - "jest-runtime": "^24.9.0", - "jest-snapshot": "^24.9.0", - "jest-util": "^24.9.0", - "jest-validate": "^24.9.0", - "jest-watcher": "^24.9.0", - "micromatch": "^3.1.10", - "p-each-series": "^1.0.0", - "realpath-native": "^1.1.0", - "rimraf": "^2.5.4", - "slash": "^2.0.0", - "strip-ansi": "^5.0.0" + "graceful-fs": "^4.2.4", + "jest-changed-files": "^26.3.0", + "jest-config": "^26.4.1", + "jest-haste-map": "^26.3.0", + "jest-message-util": "^26.3.0", + "jest-regex-util": "^26.0.0", + "jest-resolve": "^26.4.0", + "jest-resolve-dependencies": "^26.4.1", + "jest-runner": "^26.4.1", + "jest-runtime": "^26.4.1", + "jest-snapshot": "^26.4.1", + "jest-util": "^26.3.0", + "jest-validate": "^26.4.0", + "jest-watcher": "^26.3.0", + "micromatch": "^4.0.2", + "p-each-series": "^2.1.0", + "rimraf": "^3.0.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" }, "dependencies": { "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "chalk": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "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" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "micromatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", + "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", + "dev": true, + "requires": { + "braces": "^3.0.1", + "picomatch": "^2.0.5" + } + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true }, "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", "dev": true, "requires": { - "ansi-regex": "^4.1.0" + "ansi-regex": "^5.0.0" + } + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" } } } }, "@jest/environment": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-24.9.0.tgz", - "integrity": "sha512-5A1QluTPhvdIPFYnO3sZC3smkNeXPVELz7ikPbhUj0bQjB07EoE9qtLrem14ZUYWdVayYbsjVwIiL4WBIMV4aQ==", + "version": "26.3.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-26.3.0.tgz", + "integrity": "sha512-EW+MFEo0DGHahf83RAaiqQx688qpXgl99wdb8Fy67ybyzHwR1a58LHcO376xQJHfmoXTu89M09dH3J509cx2AA==", "dev": true, "requires": { - "@jest/fake-timers": "^24.9.0", - "@jest/transform": "^24.9.0", - "@jest/types": "^24.9.0", - "jest-mock": "^24.9.0" + "@jest/fake-timers": "^26.3.0", + "@jest/types": "^26.3.0", + "@types/node": "*", + "jest-mock": "^26.3.0" } }, "@jest/fake-timers": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-24.9.0.tgz", - "integrity": "sha512-eWQcNa2YSwzXWIMC5KufBh3oWRIijrQFROsIqt6v/NS9Io/gknw1jsAC9c+ih/RQX4A3O7SeWAhQeN0goKhT9A==", + "version": "26.3.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-26.3.0.tgz", + "integrity": "sha512-ZL9ytUiRwVP8ujfRepffokBvD2KbxbqMhrXSBhSdAhISCw3gOkuntisiSFv+A6HN0n0fF4cxzICEKZENLmW+1A==", "dev": true, "requires": { - "@jest/types": "^24.9.0", - "jest-message-util": "^24.9.0", - "jest-mock": "^24.9.0" - }, - "dependencies": { - "jest-message-util": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-24.9.0.tgz", - "integrity": "sha512-oCj8FiZ3U0hTP4aSui87P4L4jC37BtQwUMqk+zk/b11FR19BJDeZsZAvIHutWnmtw7r85UmR3CEWZ0HWU2mAlw==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "@jest/test-result": "^24.9.0", - "@jest/types": "^24.9.0", - "@types/stack-utils": "^1.0.1", - "chalk": "^2.0.1", - "micromatch": "^3.1.10", - "slash": "^2.0.0", - "stack-utils": "^1.0.1" - } - }, - "jest-mock": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-24.9.0.tgz", - "integrity": "sha512-3BEYN5WbSq9wd+SyLDES7AHnjH9A/ROBwmz7l2y+ol+NtSFO8DYiEBzoO1CeFc9a8DYy10EO4dDFVv/wN3zl1w==", - "dev": true, - "requires": { - "@jest/types": "^24.9.0" - } - }, - "slash": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", - "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", - "dev": true - } + "@jest/types": "^26.3.0", + "@sinonjs/fake-timers": "^6.0.1", + "@types/node": "*", + "jest-message-util": "^26.3.0", + "jest-mock": "^26.3.0", + "jest-util": "^26.3.0" + } + }, + "@jest/globals": { + "version": "26.4.1", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-26.4.1.tgz", + "integrity": "sha512-gdsHefnwjck+AwDUwW+6rmctmKEcZEEZ4F3PB5kKnub7r0dUoN1KVSyNRXtB5qpZgRYESnxgDXhpw/XYKIsAeg==", + "dev": true, + "requires": { + "@jest/environment": "^26.3.0", + "@jest/types": "^26.3.0", + "expect": "^26.4.1" } }, "@jest/reporters": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-24.9.0.tgz", - "integrity": "sha512-mu4X0yjaHrffOsWmVLzitKmmmWSQ3GGuefgNscUSWNiUNcEOSEQk9k3pERKEQVBb0Cnn88+UESIsZEMH3o88Gw==", + "version": "26.4.1", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-26.4.1.tgz", + "integrity": "sha512-aROTkCLU8++yiRGVxLsuDmZsQEKO6LprlrxtAuzvtpbIFl3eIjgIf3EUxDKgomkS25R9ZzwGEdB5weCcBZlrpQ==", "dev": true, "requires": { - "@jest/environment": "^24.9.0", - "@jest/test-result": "^24.9.0", - "@jest/transform": "^24.9.0", - "@jest/types": "^24.9.0", - "chalk": "^2.0.1", + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^26.3.0", + "@jest/test-result": "^26.3.0", + "@jest/transform": "^26.3.0", + "@jest/types": "^26.3.0", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", "exit": "^0.1.2", "glob": "^7.1.2", - "istanbul-lib-coverage": "^2.0.2", - "istanbul-lib-instrument": "^3.0.1", - "istanbul-lib-report": "^2.0.4", - "istanbul-lib-source-maps": "^3.0.1", - "istanbul-reports": "^2.2.6", - "jest-haste-map": "^24.9.0", - "jest-resolve": "^24.9.0", - "jest-runtime": "^24.9.0", - "jest-util": "^24.9.0", - "jest-worker": "^24.6.0", - "node-notifier": "^5.4.2", - "slash": "^2.0.0", + "graceful-fs": "^4.2.4", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^4.0.3", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.0.2", + "jest-haste-map": "^26.3.0", + "jest-resolve": "^26.4.0", + "jest-util": "^26.3.0", + "jest-worker": "^26.3.0", + "node-notifier": "^8.0.0", + "slash": "^3.0.0", "source-map": "^0.6.0", - "string-length": "^2.0.0" + "string-length": "^4.0.1", + "terminal-link": "^2.0.0", + "v8-to-istanbul": "^5.0.1" + }, + "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" + } + }, + "@babel/core": { + "version": "7.11.4", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.11.4.tgz", + "integrity": "sha512-5deljj5HlqRXN+5oJTY7Zs37iH3z3b++KjiKtIsJy1NrjOOVSEaJHEetLBhyu0aQOSNNZ/0IuEAan9GzRuDXHg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.11.4", + "@babel/helper-module-transforms": "^7.11.0", + "@babel/helpers": "^7.10.4", + "@babel/parser": "^7.11.4", + "@babel/template": "^7.10.4", + "@babel/traverse": "^7.11.0", + "@babel/types": "^7.11.0", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.1", + "json5": "^2.1.2", + "lodash": "^4.17.19", + "resolve": "^1.3.2", + "semver": "^5.4.1", + "source-map": "^0.5.0" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "@babel/generator": { + "version": "7.11.4", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.11.4.tgz", + "integrity": "sha512-Rn26vueFx0eOoz7iifCN2UHT6rGtnkSGWSoDRIy8jZN3B91PzeSULbswfLoOWuTuAcNwpG/mxy+uCTDnZ9Mp1g==", + "dev": true, + "requires": { + "@babel/types": "^7.11.0", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "@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==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz", + "integrity": "sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A==", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.11.0.tgz", + "integrity": "sha512-JbFlKHFntRV5qKw3YC0CvQnDZ4XMwgzzBbld7Ly4Mj4cbFy3KywcR8NtNctRToMWJOVvLINJv525Gd6wwVEx/Q==", + "dev": true, + "requires": { + "@babel/types": "^7.11.0" + } + }, + "@babel/helper-module-imports": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.10.4.tgz", + "integrity": "sha512-nEQJHqYavI217oD9+s5MUBzk6x1IlvoS9WTPfgG43CbMEeStE0v+r+TucWdx8KFGowPGvyOkDT9+7DHedIDnVw==", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-module-transforms": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.11.0.tgz", + "integrity": "sha512-02EVu8COMuTRO1TAzdMtpBPbe6aQ1w/8fePD2YgQmxZU4gpNWaL9gK3Jp7dxlkUlUCJOTaSeA+Hrm1BRQwqIhg==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.10.4", + "@babel/helper-replace-supers": "^7.10.4", + "@babel/helper-simple-access": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.11.0", + "@babel/template": "^7.10.4", + "@babel/types": "^7.11.0", + "lodash": "^4.17.19" + } + }, + "@babel/helper-optimise-call-expression": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.4.tgz", + "integrity": "sha512-n3UGKY4VXwXThEiKrgRAoVPBMqeoPgHVqiHZOanAJCG9nQUL2pLRQirUzl0ioKclHGpGqRgIOkgcIJaIWLpygg==", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-replace-supers": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.10.4.tgz", + "integrity": "sha512-sPxZfFXocEymYTdVK1UNmFPBN+Hv5mJkLPsYWwGBxZAxaWfFu+xqp7b6qWD0yjNuNL2VKc6L5M18tOXUP7NU0A==", + "dev": true, + "requires": { + "@babel/helper-member-expression-to-functions": "^7.10.4", + "@babel/helper-optimise-call-expression": "^7.10.4", + "@babel/traverse": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-simple-access": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.10.4.tgz", + "integrity": "sha512-0fMy72ej/VEvF8ULmX6yb5MtHG4uH4Dbd6I/aHDb/JVg0bbivwt9Wg+h3uMvX+QSFtwr5MeItvazbrc4jtRAXw==", + "dev": true, + "requires": { + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@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==", + "dev": true, + "requires": { + "@babel/types": "^7.11.0" + } + }, + "@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==", + "dev": true + }, + "@babel/helpers": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.10.4.tgz", + "integrity": "sha512-L2gX/XeUONeEbI78dXSrJzGdz4GQ+ZTA/aazfUsFaWjSe95kiCuOZ5HsXvkiw3iwF+mFHSRUfJU8t6YavocdXA==", + "dev": true, + "requires": { + "@babel/template": "^7.10.4", + "@babel/traverse": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/highlight": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", + "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "@babel/parser": { + "version": "7.11.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.11.4.tgz", + "integrity": "sha512-MggwidiH+E9j5Sh8pbrX5sJvMcsqS5o+7iB42M9/k0CD63MjYbdP4nhSh7uB5wnv2/RVzTZFTxzF/kIa5mrCqA==", + "dev": true + }, + "@babel/template": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.4.tgz", + "integrity": "sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/parser": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/traverse": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.11.0.tgz", + "integrity": "sha512-ZB2V+LskoWKNpMq6E5UUCrjtDUh5IOTAyIl0dTjIEoXum/iKWkoIEKIRDnUucO6f+2FzNkE0oD4RLKoPIufDtg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.11.0", + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.11.0", + "@babel/parser": "^7.11.0", + "@babel/types": "^7.11.0", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.19" + } + }, + "@babel/types": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.0.tgz", + "integrity": "sha512-O53yME4ZZI0jO1EVGtF1ePGl0LHirG4P1ibcD80XyzZcKhcMFeCXmh4Xb1ifGBIV233Qg12x4rBfQgA+tmOukA==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + }, + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "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" + } + }, + "convert-source-map": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", + "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.1" + } + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "istanbul-lib-coverage": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz", + "integrity": "sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg==", + "dev": true + }, + "istanbul-lib-instrument": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", + "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", + "dev": true, + "requires": { + "@babel/core": "^7.7.5", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.0.0", + "semver": "^6.3.0" + } + }, + "jest-worker": { + "version": "26.3.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.3.0.tgz", + "integrity": "sha512-Vmpn2F6IASefL+DVBhPzI2J9/GJUsqzomdeN+P+dK8/jKxbh8R3BtFnx3FIta7wYlPU62cpJMJQo4kuOowcMnw==", + "dev": true, + "requires": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^7.0.0" + } + }, + "json5": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz", + "integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, + "lodash": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", + "dev": true + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } } }, "@jest/source-map": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-24.9.0.tgz", - "integrity": "sha512-/Xw7xGlsZb4MJzNDgB7PW5crou5JqWiBQaz6xyPd3ArOg2nfn/PunV8+olXbbEZzNl591o5rWKE9BRDaFAuIBg==", + "version": "26.3.0", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-26.3.0.tgz", + "integrity": "sha512-hWX5IHmMDWe1kyrKl7IhFwqOuAreIwHhbe44+XH2ZRHjrKIh0LO5eLQ/vxHFeAfRwJapmxuqlGAEYLadDq6ZGQ==", "dev": true, "requires": { "callsites": "^3.0.0", - "graceful-fs": "^4.1.15", + "graceful-fs": "^4.2.4", "source-map": "^0.6.0" }, "dependencies": { @@ -1396,78 +2231,250 @@ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true + }, + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "dev": true } } }, "@jest/test-result": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-24.9.0.tgz", - "integrity": "sha512-XEFrHbBonBJ8dGp2JmF8kP/nQI/ImPpygKHwQ/SY+es59Z3L5PI4Qb9TQQMAEeYsThG1xF0k6tmG0tIKATNiiA==", + "version": "26.3.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-26.3.0.tgz", + "integrity": "sha512-a8rbLqzW/q7HWheFVMtghXV79Xk+GWwOK1FrtimpI5n1la2SY0qHri3/b0/1F0Ve0/yJmV8pEhxDfVwiUBGtgg==", "dev": true, "requires": { - "@jest/console": "^24.9.0", - "@jest/types": "^24.9.0", - "@types/istanbul-lib-coverage": "^2.0.0" + "@jest/console": "^26.3.0", + "@jest/types": "^26.3.0", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" } }, "@jest/test-sequencer": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-24.9.0.tgz", - "integrity": "sha512-6qqsU4o0kW1dvA95qfNog8v8gkRN9ph6Lz7r96IvZpHdNipP2cBcb07J1Z45mz/VIS01OHJ3pY8T5fUY38tg4A==", + "version": "26.4.1", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-26.4.1.tgz", + "integrity": "sha512-YR4PNPu1RVHxyv/HSQMjc+pBEWa6wuM7xbEX/u5M5FFg6ZM6m00m7Jf0fjRxGN6hZlY5vECmNhJu/kvJLrxR8w==", "dev": true, "requires": { - "@jest/test-result": "^24.9.0", - "jest-haste-map": "^24.9.0", - "jest-runner": "^24.9.0", - "jest-runtime": "^24.9.0" + "@jest/test-result": "^26.3.0", + "graceful-fs": "^4.2.4", + "jest-haste-map": "^26.3.0", + "jest-runner": "^26.4.1", + "jest-runtime": "^26.4.1" + }, + "dependencies": { + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "dev": true + } } }, "@jest/transform": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-24.9.0.tgz", - "integrity": "sha512-TcQUmyNRxV94S0QpMOnZl0++6RMiqpbH/ZMccFB/amku6Uwvyb1cjYX7xkp5nGNkbX4QPH/FcB6q1HBTHynLmQ==", + "version": "26.3.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-26.3.0.tgz", + "integrity": "sha512-Isj6NB68QorGoFWvcOjlUhpkT56PqNIsXKR7XfvoDlCANn/IANlh8DrKAA2l2JKC3yWSMH5wS0GwuQM20w3b2A==", "dev": true, "requires": { "@babel/core": "^7.1.0", - "@jest/types": "^24.9.0", - "babel-plugin-istanbul": "^5.1.0", - "chalk": "^2.0.1", + "@jest/types": "^26.3.0", + "babel-plugin-istanbul": "^6.0.0", + "chalk": "^4.0.0", "convert-source-map": "^1.4.0", "fast-json-stable-stringify": "^2.0.0", - "graceful-fs": "^4.1.15", - "jest-haste-map": "^24.9.0", - "jest-regex-util": "^24.9.0", - "jest-util": "^24.9.0", - "micromatch": "^3.1.10", + "graceful-fs": "^4.2.4", + "jest-haste-map": "^26.3.0", + "jest-regex-util": "^26.0.0", + "jest-util": "^26.3.0", + "micromatch": "^4.0.2", "pirates": "^4.0.1", - "realpath-native": "^1.1.0", - "slash": "^2.0.0", + "slash": "^3.0.0", "source-map": "^0.6.1", - "write-file-atomic": "2.4.1" + "write-file-atomic": "^3.0.0" }, "dependencies": { - "write-file-atomic": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.1.tgz", - "integrity": "sha512-TGHFeZEZMnv+gBFRfjAcxL5bPHrsGKtnb4qsFAws7/vlh+QfwAaySIw4AXP9ZskTTh5GWu3FLuJhsWVdiJPGvg==", + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "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" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "micromatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", + "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", + "dev": true, + "requires": { + "braces": "^3.0.1", + "picomatch": "^2.0.5" + } + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", "dev": true, "requires": { - "graceful-fs": "^4.1.11", "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.2" + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" } } } }, "@jest/types": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-24.9.0.tgz", - "integrity": "sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw==", + "version": "26.3.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.3.0.tgz", + "integrity": "sha512-BDPG23U0qDeAvU4f99haztXwdAg3hz4El95LkAM+tHAqqhiVzRpEGHHU8EDxT/AnxOrA65YjLBwDahdJ9pTLJQ==", "dev": true, "requires": { "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^1.1.1", - "@types/yargs": "^13.0.0" + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^15.0.0", + "chalk": "^4.0.0" + }, + "dependencies": { + "@types/istanbul-reports": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", + "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", + "dev": true, + "requires": { + "@types/istanbul-lib-report": "*" + } + }, + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "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" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } } }, "@mrmlnc/readdir-enhanced": { @@ -1504,6 +2511,24 @@ "eslint-plugin-react": "^7.20.6" } }, + "@sinonjs/commons": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.1.tgz", + "integrity": "sha512-892K+kWUUi3cl+LlqEWIDrhvLgdL79tECi8JZUyq6IviKy/DNhuzCRlbHUjxK89f4ypPMMaFnFuR9Ie6DoIMsw==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } + }, + "@sinonjs/fake-timers": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz", + "integrity": "sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.7.0" + } + }, "@stryker-mutator/api": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/@stryker-mutator/api/-/api-3.2.4.tgz", @@ -1982,9 +3007,9 @@ } }, "@types/babel__core": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.3.tgz", - "integrity": "sha512-8fBo0UR2CcwWxeX7WIIgJ7lXjasFxoYgRnFHUj+hRvKkpiBJbxhdAPTCY6/ZKM0uxANFVzt4yObSLuTiTnazDA==", + "version": "7.1.9", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.9.tgz", + "integrity": "sha512-sY2RsIJ5rpER1u3/aQ8OFSI7qGIy8o1NEEbgb2UaJcvOtXOMpd39ko723NBpjQFg9SIX7TXtjejZVGeIMLhoOw==", "dev": true, "requires": { "@babel/parser": "^7.1.0", @@ -1995,9 +3020,9 @@ } }, "@types/babel__generator": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.0.tgz", - "integrity": "sha512-c1mZUu4up5cp9KROs/QAw0gTeHrw/x7m52LcnvMxxOZ03DmLwPV0MlGmlgzV3cnSdjhJOZsj7E7FHeioai+egw==", + "version": "7.6.1", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.1.tgz", + "integrity": "sha512-bBKm+2VPJcMRVwNhxKu8W+5/zT7pwNEqeokFOmbvVSqGzFneNxYcEBro9Ac7/N9tlsaPYnZLK8J1LWKkMsLAew==", "dev": true, "requires": { "@babel/types": "^7.0.0" @@ -2014,9 +3039,9 @@ } }, "@types/babel__traverse": { - "version": "7.0.7", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.0.7.tgz", - "integrity": "sha512-CeBpmX1J8kWLcDEnI3Cl2Eo6RfbGvzUctA+CjZUhOKDFbLfcr7fc4usEqLNWetrlJd7RhAkyYe2czXop4fICpw==", + "version": "7.0.13", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.0.13.tgz", + "integrity": "sha512-i+zS7t6/s9cdQvbqKDARrcbrPvtJGlbYsMkazo03nTAK3RX9FNrLllXys22uiTGJapPOTZTQ35nHh4ISph4SLQ==", "dev": true, "requires": { "@babel/types": "^7.3.0" @@ -2070,6 +3095,15 @@ "@types/node": "*" } }, + "@types/graceful-fs": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.3.tgz", + "integrity": "sha512-AiHRaEB50LQg0pZmm659vNBb9f4SJ0qrAnteuzhSeAUcJKxoYgEnprg/83kppCnc2zvtCKbdZry1a5pVY3lOTQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/history": { "version": "4.7.7", "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.7.tgz", @@ -2280,6 +3314,18 @@ "integrity": "sha512-Otxmr2rrZLKRYIybtdG/sgeO+tHY20GxeDjcGmUnmmlCWyEnv2a2x1ZXBo3BTec4OiTXMQCiazB8NMBf0iRlFw==", "dev": true }, + "@types/normalize-package-data": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz", + "integrity": "sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==", + "dev": true + }, + "@types/prettier": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.0.2.tgz", + "integrity": "sha512-IkVfat549ggtkZUthUzEX49562eGikhSYeVGX97SkMFn+sTZrgRewXjQ4tPKFPCykZHkX1Zfd9OoELGqKU2jJA==", + "dev": true + }, "@types/prop-types": { "version": "15.7.3", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz", @@ -2398,9 +3444,9 @@ } }, "@types/yargs": { - "version": "13.0.2", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.2.tgz", - "integrity": "sha512-lwwgizwk/bIIU+3ELORkyuOgDjCh7zuWDFqRtPPhhVgq9N1F7CvLNKg1TX4f2duwtKQ0p044Au9r1PLIXHrIzQ==", + "version": "15.0.5", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.5.tgz", + "integrity": "sha512-Dk/IDOPtOgubt/IaevIUbTgV7doaKkoorvOyYM2CMwuDyP89bekI7H4xLIwunNYiK9jhCkmc6pUrJk3cj2AB9w==", "dev": true, "requires": { "@types/yargs-parser": "*" @@ -2720,9 +3766,9 @@ "dev": true }, "abab": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.2.tgz", - "integrity": "sha512-2scffjvioEmNz0OyDSLGWDfKCVwaKc6l9Pm9kOIREU13ClXZvHpg/nRL5xyjSSSLhOnXqft2HpsAzNEEA8cFFg==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.4.tgz", + "integrity": "sha512-Eu9ELJWCz/c1e9gTiCY+FceWxcqzjYEbqMgtndnuSqZSUCOL73TWNK2mHfIj4Cw2E/ongOp+JISVNCmovt2KYQ==", "dev": true }, "abbrev": { @@ -2742,19 +3788,19 @@ } }, "acorn": { - "version": "6.0.5", - "resolved": "https://registry.yarnpkg.com/acorn/-/acorn-6.0.5.tgz", - "integrity": "sha1-gXMMCBXz87NNjvqVy3Qwll9NiHo=", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.0.tgz", + "integrity": "sha512-+G7P8jJmCHr+S+cLfQxygbWhXy+8YTVGzAkpEbcLo2mLoL7tij/VG41QSHACSf5QgYRhMZYHuNc6drJaO0Da+w==", "dev": true }, "acorn-globals": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-4.3.4.tgz", - "integrity": "sha512-clfQEh21R+D0leSbUdWf3OcfqyaCSAQ8Ryq00bofSekfr9W8u1jyYZo6ir0xu9Gtcf7BjcHJpnbZH7JOCpP60A==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", + "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", "dev": true, "requires": { - "acorn": "^6.0.1", - "acorn-walk": "^6.0.1" + "acorn": "^7.1.1", + "acorn-walk": "^7.1.1" } }, "acorn-jsx": { @@ -2764,9 +3810,9 @@ "dev": true }, "acorn-walk": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.2.0.tgz", - "integrity": "sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", + "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", "dev": true }, "address": { @@ -2877,10 +3923,21 @@ "dev": true }, "ansi-escapes": { - "version": "3.1.0", - "resolved": "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.1.0.tgz", - "integrity": "sha1-9zIHu4EgfXX9bIPxJa8m7qN4yjA=", - "dev": true + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz", + "integrity": "sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==", + "dev": true, + "requires": { + "type-fest": "^0.11.0" + }, + "dependencies": { + "type-fest": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz", + "integrity": "sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==", + "dev": true + } + } }, "ansi-html": { "version": "0.0.7", @@ -2995,12 +4052,6 @@ "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", "dev": true }, - "array-equal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz", - "integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=", - "dev": true - }, "array-filter": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/array-filter/-/array-filter-1.0.0.tgz", @@ -3783,18 +4834,77 @@ } }, "babel-jest": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-24.9.0.tgz", - "integrity": "sha512-ntuddfyiN+EhMw58PTNL1ph4C9rECiQXjI4nMMBKBaNjXvqLdkXpPRcMSr4iyBrJg/+wz9brFUD6RhOAT6r4Iw==", + "version": "26.3.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-26.3.0.tgz", + "integrity": "sha512-sxPnQGEyHAOPF8NcUsD0g7hDCnvLL2XyblRBcgrzTWBB/mAIpWow3n1bEL+VghnnZfreLhFSBsFluRoK2tRK4g==", "dev": true, "requires": { - "@jest/transform": "^24.9.0", - "@jest/types": "^24.9.0", - "@types/babel__core": "^7.1.0", - "babel-plugin-istanbul": "^5.1.0", - "babel-preset-jest": "^24.9.0", - "chalk": "^2.4.2", - "slash": "^2.0.0" + "@jest/transform": "^26.3.0", + "@jest/types": "^26.3.0", + "@types/babel__core": "^7.1.7", + "babel-plugin-istanbul": "^6.0.0", + "babel-preset-jest": "^26.3.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", + "slash": "^3.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "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" + } + }, + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } } }, "babel-loader": { @@ -3827,23 +4937,322 @@ } }, "babel-plugin-istanbul": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-5.2.0.tgz", - "integrity": "sha512-5LphC0USA8t4i1zCtjbbNb6jJj/9+X6P37Qfirc/70EQ34xKlMW+a1RHGwxGI+SwWpNwZ27HqvzAobeqaXwiZw==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.0.0.tgz", + "integrity": "sha512-AF55rZXpe7trmEylbaE1Gv54wn6rwU03aptvRoVIGP8YykoSxqdVLV1TfwflBCE/QtHmqtP8SWlTENqbK8GCSQ==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0", - "find-up": "^3.0.0", - "istanbul-lib-instrument": "^3.3.0", - "test-exclude": "^5.2.3" + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^4.0.0", + "test-exclude": "^6.0.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" + } + }, + "@babel/core": { + "version": "7.11.4", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.11.4.tgz", + "integrity": "sha512-5deljj5HlqRXN+5oJTY7Zs37iH3z3b++KjiKtIsJy1NrjOOVSEaJHEetLBhyu0aQOSNNZ/0IuEAan9GzRuDXHg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.11.4", + "@babel/helper-module-transforms": "^7.11.0", + "@babel/helpers": "^7.10.4", + "@babel/parser": "^7.11.4", + "@babel/template": "^7.10.4", + "@babel/traverse": "^7.11.0", + "@babel/types": "^7.11.0", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.1", + "json5": "^2.1.2", + "lodash": "^4.17.19", + "resolve": "^1.3.2", + "semver": "^5.4.1", + "source-map": "^0.5.0" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, + "@babel/generator": { + "version": "7.11.4", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.11.4.tgz", + "integrity": "sha512-Rn26vueFx0eOoz7iifCN2UHT6rGtnkSGWSoDRIy8jZN3B91PzeSULbswfLoOWuTuAcNwpG/mxy+uCTDnZ9Mp1g==", + "dev": true, + "requires": { + "@babel/types": "^7.11.0", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + } + }, + "@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==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz", + "integrity": "sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A==", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.11.0.tgz", + "integrity": "sha512-JbFlKHFntRV5qKw3YC0CvQnDZ4XMwgzzBbld7Ly4Mj4cbFy3KywcR8NtNctRToMWJOVvLINJv525Gd6wwVEx/Q==", + "dev": true, + "requires": { + "@babel/types": "^7.11.0" + } + }, + "@babel/helper-module-imports": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.10.4.tgz", + "integrity": "sha512-nEQJHqYavI217oD9+s5MUBzk6x1IlvoS9WTPfgG43CbMEeStE0v+r+TucWdx8KFGowPGvyOkDT9+7DHedIDnVw==", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-module-transforms": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.11.0.tgz", + "integrity": "sha512-02EVu8COMuTRO1TAzdMtpBPbe6aQ1w/8fePD2YgQmxZU4gpNWaL9gK3Jp7dxlkUlUCJOTaSeA+Hrm1BRQwqIhg==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.10.4", + "@babel/helper-replace-supers": "^7.10.4", + "@babel/helper-simple-access": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.11.0", + "@babel/template": "^7.10.4", + "@babel/types": "^7.11.0", + "lodash": "^4.17.19" + } + }, + "@babel/helper-optimise-call-expression": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.4.tgz", + "integrity": "sha512-n3UGKY4VXwXThEiKrgRAoVPBMqeoPgHVqiHZOanAJCG9nQUL2pLRQirUzl0ioKclHGpGqRgIOkgcIJaIWLpygg==", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-replace-supers": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.10.4.tgz", + "integrity": "sha512-sPxZfFXocEymYTdVK1UNmFPBN+Hv5mJkLPsYWwGBxZAxaWfFu+xqp7b6qWD0yjNuNL2VKc6L5M18tOXUP7NU0A==", + "dev": true, + "requires": { + "@babel/helper-member-expression-to-functions": "^7.10.4", + "@babel/helper-optimise-call-expression": "^7.10.4", + "@babel/traverse": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-simple-access": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.10.4.tgz", + "integrity": "sha512-0fMy72ej/VEvF8ULmX6yb5MtHG4uH4Dbd6I/aHDb/JVg0bbivwt9Wg+h3uMvX+QSFtwr5MeItvazbrc4jtRAXw==", + "dev": true, + "requires": { + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@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==", + "dev": true, + "requires": { + "@babel/types": "^7.11.0" + } + }, + "@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==", + "dev": true + }, + "@babel/helpers": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.10.4.tgz", + "integrity": "sha512-L2gX/XeUONeEbI78dXSrJzGdz4GQ+ZTA/aazfUsFaWjSe95kiCuOZ5HsXvkiw3iwF+mFHSRUfJU8t6YavocdXA==", + "dev": true, + "requires": { + "@babel/template": "^7.10.4", + "@babel/traverse": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/highlight": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", + "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.11.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.11.4.tgz", + "integrity": "sha512-MggwidiH+E9j5Sh8pbrX5sJvMcsqS5o+7iB42M9/k0CD63MjYbdP4nhSh7uB5wnv2/RVzTZFTxzF/kIa5mrCqA==", + "dev": true + }, + "@babel/template": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.4.tgz", + "integrity": "sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/parser": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/traverse": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.11.0.tgz", + "integrity": "sha512-ZB2V+LskoWKNpMq6E5UUCrjtDUh5IOTAyIl0dTjIEoXum/iKWkoIEKIRDnUucO6f+2FzNkE0oD4RLKoPIufDtg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.11.0", + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.11.0", + "@babel/parser": "^7.11.0", + "@babel/types": "^7.11.0", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.19" + } + }, + "@babel/types": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.0.tgz", + "integrity": "sha512-O53yME4ZZI0jO1EVGtF1ePGl0LHirG4P1ibcD80XyzZcKhcMFeCXmh4Xb1ifGBIV233Qg12x4rBfQgA+tmOukA==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + }, + "convert-source-map": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", + "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.1" + } + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "istanbul-lib-coverage": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz", + "integrity": "sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg==", + "dev": true + }, + "istanbul-lib-instrument": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", + "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", + "dev": true, + "requires": { + "@babel/core": "^7.7.5", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.0.0", + "semver": "^6.3.0" + } + }, + "json5": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz", + "integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, + "lodash": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", + "dev": true + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } } }, "babel-plugin-jest-hoist": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-24.9.0.tgz", - "integrity": "sha512-2EMA2P8Vp7lG0RAzr4HXqtYwacfMErOuv1U3wrvxHX6rD1sV6xS3WXG3r8TRQ2r6w8OhvSdWt+z41hQNwNm3Xw==", + "version": "26.2.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-26.2.0.tgz", + "integrity": "sha512-B/hVMRv8Nh1sQ1a3EY8I0n4Y1Wty3NrR5ebOyVT302op+DOAau+xNEImGMsUWOC3++ZlMooCytKz+NgN8aKGbA==", "dev": true, "requires": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.0.0", "@types/babel__traverse": "^7.0.6" } }, @@ -3903,14 +5312,77 @@ "integrity": "sha512-eqj0hVcJUR57/Ug2zE1Yswsw4LhuqqHhD+8v120T1cl3kjg76QwtyBrdIk4WVwK+lAhBJVYCd/v+4nc4y+8JsA==", "dev": true }, - "babel-preset-jest": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-24.9.0.tgz", - "integrity": "sha512-izTUuhE4TMfTRPF92fFwD2QfdXaZW08qvWTFCI51V8rW5x00UuPgc3ajRoWofXOuxjfcOM5zzSYsQS3H8KGCAg==", + "babel-preset-current-node-syntax": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-0.1.3.tgz", + "integrity": "sha512-uyexu1sVwcdFnyq9o8UQYsXwXflIh8LvrF5+cKrYam93ned1CStffB3+BEcsxGSgagoA3GEyjDqO4a/58hyPYQ==", "dev": true, "requires": { - "@babel/plugin-syntax-object-rest-spread": "^7.0.0", - "babel-plugin-jest-hoist": "^24.9.0" + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.8.3", + "@babel/plugin-syntax-import-meta": "^7.8.3", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.8.3", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + }, + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz", + "integrity": "sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==", + "dev": true + }, + "@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + } + } + }, + "babel-preset-jest": { + "version": "26.3.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-26.3.0.tgz", + "integrity": "sha512-5WPdf7nyYi2/eRxCbVrE1kKCWxgWY4RsPEbdJWFm7QsesFGqjdkyLeu1zRkwM1cxK6EPIlNd6d2AxLk7J+t4pw==", + "dev": true, + "requires": { + "babel-plugin-jest-hoist": "^26.2.0", + "babel-preset-current-node-syntax": "^0.1.3" } }, "babel-preset-react-app": { @@ -4325,28 +5797,11 @@ "dev": true }, "browser-process-hrtime": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-0.1.3.tgz", - "integrity": "sha512-bRFnI4NnjO6cnyLmOV/7PVoDEMJChlcfN0z4s1YMBY989/SvlfMI1lgCnkFUs53e9gQF+w7qu7XdllSTiSl8Aw==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", + "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", "dev": true }, - "browser-resolve": { - "version": "1.11.3", - "resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-1.11.3.tgz", - "integrity": "sha512-exDi1BYWB/6raKHmDTCicQfTkqwN5fioMFV4j8BsfMU4R2DK/QfZfK7kOVkmWCNANf0snkBzqGqAJBao9gZMdQ==", - "dev": true, - "requires": { - "resolve": "1.1.7" - }, - "dependencies": { - "resolve": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", - "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", - "dev": true - } - } - }, "browserify-aes": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", @@ -4439,9 +5894,9 @@ } }, "bser": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.0.tgz", - "integrity": "sha512-8zsjWrQkkBoLK6uxASk1nJ2SKv97ltiGDo6A3wA0/yRPz+CwmEyDo0hUrhIuukG2JHpAl3bvFIixw2/3Hi0DOg==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", "dev": true, "requires": { "node-int64": "^0.4.0" @@ -4718,6 +6173,12 @@ "supports-color": "^5.3.0" } }, + "char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true + }, "character-entities": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.3.tgz", @@ -5002,46 +6463,46 @@ } }, "cliui": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", - "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", "dev": true, "requires": { - "string-width": "^3.1.0", - "strip-ansi": "^5.2.0", - "wrap-ansi": "^5.1.0" + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.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==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", "dev": true }, - "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "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==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", "dev": true, "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" } }, "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", "dev": true, "requires": { - "ansi-regex": "^4.1.0" + "ansi-regex": "^5.0.0" } } } @@ -5103,6 +6564,12 @@ "integrity": "sha512-703bOOmytCYAX9cXYqoikYIx6twmFCXsnzRQheBcTG3nzKYBR4P/+wkYeH+Mvj7qUz8zZDtdyzbxfnEi/kYzRQ==", "dev": true }, + "collect-v8-coverage": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", + "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", + "dev": true + }, "collection-visit": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", @@ -5792,18 +7259,26 @@ } }, "cssom": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", - "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz", + "integrity": "sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==", "dev": true }, "cssstyle": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-1.4.0.tgz", - "integrity": "sha512-GBrLZYZ4X4x6/QEoBnIrqb8B/f5l4+8me2dkom/j1Gtbxy0kBv6OGzKuAsGM75bkGwGAFkt56Iwg28S3XTZgSA==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", + "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", "dev": true, "requires": { - "cssom": "0.3.x" + "cssom": "~0.3.6" + }, + "dependencies": { + "cssom": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", + "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", + "dev": true + } } }, "csstype": { @@ -5848,27 +7323,14 @@ } }, "data-urls": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-1.1.0.tgz", - "integrity": "sha512-YTWYI9se1P55u58gL5GkQHW4P6VJBJ5iBT+B5a7i2Tjadhv52paJG0qHX4A0OR6/t52odI64KP2YvFpkDOi3eQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz", + "integrity": "sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==", "dev": true, "requires": { - "abab": "^2.0.0", - "whatwg-mimetype": "^2.2.0", - "whatwg-url": "^7.0.0" - }, - "dependencies": { - "whatwg-url": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.0.0.tgz", - "integrity": "sha512-37GeVSIJ3kn1JgKyjiYNmSLP1yzbpb29jdmwBSgkD9h40/hyrR/OifpVUndji3tmwGgD8qpw7iQu3RSbCrBpsQ==", - "dev": true, - "requires": { - "lodash.sortby": "^4.7.0", - "tr46": "^1.0.1", - "webidl-conversions": "^4.0.2" - } - } + "abab": "^2.0.3", + "whatwg-mimetype": "^2.3.0", + "whatwg-url": "^8.0.0" } }, "date-format": { @@ -5908,6 +7370,12 @@ "map-obj": "^1.0.0" } }, + "decimal.js": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.2.0.tgz", + "integrity": "sha512-vDPw+rDgn3bZe1+F/pyEwb1oMG2XTlRVgAa6B4KccTEpYgF8w6eQllVbQcfIJnZyvzFtFpxnpGtx8dd7DJp/Rw==", + "dev": true + }, "decode-uri-component": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", @@ -5940,6 +7408,12 @@ "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", "dev": true }, + "deepmerge": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", + "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", + "dev": true + }, "default-gateway": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-4.2.0.tgz", @@ -6125,9 +7599,9 @@ "dev": true }, "detect-newline": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz", - "integrity": "sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I=", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", "dev": true }, "detect-node": { @@ -6147,9 +7621,9 @@ } }, "diff-sequences": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-24.9.0.tgz", - "integrity": "sha512-Dj6Wk3tWyTE+Fo1rW8v0Xhwk80um6yFYKbuAxc9c3EZxIHFDYwbi34Uk42u1CdnIiVorvt4RmlSDjIPyzGC2ew==", + "version": "26.3.0", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.3.0.tgz", + "integrity": "sha512-5j5vdRcw3CNctePNYN0Wy2e/JbWT6cAYnXv5OuqPhDpyCGc0uLu2TK0zOCJWNB9kOIfYMSpIulRaDgIi4HJ6Ig==", "dev": true }, "diffie-hellman": { @@ -6271,12 +7745,20 @@ "dev": true }, "domexception": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/domexception/-/domexception-1.0.1.tgz", - "integrity": "sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz", + "integrity": "sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==", "dev": true, "requires": { - "webidl-conversions": "^4.0.2" + "webidl-conversions": "^5.0.0" + }, + "dependencies": { + "webidl-conversions": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz", + "integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==", + "dev": true + } } }, "domhandler": { @@ -6380,6 +7862,12 @@ "minimalistic-crypto-utils": "^1.0.0" } }, + "emittery": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.7.1.tgz", + "integrity": "sha512-d34LN4L6h18Bzz9xpoku2nPwKxCPlPMr3EEKTkoEBi+1/+b0lcRkRJ1UVyyZaKNeqGR3swcGl6s390DNO4YVgQ==", + "dev": true + }, "emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -6878,24 +8366,16 @@ "dev": true }, "escodegen": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.12.0.tgz", - "integrity": "sha512-TuA+EhsanGcme5T3R0L80u4t8CpbXQjegRmf7+FPTJrtCTErXFeelblRgHQa1FofEzqYYJmJ/OqjTwREp9qgmg==", + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", + "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==", "dev": true, "requires": { - "esprima": "^3.1.3", + "esprima": "^4.0.1", "estraverse": "^4.2.0", "esutils": "^2.0.2", "optionator": "^0.8.1", "source-map": "~0.6.1" - }, - "dependencies": { - "esprima": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz", - "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=", - "dev": true - } } }, "eslint": { @@ -7876,9 +9356,9 @@ } }, "exec-sh": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.3.2.tgz", - "integrity": "sha512-9sLAvzhI5nc8TpuQUh4ahMdCrWT00wPWz7j47/emR5+2qEfoZP5zzUXvx+vdx+H6ohhnsYC31iX04QLYJK8zTg==", + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.3.4.tgz", + "integrity": "sha512-sEFIkc61v75sWeOe72qyrqg2Qg0OuLESziUDk/O/z2qgS15y2gWVFrI6f2Qn/qw/0/NCfCEsmNA4zOjkwEZT1A==", "dev": true }, "execa": { @@ -7940,17 +9420,38 @@ } }, "expect": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-24.9.0.tgz", - "integrity": "sha512-wvVAx8XIol3Z5m9zvZXiyZOQ+sRJqNTIm6sGjdWlaZIeupQGO3WbYI+15D/AmEwZywL6wtJkbAbJtzkOfBuR0Q==", + "version": "26.4.1", + "resolved": "https://registry.npmjs.org/expect/-/expect-26.4.1.tgz", + "integrity": "sha512-PnsyF/VmPRH/HAWELjrIAgQ5h+4JLTiomA1A2djx+jXrCQzQ/4egZYBOEx9hShoX+mQLS4enYk6Ouxk8b4kcEw==", "dev": true, "requires": { - "@jest/types": "^24.9.0", - "ansi-styles": "^3.2.0", - "jest-get-type": "^24.9.0", - "jest-matcher-utils": "^24.9.0", - "jest-message-util": "^24.9.0", - "jest-regex-util": "^24.9.0" + "@jest/types": "^26.3.0", + "ansi-styles": "^4.0.0", + "jest-get-type": "^26.3.0", + "jest-matcher-utils": "^26.4.1", + "jest-message-util": "^26.3.0", + "jest-regex-util": "^26.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "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" + } + } } }, "express": { @@ -8143,12 +9644,12 @@ } }, "fb-watchman": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.0.tgz", - "integrity": "sha1-VOmr99+i8mzZsWNsWIwa/AXeXVg=", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.1.tgz", + "integrity": "sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg==", "dev": true, "requires": { - "bser": "^2.0.0" + "bser": "2.1.1" } }, "figgy-pudding": { @@ -9207,6 +10708,12 @@ "globule": "^1.0.0" } }, + "gensync": { + "version": "1.0.0-beta.1", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.1.tgz", + "integrity": "sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg==", + "dev": true + }, "get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -9219,6 +10726,12 @@ "integrity": "sha512-09/VS4iek66Dh2bctjRkowueRJbY1JDGR1L/zRxO1Qk8Uxs6PnqaNSqalpizPT+CDjre3hnEsuzvhgomz9qYrA==", "dev": true }, + "get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true + }, "get-port": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/get-port/-/get-port-5.0.0.tgz", @@ -9431,7 +10944,8 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", "integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=", - "dev": true + "dev": true, + "optional": true }, "gud": { "version": "1.0.0", @@ -9462,38 +10976,6 @@ "integrity": "sha512-d4sze1JNC454Wdo2fkuyzCr6aHcbL6PGGuFAz0Li/NcOm1tCHGnWDRmJP85dh9IhQErTc2svWFEX5xHIOo//kQ==", "dev": true }, - "handlebars": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.4.2.tgz", - "integrity": "sha512-cIv17+GhL8pHHnRJzGu2wwcthL5sb8uDKBHvZ2Dtu5s1YNt0ljbzKbamnc+gr69y7bzwQiBdr5+hOpRd5pnOdg==", - "dev": true, - "requires": { - "neo-async": "^2.6.0", - "optimist": "^0.6.1", - "source-map": "^0.6.1", - "uglify-js": "^3.1.4" - }, - "dependencies": { - "commander": { - "version": "2.20.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.1.tgz", - "integrity": "sha512-cCuLsMhJeWQ/ZpsFTbE765kvVfoeSddc4nU3up4fV+fDBcfUXnbITJ+JzhkdjzOqhURjZgujxaioam4RM9yGUg==", - "dev": true, - "optional": true - }, - "uglify-js": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.6.0.tgz", - "integrity": "sha512-W+jrUHJr3DXKhrsS7NUVxn3zqMOFn0hL/Ei6v0anCIMoKC93TjcflTagwIHLW7SfMFfiQuktQyFVCFHGUE0+yg==", - "dev": true, - "optional": true, - "requires": { - "commander": "~2.20.0", - "source-map": "~0.6.1" - } - } - } - }, "har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", @@ -9700,12 +11182,12 @@ } }, "html-encoding-sniffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz", - "integrity": "sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", + "integrity": "sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==", "dev": true, "requires": { - "whatwg-encoding": "^1.0.1" + "whatwg-encoding": "^1.0.5" } }, "html-entities": { @@ -9714,6 +11196,12 @@ "integrity": "sha1-DfKTUfByEWNRXfueVUPl9u7VFi8=", "dev": true }, + "html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, "html-minifier": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/html-minifier/-/html-minifier-4.0.0.tgz", @@ -9873,6 +11361,12 @@ "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", "dev": true }, + "human-signals": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", + "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", + "dev": true + }, "iconv-lite": { "version": "0.4.24", "resolved": "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -10489,6 +11983,13 @@ "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=", "dev": true }, + "is-docker": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.1.1.tgz", + "integrity": "sha512-ZOoqiXfEwtGknTiuDEy8pN2CfE3TxMHprvNer1mXiqwkOT77Rw3YVrUQ52EqAOU3QAWDQ+bQdx7HJzrv7LS2Hw==", + "dev": true, + "optional": true + }, "is-extendable": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", @@ -10613,6 +12114,12 @@ "isobject": "^3.0.1" } }, + "is-potential-custom-element-name": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.0.tgz", + "integrity": "sha1-DFLlS8yjkbssSUsh6GJtczbG45c=", + "dev": true + }, "is-promise": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", @@ -10790,53 +12297,62 @@ } }, "istanbul-lib-report": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-2.0.8.tgz", - "integrity": "sha512-fHBeG573EIihhAblwgxrSenp0Dby6tJMFR/HvlerBsrCTD5bkUuoNtn3gVh29ZCS824cGGBPn7Sg7cNk+2xUsQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", "dev": true, "requires": { - "istanbul-lib-coverage": "^2.0.5", - "make-dir": "^2.1.0", - "supports-color": "^6.1.0" + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^3.0.0", + "supports-color": "^7.1.0" }, "dependencies": { + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "istanbul-lib-coverage": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz", + "integrity": "sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg==", + "dev": true + }, "make-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", "dev": true, "requires": { - "pify": "^4.0.1", - "semver": "^5.6.0" + "semver": "^6.0.0" } }, - "pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "dev": true }, "supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", "dev": true, "requires": { - "has-flag": "^3.0.0" + "has-flag": "^4.0.0" } } } }, "istanbul-lib-source-maps": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-3.0.6.tgz", - "integrity": "sha512-R47KzMtDJH6X4/YW9XTx+jrLnZnscW4VpNN+1PViSYTejLVPWv7oov+Duf8YQSPyVRUvueQqz1TcsC6mooZTXw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.0.tgz", + "integrity": "sha512-c16LpFRkR8vQXyHZ5nLpY35JZtzj1PQY1iZmesUbf1FZHbIupcWfjgOXBY9YHkLEQ6puz1u4Dgj6qmU/DisrZg==", "dev": true, "requires": { "debug": "^4.1.1", - "istanbul-lib-coverage": "^2.0.5", - "make-dir": "^2.1.0", - "rimraf": "^2.6.3", + "istanbul-lib-coverage": "^3.0.0", "source-map": "^0.6.1" }, "dependencies": { @@ -10849,55 +12365,108 @@ "ms": "^2.1.1" } }, - "make-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", - "dev": true, - "requires": { - "pify": "^4.0.1", - "semver": "^5.6.0" - } + "istanbul-lib-coverage": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz", + "integrity": "sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg==", + "dev": true }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true - }, - "pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "dev": true } } }, "istanbul-reports": { - "version": "2.2.6", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-2.2.6.tgz", - "integrity": "sha512-SKi4rnMyLBKe0Jy2uUdx28h8oG7ph2PPuQPvIAh31d+Ci+lSiEu4C+h3oBPuJ9+mPKhOyW0M8gY4U5NM1WLeXA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.0.2.tgz", + "integrity": "sha512-9tZvz7AiR3PEDNGiV9vIouQ/EAcqMXFmkcA1CDFTwOB98OZVDL0PH9glHotf5Ugp6GCOTypfzGWI/OqjWNCRUw==", "dev": true, "requires": { - "handlebars": "^4.1.2" + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" } }, "jest": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest/-/jest-24.9.0.tgz", - "integrity": "sha512-YvkBL1Zm7d2B1+h5fHEOdyjCG+sGMz4f8D86/0HiqJ6MB4MnDc8FgP5vdWsGnemOQro7lnYo8UakZ3+5A0jxGw==", + "version": "26.4.1", + "resolved": "https://registry.npmjs.org/jest/-/jest-26.4.1.tgz", + "integrity": "sha512-q+az+ZXFOTxTlD6BRIMcZC+a33O9lsryV4Wo9gU4D/AI+Y6KKgVRCmyzpc4H2gWv0rn45lACukmMS2uSB7e1LA==", "dev": true, "requires": { - "import-local": "^2.0.0", - "jest-cli": "^24.9.0" + "@jest/core": "^26.4.1", + "import-local": "^3.0.2", + "jest-cli": "^26.4.1" }, "dependencies": { + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, "ci-info": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", "dev": true }, + "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" + } + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "import-local": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.0.2.tgz", + "integrity": "sha512-vjL3+w0oulAVZ0hBHnxa/Nm5TAurf9YLQJDhqRZyqb+VKGOB6LU8t9H1Nr5CIo16vh9XfJTOoHwU0B71S557gA==", + "dev": true, + "requires": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + } + }, "is-ci": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", @@ -10908,325 +12477,864 @@ } }, "jest-cli": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-24.9.0.tgz", - "integrity": "sha512-+VLRKyitT3BWoMeSUIHRxV/2g8y9gw91Jh5z2UmXZzkZKpbC08CSehVxgHUwTpy+HwGcns/tqafQDJW7imYvGg==", + "version": "26.4.1", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-26.4.1.tgz", + "integrity": "sha512-c6px+IOO0OsZ7X/uSr65wcjZnd7NYNUDWFT5OETyCnJRkkwoTER7gneRDrwgr3Ex5+gCGO7D/IMWxUHB/L624A==", "dev": true, "requires": { - "@jest/core": "^24.9.0", - "@jest/test-result": "^24.9.0", - "@jest/types": "^24.9.0", - "chalk": "^2.0.1", + "@jest/core": "^26.4.1", + "@jest/test-result": "^26.3.0", + "@jest/types": "^26.3.0", + "chalk": "^4.0.0", "exit": "^0.1.2", - "import-local": "^2.0.0", + "graceful-fs": "^4.2.4", + "import-local": "^3.0.2", "is-ci": "^2.0.0", - "jest-config": "^24.9.0", - "jest-util": "^24.9.0", - "jest-validate": "^24.9.0", + "jest-config": "^26.4.1", + "jest-util": "^26.3.0", + "jest-validate": "^26.4.0", "prompts": "^2.0.1", - "realpath-native": "^1.1.0", - "yargs": "^13.3.0" + "yargs": "^15.3.1" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "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" + } + }, + "resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "requires": { + "resolve-from": "^5.0.0" + } + }, + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" } } } }, "jest-changed-files": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-24.9.0.tgz", - "integrity": "sha512-6aTWpe2mHF0DhL28WjdkO8LyGjs3zItPET4bMSeXU6T3ub4FPMw+mcOcbdGXQOAfmLcxofD23/5Bl9Z4AkFwqg==", + "version": "26.3.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-26.3.0.tgz", + "integrity": "sha512-1C4R4nijgPltX6fugKxM4oQ18zimS7LqQ+zTTY8lMCMFPrxqBFb7KJH0Z2fRQJvw2Slbaipsqq7s1mgX5Iot+g==", "dev": true, "requires": { - "@jest/types": "^24.9.0", - "execa": "^1.0.0", - "throat": "^4.0.0" + "@jest/types": "^26.3.0", + "execa": "^4.0.0", + "throat": "^5.0.0" }, "dependencies": { - "execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", "dev": true, "requires": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "execa": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/execa/-/execa-4.0.3.tgz", + "integrity": "sha512-WFDXGHckXPWZX19t1kCsXzOpqX9LWYNqn4C+HqZlk/V0imTkzJZqf87ZBhvpHaftERYknpk0fjSylnXVlVgI0A==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.0", + "get-stream": "^5.0.0", + "human-signals": "^1.1.1", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.0", + "onetime": "^5.1.0", + "signal-exit": "^3.0.2", + "strip-final-newline": "^2.0.0" } }, "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", "dev": true, "requires": { "pump": "^3.0.0" } + }, + "is-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", + "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", + "dev": true + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true + }, + "npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "requires": { + "path-key": "^3.0.0" + } + }, + "onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } } } }, "jest-config": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-24.9.0.tgz", - "integrity": "sha512-RATtQJtVYQrp7fvWg6f5y3pEFj9I+H8sWw4aKxnDZ96mob5i5SD6ZEGWgMLXQ4LE8UurrjbdlLWdUeo+28QpfQ==", + "version": "26.4.1", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-26.4.1.tgz", + "integrity": "sha512-0kUnVceEax0sYN+wdkNYF7fxjYKbsvmKmjVWwJvsSYA2p94bIL6wSy3oehewev7L9Dp/FDZFhmc9dyOoavdT6A==", "dev": true, "requires": { "@babel/core": "^7.1.0", - "@jest/test-sequencer": "^24.9.0", - "@jest/types": "^24.9.0", - "babel-jest": "^24.9.0", - "chalk": "^2.0.1", + "@jest/test-sequencer": "^26.4.1", + "@jest/types": "^26.3.0", + "babel-jest": "^26.3.0", + "chalk": "^4.0.0", + "deepmerge": "^4.2.2", "glob": "^7.1.1", - "jest-environment-jsdom": "^24.9.0", - "jest-environment-node": "^24.9.0", - "jest-get-type": "^24.9.0", - "jest-jasmine2": "^24.9.0", - "jest-regex-util": "^24.3.0", - "jest-resolve": "^24.9.0", - "jest-util": "^24.9.0", - "jest-validate": "^24.9.0", - "micromatch": "^3.1.10", - "pretty-format": "^24.9.0", - "realpath-native": "^1.1.0" + "graceful-fs": "^4.2.4", + "jest-environment-jsdom": "^26.3.0", + "jest-environment-node": "^26.3.0", + "jest-get-type": "^26.3.0", + "jest-jasmine2": "^26.4.1", + "jest-regex-util": "^26.0.0", + "jest-resolve": "^26.4.0", + "jest-util": "^26.3.0", + "jest-validate": "^26.4.0", + "micromatch": "^4.0.2", + "pretty-format": "^26.4.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "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" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "micromatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", + "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", + "dev": true, + "requires": { + "braces": "^3.0.1", + "picomatch": "^2.0.5" + } + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + } } }, "jest-diff": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-24.9.0.tgz", - "integrity": "sha512-qMfrTs8AdJE2iqrTp0hzh7kTd2PQWrsFyj9tORoKmu32xjPjeE4NyjVRDz8ybYwqS2ik8N4hsIpiVTyFeo2lBQ==", + "version": "26.4.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-26.4.0.tgz", + "integrity": "sha512-wwC38HlOW+iTq6j5tkj/ZamHn6/nrdcEOc/fKaVILNtN2NLWGdkfRaHWwfNYr5ehaLvuoG2LfCZIcWByVj0gjg==", "dev": true, "requires": { - "chalk": "^2.0.1", - "diff-sequences": "^24.9.0", - "jest-get-type": "^24.9.0", - "pretty-format": "^24.9.0" + "chalk": "^4.0.0", + "diff-sequences": "^26.3.0", + "jest-get-type": "^26.3.0", + "pretty-format": "^26.4.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "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" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } } }, "jest-docblock": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-24.9.0.tgz", - "integrity": "sha512-F1DjdpDMJMA1cN6He0FNYNZlo3yYmOtRUnktrT9Q37njYzC5WEaDdmbynIgy0L/IvXvvgsG8OsqhLPXTpfmZAA==", + "version": "26.0.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-26.0.0.tgz", + "integrity": "sha512-RDZ4Iz3QbtRWycd8bUEPxQsTlYazfYn/h5R65Fc6gOfwozFhoImx+affzky/FFBuqISPTqjXomoIGJVKBWoo0w==", "dev": true, "requires": { - "detect-newline": "^2.1.0" + "detect-newline": "^3.0.0" } }, "jest-each": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-24.9.0.tgz", - "integrity": "sha512-ONi0R4BvW45cw8s2Lrx8YgbeXL1oCQ/wIDwmsM3CqM/nlblNCPmnC3IPQlMbRFZu3wKdQ2U8BqM6lh3LJ5Bsog==", + "version": "26.4.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-26.4.0.tgz", + "integrity": "sha512-+cyBh1ehs6thVT/bsZVG+WwmRn2ix4Q4noS9yLZgM10yGWPW12/TDvwuOV2VZXn1gi09/ZwJKJWql6YW1C9zNw==", "dev": true, "requires": { - "@jest/types": "^24.9.0", - "chalk": "^2.0.1", - "jest-get-type": "^24.9.0", - "jest-util": "^24.9.0", - "pretty-format": "^24.9.0" + "@jest/types": "^26.3.0", + "chalk": "^4.0.0", + "jest-get-type": "^26.3.0", + "jest-util": "^26.3.0", + "pretty-format": "^26.4.0" }, "dependencies": { - "ansi-regex": { + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "chalk": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true - }, - "ci-info": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", - "dev": true - }, - "is-ci": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", - "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", "dev": true, "requires": { - "ci-info": "^2.0.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" } }, - "jest-get-type": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-24.9.0.tgz", - "integrity": "sha512-lUseMzAley4LhIcpSP9Jf+fTrQ4a1yHQwLNeeVa2cEmbCGeoZAtYPOIv8JaxLD/sUpKxetKGP+gsHl8f8TSj8Q==", - "dev": true - }, - "jest-util": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-24.9.0.tgz", - "integrity": "sha512-x+cZU8VRmOJxbA1K5oDBdxQmdq0OIdADarLxk0Mq+3XS4jgvhG/oKGWcIDCtPG0HgjxOYvF+ilPJQsAyXfbNOg==", + "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": { - "@jest/console": "^24.9.0", - "@jest/fake-timers": "^24.9.0", - "@jest/source-map": "^24.9.0", - "@jest/test-result": "^24.9.0", - "@jest/types": "^24.9.0", - "callsites": "^3.0.0", - "chalk": "^2.0.1", - "graceful-fs": "^4.1.15", - "is-ci": "^2.0.0", - "mkdirp": "^0.5.1", - "slash": "^2.0.0", - "source-map": "^0.6.0" + "color-name": "~1.1.4" } }, - "pretty-format": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-24.9.0.tgz", - "integrity": "sha512-00ZMZUiHaJrNfk33guavqgvfJS30sLYf0f8+Srklv0AMPodGGHcoHgksZ3OThYnIvOd+8yMCn0YiEOogjlgsnA==", + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", "dev": true, "requires": { - "@jest/types": "^24.9.0", - "ansi-regex": "^4.0.0", - "ansi-styles": "^3.2.0", - "react-is": "^16.8.4" + "has-flag": "^4.0.0" } - }, - "react-is": { - "version": "16.9.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.9.0.tgz", - "integrity": "sha512-tJBzzzIgnnRfEm046qRcURvwQnZVXmuCbscxUO5RWrGTXpon2d4c8mI0D8WE6ydVIm29JiLB6+RslkIvym9Rjw==", - "dev": true - }, - "slash": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", - "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", - "dev": true } } }, "jest-environment-jsdom": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-24.9.0.tgz", - "integrity": "sha512-Zv9FV9NBRzLuALXjvRijO2351DRQeLYXtpD4xNvfoVFw21IOKNhZAEUKcbiEtjTkm2GsJ3boMVgkaR7rN8qetA==", + "version": "26.3.0", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-26.3.0.tgz", + "integrity": "sha512-zra8He2btIMJkAzvLaiZ9QwEPGEetbxqmjEBQwhH3CA+Hhhu0jSiEJxnJMbX28TGUvPLxBt/zyaTLrOPF4yMJA==", "dev": true, "requires": { - "@jest/environment": "^24.9.0", - "@jest/fake-timers": "^24.9.0", - "@jest/types": "^24.9.0", - "jest-mock": "^24.9.0", - "jest-util": "^24.9.0", - "jsdom": "^11.5.1" + "@jest/environment": "^26.3.0", + "@jest/fake-timers": "^26.3.0", + "@jest/types": "^26.3.0", + "@types/node": "*", + "jest-mock": "^26.3.0", + "jest-util": "^26.3.0", + "jsdom": "^16.2.2" } }, "jest-environment-node": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-24.9.0.tgz", - "integrity": "sha512-6d4V2f4nxzIzwendo27Tr0aFm+IXWa0XEUnaH6nU0FMaozxovt+sfRvh4J47wL1OvF83I3SSTu0XK+i4Bqe7uA==", + "version": "26.3.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-26.3.0.tgz", + "integrity": "sha512-c9BvYoo+FGcMj5FunbBgtBnbR5qk3uky8PKyRVpSfe2/8+LrNQMiXX53z6q2kY+j15SkjQCOSL/6LHnCPLVHNw==", "dev": true, "requires": { - "@jest/environment": "^24.9.0", - "@jest/fake-timers": "^24.9.0", - "@jest/types": "^24.9.0", - "jest-mock": "^24.9.0", - "jest-util": "^24.9.0" + "@jest/environment": "^26.3.0", + "@jest/fake-timers": "^26.3.0", + "@jest/types": "^26.3.0", + "@types/node": "*", + "jest-mock": "^26.3.0", + "jest-util": "^26.3.0" } }, "jest-get-type": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-24.9.0.tgz", - "integrity": "sha512-lUseMzAley4LhIcpSP9Jf+fTrQ4a1yHQwLNeeVa2cEmbCGeoZAtYPOIv8JaxLD/sUpKxetKGP+gsHl8f8TSj8Q==", + "version": "26.3.0", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.3.0.tgz", + "integrity": "sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==", "dev": true }, "jest-haste-map": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-24.9.0.tgz", - "integrity": "sha512-kfVFmsuWui2Sj1Rp1AJ4D9HqJwE4uwTlS/vO+eRUaMmd54BFpli2XhMQnPC2k4cHFVbB2Q2C+jtI1AGLgEnCjQ==", + "version": "26.3.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-26.3.0.tgz", + "integrity": "sha512-DHWBpTJgJhLLGwE5Z1ZaqLTYqeODQIZpby0zMBsCU9iRFHYyhklYqP4EiG73j5dkbaAdSZhgB938mL51Q5LeZA==", "dev": true, "requires": { - "@jest/types": "^24.9.0", - "anymatch": "^2.0.0", + "@jest/types": "^26.3.0", + "@types/graceful-fs": "^4.1.2", + "@types/node": "*", + "anymatch": "^3.0.3", "fb-watchman": "^2.0.0", - "fsevents": "^1.2.7", - "graceful-fs": "^4.1.15", - "invariant": "^2.2.4", - "jest-serializer": "^24.9.0", - "jest-util": "^24.9.0", - "jest-worker": "^24.9.0", - "micromatch": "^3.1.10", + "fsevents": "^2.1.2", + "graceful-fs": "^4.2.4", + "jest-regex-util": "^26.0.0", + "jest-serializer": "^26.3.0", + "jest-util": "^26.3.0", + "jest-worker": "^26.3.0", + "micromatch": "^4.0.2", "sane": "^4.0.3", "walker": "^1.0.7" + }, + "dependencies": { + "anymatch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", + "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "fsevents": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", + "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", + "dev": true, + "optional": true + }, + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "jest-worker": { + "version": "26.3.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.3.0.tgz", + "integrity": "sha512-Vmpn2F6IASefL+DVBhPzI2J9/GJUsqzomdeN+P+dK8/jKxbh8R3BtFnx3FIta7wYlPU62cpJMJQo4kuOowcMnw==", + "dev": true, + "requires": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^7.0.0" + } + }, + "micromatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", + "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", + "dev": true, + "requires": { + "braces": "^3.0.1", + "picomatch": "^2.0.5" + } + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + } } }, "jest-jasmine2": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-24.9.0.tgz", - "integrity": "sha512-Cq7vkAgaYKp+PsX+2/JbTarrk0DmNhsEtqBXNwUHkdlbrTBLtMJINADf2mf5FkowNsq8evbPc07/qFO0AdKTzw==", + "version": "26.4.1", + "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-26.4.1.tgz", + "integrity": "sha512-GMPqJXyAWpohCg4wfA82lwac65lmgANH4/rOhNNaAN9yjInMAeMExQcWE1xb3fcCgLwibqeAuqVrV83oQl+szg==", "dev": true, "requires": { "@babel/traverse": "^7.1.0", - "@jest/environment": "^24.9.0", - "@jest/test-result": "^24.9.0", - "@jest/types": "^24.9.0", - "chalk": "^2.0.1", + "@jest/environment": "^26.3.0", + "@jest/source-map": "^26.3.0", + "@jest/test-result": "^26.3.0", + "@jest/types": "^26.3.0", + "@types/node": "*", + "chalk": "^4.0.0", "co": "^4.6.0", - "expect": "^24.9.0", + "expect": "^26.4.1", "is-generator-fn": "^2.0.0", - "jest-each": "^24.9.0", - "jest-matcher-utils": "^24.9.0", - "jest-message-util": "^24.9.0", - "jest-runtime": "^24.9.0", - "jest-snapshot": "^24.9.0", - "jest-util": "^24.9.0", - "pretty-format": "^24.9.0", - "throat": "^4.0.0" + "jest-each": "^26.4.0", + "jest-matcher-utils": "^26.4.1", + "jest-message-util": "^26.3.0", + "jest-runtime": "^26.4.1", + "jest-snapshot": "^26.4.1", + "jest-util": "^26.3.0", + "pretty-format": "^26.4.0", + "throat": "^5.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "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" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } } }, "jest-leak-detector": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-24.9.0.tgz", - "integrity": "sha512-tYkFIDsiKTGwb2FG1w8hX9V0aUb2ot8zY/2nFg087dUageonw1zrLMP4W6zsRO59dPkTSKie+D4rhMuP9nRmrA==", + "version": "26.4.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-26.4.0.tgz", + "integrity": "sha512-7EXKKEKnAWUPyiVtGZzJflbPOtYUdlNoevNVOkAcPpdR8xWiYKPGNGA6sz25S+8YhZq3rmkQJYAh3/P0VnoRwA==", "dev": true, "requires": { - "jest-get-type": "^24.9.0", - "pretty-format": "^24.9.0" + "jest-get-type": "^26.3.0", + "pretty-format": "^26.4.0" } }, "jest-matcher-utils": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-24.9.0.tgz", - "integrity": "sha512-OZz2IXsu6eaiMAwe67c1T+5tUAtQyQx27/EMEkbFAGiw52tB9em+uGbzpcgYVpA8wl0hlxKPZxrly4CXU/GjHA==", + "version": "26.4.1", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-26.4.1.tgz", + "integrity": "sha512-nmHWaOz54R/w6zJju5tuW0bw6+m38Rb1jnDKehKM/bOngDDL0UwtN634cRxpFoUNVRUrX8Wa0Z34xq/f8iuP5A==", "dev": true, "requires": { - "chalk": "^2.0.1", - "jest-diff": "^24.9.0", - "jest-get-type": "^24.9.0", - "pretty-format": "^24.9.0" + "chalk": "^4.0.0", + "jest-diff": "^26.4.0", + "jest-get-type": "^26.3.0", + "pretty-format": "^26.4.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "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" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } } }, "jest-message-util": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-24.9.0.tgz", - "integrity": "sha512-oCj8FiZ3U0hTP4aSui87P4L4jC37BtQwUMqk+zk/b11FR19BJDeZsZAvIHutWnmtw7r85UmR3CEWZ0HWU2mAlw==", + "version": "26.3.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-26.3.0.tgz", + "integrity": "sha512-xIavRYqr4/otGOiLxLZGj3ieMmjcNE73Ui+LdSW/Y790j5acqCsAdDiLIbzHCZMpN07JOENRWX5DcU+OQ+TjTA==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", - "@jest/test-result": "^24.9.0", - "@jest/types": "^24.9.0", + "@jest/types": "^26.3.0", "@types/stack-utils": "^1.0.1", - "chalk": "^2.0.1", - "micromatch": "^3.1.10", - "slash": "^2.0.0", - "stack-utils": "^1.0.1" + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", + "micromatch": "^4.0.2", + "slash": "^3.0.0", + "stack-utils": "^2.0.2" + }, + "dependencies": { + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "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" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "micromatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", + "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", + "dev": true, + "requires": { + "braces": "^3.0.1", + "picomatch": "^2.0.5" + } + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + } } }, "jest-mock": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-24.9.0.tgz", - "integrity": "sha512-3BEYN5WbSq9wd+SyLDES7AHnjH9A/ROBwmz7l2y+ol+NtSFO8DYiEBzoO1CeFc9a8DYy10EO4dDFVv/wN3zl1w==", + "version": "26.3.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-26.3.0.tgz", + "integrity": "sha512-PeaRrg8Dc6mnS35gOo/CbZovoDPKAeB1FICZiuagAgGvbWdNNyjQjkOaGUa/3N3JtpQ/Mh9P4A2D4Fv51NnP8Q==", "dev": true, "requires": { - "@jest/types": "^24.9.0" + "@jest/types": "^26.3.0", + "@types/node": "*" } }, "jest-pnp-resolver": { @@ -11236,160 +13344,573 @@ "dev": true }, "jest-regex-util": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-24.9.0.tgz", - "integrity": "sha512-05Cmb6CuxaA+Ys6fjr3PhvV3bGQmO+2p2La4hFbU+W5uOc479f7FdLXUWXw4pYMAhhSZIuKHwSXSu6CsSBAXQA==", + "version": "26.0.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-26.0.0.tgz", + "integrity": "sha512-Gv3ZIs/nA48/Zvjrl34bf+oD76JHiGDUxNOVgUjh3j890sblXryjY4rss71fPtD/njchl6PSE2hIhvyWa1eT0A==", "dev": true }, "jest-resolve": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-24.9.0.tgz", - "integrity": "sha512-TaLeLVL1l08YFZAt3zaPtjiVvyy4oSA6CRe+0AFPPVX3Q/VI0giIWWoAvoS5L96vj9Dqxj4fB5p2qrHCmTU/MQ==", + "version": "26.4.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-26.4.0.tgz", + "integrity": "sha512-bn/JoZTEXRSlEx3+SfgZcJAVuTMOksYq9xe9O6s4Ekg84aKBObEaVXKOEilULRqviSLAYJldnoWV9c07kwtiCg==", "dev": true, "requires": { - "@jest/types": "^24.9.0", - "browser-resolve": "^1.11.3", - "chalk": "^2.0.1", - "jest-pnp-resolver": "^1.2.1", - "realpath-native": "^1.1.0" - } - }, - "jest-resolve-dependencies": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-24.9.0.tgz", - "integrity": "sha512-Fm7b6AlWnYhT0BXy4hXpactHIqER7erNgIsIozDXWl5dVm+k8XdGVe1oTg1JyaFnOxarMEbax3wyRJqGP2Pq+g==", - "dev": true, - "requires": { - "@jest/types": "^24.9.0", - "jest-regex-util": "^24.3.0", - "jest-snapshot": "^24.9.0" - } - }, - "jest-runner": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-24.9.0.tgz", - "integrity": "sha512-KksJQyI3/0mhcfspnxxEOBueGrd5E4vV7ADQLT9ESaCzz02WnbdbKWIf5Mkaucoaj7obQckYPVX6JJhgUcoWWg==", - "dev": true, - "requires": { - "@jest/console": "^24.7.1", - "@jest/environment": "^24.9.0", - "@jest/test-result": "^24.9.0", - "@jest/types": "^24.9.0", - "chalk": "^2.4.2", - "exit": "^0.1.2", - "graceful-fs": "^4.1.15", - "jest-config": "^24.9.0", - "jest-docblock": "^24.3.0", - "jest-haste-map": "^24.9.0", - "jest-jasmine2": "^24.9.0", - "jest-leak-detector": "^24.9.0", - "jest-message-util": "^24.9.0", - "jest-resolve": "^24.9.0", - "jest-runtime": "^24.9.0", - "jest-util": "^24.9.0", - "jest-worker": "^24.6.0", - "source-map-support": "^0.5.6", - "throat": "^4.0.0" - } - }, - "jest-runtime": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-24.9.0.tgz", - "integrity": "sha512-8oNqgnmF3v2J6PVRM2Jfuj8oX3syKmaynlDMMKQ4iyzbQzIG6th5ub/lM2bCMTmoTKM3ykcUYI2Pw9xwNtjMnw==", - "dev": true, - "requires": { - "@jest/console": "^24.7.1", - "@jest/environment": "^24.9.0", - "@jest/source-map": "^24.3.0", - "@jest/transform": "^24.9.0", - "@jest/types": "^24.9.0", - "@types/yargs": "^13.0.0", - "chalk": "^2.0.1", - "exit": "^0.1.2", - "glob": "^7.1.3", - "graceful-fs": "^4.1.15", - "jest-config": "^24.9.0", - "jest-haste-map": "^24.9.0", - "jest-message-util": "^24.9.0", - "jest-mock": "^24.9.0", - "jest-regex-util": "^24.3.0", - "jest-resolve": "^24.9.0", - "jest-snapshot": "^24.9.0", - "jest-util": "^24.9.0", - "jest-validate": "^24.9.0", - "realpath-native": "^1.1.0", - "slash": "^2.0.0", - "strip-bom": "^3.0.0", - "yargs": "^13.3.0" - } - }, - "jest-serializer": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-24.9.0.tgz", - "integrity": "sha512-DxYipDr8OvfrKH3Kel6NdED3OXxjvxXZ1uIY2I9OFbGg+vUkkg7AGvi65qbhbWNPvDckXmzMPbK3u3HaDO49bQ==", - "dev": true - }, - "jest-snapshot": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-24.9.0.tgz", - "integrity": "sha512-uI/rszGSs73xCM0l+up7O7a40o90cnrk429LOiK3aeTvfC0HHmldbd81/B7Ix81KSFe1lwkbl7GnBGG4UfuDew==", - "dev": true, - "requires": { - "@babel/types": "^7.0.0", - "@jest/types": "^24.9.0", - "chalk": "^2.0.1", - "expect": "^24.9.0", - "jest-diff": "^24.9.0", - "jest-get-type": "^24.9.0", - "jest-matcher-utils": "^24.9.0", - "jest-message-util": "^24.9.0", - "jest-resolve": "^24.9.0", - "mkdirp": "^0.5.1", - "natural-compare": "^1.4.0", - "pretty-format": "^24.9.0", - "semver": "^6.2.0" + "@jest/types": "^26.3.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^26.3.0", + "read-pkg-up": "^7.0.1", + "resolve": "^1.17.0", + "slash": "^3.0.0" }, "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "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" + } + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "jest-pnp-resolver": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz", + "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==", + "dev": true + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "parse-json": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.1.tgz", + "integrity": "sha512-ztoZ4/DYeXQq4E21v169sC8qWINGpcosGv9XhTDvg9/hWvx/zrFkc9BiWxR58OJLHGk28j5BL0SDLeV2WmFZlQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1", + "lines-and-columns": "^1.1.6" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "read-pkg": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", + "dev": true, + "requires": { + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" + }, + "dependencies": { + "type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", + "dev": true + } + } + }, + "read-pkg-up": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", + "dev": true, + "requires": { + "find-up": "^4.1.0", + "read-pkg": "^5.2.0", + "type-fest": "^0.8.1" + } + }, + "resolve": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", + "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", "dev": true } } }, - "jest-util": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-24.9.0.tgz", - "integrity": "sha512-x+cZU8VRmOJxbA1K5oDBdxQmdq0OIdADarLxk0Mq+3XS4jgvhG/oKGWcIDCtPG0HgjxOYvF+ilPJQsAyXfbNOg==", + "jest-resolve-dependencies": { + "version": "26.4.1", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-26.4.1.tgz", + "integrity": "sha512-Gx4JfQ1k/hGb4lqVOOx8TPOkNtyJIQSHcJU68pB+sdyDJi9rbMxD1XXiYyaEq9WXufiZo90k9GTK6z6a5m0SQw==", "dev": true, "requires": { - "@jest/console": "^24.9.0", - "@jest/fake-timers": "^24.9.0", - "@jest/source-map": "^24.9.0", - "@jest/test-result": "^24.9.0", - "@jest/types": "^24.9.0", - "callsites": "^3.0.0", - "chalk": "^2.0.1", - "graceful-fs": "^4.1.15", - "is-ci": "^2.0.0", - "mkdirp": "^0.5.1", - "slash": "^2.0.0", - "source-map": "^0.6.0" + "@jest/types": "^26.3.0", + "jest-regex-util": "^26.0.0", + "jest-snapshot": "^26.4.1" + } + }, + "jest-runner": { + "version": "26.4.1", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-26.4.1.tgz", + "integrity": "sha512-QcKwn1YNlzFumTtFsocETgIm13KNt2X8sae4wcqsF3JnxGUcYYUGBstCQhtAG4fKD/TKThHkgE/ZgQVKipj7oA==", + "dev": true, + "requires": { + "@jest/console": "^26.3.0", + "@jest/environment": "^26.3.0", + "@jest/test-result": "^26.3.0", + "@jest/types": "^26.3.0", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.7.1", + "exit": "^0.1.2", + "graceful-fs": "^4.2.4", + "jest-config": "^26.4.1", + "jest-docblock": "^26.0.0", + "jest-haste-map": "^26.3.0", + "jest-leak-detector": "^26.4.0", + "jest-message-util": "^26.3.0", + "jest-resolve": "^26.4.0", + "jest-runtime": "^26.4.1", + "jest-util": "^26.3.0", + "jest-worker": "^26.3.0", + "source-map-support": "^0.5.6", + "throat": "^5.0.0" }, "dependencies": { - "callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "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" + } + }, + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", "dev": true }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "jest-worker": { + "version": "26.3.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.3.0.tgz", + "integrity": "sha512-Vmpn2F6IASefL+DVBhPzI2J9/GJUsqzomdeN+P+dK8/jKxbh8R3BtFnx3FIta7wYlPU62cpJMJQo4kuOowcMnw==", + "dev": true, + "requires": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^7.0.0" + } + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-runtime": { + "version": "26.4.1", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-26.4.1.tgz", + "integrity": "sha512-zXPQBS4iL/CEZtDfX+rDz+oZ/inQK/EYOeVt3uDWu8kwSdP/Cw4yOZtCTPApeNsGtZy6X5WQ1U+fyagN1B/Qkw==", + "dev": true, + "requires": { + "@jest/console": "^26.3.0", + "@jest/environment": "^26.3.0", + "@jest/fake-timers": "^26.3.0", + "@jest/globals": "^26.4.1", + "@jest/source-map": "^26.3.0", + "@jest/test-result": "^26.3.0", + "@jest/transform": "^26.3.0", + "@jest/types": "^26.3.0", + "@types/yargs": "^15.0.0", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.4", + "jest-config": "^26.4.1", + "jest-haste-map": "^26.3.0", + "jest-message-util": "^26.3.0", + "jest-mock": "^26.3.0", + "jest-regex-util": "^26.0.0", + "jest-resolve": "^26.4.0", + "jest-snapshot": "^26.4.1", + "jest-util": "^26.3.0", + "jest-validate": "^26.4.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0", + "yargs": "^15.3.1" + }, + "dependencies": { + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "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" + } + }, + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, + "strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-serializer": { + "version": "26.3.0", + "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-26.3.0.tgz", + "integrity": "sha512-IDRBQBLPlKa4flg77fqg0n/pH87tcRKwe8zxOVTWISxGpPHYkRZ1dXKyh04JOja7gppc60+soKVZ791mruVdow==", + "dev": true, + "requires": { + "@types/node": "*", + "graceful-fs": "^4.2.4" + }, + "dependencies": { + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "dev": true + } + } + }, + "jest-snapshot": { + "version": "26.4.1", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-26.4.1.tgz", + "integrity": "sha512-5DsxbSSuYA8rZ/ynO+l5J65wSIyzDB2AXjuIvep90YmtslrROqDtba2hBgq1Cj6L6A0j/jv6h8JydEe2WYPM/g==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0", + "@jest/types": "^26.3.0", + "@types/prettier": "^2.0.0", + "chalk": "^4.0.0", + "expect": "^26.4.1", + "graceful-fs": "^4.2.4", + "jest-diff": "^26.4.0", + "jest-get-type": "^26.3.0", + "jest-haste-map": "^26.3.0", + "jest-matcher-utils": "^26.4.1", + "jest-message-util": "^26.3.0", + "jest-resolve": "^26.4.0", + "natural-compare": "^1.4.0", + "pretty-format": "^26.4.0", + "semver": "^7.3.2" + }, + "dependencies": { + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "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" + } + }, + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "semver": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-util": { + "version": "26.3.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-26.3.0.tgz", + "integrity": "sha512-4zpn6bwV0+AMFN0IYhH/wnzIQzRaYVrz1A8sYnRnj4UXDXbOVtWmlaZkO9mipFqZ13okIfN87aDoJWB7VH6hcw==", + "dev": true, + "requires": { + "@jest/types": "^26.3.0", + "@types/node": "*", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", + "is-ci": "^2.0.0", + "micromatch": "^4.0.2" + }, + "dependencies": { + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, "ci-info": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", "dev": true }, + "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" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, "is-ci": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", @@ -11398,44 +13919,168 @@ "requires": { "ci-info": "^2.0.0" } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "micromatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", + "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", + "dev": true, + "requires": { + "braces": "^3.0.1", + "picomatch": "^2.0.5" + } + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } } } }, "jest-validate": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-24.9.0.tgz", - "integrity": "sha512-HPIt6C5ACwiqSiwi+OfSSHbK8sG7akG8eATl+IPKaeIjtPOeBUd/g3J7DghugzxrGjI93qS/+RPKe1H6PqvhRQ==", + "version": "26.4.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-26.4.0.tgz", + "integrity": "sha512-t56Z/FRMrLP6mpmje7/YgHy0wOzcuc6i3LBXz6kjmsUWYN62OuMdC86Vg9/dX59SvyitSqqegOrx+h7BkNXeaQ==", "dev": true, "requires": { - "@jest/types": "^24.9.0", - "camelcase": "^5.3.1", - "chalk": "^2.0.1", - "jest-get-type": "^24.9.0", + "@jest/types": "^26.3.0", + "camelcase": "^6.0.0", + "chalk": "^4.0.0", + "jest-get-type": "^26.3.0", "leven": "^3.1.0", - "pretty-format": "^24.9.0" + "pretty-format": "^26.4.0" }, "dependencies": { + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.0.0.tgz", + "integrity": "sha512-8KMDF1Vz2gzOq54ONPJS65IvTUaB1cHJ2DMM7MbPmLZljDH1qpzzLsWdiN9pHh6qvkRVDTi/07+eNGch/oLU4w==", "dev": true + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "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" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } } } }, "jest-watcher": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-24.9.0.tgz", - "integrity": "sha512-+/fLOfKPXXYJDYlks62/4R4GoT+GU1tYZed99JSCOsmzkkF7727RqKrjNAxtfO4YpGv11wybgRvCjR73lK2GZw==", + "version": "26.3.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-26.3.0.tgz", + "integrity": "sha512-XnLdKmyCGJ3VoF6G/p5ohbJ04q/vv5aH9ENI+i6BL0uu9WWB6Z7Z2lhQQk0d2AVZcRGp1yW+/TsoToMhBFPRdQ==", "dev": true, "requires": { - "@jest/test-result": "^24.9.0", - "@jest/types": "^24.9.0", - "@types/yargs": "^13.0.0", - "ansi-escapes": "^3.0.0", - "chalk": "^2.0.1", - "jest-util": "^24.9.0", - "string-length": "^2.0.0" + "@jest/test-result": "^26.3.0", + "@jest/types": "^26.3.0", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "jest-util": "^26.3.0", + "string-length": "^4.0.1" + }, + "dependencies": { + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "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" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } } }, "jest-worker": { @@ -11493,50 +14138,90 @@ "dev": true }, "jsdom": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-11.12.0.tgz", - "integrity": "sha512-y8Px43oyiBM13Zc1z780FrfNLJCXTL40EWlty/LXUtcjykRBNgLlCjWXpfSPBl2iv+N7koQN+dvqszHZgT/Fjw==", + "version": "16.4.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.4.0.tgz", + "integrity": "sha512-lYMm3wYdgPhrl7pDcRmvzPhhrGVBeVhPIqeHjzeiHN3DFmD1RBpbExbi8vU7BJdH8VAZYovR8DMt0PNNDM7k8w==", "dev": true, "requires": { - "abab": "^2.0.0", - "acorn": "^5.5.3", - "acorn-globals": "^4.1.0", - "array-equal": "^1.0.0", - "cssom": ">= 0.3.2 < 0.4.0", - "cssstyle": "^1.0.0", - "data-urls": "^1.0.0", - "domexception": "^1.0.1", - "escodegen": "^1.9.1", - "html-encoding-sniffer": "^1.0.2", - "left-pad": "^1.3.0", - "nwsapi": "^2.0.7", - "parse5": "4.0.0", - "pn": "^1.1.0", - "request": "^2.87.0", - "request-promise-native": "^1.0.5", - "sax": "^1.2.4", - "symbol-tree": "^3.2.2", - "tough-cookie": "^2.3.4", - "w3c-hr-time": "^1.0.1", - "webidl-conversions": "^4.0.2", - "whatwg-encoding": "^1.0.3", - "whatwg-mimetype": "^2.1.0", - "whatwg-url": "^6.4.1", - "ws": "^5.2.0", + "abab": "^2.0.3", + "acorn": "^7.1.1", + "acorn-globals": "^6.0.0", + "cssom": "^0.4.4", + "cssstyle": "^2.2.0", + "data-urls": "^2.0.0", + "decimal.js": "^10.2.0", + "domexception": "^2.0.1", + "escodegen": "^1.14.1", + "html-encoding-sniffer": "^2.0.1", + "is-potential-custom-element-name": "^1.0.0", + "nwsapi": "^2.2.0", + "parse5": "5.1.1", + "request": "^2.88.2", + "request-promise-native": "^1.0.8", + "saxes": "^5.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^3.0.1", + "w3c-hr-time": "^1.0.2", + "w3c-xmlserializer": "^2.0.0", + "webidl-conversions": "^6.1.0", + "whatwg-encoding": "^1.0.5", + "whatwg-mimetype": "^2.3.0", + "whatwg-url": "^8.0.0", + "ws": "^7.2.3", "xml-name-validator": "^3.0.0" }, "dependencies": { - "acorn": { - "version": "5.7.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", - "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==", + "parse5": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", + "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==", "dev": true }, - "parse5": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-4.0.0.tgz", - "integrity": "sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA==", + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", "dev": true + }, + "request": { + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "dev": true, + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "dependencies": { + "tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "dev": true, + "requires": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + } + } } } }, @@ -11723,12 +14408,6 @@ "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.5.1.tgz", "integrity": "sha512-ekM9KAeG99tYisNBg0IzEywAlp0hYI5XRipsqRXyRTeuU8jcuntilpp+eFf5gaE0xubc9RuSNIVtByEKwqFV0w==" }, - "left-pad": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz", - "integrity": "sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==", - "dev": true - }, "leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -11745,6 +14424,12 @@ "type-check": "~0.3.2" } }, + "lines-and-columns": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", + "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=", + "dev": true + }, "load-json-file": { "version": "1.1.0", "resolved": "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz", @@ -12835,16 +15520,54 @@ "dev": true }, "node-notifier": { - "version": "5.4.3", - "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-5.4.3.tgz", - "integrity": "sha512-M4UBGcs4jeOK9CjTsYwkvH6/MzuUmGCyTW+kCY7uO+1ZVr0+FHGdPdIf5CCLqAaxnRrWidyoQlNkMIIVwbKB8Q==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-8.0.0.tgz", + "integrity": "sha512-46z7DUmcjoYdaWyXouuFNNfUo6eFa94t23c53c+lG/9Cvauk4a98rAUp9672X5dxGdQmLpPzTxzu8f/OeEPaFA==", "dev": true, + "optional": true, "requires": { "growly": "^1.3.0", - "is-wsl": "^1.1.0", - "semver": "^5.5.0", + "is-wsl": "^2.2.0", + "semver": "^7.3.2", "shellwords": "^0.1.1", - "which": "^1.3.0" + "uuid": "^8.3.0", + "which": "^2.0.2" + }, + "dependencies": { + "is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "optional": true, + "requires": { + "is-docker": "^2.0.0" + } + }, + "semver": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", + "dev": true, + "optional": true + }, + "uuid": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.0.tgz", + "integrity": "sha512-fX6Z5o4m6XsXBdli9g7DtWgAx+osMsRRZFKma1mIUsLCz6vRvv+pz5VNbyu9UEDzpMWulZfvpgb/cmDXVulYFQ==", + "dev": true, + "optional": true + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "optional": true, + "requires": { + "isexe": "^2.0.0" + } + } } }, "node-releases": { @@ -13029,9 +15752,9 @@ "dev": true }, "nwsapi": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.1.4.tgz", - "integrity": "sha512-iGfd9Y6SFdTNldEy2L0GUhcarIutFmk+MPWIn9dmj8NMIup03G08uUF2KGbbmv/Ux4RT0VZJoP/sVbWA6d/VIw==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz", + "integrity": "sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==", "dev": true }, "oauth-sign": { @@ -13303,30 +16026,6 @@ "is-wsl": "^1.1.0" } }, - "optimist": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", - "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", - "dev": true, - "requires": { - "minimist": "~0.0.1", - "wordwrap": "~0.0.2" - }, - "dependencies": { - "minimist": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", - "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=", - "dev": true - }, - "wordwrap": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", - "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", - "dev": true - } - } - }, "optimize-css-assets-webpack-plugin": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/optimize-css-assets-webpack-plugin/-/optimize-css-assets-webpack-plugin-5.0.3.tgz", @@ -13338,17 +16037,17 @@ } }, "optionator": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", - "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", "dev": true, "requires": { "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.4", + "fast-levenshtein": "~2.0.6", "levn": "~0.3.0", "prelude-ls": "~1.1.2", "type-check": "~0.3.2", - "wordwrap": "~1.0.0" + "word-wrap": "~1.2.3" } }, "original": { @@ -13404,13 +16103,10 @@ "dev": true }, "p-each-series": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-1.0.0.tgz", - "integrity": "sha1-kw89Et0fUOdDRFeiLNbwSsatf3E=", - "dev": true, - "requires": { - "p-reduce": "^1.0.0" - } + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-2.1.0.tgz", + "integrity": "sha512-ZuRs1miPT4HrjFa+9fRfOFXxGJfORgelKV9f9nNOWw2gl6gVsRaVDOQP0+MI0G0wGKns1Yacsu0GjOFbTK0JFQ==", + "dev": true }, "p-finally": { "version": "1.0.0", @@ -13448,12 +16144,6 @@ "integrity": "sha1-5OlPMR6rvIYzoeeZCBZfyiYkG2s=", "dev": true }, - "p-reduce": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-reduce/-/p-reduce-1.0.0.tgz", - "integrity": "sha1-GMKw3ZNqRpClKfgjH1ig/bakffo=", - "dev": true - }, "p-retry": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-3.0.1.tgz", @@ -13767,12 +16457,6 @@ } } }, - "pn": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/pn/-/pn-1.1.0.tgz", - "integrity": "sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==", - "dev": true - }, "pnp-webpack-plugin": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/pnp-webpack-plugin/-/pnp-webpack-plugin-1.5.0.tgz", @@ -14998,27 +17682,46 @@ } }, "pretty-format": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-24.9.0.tgz", - "integrity": "sha512-00ZMZUiHaJrNfk33guavqgvfJS30sLYf0f8+Srklv0AMPodGGHcoHgksZ3OThYnIvOd+8yMCn0YiEOogjlgsnA==", + "version": "26.4.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.4.0.tgz", + "integrity": "sha512-mEEwwpCseqrUtuMbrJG4b824877pM5xald3AkilJ47Po2YLr97/siejYQHqj2oDQBeJNbu+Q0qUuekJ8F0NAPg==", "dev": true, "requires": { - "@jest/types": "^24.9.0", - "ansi-regex": "^4.0.0", - "ansi-styles": "^3.2.0", - "react-is": "^16.8.4" + "@jest/types": "^26.3.0", + "ansi-regex": "^5.0.0", + "ansi-styles": "^4.0.0", + "react-is": "^16.12.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==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", "dev": true }, + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "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" + } + }, "react-is": { - "version": "16.10.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.10.2.tgz", - "integrity": "sha512-INBT1QEgtcCCgvccr5/86CfD71fw9EPmDxgiJX4I2Ddr6ZsV6iFXsuby+qWJPtmNuMY0zByTsG4468P7nHuNWA==", + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "dev": true } } @@ -15062,13 +17765,13 @@ "dev": true }, "prompts": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.2.1.tgz", - "integrity": "sha512-VObPvJiWPhpZI6C5m60XOzTfnYg/xc/an+r9VYymj9WJW3B/DIH+REzjpAACPf8brwPeP+7vz3bIim3S+AaMjw==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.3.2.tgz", + "integrity": "sha512-Q06uKs2CkNYVID0VqwfAl9mipo99zkBv/n2JtWY89Yxa3ZabWSrs0e2KTudKVa3peLUvYXMefDqIleLPVUBZMA==", "dev": true, "requires": { "kleur": "^3.0.3", - "sisteransi": "^1.0.3" + "sisteransi": "^1.0.4" } }, "prop-types": { @@ -15876,15 +18579,6 @@ "readable-stream": "^2.0.2" } }, - "realpath-native": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/realpath-native/-/realpath-native-1.1.0.tgz", - "integrity": "sha512-wlgPA6cCIIg9gKz0fgAPjnzh4yR/LnXovwuo9hvyGvx3h8nX4+/iLZplfUWasXpqD8BdnGnP5njOFjkUwPzvjA==", - "dev": true, - "requires": { - "util.promisify": "^1.0.0" - } - }, "recursive-readdir": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.2.tgz", @@ -16265,23 +18959,43 @@ } }, "request-promise-core": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.2.tgz", - "integrity": "sha512-UHYyq1MO8GsefGEt7EprS8UrXsm1TxEvFUX1IMTuSLU2Rh7fTIdFtl8xD7JiEYiWU2dl+NYAjCTksTehQUxPag==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.4.tgz", + "integrity": "sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw==", "dev": true, "requires": { - "lodash": "^4.17.11" + "lodash": "^4.17.19" + }, + "dependencies": { + "lodash": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", + "dev": true + } } }, "request-promise-native": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.7.tgz", - "integrity": "sha512-rIMnbBdgNViL37nZ1b3L/VfPOpSi0TqVDQPAvO6U14lMzOLrt5nilxCQqtDKhZeDiW0/hkCXGoQjhgJd/tCh6w==", + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.9.tgz", + "integrity": "sha512-wcW+sIUiWnKgNY0dqCpOZkUbF/I+YPi+f09JZIDa39Ec+q82CpSYniDp+ISgTTbKmnpJWASeJBPZmoxH84wt3g==", "dev": true, "requires": { - "request-promise-core": "1.1.2", + "request-promise-core": "1.1.4", "stealthy-require": "^1.1.1", "tough-cookie": "^2.3.3" + }, + "dependencies": { + "tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "dev": true, + "requires": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + } } }, "require-directory": { @@ -16696,6 +19410,15 @@ "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", "dev": true }, + "saxes": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", + "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", + "dev": true, + "requires": { + "xmlchars": "^2.2.0" + } + }, "scheduler": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.19.1.tgz", @@ -17052,7 +19775,8 @@ "version": "0.1.1", "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz", "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==", - "dev": true + "dev": true, + "optional": true }, "side-channel": { "version": "1.0.2", @@ -17147,9 +19871,9 @@ } }, "sisteransi": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.3.tgz", - "integrity": "sha512-SbEG75TzH8G7eVXFSN5f9EExILKfly7SUvVY5DhhYLvfhKqhDFY0OzevWa/zwak0RLRfWS5AvfMWpd9gJvr5Yg==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", "dev": true }, "slash": { @@ -17506,10 +20230,21 @@ "dev": true }, "stack-utils": { - "version": "1.0.2", - "resolved": "https://registry.yarnpkg.com/stack-utils/-/stack-utils-1.0.2.tgz", - "integrity": "sha1-M+ujiXeIVYvr/C2wWdwVjsNs67g=", - "dev": true + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.2.tgz", + "integrity": "sha512-0H7QK2ECz3fyZMzQ8rH0j2ykpfbnd20BFtfg/SqVC2+sCTtcw0aDTGB7dk+de4U4uUeuz6nOtJcrkFFLG1B0Rg==", + "dev": true, + "requires": { + "escape-string-regexp": "^2.0.0" + }, + "dependencies": { + "escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true + } + } }, "state-toggle": { "version": "1.0.2", @@ -17628,28 +20363,28 @@ "dev": true }, "string-length": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-2.0.0.tgz", - "integrity": "sha1-1A27aGo6zpYMHP/KVivyxF+DY+0=", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.1.tgz", + "integrity": "sha512-PKyXUd0LK0ePjSOnWn34V2uD6acUWev9uy0Ft05k0E8xRW+SKcA0F7eMr7h5xlzfn+4O3N+55rduYyet3Jk+jw==", "dev": true, "requires": { - "astral-regex": "^1.0.0", - "strip-ansi": "^4.0.0" + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" }, "dependencies": { "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", "dev": true }, "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", "dev": true, "requires": { - "ansi-regex": "^3.0.0" + "ansi-regex": "^5.0.0" } } } @@ -18083,6 +20818,12 @@ "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", "dev": true }, + "strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true + }, "strip-indent": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", @@ -18719,6 +21460,33 @@ "has-flag": "^3.0.0" } }, + "supports-hyperlinks": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.1.0.tgz", + "integrity": "sha512-zoE5/e+dnEijk6ASB6/qrK+oYdm2do1hjoLWrqUC/8WEIW1gbxFcKuBof7sW8ArN6e+AYvsE8HBGiVRWL/F5CA==", + "dev": true, + "requires": { + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + }, + "dependencies": { + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, "surrial": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/surrial/-/surrial-2.0.2.tgz", @@ -18858,6 +21626,16 @@ "execa": "^0.7.0" } }, + "terminal-link": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", + "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", + "dev": true, + "requires": { + "ansi-escapes": "^4.2.1", + "supports-hyperlinks": "^2.0.0" + } + }, "terser": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/terser/-/terser-4.3.7.tgz", @@ -18993,48 +21771,28 @@ } }, "test-exclude": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-5.2.3.tgz", - "integrity": "sha512-M+oxtseCFO3EDtAaGH7iiej3CBkzXqFMbzqYAACdzKui4eZA+pq3tZEwChvOdNfa7xxy8BfbmgJSIr43cC/+2g==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", "dev": true, "requires": { - "glob": "^7.1.3", - "minimatch": "^3.0.4", - "read-pkg-up": "^4.0.0", - "require-main-filename": "^2.0.0" + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" }, "dependencies": { - "load-json-file": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", - "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", "dev": true, "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^4.0.0", - "pify": "^3.0.0", - "strip-bom": "^3.0.0" - } - }, - "read-pkg": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", - "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", - "dev": true, - "requires": { - "load-json-file": "^4.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^3.0.0" - } - }, - "read-pkg-up": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-4.0.0.tgz", - "integrity": "sha512-6etQSH7nJGsK0RbG/2TeDzZFa8shjQ1um+SwQQ5cwKy0dhSXdOncEhb1CPpvQG4h7FyOV6EB6YlV0yJvZQNAkA==", - "dev": true, - "requires": { - "find-up": "^3.0.0", - "read-pkg": "^3.0.0" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" } } } @@ -19046,9 +21804,9 @@ "dev": true }, "throat": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/throat/-/throat-4.1.0.tgz", - "integrity": "sha1-iQN8vJLFarGJJua6TLsgDhVnKmo=", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz", + "integrity": "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==", "dev": true }, "through": { @@ -19231,22 +21989,23 @@ "dev": true }, "tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-3.0.1.tgz", + "integrity": "sha512-yQyJ0u4pZsv9D4clxO69OEjLWYw+jbgspjTue4lTQZLfV0c5l1VmK2y1JK8E9ahdpltPOaAThPcp5nKPUgSnsg==", "dev": true, "requires": { + "ip-regex": "^2.1.0", "psl": "^1.1.28", "punycode": "^2.1.1" } }, "tr46": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", - "integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.0.2.tgz", + "integrity": "sha512-3n1qG+/5kg+jrbTzwAykB5yRYtQCTqOGKq5U5PE3b0a1/mzo6snDhjGS0zJVJunO0NrT3Dg1MLy5TjWP/UJppg==", "dev": true, "requires": { - "punycode": "^2.1.0" + "punycode": "^2.1.1" } }, "tree-kill": { @@ -19496,6 +22255,12 @@ } } }, + "ts-mockery": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/ts-mockery/-/ts-mockery-1.2.0.tgz", + "integrity": "sha512-ArGPMUzO4H25KBYVTWmmE36y5bCOFAwC7XdW4CLTqYg+gQcvxJzKoj5URSc+luzwI8QdtwAkHtazBmrKepX81g==", + "dev": true + }, "ts-pnp": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/ts-pnp/-/ts-pnp-1.1.4.tgz", @@ -19582,6 +22347,12 @@ "prelude-ls": "~1.1.2" } }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true + }, "type-fest": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.3.1.tgz", @@ -19634,6 +22405,15 @@ "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", "dev": true }, + "typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "dev": true, + "requires": { + "is-typedarray": "^1.0.0" + } + }, "typescript": { "version": "3.9.7", "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.7.tgz", @@ -20096,6 +22876,25 @@ "integrity": "sha512-8OQ9CL+VWyt3JStj7HX7/ciTL2V3Rl1Wf5OL+SNTm0yK1KvtReVulksyeRnCANHHuUxHlQig+JJDlUhBt1NQDQ==", "dev": true }, + "v8-to-istanbul": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-5.0.1.tgz", + "integrity": "sha512-mbDNjuDajqYe3TXFk5qxcQy8L1msXNE37WTlLoqqpBfRsimbNcrlhQlDPntmECEcUvdC+AQ8CyMMf6EUx1r74Q==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^1.6.0", + "source-map": "^0.7.3" + }, + "dependencies": { + "source-map": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", + "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", + "dev": true + } + } + }, "validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", @@ -20176,12 +22975,21 @@ "dev": true }, "w3c-hr-time": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.1.tgz", - "integrity": "sha1-gqwr/2PZUOqeMYmlimViX+3xkEU=", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", + "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", "dev": true, "requires": { - "browser-process-hrtime": "^0.1.2" + "browser-process-hrtime": "^1.0.0" + } + }, + "w3c-xmlserializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz", + "integrity": "sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==", + "dev": true, + "requires": { + "xml-name-validator": "^3.0.0" } }, "walker": { @@ -20222,9 +23030,9 @@ } }, "webidl-conversions": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", - "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", + "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==", "dev": true }, "webpack": { @@ -20867,14 +23675,22 @@ "dev": true }, "whatwg-url": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-6.5.0.tgz", - "integrity": "sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.1.0.tgz", + "integrity": "sha512-vEIkwNi9Hqt4TV9RdnaBPNt+E2Sgmo3gePebCRgZ1R7g6d23+53zCTnuB0amKI4AXq6VM8jj2DUAa0S1vjJxkw==", "dev": true, "requires": { "lodash.sortby": "^4.7.0", - "tr46": "^1.0.1", - "webidl-conversions": "^4.0.2" + "tr46": "^2.0.2", + "webidl-conversions": "^5.0.0" + }, + "dependencies": { + "webidl-conversions": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz", + "integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==", + "dev": true + } } }, "which": { @@ -20916,12 +23732,6 @@ "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", "dev": true }, - "wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", - "dev": true - }, "workbox-background-sync": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/workbox-background-sync/-/workbox-background-sync-4.3.1.tgz", @@ -21140,46 +23950,65 @@ } }, "wrap-ansi": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", - "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", "dev": true, "requires": { - "ansi-styles": "^3.2.0", - "string-width": "^3.0.0", - "strip-ansi": "^5.0.0" + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.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==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", "dev": true }, - "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "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" + } + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "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==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", "dev": true, "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" } }, "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", "dev": true, "requires": { - "ansi-regex": "^4.1.0" + "ansi-regex": "^5.0.0" } } } @@ -21211,13 +24040,10 @@ } }, "ws": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-5.2.2.tgz", - "integrity": "sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA==", - "dev": true, - "requires": { - "async-limiter": "~1.0.0" - } + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.3.1.tgz", + "integrity": "sha512-D3RuNkynyHmEJIpD2qrgVkc9DQ23OrN/moAwZX4L8DfvszsJxpjQuUq3LMx6HoYji9fbIOBY18XWBsAux1ZZUA==", + "dev": true }, "x-is-string": { "version": "0.1.0", @@ -21237,6 +24063,12 @@ "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", "dev": true }, + "xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true + }, "xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", @@ -21256,61 +24088,96 @@ "dev": true }, "yargs": { - "version": "13.3.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.0.tgz", - "integrity": "sha512-2eehun/8ALW8TLoIl7MVaRUrg+yCnenu8B4kBlRxj3GJGDKU1Og7sMXPNm1BYyM1DOJmTZ4YeN/Nwxv+8XJsUA==", + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", "dev": true, "requires": { - "cliui": "^5.0.0", - "find-up": "^3.0.0", + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", "get-caller-file": "^2.0.1", "require-directory": "^2.1.1", "require-main-filename": "^2.0.0", "set-blocking": "^2.0.0", - "string-width": "^3.0.0", + "string-width": "^4.2.0", "which-module": "^2.0.0", "y18n": "^4.0.0", - "yargs-parser": "^13.1.1" + "yargs-parser": "^18.1.2" }, "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==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", "dev": true }, - "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "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==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", "dev": true, "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" } }, "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", "dev": true, "requires": { - "ansi-regex": "^4.1.0" + "ansi-regex": "^5.0.0" } } } }, "yargs-parser": { - "version": "13.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.1.tgz", - "integrity": "sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ==", + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", "dev": true, "requires": { "camelcase": "^5.0.0", diff --git a/package.json b/package.json index 2859341e..6d8d8aea 100644 --- a/package.json +++ b/package.json @@ -68,6 +68,8 @@ }, "devDependencies": { "@babel/core": "^7.6.2", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.10.4", + "@babel/plugin-proposal-optional-chaining": "^7.11.0", "@shlinkio/eslint-config-js-coding-standard": "~1.1.0", "@stryker-mutator/core": "^3.2.4", "@stryker-mutator/javascript-mutator": "^3.2.4", @@ -84,7 +86,7 @@ "adm-zip": "^0.4.13", "autoprefixer": "^9.6.3", "babel-core": "7.0.0-bridge.0", - "babel-jest": "^24.9.0", + "babel-jest": "^26.3.0", "babel-loader": "^8.0.6", "babel-plugin-named-asset-import": "^0.3.4", "babel-preset-react-app": "^9.0.2", @@ -104,9 +106,9 @@ "fs-extra": "^8.1.0", "html-webpack-plugin": "^4.0.0-beta.8", "identity-obj-proxy": "^3.0.0", - "jest": "^24.9.0", + "jest": "^26.4.1", "jest-pnp-resolver": "^1.2.1", - "jest-resolve": "^24.9.0", + "jest-resolve": "^26.4.0", "mini-css-extract-plugin": "^0.8.0", "node-sass": "^4.12.0", "object-assign": "^4.1.1", @@ -134,6 +136,7 @@ "sw-precache-webpack-plugin": "^0.11.5", "terser-webpack-plugin": "^2.1.2", "ts-jest": "^26.0.0", + "ts-mockery": "^1.2.0", "typescript": "^3.9.3", "url-loader": "^2.2.0", "webpack": "^4.41.0", @@ -145,6 +148,10 @@ "babel": { "presets": [ "react-app" + ], + "plugins": [ + "@babel/plugin-proposal-optional-chaining", + "@babel/plugin-proposal-nullish-coalescing-operator" ] }, "browserslist": [ diff --git a/src/servers/data/index.ts b/src/servers/data/index.ts new file mode 100644 index 00000000..5d699c39 --- /dev/null +++ b/src/servers/data/index.ts @@ -0,0 +1,15 @@ +export interface RegularServer { + id: string; + name: string; + url: string; + apiKey: string; + version?: string; + printableVersion?: string; + serverNotReachable?: boolean; +} + +interface NotFoundServer { + serverNotFound: true; +} + +export type Server = RegularServer | NotFoundServer; diff --git a/src/servers/services/ServersImporter.js b/src/servers/services/ServersImporter.js deleted file mode 100644 index 35ce8ef9..00000000 --- a/src/servers/services/ServersImporter.js +++ /dev/null @@ -1,23 +0,0 @@ -export default class ServersImporter { - constructor(csvjson) { - this.csvjson = csvjson; - } - - importServersFromFile = (file) => { - if (!file || file.type !== 'text/csv') { - return Promise.reject('No file provided or file is not a CSV'); - } - - const reader = new FileReader(); - - return new Promise((resolve) => { - reader.addEventListener('loadend', (e) => { - const content = e.target.result; - const servers = this.csvjson.toObject(content); - - resolve(servers); - }); - reader.readAsText(file); - }); - }; -} diff --git a/src/servers/services/ServersImporter.ts b/src/servers/services/ServersImporter.ts new file mode 100644 index 00000000..d65be66b --- /dev/null +++ b/src/servers/services/ServersImporter.ts @@ -0,0 +1,26 @@ +import { CsvJson } from 'csvjson'; +import { RegularServer } from '../data'; + +const CSV_MIME_TYPE = 'text/csv'; + +export default class ServersImporter { + public constructor(private readonly csvjson: CsvJson, private readonly fileReaderFactory: () => FileReader) {} + + public importServersFromFile = async (file?: File): Promise => { + if (!file || file.type !== CSV_MIME_TYPE) { + throw new Error('No file provided or file is not a CSV'); + } + + const reader = this.fileReaderFactory(); + + return new Promise((resolve) => { + reader.addEventListener('loadend', (e: ProgressEvent) => { + const content = e.target?.result?.toString() ?? ''; + const servers = this.csvjson.toObject(content); + + resolve(servers); + }); + reader.readAsText(file); + }); + }; +} diff --git a/src/servers/services/provideServices.js b/src/servers/services/provideServices.js index 3d941eec..7b8e7d99 100644 --- a/src/servers/services/provideServices.js +++ b/src/servers/services/provideServices.js @@ -41,7 +41,8 @@ const provideServices = (bottle, connect, withRouter) => { // Services bottle.constant('csvjson', csvjson); - bottle.service('ServersImporter', ServersImporter, 'csvjson'); + bottle.constant('fileReaderFactory', () => new FileReader()); + bottle.service('ServersImporter', ServersImporter, 'csvjson', 'fileReaderFactory'); bottle.service('ServersExporter', ServersExporter, 'Storage', 'window', 'csvjson'); // Actions diff --git a/test/servers/services/ServersImporter.test.js b/test/servers/services/ServersImporter.test.js deleted file mode 100644 index 217cc0bf..00000000 --- a/test/servers/services/ServersImporter.test.js +++ /dev/null @@ -1,46 +0,0 @@ -import ServersImporter from '../../../src/servers/services/ServersImporter'; - -describe('ServersImporter', () => { - const servers = [{ name: 'foo' }, { name: 'bar' }]; - const csvjsonMock = { - toObject: jest.fn(() => servers), - }; - const importer = new ServersImporter(csvjsonMock); - - beforeEach(() => csvjsonMock.toObject.mockClear()); - - describe('importServersFromFile', () => { - it('rejects with error if no file was provided', async () => { - try { - await importer.importServersFromFile(); - } catch (e) { - expect(e).toEqual('No file provided or file is not a CSV'); - } - }); - - it('rejects with error if provided file is not a CSV', async () => { - try { - await importer.importServersFromFile({ type: 'text/html' }); - } catch (e) { - expect(e).toEqual('No file provided or file is not a CSV'); - } - }); - - it('reads file when a CSV is provided', async () => { - const readAsText = jest.fn(() => ''); - - global.FileReader = class FileReader { - constructor() { - this.readAsText = readAsText; - this.addEventListener = (eventName, listener) => - listener({ target: { result: '' } }); - } - }; - - await importer.importServersFromFile({ type: 'text/csv' }); - - expect(readAsText).toHaveBeenCalledTimes(1); - expect(csvjsonMock.toObject).toHaveBeenCalledTimes(1); - }); - }); -}); diff --git a/test/servers/services/ServersImporter.test.ts b/test/servers/services/ServersImporter.test.ts new file mode 100644 index 00000000..c9d0d466 --- /dev/null +++ b/test/servers/services/ServersImporter.test.ts @@ -0,0 +1,39 @@ +import { Mock } from 'ts-mockery'; +import ServersImporter from '../../../src/servers/services/ServersImporter'; +import { RegularServer } from '../../../src/servers/data'; + +describe('ServersImporter', () => { + const servers: RegularServer[] = [ Mock.all(), Mock.all() ]; + const csvjsonMock = { + toObject: jest.fn().mockReturnValue(servers), + }; + const readAsText = jest.fn(); + const fileReaderMock = Mock.of({ + readAsText, + addEventListener: (_eventName: string, listener: Function) => listener({ target: { result: '' } }), + }); + const importer = new ServersImporter(csvjsonMock, () => fileReaderMock); + + beforeEach(jest.clearAllMocks); + + describe('importServersFromFile', () => { + it('rejects with error if no file was provided', async () => { + await expect(importer.importServersFromFile()).rejects.toEqual( + new Error('No file provided or file is not a CSV'), + ); + }); + + it('rejects with error if provided file is not a CSV', async () => { + await expect(importer.importServersFromFile(Mock.of({ type: 'text/html' }))).rejects.toEqual( + new Error('No file provided or file is not a CSV'), + ); + }); + + it('reads file when a CSV is provided', async () => { + await importer.importServersFromFile(Mock.of({ type: 'text/csv' })); + + expect(readAsText).toHaveBeenCalledTimes(1); + expect(csvjsonMock.toObject).toHaveBeenCalledTimes(1); + }); + }); +}); From 39663ba936910b23d249169808177cbc989a666f Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 22 Aug 2020 11:20:27 +0200 Subject: [PATCH 07/59] Migrated to TS first component where some dependency was being injected --- package-lock.json | 10 ++++ package.json | 1 + src/servers/helpers/ImportServersBtn.js | 48 ------------------- src/servers/helpers/ImportServersBtn.tsx | 46 ++++++++++++++++++ src/servers/services/ServersImporter.ts | 2 +- ...sBtn.test.js => ImportServersBtn.test.tsx} | 20 ++++---- 6 files changed, 68 insertions(+), 59 deletions(-) delete mode 100644 src/servers/helpers/ImportServersBtn.js create mode 100644 src/servers/helpers/ImportServersBtn.tsx rename test/servers/helpers/{ImportServersBtn.test.js => ImportServersBtn.test.tsx} (78%) diff --git a/package-lock.json b/package-lock.json index 3c9c4b58..f1535a30 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3410,6 +3410,16 @@ "@types/react-router": "*" } }, + "@types/reactstrap": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/@types/reactstrap/-/reactstrap-8.5.1.tgz", + "integrity": "sha512-oEedcEGoX8EqDymsjrjzTnmaf3FuDY9qKLZMA9cH1ZkkqBc2V4i2sJ6ssXEod+GHQ5XH2r52uvbMkjEkjEZHDQ==", + "dev": true, + "requires": { + "@types/react": "*", + "popper.js": "^1.14.1" + } + }, "@types/stack-utils": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz", diff --git a/package.json b/package.json index 6d8d8aea..e84df9f7 100644 --- a/package.json +++ b/package.json @@ -83,6 +83,7 @@ "@types/react-dom": "^16.9.8", "@types/react-redux": "^7.1.9", "@types/react-router-dom": "^5.1.5", + "@types/reactstrap": "^8.5.1", "adm-zip": "^0.4.13", "autoprefixer": "^9.6.3", "babel-core": "7.0.0-bridge.0", diff --git a/src/servers/helpers/ImportServersBtn.js b/src/servers/helpers/ImportServersBtn.js deleted file mode 100644 index 7bde4df7..00000000 --- a/src/servers/helpers/ImportServersBtn.js +++ /dev/null @@ -1,48 +0,0 @@ -import React, { useRef } from 'react'; -import { UncontrolledTooltip } from 'reactstrap'; -import PropTypes from 'prop-types'; - -const propTypes = { - onImport: PropTypes.func, - createServers: PropTypes.func, - fileRef: PropTypes.oneOfType([ PropTypes.object, PropTypes.node ]), -}; - -// FIXME Replace with typescript: (ServersImporter) -const ImportServersBtn = ({ importServersFromFile }) => { - const ImportServersBtnComp = ({ createServers, fileRef, onImport = () => '' }) => { - const ref = fileRef || useRef(); - const onChange = ({ target }) => - importServersFromFile(target.files[0]) - .then(createServers) - .then(onImport) - .then(() => { - // Reset input after processing file - target.value = null; - }); - - return ( - - - - You can create servers by importing a CSV file with columns name, apiKey and url. - - - - - ); - }; - - ImportServersBtnComp.propTypes = propTypes; - - return ImportServersBtnComp; -}; - -export default ImportServersBtn; diff --git a/src/servers/helpers/ImportServersBtn.tsx b/src/servers/helpers/ImportServersBtn.tsx new file mode 100644 index 00000000..e77d69d2 --- /dev/null +++ b/src/servers/helpers/ImportServersBtn.tsx @@ -0,0 +1,46 @@ +import React, { useRef, RefObject, ChangeEvent, MutableRefObject } from 'react'; +import { UncontrolledTooltip } from 'reactstrap'; +import ServersImporter from '../services/ServersImporter'; +import { Server } from '../data'; + +type Ref = RefObject | MutableRefObject; + +interface ImportServersBtnProps { + createServers: (servers: Server[]) => void; + fileRef: Ref; + onImport?: () => void; +} + +const ImportServersBtn = ({ importServersFromFile }: ServersImporter) => ( + { createServers, fileRef, onImport = () => {} }: ImportServersBtnProps, +) => { + const ref = fileRef ?? useRef(); + const onChange = async ({ target }: ChangeEvent) => + importServersFromFile(target.files?.[0]) + .then(createServers) + .then(onImport) + .then(() => { + // Reset input after processing file + (target as { value: string | null }).value = null; + }); + + return ( + + + + You can create servers by importing a CSV file with columns name, apiKey and url. + + + + + ); +}; + +export default ImportServersBtn; diff --git a/src/servers/services/ServersImporter.ts b/src/servers/services/ServersImporter.ts index d65be66b..17ab38e3 100644 --- a/src/servers/services/ServersImporter.ts +++ b/src/servers/services/ServersImporter.ts @@ -6,7 +6,7 @@ const CSV_MIME_TYPE = 'text/csv'; export default class ServersImporter { public constructor(private readonly csvjson: CsvJson, private readonly fileReaderFactory: () => FileReader) {} - public importServersFromFile = async (file?: File): Promise => { + public importServersFromFile = async (file?: File | null): Promise => { if (!file || file.type !== CSV_MIME_TYPE) { throw new Error('No file provided or file is not a CSV'); } diff --git a/test/servers/helpers/ImportServersBtn.test.js b/test/servers/helpers/ImportServersBtn.test.tsx similarity index 78% rename from test/servers/helpers/ImportServersBtn.test.js rename to test/servers/helpers/ImportServersBtn.test.tsx index fc203d28..7f373fbb 100644 --- a/test/servers/helpers/ImportServersBtn.test.js +++ b/test/servers/helpers/ImportServersBtn.test.tsx @@ -1,24 +1,24 @@ import React from 'react'; -import { shallow } from 'enzyme'; +import { shallow, ShallowWrapper } from 'enzyme'; import { UncontrolledTooltip } from 'reactstrap'; +import { Mock } from 'ts-mockery'; import importServersBtnConstruct from '../../../src/servers/helpers/ImportServersBtn'; +import ServersImporter from '../../../src/servers/services/ServersImporter'; describe('', () => { - let wrapper; + let wrapper: ShallowWrapper; const onImportMock = jest.fn(); const createServersMock = jest.fn(); - const serversImporterMock = { + const serversImporterMock = Mock.of({ importServersFromFile: jest.fn().mockResolvedValue([]), - }; + }); + const click = jest.fn(); const fileRef = { - current: { click: jest.fn() }, + current: Mock.of({ click }), }; beforeEach(() => { - onImportMock.mockReset(); - createServersMock.mockReset(); - serversImporterMock.importServersFromFile.mockClear(); - fileRef.current.click.mockReset(); + jest.clearAllMocks(); const ImportServersBtn = importServersBtnConstruct(serversImporterMock); @@ -39,7 +39,7 @@ describe('', () => { btn.simulate('click'); - expect(fileRef.current.click).toHaveBeenCalledTimes(1); + expect(click).toHaveBeenCalledTimes(1); }); it('imports servers when file input changes', (done) => { From 2db85c27835d38cf3fed17358990b91117d96ced Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 22 Aug 2020 13:41:54 +0200 Subject: [PATCH 08/59] Migrated to typescript first component getting another component with props injected --- package-lock.json | 6 ++ package.json | 1 + src/servers/CreateServer.js | 57 ------------------- src/servers/CreateServer.tsx | 52 +++++++++++++++++ src/servers/data/index.ts | 9 ++- src/servers/helpers/ImportServersBtn.tsx | 11 ++-- ...teServer.test.js => CreateServer.test.tsx} | 17 +++--- 7 files changed, 81 insertions(+), 72 deletions(-) delete mode 100644 src/servers/CreateServer.js create mode 100644 src/servers/CreateServer.tsx rename test/servers/{CreateServer.test.js => CreateServer.test.tsx} (79%) diff --git a/package-lock.json b/package-lock.json index f1535a30..0cb292a7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3432,6 +3432,12 @@ "integrity": "sha512-FvUupuM3rlRsRtCN+fDudtmytGO6iHJuuRKS1Ss0pG5z8oX0diNEw94UEL7hgDbpN94rgaK5R7sWm6RrSkZuAQ==", "dev": true }, + "@types/uuid": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.0.tgz", + "integrity": "sha512-eQ9qFW/fhfGJF8WKHGEHZEyVWfZxrT+6CLIJGBcZPfxUh/+BnEj+UCGYMlr9qZuX/2AltsvwrGqp0LhEW8D0zQ==", + "dev": true + }, "@types/vfile": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/@types/vfile/-/vfile-3.0.2.tgz", diff --git a/package.json b/package.json index e84df9f7..d7fc43da 100644 --- a/package.json +++ b/package.json @@ -84,6 +84,7 @@ "@types/react-redux": "^7.1.9", "@types/react-router-dom": "^5.1.5", "@types/reactstrap": "^8.5.1", + "@types/uuid": "^8.3.0", "adm-zip": "^0.4.13", "autoprefixer": "^9.6.3", "babel-core": "7.0.0-bridge.0", diff --git a/src/servers/CreateServer.js b/src/servers/CreateServer.js deleted file mode 100644 index 91e11862..00000000 --- a/src/servers/CreateServer.js +++ /dev/null @@ -1,57 +0,0 @@ -import React, { useEffect } from 'react'; -import { v4 as uuid } from 'uuid'; -import PropTypes from 'prop-types'; -import NoMenuLayout from '../common/NoMenuLayout'; -import { ServerForm } from './helpers/ServerForm'; -import './CreateServer.scss'; - -const SHOW_IMPORT_MSG_TIME = 4000; -const propTypes = { - createServer: PropTypes.func, - history: PropTypes.shape({ - push: PropTypes.func, - }), - resetSelectedServer: PropTypes.func, -}; - -const CreateServer = (ImportServersBtn, useStateFlagTimeout) => { - const CreateServerComp = ({ createServer, history: { push }, resetSelectedServer }) => { - const [ serversImported, setServersImported ] = useStateFlagTimeout(false, SHOW_IMPORT_MSG_TIME); - const handleSubmit = (serverData) => { - const id = uuid(); - const server = { id, ...serverData }; - - createServer(server); - push(`/server/${id}/list-short-urls/1`); - }; - - useEffect(() => { - resetSelectedServer(); - }, []); - - return ( - - - - - - - {serversImported && ( -
-
-
- Servers properly imported. You can now select one from the list :) -
-
-
- )} -
- ); - }; - - CreateServerComp.propTypes = propTypes; - - return CreateServerComp; -}; - -export default CreateServer; diff --git a/src/servers/CreateServer.tsx b/src/servers/CreateServer.tsx new file mode 100644 index 00000000..2dff13d8 --- /dev/null +++ b/src/servers/CreateServer.tsx @@ -0,0 +1,52 @@ +import React, { FC, useEffect } from 'react'; +import { v4 as uuid } from 'uuid'; +import { RouterProps } from 'react-router'; +import NoMenuLayout from '../common/NoMenuLayout'; +import { ServerForm } from './helpers/ServerForm'; +import { ImportServersBtnProps } from './helpers/ImportServersBtn'; +import { NewServerData, RegularServer } from './data'; +import './CreateServer.scss'; + +const SHOW_IMPORT_MSG_TIME = 4000; + +interface CreateServerProps extends RouterProps { + createServer: (server: RegularServer) => void; + resetSelectedServer: Function; +} + +const CreateServer = (ImportServersBtn: FC, useStateFlagTimeout: Function) => ( + { createServer, history: { push }, resetSelectedServer }: CreateServerProps, +) => { + const [ serversImported, setServersImported ] = useStateFlagTimeout(false, SHOW_IMPORT_MSG_TIME); + const handleSubmit = (serverData: NewServerData) => { + const id = uuid(); + + createServer({ ...serverData, id }); + push(`/server/${id}/list-short-urls/1`); + }; + + useEffect(() => { + resetSelectedServer(); + }, []); + + return ( + + + + + + + {serversImported && ( +
+
+
+ Servers properly imported. You can now select one from the list :) +
+
+
+ )} +
+ ); +}; + +export default CreateServer; diff --git a/src/servers/data/index.ts b/src/servers/data/index.ts index 5d699c39..448ab6c6 100644 --- a/src/servers/data/index.ts +++ b/src/servers/data/index.ts @@ -1,11 +1,14 @@ -export interface RegularServer { - id: string; +export interface NewServerData { name: string; url: string; apiKey: string; +} + +export interface RegularServer extends NewServerData { + id: string; version?: string; printableVersion?: string; - serverNotReachable?: boolean; + serverNotReachable?: true; } interface NotFoundServer { diff --git a/src/servers/helpers/ImportServersBtn.tsx b/src/servers/helpers/ImportServersBtn.tsx index e77d69d2..53e448b7 100644 --- a/src/servers/helpers/ImportServersBtn.tsx +++ b/src/servers/helpers/ImportServersBtn.tsx @@ -5,14 +5,17 @@ import { Server } from '../data'; type Ref = RefObject | MutableRefObject; -interface ImportServersBtnProps { - createServers: (servers: Server[]) => void; - fileRef: Ref; +export interface ImportServersBtnProps { onImport?: () => void; } +interface ImportServersBtnConnectProps { + createServers: (servers: Server[]) => void; + fileRef: Ref; +} + const ImportServersBtn = ({ importServersFromFile }: ServersImporter) => ( - { createServers, fileRef, onImport = () => {} }: ImportServersBtnProps, + { createServers, fileRef, onImport = () => {} }: ImportServersBtnConnectProps & ImportServersBtnProps, ) => { const ref = fileRef ?? useRef(); const onChange = async ({ target }: ChangeEvent) => diff --git a/test/servers/CreateServer.test.js b/test/servers/CreateServer.test.tsx similarity index 79% rename from test/servers/CreateServer.test.js rename to test/servers/CreateServer.test.tsx index 98863b2f..4d7b46ed 100644 --- a/test/servers/CreateServer.test.js +++ b/test/servers/CreateServer.test.tsx @@ -1,16 +1,17 @@ import React from 'react'; -import { shallow } from 'enzyme'; +import { shallow, ShallowWrapper } from 'enzyme'; import { identity } from 'ramda'; +import { Mock } from 'ts-mockery'; +import { History } from 'history'; import createServerConstruct from '../../src/servers/CreateServer'; import { ServerForm } from '../../src/servers/helpers/ServerForm'; describe('', () => { - let wrapper; - const ImportServersBtn = () => ''; + let wrapper: ShallowWrapper; + const ImportServersBtn = () => null; const createServerMock = jest.fn(); - const historyMock = { - push: jest.fn(), - }; + const push = jest.fn(); + const historyMock = Mock.of({ push }); const createWrapper = (serversImported = false) => { const CreateServer = createServerConstruct(ImportServersBtn, () => [ serversImported, () => '' ]); @@ -23,7 +24,7 @@ describe('', () => { afterEach(() => { jest.resetAllMocks(); - wrapper && wrapper.unmount(); + wrapper?.unmount(); }); it('renders components', () => { @@ -46,6 +47,6 @@ describe('', () => { form.simulate('submit', {}); expect(createServerMock).toHaveBeenCalledTimes(1); - expect(historyMock.push).toHaveBeenCalledTimes(1); + expect(push).toHaveBeenCalledTimes(1); }); }); From 7c67fa4149e53a233db635d8701fe81416cb62fd Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 22 Aug 2020 17:58:44 +0200 Subject: [PATCH 09/59] Migrate CreateServer component to Typescript --- package-lock.json | 6 +++++ package.json | 1 + src/servers/CreateServer.tsx | 33 ++++++++++++++++-------- src/servers/helpers/ImportServersBtn.tsx | 15 +++++++---- src/utils/helpers/hooks.js | 26 ------------------- src/utils/helpers/hooks.ts | 32 +++++++++++++++++++++++ test/servers/CreateServer.test.tsx | 21 ++++++++++++--- 7 files changed, 88 insertions(+), 46 deletions(-) delete mode 100644 src/utils/helpers/hooks.js create mode 100644 src/utils/helpers/hooks.ts diff --git a/package-lock.json b/package-lock.json index 0cb292a7..26e614cf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3056,6 +3056,12 @@ "@types/node": "*" } }, + "@types/classnames": { + "version": "2.2.10", + "resolved": "https://registry.npmjs.org/@types/classnames/-/classnames-2.2.10.tgz", + "integrity": "sha512-1UzDldn9GfYYEsWWnn/P4wkTlkZDH7lDb0wBMGbtIQc9zXEQq7FlKBdZUn6OBqD8sKZZ2RQO2mAjGpXiDGoRmQ==", + "dev": true + }, "@types/color-name": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", diff --git a/package.json b/package.json index d7fc43da..b21786c2 100644 --- a/package.json +++ b/package.json @@ -75,6 +75,7 @@ "@stryker-mutator/javascript-mutator": "^3.2.4", "@stryker-mutator/jest-runner": "^3.2.4", "@svgr/webpack": "^4.3.3", + "@types/classnames": "^2.2.10", "@types/enzyme": "^3.10.5", "@types/jest": "^26.0.10", "@types/moment": "^2.13.0", diff --git a/src/servers/CreateServer.tsx b/src/servers/CreateServer.tsx index 2dff13d8..2da3e601 100644 --- a/src/servers/CreateServer.tsx +++ b/src/servers/CreateServer.tsx @@ -1,7 +1,9 @@ import React, { FC, useEffect } from 'react'; import { v4 as uuid } from 'uuid'; import { RouterProps } from 'react-router'; +import classNames from 'classnames'; import NoMenuLayout from '../common/NoMenuLayout'; +import { StateFlagTimeout } from '../utils/helpers/hooks'; import { ServerForm } from './helpers/ServerForm'; import { ImportServersBtnProps } from './helpers/ImportServersBtn'; import { NewServerData, RegularServer } from './data'; @@ -14,10 +16,26 @@ interface CreateServerProps extends RouterProps { resetSelectedServer: Function; } -const CreateServer = (ImportServersBtn: FC, useStateFlagTimeout: Function) => ( +const Result: FC<{ type: 'success' | 'error' }> = ({ children, type }) => ( +
+
+
+ {children} +
+
+
+); + +const CreateServer = (ImportServersBtn: FC, useStateFlagTimeout: StateFlagTimeout) => ( { createServer, history: { push }, resetSelectedServer }: CreateServerProps, ) => { const [ serversImported, setServersImported ] = useStateFlagTimeout(false, SHOW_IMPORT_MSG_TIME); + const [ errorImporting, setErrorImporting ] = useStateFlagTimeout(false, SHOW_IMPORT_MSG_TIME); const handleSubmit = (serverData: NewServerData) => { const id = uuid(); @@ -32,19 +50,12 @@ const CreateServer = (ImportServersBtn: FC, useStateFlagT return ( - + - {serversImported && ( -
-
-
- Servers properly imported. You can now select one from the list :) -
-
-
- )} + {serversImported && Servers properly imported. You can now select one from the list :)} + {errorImporting && The servers could not be imported. Make sure the format is correct.}
); }; diff --git a/src/servers/helpers/ImportServersBtn.tsx b/src/servers/helpers/ImportServersBtn.tsx index 53e448b7..86ba43d6 100644 --- a/src/servers/helpers/ImportServersBtn.tsx +++ b/src/servers/helpers/ImportServersBtn.tsx @@ -7,16 +7,20 @@ type Ref = RefObject | MutableRefObject; export interface ImportServersBtnProps { onImport?: () => void; + onImportError?: () => void; } -interface ImportServersBtnConnectProps { +interface ImportServersBtnConnectProps extends ImportServersBtnProps { createServers: (servers: Server[]) => void; fileRef: Ref; } -const ImportServersBtn = ({ importServersFromFile }: ServersImporter) => ( - { createServers, fileRef, onImport = () => {} }: ImportServersBtnConnectProps & ImportServersBtnProps, -) => { +const ImportServersBtn = ({ importServersFromFile }: ServersImporter) => ({ + createServers, + fileRef, + onImport = () => {}, + onImportError = () => {}, +}: ImportServersBtnConnectProps) => { const ref = fileRef ?? useRef(); const onChange = async ({ target }: ChangeEvent) => importServersFromFile(target.files?.[0]) @@ -25,7 +29,8 @@ const ImportServersBtn = ({ importServersFromFile }: ServersImporter) => ( .then(() => { // Reset input after processing file (target as { value: string | null }).value = null; - }); + }) + .catch(onImportError); return ( diff --git a/src/utils/helpers/hooks.js b/src/utils/helpers/hooks.js deleted file mode 100644 index d0852714..00000000 --- a/src/utils/helpers/hooks.js +++ /dev/null @@ -1,26 +0,0 @@ -import { useState, useRef } from 'react'; - -const DEFAULT_DELAY = 2000; - -export const useStateFlagTimeout = (setTimeout, clearTimeout) => (initialValue = false, delay = DEFAULT_DELAY) => { - const [ flag, setFlag ] = useState(initialValue); - const timeout = useRef(undefined); - const callback = () => { - setFlag(!initialValue); - - if (timeout.current) { - clearTimeout(timeout.current); - } - - timeout.current = setTimeout(() => setFlag(initialValue), delay); - }; - - return [ flag, callback ]; -}; - -// Return [ flag, toggle, enable, disable ] -export const useToggle = (initialValue = false) => { - const [ flag, setFlag ] = useState(initialValue); - - return [ flag, () => setFlag(!flag), () => setFlag(true), () => setFlag(false) ]; -}; diff --git a/src/utils/helpers/hooks.ts b/src/utils/helpers/hooks.ts new file mode 100644 index 00000000..6e2c986c --- /dev/null +++ b/src/utils/helpers/hooks.ts @@ -0,0 +1,32 @@ +import { useState, useRef } from 'react'; + +const DEFAULT_DELAY = 2000; + +export type StateFlagTimeout = (initialValue?: boolean, delay?: number) => [ boolean, () => void ]; + +export const useStateFlagTimeout = ( + setTimeout: (callback: Function, timeout: number) => number, + clearTimeout: (timer: number) => void, +): StateFlagTimeout => (initialValue = false, delay = DEFAULT_DELAY) => { + const [ flag, setFlag ] = useState(initialValue); + const timeout = useRef(undefined); + const callback = () => { + setFlag(!initialValue); + + if (timeout.current) { + clearTimeout(timeout.current); + } + + timeout.current = setTimeout(() => setFlag(initialValue), delay); + }; + + return [ flag, callback ]; +}; + +type ToggleResult = [ boolean, (flag: boolean) => void, () => void, () => void ]; + +export const useToggle = (initialValue = false): ToggleResult => { + const [ flag, setFlag ] = useState(initialValue); + + return [ flag, () => setFlag(!flag), () => setFlag(true), () => setFlag(false) ]; +}; diff --git a/test/servers/CreateServer.test.tsx b/test/servers/CreateServer.test.tsx index 4d7b46ed..a1779354 100644 --- a/test/servers/CreateServer.test.tsx +++ b/test/servers/CreateServer.test.tsx @@ -12,8 +12,11 @@ describe('', () => { const createServerMock = jest.fn(); const push = jest.fn(); const historyMock = Mock.of({ push }); - const createWrapper = (serversImported = false) => { - const CreateServer = createServerConstruct(ImportServersBtn, () => [ serversImported, () => '' ]); + const createWrapper = (serversImported = false, importFailed = false) => { + const useStateFlagTimeout = jest.fn() + .mockReturnValueOnce([ serversImported, () => '' ]) + .mockReturnValueOnce([ importFailed, () => '' ]); + const CreateServer = createServerConstruct(ImportServersBtn, useStateFlagTimeout); wrapper = shallow( , @@ -31,13 +34,23 @@ describe('', () => { const wrapper = createWrapper(); expect(wrapper.find(ServerForm)).toHaveLength(1); - expect(wrapper.find('.create-server__import-success-msg')).toHaveLength(0); + expect(wrapper.find('Result')).toHaveLength(0); }); it('shows success message when imported is true', () => { const wrapper = createWrapper(true); + const result = wrapper.find('Result'); - expect(wrapper.find('.create-server__import-success-msg')).toHaveLength(1); + expect(result).toHaveLength(1); + expect(result.prop('type')).toEqual('success'); + }); + + it('shows error message when import failed', () => { + const wrapper = createWrapper(false, true); + const result = wrapper.find('Result'); + + expect(result).toHaveLength(1); + expect(result.prop('type')).toEqual('error'); }); it('creates server and redirects to it when form is submitted', () => { From 62df46d648b5c7114f5bf922a029045ec1ec89f2 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 22 Aug 2020 18:32:48 +0200 Subject: [PATCH 10/59] Refactored many helpers to Typescript --- package-lock.json | 15 +++++++++ package.json | 1 + src/index.tsx | 2 +- src/utils/helpers/date.ts | 2 +- src/utils/helpers/leaflet.js | 16 ++++++++++ src/utils/helpers/numbers.js | 8 ----- src/utils/helpers/numbers.ts | 7 ++++ src/utils/helpers/pagination.js | 27 ---------------- src/utils/helpers/pagination.ts | 29 +++++++++++++++++ src/utils/helpers/version.js | 27 ---------------- src/utils/helpers/version.ts | 32 +++++++++++++++++++ src/utils/utils.js | 32 ------------------- src/utils/utils.ts | 23 +++++++++++++ .../helpers/{date.test.js => date.test.ts} | 0 test/utils/helpers/leaflet.test.js | 19 +++++++++++ .../{numbers.test.js => numbers.test.ts} | 0 .../{version.test.js => version.test.ts} | 0 test/utils/utils.test.js | 18 +---------- 18 files changed, 145 insertions(+), 113 deletions(-) create mode 100644 src/utils/helpers/leaflet.js delete mode 100644 src/utils/helpers/numbers.js create mode 100644 src/utils/helpers/numbers.ts delete mode 100644 src/utils/helpers/pagination.js create mode 100644 src/utils/helpers/pagination.ts delete mode 100644 src/utils/helpers/version.js create mode 100644 src/utils/helpers/version.ts delete mode 100644 src/utils/utils.js create mode 100644 src/utils/utils.ts rename test/utils/helpers/{date.test.js => date.test.ts} (100%) create mode 100644 test/utils/helpers/leaflet.test.js rename test/utils/helpers/{numbers.test.js => numbers.test.ts} (100%) rename test/utils/helpers/{version.test.js => version.test.ts} (100%) diff --git a/package-lock.json b/package-lock.json index 26e614cf..e67e9e4e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3090,6 +3090,12 @@ "integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==", "dev": true }, + "@types/geojson": { + "version": "7946.0.7", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.7.tgz", + "integrity": "sha512-wE2v81i4C4Ol09RtsWFAqg3BUitWbHSpSlIo+bNdsCJijO9sjme+zm+73ZMCa/qMC8UEERxzGbvmr1cffo2SiQ==", + "dev": true + }, "@types/glob": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.1.tgz", @@ -3299,6 +3305,15 @@ "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", "dev": true }, + "@types/leaflet": { + "version": "1.5.17", + "resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.5.17.tgz", + "integrity": "sha512-2XYq9k6kNjhNI7PaTz8Rdxcc8Vzwu97OaS9CtcrTxnTSxFUGwjlGjTDvhTLJU+JRSfZ4lBwGcl0SjZHALdVr6g==", + "dev": true, + "requires": { + "@types/geojson": "*" + } + }, "@types/minimatch": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", diff --git a/package.json b/package.json index b21786c2..8454ae72 100644 --- a/package.json +++ b/package.json @@ -78,6 +78,7 @@ "@types/classnames": "^2.2.10", "@types/enzyme": "^3.10.5", "@types/jest": "^26.0.10", + "@types/leaflet": "^1.5.17", "@types/moment": "^2.13.0", "@types/ramda": "^0.27.14", "@types/react": "^16.9.46", diff --git a/src/index.tsx b/src/index.tsx index 822c2988..836dfa75 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -6,7 +6,7 @@ import { homepage } from '../package.json'; import registerServiceWorker from './registerServiceWorker'; import container from './container'; import store from './container/store'; -import { fixLeafletIcons } from './utils/utils'; +import { fixLeafletIcons } from './utils/helpers/leaflet'; import 'bootstrap/dist/css/bootstrap.min.css'; import 'react-datepicker/dist/react-datepicker.css'; import 'leaflet/dist/leaflet.css'; diff --git a/src/utils/helpers/date.ts b/src/utils/helpers/date.ts index f8da9a77..c4a44b3c 100644 --- a/src/utils/helpers/date.ts +++ b/src/utils/helpers/date.ts @@ -10,4 +10,4 @@ const formatDateFromFormat = (date?: NullableDate, format?: string): NullableDat export const formatDate = (format = 'YYYY-MM-DD') => (date?: NullableDate) => formatDateFromFormat(date, format); -export const formatIsoDate = (date: NullableDate) => formatDateFromFormat(date, undefined); +export const formatIsoDate = (date?: NullableDate) => formatDateFromFormat(date, undefined); diff --git a/src/utils/helpers/leaflet.js b/src/utils/helpers/leaflet.js new file mode 100644 index 00000000..8b9ac82f --- /dev/null +++ b/src/utils/helpers/leaflet.js @@ -0,0 +1,16 @@ +// TODO Migrate this file to Typescript + +import L from 'leaflet'; +import marker2x from 'leaflet/dist/images/marker-icon-2x.png'; +import marker from 'leaflet/dist/images/marker-icon.png'; +import markerShadow from 'leaflet/dist/images/marker-shadow.png'; + +export const fixLeafletIcons = () => { + delete L.Icon.Default.prototype._getIconUrl; + + L.Icon.Default.mergeOptions({ + iconRetinaUrl: marker2x, + iconUrl: marker, + shadowUrl: markerShadow, + }); +}; diff --git a/src/utils/helpers/numbers.js b/src/utils/helpers/numbers.js deleted file mode 100644 index b9e0757f..00000000 --- a/src/utils/helpers/numbers.js +++ /dev/null @@ -1,8 +0,0 @@ -const TEN_ROUNDING_NUMBER = 10; -const { ceil } = Math; - -const formatter = new Intl.NumberFormat('en-US'); - -export const prettify = (number) => formatter.format(number); - -export const roundTen = (number) => ceil(number / TEN_ROUNDING_NUMBER) * TEN_ROUNDING_NUMBER; diff --git a/src/utils/helpers/numbers.ts b/src/utils/helpers/numbers.ts new file mode 100644 index 00000000..559184bf --- /dev/null +++ b/src/utils/helpers/numbers.ts @@ -0,0 +1,7 @@ +const TEN_ROUNDING_NUMBER = 10; +const { ceil } = Math; +const formatter = new Intl.NumberFormat('en-US'); + +export const prettify = (number: number) => formatter.format(number); + +export const roundTen = (number: number) => ceil(number / TEN_ROUNDING_NUMBER) * TEN_ROUNDING_NUMBER; diff --git a/src/utils/helpers/pagination.js b/src/utils/helpers/pagination.js deleted file mode 100644 index be66f750..00000000 --- a/src/utils/helpers/pagination.js +++ /dev/null @@ -1,27 +0,0 @@ -import { max, min, range } from 'ramda'; - -export const ELLIPSIS = '...'; - -export const progressivePagination = (currentPage, pageCount) => { - const delta = 2; - const pages = range( - max(delta, currentPage - delta), - min(pageCount - 1, currentPage + delta) + 1, - ); - - if (currentPage - delta > delta) { - pages.unshift(ELLIPSIS); - } - if (currentPage + delta < pageCount - 1) { - pages.push(ELLIPSIS); - } - - pages.unshift(1); - pages.push(pageCount); - - return pages; -}; - -export const keyForPage = (pageNumber, index) => pageNumber !== ELLIPSIS ? pageNumber : `${pageNumber}_${index}`; - -export const isPageDisabled = (pageNumber) => pageNumber === ELLIPSIS; diff --git a/src/utils/helpers/pagination.ts b/src/utils/helpers/pagination.ts new file mode 100644 index 00000000..46a5d1b3 --- /dev/null +++ b/src/utils/helpers/pagination.ts @@ -0,0 +1,29 @@ +import { max, min, range } from 'ramda'; + +export const ELLIPSIS = '...'; + +type NumberOrEllipsis = number | '...'; + +export const progressivePagination = (currentPage: number, pageCount: number): NumberOrEllipsis[] => { + const delta = 2; + const pages: NumberOrEllipsis[] = range( + max(delta, currentPage - delta), + min(pageCount - 1, currentPage + delta) + 1, + ); + + if (currentPage - delta > delta) { + pages.unshift(ELLIPSIS); + } + if (currentPage + delta < pageCount - 1) { + pages.push(ELLIPSIS); + } + + pages.unshift(1); + pages.push(pageCount); + + return pages; +}; + +export const keyForPage = (pageNumber: NumberOrEllipsis, index: number) => pageNumber !== ELLIPSIS ? pageNumber : `${pageNumber}_${index}`; + +export const isPageDisabled = (pageNumber: NumberOrEllipsis) => pageNumber === ELLIPSIS; diff --git a/src/utils/helpers/version.js b/src/utils/helpers/version.js deleted file mode 100644 index 5d62603a..00000000 --- a/src/utils/helpers/version.js +++ /dev/null @@ -1,27 +0,0 @@ -import { compare } from 'compare-versions'; -import { identity, memoizeWith } from 'ramda'; -import { hasValue } from '../utils'; - -export const versionMatch = (versionToMatch, { maxVersion, minVersion }) => { - if (!hasValue(versionToMatch)) { - return false; - } - - const matchesMinVersion = !minVersion || compare(versionToMatch, minVersion, '>='); - const matchesMaxVersion = !maxVersion || compare(versionToMatch, maxVersion, '<='); - - return !!(matchesMaxVersion && matchesMinVersion); -}; - -const versionIsValidSemVer = memoizeWith(identity, (version) => { - try { - return compare(version, version, '='); - } catch (e) { - return false; - } -}); - -export const versionToPrintable = (version) => !versionIsValidSemVer(version) ? version : `v${version}`; - -export const versionToSemVer = (defaultValue = 'latest') => - (version) => versionIsValidSemVer(version) ? version : defaultValue; diff --git a/src/utils/helpers/version.ts b/src/utils/helpers/version.ts new file mode 100644 index 00000000..a44a0251 --- /dev/null +++ b/src/utils/helpers/version.ts @@ -0,0 +1,32 @@ +import { compare } from 'compare-versions'; +import { identity, memoizeWith } from 'ramda'; +import { Empty, hasValue } from '../utils'; + +export interface Versions { + maxVersion?: string; + minVersion?: string; +} + +export const versionMatch = (versionToMatch: string | Empty, { maxVersion, minVersion }: Versions): boolean => { + if (!hasValue(versionToMatch)) { + return false; + } + + const matchesMinVersion = !minVersion || compare(versionToMatch, minVersion, '>='); + const matchesMaxVersion = !maxVersion || compare(versionToMatch, maxVersion, '<='); + + return matchesMaxVersion && matchesMinVersion; +}; + +const versionIsValidSemVer = memoizeWith(identity, (version: string) => { + try { + return compare(version, version, '='); + } catch (e) { + return false; + } +}); + +export const versionToPrintable = (version: string) => !versionIsValidSemVer(version) ? version : `v${version}`; + +export const versionToSemVer = (defaultValue = 'latest') => + (version: string) => versionIsValidSemVer(version) ? version : defaultValue; diff --git a/src/utils/utils.js b/src/utils/utils.js deleted file mode 100644 index d8758be1..00000000 --- a/src/utils/utils.js +++ /dev/null @@ -1,32 +0,0 @@ -import L from 'leaflet'; -import marker2x from 'leaflet/dist/images/marker-icon-2x.png'; -import marker from 'leaflet/dist/images/marker-icon.png'; -import markerShadow from 'leaflet/dist/images/marker-shadow.png'; -import { isEmpty, isNil, range } from 'ramda'; - -export const determineOrderDir = (clickedField, currentOrderField, currentOrderDir) => { - if (currentOrderField !== clickedField) { - return 'ASC'; - } - - const newOrderMap = { - ASC: 'DESC', - DESC: undefined, - }; - - return currentOrderDir ? newOrderMap[currentOrderDir] : 'ASC'; -}; - -export const fixLeafletIcons = () => { - delete L.Icon.Default.prototype._getIconUrl; - - L.Icon.Default.mergeOptions({ - iconRetinaUrl: marker2x, - iconUrl: marker, - shadowUrl: markerShadow, - }); -}; - -export const rangeOf = (size, mappingFn, startAt = 1) => range(startAt, size + 1).map(mappingFn); - -export const hasValue = (value) => !isNil(value) && !isEmpty(value); diff --git a/src/utils/utils.ts b/src/utils/utils.ts new file mode 100644 index 00000000..3fc26d0f --- /dev/null +++ b/src/utils/utils.ts @@ -0,0 +1,23 @@ +import { isEmpty, isNil, range } from 'ramda'; + +export type OrderDir = 'ASC' | 'DESC' | undefined; + +export const determineOrderDir = (currentField: string, newField: string, currentOrderDir: OrderDir): OrderDir => { + if (currentField !== newField) { + return 'ASC'; + } + + const newOrderMap: Record<'ASC' | 'DESC', OrderDir> = { + ASC: 'DESC', + DESC: undefined, + }; + + return currentOrderDir ? newOrderMap[currentOrderDir] : 'ASC'; +}; + +export const rangeOf = (size: number, mappingFn: (value: number) => T, startAt = 1): T[] => + range(startAt, size + 1).map(mappingFn); + +export type Empty = null | undefined | '' | never[]; + +export const hasValue = (value: T | Empty): value is T => !isNil(value) && !isEmpty(value); diff --git a/test/utils/helpers/date.test.js b/test/utils/helpers/date.test.ts similarity index 100% rename from test/utils/helpers/date.test.js rename to test/utils/helpers/date.test.ts diff --git a/test/utils/helpers/leaflet.test.js b/test/utils/helpers/leaflet.test.js new file mode 100644 index 00000000..9fc1a4d0 --- /dev/null +++ b/test/utils/helpers/leaflet.test.js @@ -0,0 +1,19 @@ +import L from 'leaflet'; +import marker2x from 'leaflet/dist/images/marker-icon-2x.png'; +import marker from 'leaflet/dist/images/marker-icon.png'; +import markerShadow from 'leaflet/dist/images/marker-shadow.png'; +import { fixLeafletIcons } from '../../../src/utils/helpers/leaflet'; + +describe('leaflet', () => { + describe('fixLeafletIcons', () => { + it('updates icons used by leaflet', () => { + fixLeafletIcons(); + + const { iconRetinaUrl, iconUrl, shadowUrl } = L.Icon.Default.prototype.options; + + expect(iconRetinaUrl).toEqual(marker2x); + expect(iconUrl).toEqual(marker); + expect(shadowUrl).toEqual(markerShadow); + }); + }); +}); diff --git a/test/utils/helpers/numbers.test.js b/test/utils/helpers/numbers.test.ts similarity index 100% rename from test/utils/helpers/numbers.test.js rename to test/utils/helpers/numbers.test.ts diff --git a/test/utils/helpers/version.test.js b/test/utils/helpers/version.test.ts similarity index 100% rename from test/utils/helpers/version.test.js rename to test/utils/helpers/version.test.ts diff --git a/test/utils/utils.test.js b/test/utils/utils.test.js index 66c92618..04dc4617 100644 --- a/test/utils/utils.test.js +++ b/test/utils/utils.test.js @@ -1,8 +1,4 @@ -import L from 'leaflet'; -import marker2x from 'leaflet/dist/images/marker-icon-2x.png'; -import marker from 'leaflet/dist/images/marker-icon.png'; -import markerShadow from 'leaflet/dist/images/marker-shadow.png'; -import { determineOrderDir, fixLeafletIcons, rangeOf } from '../../src/utils/utils'; +import { determineOrderDir, rangeOf } from '../../src/utils/utils'; describe('utils', () => { describe('determineOrderDir', () => { @@ -27,18 +23,6 @@ describe('utils', () => { }); }); - describe('fixLeafletIcons', () => { - it('updates icons used by leaflet', () => { - fixLeafletIcons(); - - const { iconRetinaUrl, iconUrl, shadowUrl } = L.Icon.Default.prototype.options; - - expect(iconRetinaUrl).toEqual(marker2x); - expect(iconUrl).toEqual(marker); - expect(shadowUrl).toEqual(markerShadow); - }); - }); - describe('rangeOf', () => { const func = (i) => `result_${i}`; const size = 5; From 2eba607874ecd7b68acc737f537d2adacdb9ec5d Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 22 Aug 2020 19:03:25 +0200 Subject: [PATCH 11/59] More elements migrated to typescript --- .../{BooleanControl.js => BooleanControl.tsx} | 30 +++++++++---------- src/utils/Checkbox.js | 8 ----- src/utils/Checkbox.tsx | 6 ++++ src/utils/ToggleSwitch.js | 8 ----- src/utils/ToggleSwitch.tsx | 6 ++++ src/utils/helpers/{visits.js => visits.ts} | 16 ++++++---- src/utils/utils.ts | 2 +- .../{Checkbox.test.js => Checkbox.test.tsx} | 16 +++++----- test/utils/{utils.test.js => utils.test.ts} | 0 9 files changed, 47 insertions(+), 45 deletions(-) rename src/utils/{BooleanControl.js => BooleanControl.tsx} (50%) delete mode 100644 src/utils/Checkbox.js create mode 100644 src/utils/Checkbox.tsx delete mode 100644 src/utils/ToggleSwitch.js create mode 100644 src/utils/ToggleSwitch.tsx rename src/utils/helpers/{visits.js => visits.ts} (59%) rename test/utils/{Checkbox.test.js => Checkbox.test.tsx} (76%) rename test/utils/{utils.test.js => utils.test.ts} (100%) diff --git a/src/utils/BooleanControl.js b/src/utils/BooleanControl.tsx similarity index 50% rename from src/utils/BooleanControl.js rename to src/utils/BooleanControl.tsx index 3daa7d9f..acf46c93 100644 --- a/src/utils/BooleanControl.js +++ b/src/utils/BooleanControl.tsx @@ -1,23 +1,23 @@ -import React from 'react'; -import PropTypes from 'prop-types'; +import React, { ChangeEvent, FC } from 'react'; import classNames from 'classnames'; import { v4 as uuid } from 'uuid'; +import { identity } from 'ramda'; -export const basePropTypes = { - checked: PropTypes.bool.isRequired, - onChange: PropTypes.func.isRequired, - children: PropTypes.oneOfType([ PropTypes.string, PropTypes.node ]), - className: PropTypes.string, -}; +export interface BooleanControlProps { + checked?: boolean; + onChange?: (checked: boolean, e: ChangeEvent) => void; + className?: string; +} -const propTypes = { - ...basePropTypes, - type: PropTypes.oneOf([ 'switch', 'checkbox' ]).isRequired, -}; +interface BooleanControlWithTypeProps extends BooleanControlProps { + type: 'switch' | 'checkbox'; +} -const BooleanControl = ({ checked, onChange, className, children, type }) => { +const BooleanControl: FC = ( + { checked = false, onChange = identity, className, children, type }, +) => { const id = uuid(); - const onChecked = (e) => onChange(e.target.checked, e); + const onChecked = (e: ChangeEvent) => onChange(e.target.checked, e); const typeClasses = { 'custom-switch': type === 'switch', 'custom-checkbox': type === 'checkbox', @@ -31,6 +31,4 @@ const BooleanControl = ({ checked, onChange, className, children, type }) => { ); }; -BooleanControl.propTypes = propTypes; - export default BooleanControl; diff --git a/src/utils/Checkbox.js b/src/utils/Checkbox.js deleted file mode 100644 index b253bdfc..00000000 --- a/src/utils/Checkbox.js +++ /dev/null @@ -1,8 +0,0 @@ -import React from 'react'; -import BooleanControl, { basePropTypes } from './BooleanControl'; - -const Checkbox = (props) => ; - -Checkbox.propTypes = basePropTypes; - -export default Checkbox; diff --git a/src/utils/Checkbox.tsx b/src/utils/Checkbox.tsx new file mode 100644 index 00000000..56bc1181 --- /dev/null +++ b/src/utils/Checkbox.tsx @@ -0,0 +1,6 @@ +import React, { FC } from 'react'; +import BooleanControl, { BooleanControlProps } from './BooleanControl'; + +const Checkbox: FC = (props) => ; + +export default Checkbox; diff --git a/src/utils/ToggleSwitch.js b/src/utils/ToggleSwitch.js deleted file mode 100644 index 8f45e96f..00000000 --- a/src/utils/ToggleSwitch.js +++ /dev/null @@ -1,8 +0,0 @@ -import React from 'react'; -import BooleanControl, { basePropTypes } from './BooleanControl'; - -const ToggleSwitch = (props) => ; - -ToggleSwitch.propTypes = basePropTypes; - -export default ToggleSwitch; diff --git a/src/utils/ToggleSwitch.tsx b/src/utils/ToggleSwitch.tsx new file mode 100644 index 00000000..3427b143 --- /dev/null +++ b/src/utils/ToggleSwitch.tsx @@ -0,0 +1,6 @@ +import React, { FC } from 'react'; +import BooleanControl, { BooleanControlProps } from './BooleanControl'; + +const ToggleSwitch: FC = (props) => ; + +export default ToggleSwitch; diff --git a/src/utils/helpers/visits.js b/src/utils/helpers/visits.ts similarity index 59% rename from src/utils/helpers/visits.js rename to src/utils/helpers/visits.ts index 51d898f4..4bd0ab49 100644 --- a/src/utils/helpers/visits.js +++ b/src/utils/helpers/visits.ts @@ -1,6 +1,6 @@ import bowser from 'bowser'; import { zipObj } from 'ramda'; -import { hasValue } from '../utils'; +import { Empty, hasValue } from '../utils'; const DEFAULT = 'Others'; const BROWSERS_WHITELIST = [ @@ -17,17 +17,22 @@ const BROWSERS_WHITELIST = [ 'WeChat', ]; -export const parseUserAgent = (userAgent) => { +interface UserAgent { + browser: string; + os: string; +} + +export const parseUserAgent = (userAgent: string | Empty): UserAgent => { if (!hasValue(userAgent)) { return { browser: DEFAULT, os: DEFAULT }; } const { browser: { name: browser }, os: { name: os } } = bowser.parse(userAgent); - return { os: os || DEFAULT, browser: browser && BROWSERS_WHITELIST.includes(browser) ? browser : DEFAULT }; + return { os: os ?? DEFAULT, browser: browser && BROWSERS_WHITELIST.includes(browser) ? browser : DEFAULT }; }; -export const extractDomain = (url) => { +export const extractDomain = (url: string | Empty): string => { if (!hasValue(url)) { return 'Direct'; } @@ -37,4 +42,5 @@ export const extractDomain = (url) => { return domain.split(':')[0]; }; -export const fillTheGaps = (stats, labels) => Object.values({ ...zipObj(labels, labels.map(() => 0)), ...stats }); +export const fillTheGaps = (stats: Record, labels: string[]): number[] => + Object.values({ ...zipObj(labels, labels.map(() => 0)), ...stats }); diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 3fc26d0f..27521f1e 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -2,7 +2,7 @@ import { isEmpty, isNil, range } from 'ramda'; export type OrderDir = 'ASC' | 'DESC' | undefined; -export const determineOrderDir = (currentField: string, newField: string, currentOrderDir: OrderDir): OrderDir => { +export const determineOrderDir = (currentField: string, newField: string, currentOrderDir?: OrderDir): OrderDir => { if (currentField !== newField) { return 'ASC'; } diff --git a/test/utils/Checkbox.test.js b/test/utils/Checkbox.test.tsx similarity index 76% rename from test/utils/Checkbox.test.js rename to test/utils/Checkbox.test.tsx index 78c8afec..b923e3f8 100644 --- a/test/utils/Checkbox.test.js +++ b/test/utils/Checkbox.test.tsx @@ -1,17 +1,19 @@ -import React from 'react'; -import { mount } from 'enzyme'; +import React, { ChangeEvent, PropsWithChildren } from 'react'; +import { mount, ReactWrapper } from 'enzyme'; +import { Mock } from 'ts-mockery'; import Checkbox from '../../src/utils/Checkbox'; +import { BooleanControlProps } from '../../src/utils/BooleanControl'; describe('', () => { - let wrapped; + let wrapped: ReactWrapper; - const createComponent = (props = {}) => { + const createComponent = (props: PropsWithChildren = {}) => { wrapped = mount(); return wrapped; }; - afterEach(() => wrapped && wrapped.unmount()); + afterEach(() => wrapped?.unmount()); it('includes extra class names when provided', () => { const classNames = [ 'foo', 'bar', 'baz' ]; @@ -55,11 +57,11 @@ describe('', () => { it('changes checked status on input change', () => { const onChange = jest.fn(); - const e = { target: { checked: false } }; + const e = Mock.of>({ target: { checked: false } }); const wrapped = createComponent({ checked: true, onChange }); const input = wrapped.find('input'); - input.prop('onChange')(e); + (input.prop('onChange') as Function)(e); expect(onChange).toHaveBeenCalledWith(false, e); }); diff --git a/test/utils/utils.test.js b/test/utils/utils.test.ts similarity index 100% rename from test/utils/utils.test.js rename to test/utils/utils.test.ts From e193a692e8058bcb5b2fee0ec9fa4329d2e28595 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 23 Aug 2020 09:03:44 +0200 Subject: [PATCH 12/59] Migrated all service providers to typescript --- .../{provideServices.js => provideServices.ts} | 6 ++++-- src/container/index.ts | 4 ++-- src/container/types.ts | 1 + .../{provideServices.js => provideServices.ts} | 3 ++- .../{provideServices.js => provideServices.ts} | 5 ++++- .../{provideServices.js => provideServices.ts} | 7 +++++-- .../{provideServices.js => provideServices.ts} | 7 +++++-- .../{provideServices.js => provideServices.ts} | 5 +++-- .../{provideServices.js => provideServices.ts} | 4 +++- test/utils/Checkbox.test.tsx | 11 +++-------- 10 files changed, 32 insertions(+), 21 deletions(-) rename src/common/services/{provideServices.js => provideServices.ts} (84%) create mode 100644 src/container/types.ts rename src/mercure/services/{provideServices.js => provideServices.ts} (71%) rename src/settings/services/{provideServices.js => provideServices.ts} (74%) rename src/short-urls/services/{provideServices.js => provideServices.ts} (93%) rename src/tags/services/{provideServices.js => provideServices.ts} (85%) rename src/utils/services/{provideServices.js => provideServices.ts} (83%) rename src/visits/services/{provideServices.js => provideServices.ts} (92%) diff --git a/src/common/services/provideServices.js b/src/common/services/provideServices.ts similarity index 84% rename from src/common/services/provideServices.js rename to src/common/services/provideServices.ts index 87454117..ad3f1bb3 100644 --- a/src/common/services/provideServices.js +++ b/src/common/services/provideServices.ts @@ -1,3 +1,4 @@ +import Bottle, { Decorator } from 'bottlejs'; import ScrollToTop from '../ScrollToTop'; import MainHeader from '../MainHeader'; import Home from '../Home'; @@ -5,9 +6,10 @@ import MenuLayout from '../MenuLayout'; import AsideMenu from '../AsideMenu'; import ErrorHandler from '../ErrorHandler'; import ShlinkVersions from '../ShlinkVersions'; +import { ConnectDecorator } from '../../container/types'; -const provideServices = (bottle, connect, withRouter) => { - bottle.constant('window', global.window); +const provideServices = (bottle: Bottle, connect: ConnectDecorator, withRouter: Decorator) => { + bottle.constant('window', (global as any).window); bottle.constant('console', global.console); bottle.serviceFactory('ScrollToTop', ScrollToTop, 'window'); diff --git a/src/container/index.ts b/src/container/index.ts index ae20ae63..9b9a7c14 100644 --- a/src/container/index.ts +++ b/src/container/index.ts @@ -11,6 +11,7 @@ import provideTagsServices from '../tags/services/provideServices'; import provideUtilsServices from '../utils/services/provideServices'; import provideMercureServices from '../mercure/services/provideServices'; import provideSettingsServices from '../settings/services/provideServices'; +import { ConnectDecorator } from './types'; type ActionMap = Record; @@ -20,11 +21,10 @@ const { container } = bottle; const lazyService = (container: IContainer, serviceName: string) => (...args: any[]) => container[serviceName](...args); const mapActionService = (map: ActionMap, actionName: string): ActionMap => ({ ...map, - // Wrap actual action service in a function so that it is lazily created the first time it is called [actionName]: lazyService(container, actionName), }); -const connect = (propsFromState: string[], actionServiceNames: string[] = []) => +const connect: ConnectDecorator = (propsFromState: string[], actionServiceNames: string[] = []) => reduxConnect( propsFromState ? pick(propsFromState) : null, actionServiceNames.reduce(mapActionService, {}), diff --git a/src/container/types.ts b/src/container/types.ts new file mode 100644 index 00000000..27759ea8 --- /dev/null +++ b/src/container/types.ts @@ -0,0 +1 @@ +export type ConnectDecorator = (props: string[], actions?: string[]) => any; diff --git a/src/mercure/services/provideServices.js b/src/mercure/services/provideServices.ts similarity index 71% rename from src/mercure/services/provideServices.js rename to src/mercure/services/provideServices.ts index 152ebe4a..bf29b207 100644 --- a/src/mercure/services/provideServices.js +++ b/src/mercure/services/provideServices.ts @@ -1,6 +1,7 @@ +import Bottle from 'bottlejs'; import { loadMercureInfo } from '../reducers/mercureInfo'; -const provideServices = (bottle) => { +const provideServices = (bottle: Bottle) => { // Actions bottle.serviceFactory('loadMercureInfo', loadMercureInfo, 'buildShlinkApiClient'); }; diff --git a/src/settings/services/provideServices.js b/src/settings/services/provideServices.ts similarity index 74% rename from src/settings/services/provideServices.js rename to src/settings/services/provideServices.ts index 1c9e7863..ed8bb7d0 100644 --- a/src/settings/services/provideServices.js +++ b/src/settings/services/provideServices.ts @@ -1,11 +1,14 @@ +import Bottle from 'bottlejs'; import RealTimeUpdates from '../RealTimeUpdates'; import Settings from '../Settings'; import { setRealTimeUpdates } from '../reducers/settings'; +import { ConnectDecorator } from '../../container/types'; -const provideServices = (bottle, connect) => { +const provideServices = (bottle: Bottle, connect: ConnectDecorator) => { // Components bottle.serviceFactory('Settings', Settings, 'RealTimeUpdates'); + // Services bottle.serviceFactory('RealTimeUpdates', () => RealTimeUpdates); bottle.decorator('RealTimeUpdates', connect([ 'settings' ], [ 'setRealTimeUpdates' ])); diff --git a/src/short-urls/services/provideServices.js b/src/short-urls/services/provideServices.ts similarity index 93% rename from src/short-urls/services/provideServices.js rename to src/short-urls/services/provideServices.ts index ad1f71fa..a5f43f4c 100644 --- a/src/short-urls/services/provideServices.js +++ b/src/short-urls/services/provideServices.ts @@ -1,5 +1,6 @@ import { connect as reduxConnect } from 'react-redux'; import { assoc } from 'ramda'; +import Bottle from 'bottlejs'; import ShortUrls from '../ShortUrls'; import SearchBar from '../SearchBar'; import ShortUrlsList from '../ShortUrlsList'; @@ -18,14 +19,16 @@ import { editShortUrlTags, resetShortUrlsTags } from '../reducers/shortUrlTags'; import { editShortUrlMeta, resetShortUrlMeta } from '../reducers/shortUrlMeta'; import { resetShortUrlParams } from '../reducers/shortUrlsListParams'; import { editShortUrl } from '../reducers/shortUrlEdition'; +import { ConnectDecorator } from '../../container/types'; -const provideServices = (bottle, connect) => { +const provideServices = (bottle: Bottle, connect: ConnectDecorator) => { // Components bottle.serviceFactory('ShortUrls', ShortUrls, 'SearchBar', 'ShortUrlsList'); bottle.decorator('ShortUrls', reduxConnect( - (state) => assoc('shortUrlsList', state.shortUrlsList.shortUrls, state.shortUrlsList), + (state: any) => assoc('shortUrlsList', state.shortUrlsList.shortUrls, state.shortUrlsList), )); + // Services bottle.serviceFactory('SearchBar', SearchBar, 'ColorGenerator', 'ForServerVersion'); bottle.decorator('SearchBar', connect([ 'shortUrlsListParams' ], [ 'listShortUrls' ])); diff --git a/src/tags/services/provideServices.js b/src/tags/services/provideServices.ts similarity index 85% rename from src/tags/services/provideServices.js rename to src/tags/services/provideServices.ts index 66204c16..be38b20d 100644 --- a/src/tags/services/provideServices.js +++ b/src/tags/services/provideServices.ts @@ -1,3 +1,4 @@ +import Bottle, { IContainer } from 'bottlejs'; import TagsSelector from '../helpers/TagsSelector'; import TagCard from '../TagCard'; import DeleteTagConfirmModal from '../helpers/DeleteTagConfirmModal'; @@ -6,8 +7,9 @@ import TagsList from '../TagsList'; import { filterTags, listTags } from '../reducers/tagsList'; import { deleteTag, tagDeleted } from '../reducers/tagDelete'; import { editTag, tagEdited } from '../reducers/tagEdit'; +import { ConnectDecorator } from '../../container/types'; -const provideServices = (bottle, connect) => { +const provideServices = (bottle: Bottle, connect: ConnectDecorator) => { // Components bottle.serviceFactory('TagsSelector', TagsSelector, 'ColorGenerator'); bottle.decorator('TagsSelector', connect([ 'tagsList' ], [ 'listTags' ])); @@ -34,7 +36,8 @@ const provideServices = (bottle, connect) => { )); // Actions - const listTagsActionFactory = (force) => ({ buildShlinkApiClient }) => listTags(buildShlinkApiClient, force); + const listTagsActionFactory = (force: boolean) => + ({ buildShlinkApiClient }: IContainer) => listTags(buildShlinkApiClient, force); bottle.factory('listTags', listTagsActionFactory(false)); bottle.factory('forceListTags', listTagsActionFactory(true)); diff --git a/src/utils/services/provideServices.js b/src/utils/services/provideServices.ts similarity index 83% rename from src/utils/services/provideServices.js rename to src/utils/services/provideServices.ts index 5bc62442..458cb680 100644 --- a/src/utils/services/provideServices.js +++ b/src/utils/services/provideServices.ts @@ -1,11 +1,12 @@ import axios from 'axios'; +import Bottle from 'bottlejs'; import { useStateFlagTimeout } from '../helpers/hooks'; import Storage from './Storage'; import ColorGenerator from './ColorGenerator'; import buildShlinkApiClient from './ShlinkApiClientBuilder'; -const provideServices = (bottle) => { - bottle.constant('localStorage', global.localStorage); +const provideServices = (bottle: Bottle) => { + bottle.constant('localStorage', (global as any).localStorage); bottle.service('Storage', Storage, 'localStorage'); bottle.service('ColorGenerator', ColorGenerator, 'Storage'); diff --git a/src/visits/services/provideServices.js b/src/visits/services/provideServices.ts similarity index 92% rename from src/visits/services/provideServices.js rename to src/visits/services/provideServices.ts index cc7062f5..0d0c0b09 100644 --- a/src/visits/services/provideServices.js +++ b/src/visits/services/provideServices.ts @@ -1,3 +1,4 @@ +import Bottle from 'bottlejs'; import ShortUrlVisits from '../ShortUrlVisits'; import { cancelGetShortUrlVisits, getShortUrlVisits } from '../reducers/shortUrlVisits'; import { getShortUrlDetail } from '../reducers/shortUrlDetail'; @@ -7,9 +8,10 @@ import VisitsStats from '../VisitsStats'; import { createNewVisit } from '../reducers/visitCreation'; import { cancelGetTagVisits, getTagVisits } from '../reducers/tagVisits'; import TagVisits from '../TagVisits'; +import { ConnectDecorator } from '../../container/types'; import * as visitsParser from './VisitsParser'; -const provideServices = (bottle, connect) => { +const provideServices = (bottle: Bottle, connect: ConnectDecorator) => { // Components bottle.serviceFactory('OpenMapModalBtn', OpenMapModalBtn, 'MapModal'); bottle.serviceFactory('MapModal', () => MapModal); diff --git a/test/utils/Checkbox.test.tsx b/test/utils/Checkbox.test.tsx index b923e3f8..9aac2863 100644 --- a/test/utils/Checkbox.test.tsx +++ b/test/utils/Checkbox.test.tsx @@ -17,12 +17,10 @@ describe('', () => { it('includes extra class names when provided', () => { const classNames = [ 'foo', 'bar', 'baz' ]; - const checked = false; - const onChange = () => {}; expect.assertions(classNames.length); classNames.forEach((className) => { - const wrapped = createComponent({ className, checked, onChange }); + const wrapped = createComponent({ className }); expect(wrapped.prop('className')).toContain(className); }); @@ -30,11 +28,10 @@ describe('', () => { it('marks input as checked if defined', () => { const checkeds = [ true, false ]; - const onChange = () => {}; expect.assertions(checkeds.length); checkeds.forEach((checked) => { - const wrapped = createComponent({ checked, onChange }); + const wrapped = createComponent({ checked }); const input = wrapped.find('input'); expect(input.prop('checked')).toEqual(checked); @@ -43,12 +40,10 @@ describe('', () => { it('renders provided children inside the label', () => { const labels = [ 'foo', 'bar', 'baz' ]; - const checked = false; - const onChange = () => {}; expect.assertions(labels.length); labels.forEach((children) => { - const wrapped = createComponent({ children, checked, onChange }); + const wrapped = createComponent({ children }); const label = wrapped.find('label'); expect(label.text()).toEqual(children); From 87e64e58991348247d0de83e7c00370c6a65322b Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 23 Aug 2020 09:52:09 +0200 Subject: [PATCH 13/59] Migrated first reducer to typescript, adding also type for the shared app state --- package-lock.json | 6 ++ package.json | 1 + src/container/types.ts | 24 ++++++++ src/mercure/reducers/mercureInfo.js | 49 --------------- src/mercure/reducers/mercureInfo.ts | 59 +++++++++++++++++++ src/reducers/index.ts | 3 +- src/utils/services/types.ts | 11 ++++ ...ercureInfo.test.js => mercureInfo.test.ts} | 23 +++++--- 8 files changed, 117 insertions(+), 59 deletions(-) delete mode 100644 src/mercure/reducers/mercureInfo.js create mode 100644 src/mercure/reducers/mercureInfo.ts create mode 100644 src/utils/services/types.ts rename test/mercure/reducers/{mercureInfo.test.js => mercureInfo.test.ts} (73%) diff --git a/package-lock.json b/package-lock.json index e67e9e4e..a644d0e7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3441,6 +3441,12 @@ "popper.js": "^1.14.1" } }, + "@types/redux-actions": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@types/redux-actions/-/redux-actions-2.6.1.tgz", + "integrity": "sha512-zKgK+ATp3sswXs6sOYo1tk8xdXTy4CTaeeYrVQlClCjeOpag5vzPo0ASWiiBJ7vsiQRAdb3VkuFLnDoBimF67g==", + "dev": true + }, "@types/stack-utils": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz", diff --git a/package.json b/package.json index 8454ae72..e82a37e8 100644 --- a/package.json +++ b/package.json @@ -86,6 +86,7 @@ "@types/react-redux": "^7.1.9", "@types/react-router-dom": "^5.1.5", "@types/reactstrap": "^8.5.1", + "@types/redux-actions": "^2.6.1", "@types/uuid": "^8.3.0", "adm-zip": "^0.4.13", "autoprefixer": "^9.6.3", diff --git a/src/container/types.ts b/src/container/types.ts index 27759ea8..85486e2e 100644 --- a/src/container/types.ts +++ b/src/container/types.ts @@ -1 +1,25 @@ +import { MercureInfo } from '../mercure/reducers/mercureInfo'; + export type ConnectDecorator = (props: string[], actions?: string[]) => any; + +export interface ShlinkState { + servers: any; + selectedServer: any; + shortUrlsList: any; + shortUrlsListParams: any; + shortUrlCreationResult: any; + shortUrlDeletion: any; + shortUrlTags: any; + shortUrlMeta: any; + shortUrlEdition: any; + shortUrlVisits: any; + tagVisits: any; + shortUrlDetail: any; + tagsList: any; + tagDelete: any; + tagEdit: any; + mercureInfo: MercureInfo; + settings: any; +} + +export type GetState = () => ShlinkState; diff --git a/src/mercure/reducers/mercureInfo.js b/src/mercure/reducers/mercureInfo.js deleted file mode 100644 index 62a9b9db..00000000 --- a/src/mercure/reducers/mercureInfo.js +++ /dev/null @@ -1,49 +0,0 @@ -import { handleActions } from 'redux-actions'; -import PropTypes from 'prop-types'; - -/* eslint-disable padding-line-between-statements */ -export const GET_MERCURE_INFO_START = 'shlink/mercure/GET_MERCURE_INFO_START'; -export const GET_MERCURE_INFO_ERROR = 'shlink/mercure/GET_MERCURE_INFO_ERROR'; -export const GET_MERCURE_INFO = 'shlink/mercure/GET_MERCURE_INFO'; -/* eslint-enable padding-line-between-statements */ - -export const MercureInfoType = PropTypes.shape({ - token: PropTypes.string, - mercureHubUrl: PropTypes.string, - loading: PropTypes.bool, - error: PropTypes.bool, -}); - -const initialState = { - token: undefined, - mercureHubUrl: undefined, - loading: true, - error: false, -}; - -export default handleActions({ - [GET_MERCURE_INFO_START]: (state) => ({ ...state, loading: true, error: false }), - [GET_MERCURE_INFO_ERROR]: (state) => ({ ...state, loading: false, error: true }), - [GET_MERCURE_INFO]: (state, { token, mercureHubUrl }) => ({ token, mercureHubUrl, loading: false, error: false }), -}, initialState); - -export const loadMercureInfo = (buildShlinkApiClient) => () => async (dispatch, getState) => { - dispatch({ type: GET_MERCURE_INFO_START }); - - const { settings } = getState(); - const { mercureInfo } = buildShlinkApiClient(getState); - - if (!settings.realTimeUpdates.enabled) { - dispatch({ type: GET_MERCURE_INFO_ERROR }); - - return; - } - - try { - const result = await mercureInfo(); - - dispatch({ type: GET_MERCURE_INFO, ...result }); - } catch (e) { - dispatch({ type: GET_MERCURE_INFO_ERROR }); - } -}; diff --git a/src/mercure/reducers/mercureInfo.ts b/src/mercure/reducers/mercureInfo.ts new file mode 100644 index 00000000..a5e251fc --- /dev/null +++ b/src/mercure/reducers/mercureInfo.ts @@ -0,0 +1,59 @@ +import { handleActions } from 'redux-actions'; +import PropTypes from 'prop-types'; +import { Dispatch } from 'redux'; +import { ShlinkApiClientBuilder, ShlinkMercureInfo } from '../../utils/services/types'; +import { GetState } from '../../container/types'; + +/* eslint-disable padding-line-between-statements */ +export const GET_MERCURE_INFO_START = 'shlink/mercure/GET_MERCURE_INFO_START'; +export const GET_MERCURE_INFO_ERROR = 'shlink/mercure/GET_MERCURE_INFO_ERROR'; +export const GET_MERCURE_INFO = 'shlink/mercure/GET_MERCURE_INFO'; +/* eslint-enable padding-line-between-statements */ + +/** @deprecated Use MercureInfo interface */ +export const MercureInfoType = PropTypes.shape({ + token: PropTypes.string, + mercureHubUrl: PropTypes.string, + loading: PropTypes.bool, + error: PropTypes.bool, +}); + +export interface MercureInfo { + token?: string; + mercureHubUrl?: string; + loading: boolean; + error: boolean; +} + +const initialState: MercureInfo = { + loading: true, + error: false, +}; + +export default handleActions({ + [GET_MERCURE_INFO_START]: (state) => ({ ...state, loading: true, error: false }), + [GET_MERCURE_INFO_ERROR]: (state) => ({ ...state, loading: false, error: true }), + [GET_MERCURE_INFO]: (_, { payload }) => ({ ...payload, loading: false, error: false }), +}, initialState); + +export const loadMercureInfo = (buildShlinkApiClient: ShlinkApiClientBuilder) => + () => async (dispatch: Dispatch, getState: GetState) => { + dispatch({ type: GET_MERCURE_INFO_START }); + + const { settings } = getState(); + const { mercureInfo } = buildShlinkApiClient(getState); + + if (!settings.realTimeUpdates.enabled) { + dispatch({ type: GET_MERCURE_INFO_ERROR }); + + return; + } + + try { + const payload = await mercureInfo(); + + dispatch({ type: GET_MERCURE_INFO, payload }); + } catch (e) { + dispatch({ type: GET_MERCURE_INFO_ERROR }); + } + }; diff --git a/src/reducers/index.ts b/src/reducers/index.ts index 1cdf34fd..58bf18ad 100644 --- a/src/reducers/index.ts +++ b/src/reducers/index.ts @@ -16,8 +16,9 @@ import tagDeleteReducer from '../tags/reducers/tagDelete'; import tagEditReducer from '../tags/reducers/tagEdit'; import mercureInfoReducer from '../mercure/reducers/mercureInfo'; import settingsReducer from '../settings/reducers/settings'; +import { ShlinkState } from '../container/types'; -export default combineReducers({ +export default combineReducers({ servers: serversReducer, selectedServer: selectedServerReducer, shortUrlsList: shortUrlsListReducer, diff --git a/src/utils/services/types.ts b/src/utils/services/types.ts new file mode 100644 index 00000000..c5f5a474 --- /dev/null +++ b/src/utils/services/types.ts @@ -0,0 +1,11 @@ +import { RegularServer } from '../../servers/data'; +import { GetState } from '../../container/types'; +import ShlinkApiClient from './ShlinkApiClient'; + +// FIXME Move to ShlinkApiClientBuilder +export type ShlinkApiClientBuilder = (getStateOrSelectedServer: RegularServer | GetState) => ShlinkApiClient; + +export interface ShlinkMercureInfo { + token: string; + mercureHubUrl: string; +} diff --git a/test/mercure/reducers/mercureInfo.test.js b/test/mercure/reducers/mercureInfo.test.ts similarity index 73% rename from test/mercure/reducers/mercureInfo.test.js rename to test/mercure/reducers/mercureInfo.test.ts index c5219548..eb48e1f9 100644 --- a/test/mercure/reducers/mercureInfo.test.js +++ b/test/mercure/reducers/mercureInfo.test.ts @@ -1,9 +1,14 @@ +import { Mock } from 'ts-mockery'; +import { Action } from 'redux-actions'; import reducer, { GET_MERCURE_INFO_START, GET_MERCURE_INFO_ERROR, GET_MERCURE_INFO, loadMercureInfo, -} from '../../../src/mercure/reducers/mercureInfo.js'; +} from '../../../src/mercure/reducers/mercureInfo'; +import { ShlinkMercureInfo } from '../../../src/utils/services/types'; +import ShlinkApiClient from '../../../src/utils/services/ShlinkApiClient'; +import { GetState } from '../../../src/container/types'; describe('mercureInfoReducer', () => { const mercureInfo = { @@ -13,21 +18,21 @@ describe('mercureInfoReducer', () => { describe('reducer', () => { it('returns loading on GET_MERCURE_INFO_START', () => { - expect(reducer({}, { type: GET_MERCURE_INFO_START })).toEqual({ + expect(reducer(undefined, { type: GET_MERCURE_INFO_START } as Action)).toEqual({ loading: true, error: false, }); }); it('returns error on GET_MERCURE_INFO_ERROR', () => { - expect(reducer({}, { type: GET_MERCURE_INFO_ERROR })).toEqual({ + expect(reducer(undefined, { type: GET_MERCURE_INFO_ERROR } as Action)).toEqual({ loading: false, error: true, }); }); it('returns mercure info on GET_MERCURE_INFO', () => { - expect(reducer({}, { type: GET_MERCURE_INFO, ...mercureInfo })).toEqual({ + expect(reducer(undefined, { type: GET_MERCURE_INFO, payload: mercureInfo })).toEqual({ ...mercureInfo, loading: false, error: false, @@ -36,15 +41,15 @@ describe('mercureInfoReducer', () => { }); describe('loadMercureInfo', () => { - const createApiClientMock = (result) => ({ - mercureInfo: jest.fn(() => result), + const createApiClientMock = (result: Promise) => Mock.of({ + mercureInfo: jest.fn().mockReturnValue(result), }); const dispatch = jest.fn(); - const createGetStateMock = (enabled) => jest.fn(() => ({ + const createGetStateMock = (enabled: boolean): GetState => jest.fn().mockReturnValue({ settings: { realTimeUpdates: { enabled }, }, - })); + }); afterEach(jest.resetAllMocks); @@ -69,7 +74,7 @@ describe('mercureInfoReducer', () => { expect(apiClientMock.mercureInfo).toHaveBeenCalledTimes(1); expect(dispatch).toHaveBeenCalledTimes(2); expect(dispatch).toHaveBeenNthCalledWith(1, { type: GET_MERCURE_INFO_START }); - expect(dispatch).toHaveBeenNthCalledWith(2, { type: GET_MERCURE_INFO, ...mercureInfo }); + expect(dispatch).toHaveBeenNthCalledWith(2, { type: GET_MERCURE_INFO, payload: mercureInfo }); }); it('throws error on failure', async () => { From dc7813806697949263586ba195358fd6998a380c Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 23 Aug 2020 10:20:31 +0200 Subject: [PATCH 14/59] Migrate servers reducer to typescript --- src/container/types.ts | 3 +- src/servers/reducers/servers.js | 35 -------------- src/servers/reducers/servers.ts | 48 +++++++++++++++++++ .../{servers.test.js => servers.test.ts} | 16 ++++--- 4 files changed, 59 insertions(+), 43 deletions(-) delete mode 100644 src/servers/reducers/servers.js create mode 100644 src/servers/reducers/servers.ts rename test/servers/reducers/{servers.test.js => servers.test.ts} (83%) diff --git a/src/container/types.ts b/src/container/types.ts index 85486e2e..42647c60 100644 --- a/src/container/types.ts +++ b/src/container/types.ts @@ -1,9 +1,10 @@ import { MercureInfo } from '../mercure/reducers/mercureInfo'; +import { ServersMap } from '../servers/reducers/servers'; export type ConnectDecorator = (props: string[], actions?: string[]) => any; export interface ShlinkState { - servers: any; + servers: ServersMap; selectedServer: any; shortUrlsList: any; shortUrlsListParams: any; diff --git a/src/servers/reducers/servers.js b/src/servers/reducers/servers.js deleted file mode 100644 index 3173d1d6..00000000 --- a/src/servers/reducers/servers.js +++ /dev/null @@ -1,35 +0,0 @@ -import { handleActions } from 'redux-actions'; -import { pipe, assoc, map, reduce, dissoc } from 'ramda'; -import { v4 as uuid } from 'uuid'; - -/* eslint-disable padding-line-between-statements */ -export const EDIT_SERVER = 'shlink/servers/EDIT_SERVER'; -export const DELETE_SERVER = 'shlink/servers/DELETE_SERVER'; -export const CREATE_SERVERS = 'shlink/servers/CREATE_SERVERS'; -/* eslint-enable padding-line-between-statements */ - -const initialState = {}; - -const assocId = (server) => assoc('id', server.id || uuid(), server); - -export default handleActions({ - [CREATE_SERVERS]: (state, { newServers }) => ({ ...state, ...newServers }), - [DELETE_SERVER]: (state, { serverId }) => dissoc(serverId, state), - [EDIT_SERVER]: (state, { serverId, serverData }) => !state[serverId] - ? state - : assoc(serverId, { ...state[serverId], ...serverData }, state), -}, initialState); - -export const createServer = (server) => createServers([ server ]); - -const serversListToMap = reduce((acc, server) => assoc(server.id, server, acc), {}); - -export const createServers = pipe( - map(assocId), - serversListToMap, - (newServers) => ({ type: CREATE_SERVERS, newServers }), -); - -export const editServer = (serverId, serverData) => ({ type: EDIT_SERVER, serverId, serverData }); - -export const deleteServer = ({ id }) => ({ type: DELETE_SERVER, serverId: id }); diff --git a/src/servers/reducers/servers.ts b/src/servers/reducers/servers.ts new file mode 100644 index 00000000..31a66085 --- /dev/null +++ b/src/servers/reducers/servers.ts @@ -0,0 +1,48 @@ +import { handleActions } from 'redux-actions'; +import { pipe, assoc, map, reduce, dissoc } from 'ramda'; +import { v4 as uuid } from 'uuid'; +import { RegularServer, NewServerData } from '../data'; + +/* eslint-disable padding-line-between-statements */ +export const EDIT_SERVER = 'shlink/servers/EDIT_SERVER'; +export const DELETE_SERVER = 'shlink/servers/DELETE_SERVER'; +export const CREATE_SERVERS = 'shlink/servers/CREATE_SERVERS'; +/* eslint-enable padding-line-between-statements */ + +export type ServersMap = Record; + +const initialState: ServersMap = {}; + +const serverWithId = (server: RegularServer | NewServerData): RegularServer => { + if ((server as RegularServer).id) { + return server as RegularServer; + } + + return assoc('id', uuid(), server); +}; + +export default handleActions({ + [CREATE_SERVERS]: (state, { newServers }: any) => ({ ...state, ...newServers }), + [DELETE_SERVER]: (state, { serverId }: any) => dissoc(serverId, state), + [EDIT_SERVER]: (state, { serverId, serverData }: any) => !state[serverId] + ? state + : assoc(serverId, { ...state[serverId], ...serverData }, state), +}, initialState); + +const serversListToMap = reduce((acc, server) => assoc(server.id, server, acc), {}); + +export const createServers = pipe( + map(serverWithId), + serversListToMap, + (newServers: ServersMap) => ({ type: CREATE_SERVERS, newServers }), +); + +export const createServer = (server: RegularServer) => createServers([ server ]); + +export const editServer = (serverId: string, serverData: Partial) => ({ + type: EDIT_SERVER, + serverId, + serverData, +}); + +export const deleteServer = ({ id }: RegularServer) => ({ type: DELETE_SERVER, serverId: id }); diff --git a/test/servers/reducers/servers.test.js b/test/servers/reducers/servers.test.ts similarity index 83% rename from test/servers/reducers/servers.test.js rename to test/servers/reducers/servers.test.ts index 319e18d2..e4ad7e58 100644 --- a/test/servers/reducers/servers.test.js +++ b/test/servers/reducers/servers.test.ts @@ -1,4 +1,5 @@ import { values } from 'ramda'; +import { Mock } from 'ts-mockery'; import reducer, { createServer, deleteServer, @@ -8,11 +9,12 @@ import reducer, { DELETE_SERVER, CREATE_SERVERS, } from '../../../src/servers/reducers/servers'; +import { RegularServer } from '../../../src/servers/data'; describe('serverReducer', () => { const list = { - abc123: { id: 'abc123' }, - def456: { id: 'def456' }, + abc123: Mock.of({ id: 'abc123' }), + def456: Mock.of({ id: 'def456' }), }; afterEach(jest.clearAllMocks); @@ -21,14 +23,14 @@ describe('serverReducer', () => { it('returns edited server when action is EDIT_SERVER', () => expect(reducer( list, - { type: EDIT_SERVER, serverId: 'abc123', serverData: { foo: 'foo' } }, + { type: EDIT_SERVER, serverId: 'abc123', serverData: { foo: 'foo' } } as any, )).toEqual({ abc123: { id: 'abc123', foo: 'foo' }, def456: { id: 'def456' }, })); it('removes server when action is DELETE_SERVER', () => - expect(reducer(list, { type: DELETE_SERVER, serverId: 'abc123' })).toEqual({ + expect(reducer(list, { type: DELETE_SERVER, serverId: 'abc123' } as any)).toEqual({ def456: { id: 'def456' }, })); @@ -38,7 +40,7 @@ describe('serverReducer', () => { newServers: { ghi789: { id: 'ghi789' }, }, - })).toEqual({ + } as any)).toEqual({ abc123: { id: 'abc123' }, def456: { id: 'def456' }, ghi789: { id: 'ghi789' }, @@ -48,7 +50,7 @@ describe('serverReducer', () => { describe('action creators', () => { describe('createServer', () => { it('returns expected action', () => { - const serverToCreate = { id: 'abc123' }; + const serverToCreate = Mock.of({ id: 'abc123' }); const result = createServer(serverToCreate); expect(result).toEqual(expect.objectContaining({ type: CREATE_SERVERS })); @@ -66,7 +68,7 @@ describe('serverReducer', () => { describe('deleteServer', () => { it('returns expected action', () => { - const serverToDelete = { id: 'abc123' }; + const serverToDelete = Mock.of({ id: 'abc123' }); const result = deleteServer(serverToDelete); expect(result).toEqual({ type: DELETE_SERVER, serverId: 'abc123' }); From 1b7e1e2b5b864a00f81271313645f84c45466c37 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 23 Aug 2020 10:51:42 +0200 Subject: [PATCH 15/59] Tweaked server types and data --- src/container/types.ts | 3 +- src/servers/CreateServer.tsx | 4 +- src/servers/data/index.ts | 20 +++++++--- src/servers/helpers/ImportServersBtn.tsx | 4 +- .../{selectedServer.js => selectedServer.ts} | 40 ++++++++++++------- src/servers/reducers/servers.ts | 16 ++++---- src/servers/services/ServersImporter.ts | 6 +-- src/utils/services/types.ts | 9 ++++- 8 files changed, 64 insertions(+), 38 deletions(-) rename src/servers/reducers/{selectedServer.js => selectedServer.ts} (58%) diff --git a/src/container/types.ts b/src/container/types.ts index 42647c60..c8fd2ff7 100644 --- a/src/container/types.ts +++ b/src/container/types.ts @@ -1,11 +1,12 @@ import { MercureInfo } from '../mercure/reducers/mercureInfo'; import { ServersMap } from '../servers/reducers/servers'; +import { SelectedServer } from '../servers/data'; export type ConnectDecorator = (props: string[], actions?: string[]) => any; export interface ShlinkState { servers: ServersMap; - selectedServer: any; + selectedServer: SelectedServer; shortUrlsList: any; shortUrlsListParams: any; shortUrlCreationResult: any; diff --git a/src/servers/CreateServer.tsx b/src/servers/CreateServer.tsx index 2da3e601..dd140b3f 100644 --- a/src/servers/CreateServer.tsx +++ b/src/servers/CreateServer.tsx @@ -6,13 +6,13 @@ import NoMenuLayout from '../common/NoMenuLayout'; import { StateFlagTimeout } from '../utils/helpers/hooks'; import { ServerForm } from './helpers/ServerForm'; import { ImportServersBtnProps } from './helpers/ImportServersBtn'; -import { NewServerData, RegularServer } from './data'; +import { NewServerData, ServerWithId } from './data'; import './CreateServer.scss'; const SHOW_IMPORT_MSG_TIME = 4000; interface CreateServerProps extends RouterProps { - createServer: (server: RegularServer) => void; + createServer: (server: ServerWithId) => void; resetSelectedServer: Function; } diff --git a/src/servers/data/index.ts b/src/servers/data/index.ts index 448ab6c6..97ade453 100644 --- a/src/servers/data/index.ts +++ b/src/servers/data/index.ts @@ -4,15 +4,23 @@ export interface NewServerData { apiKey: string; } -export interface RegularServer extends NewServerData { +export interface ServerWithId { id: string; - version?: string; - printableVersion?: string; - serverNotReachable?: true; } -interface NotFoundServer { +export interface ReachableServer extends ServerWithId { + version: string; + printableVersion: string; +} + +export interface NonReachableServer extends ServerWithId { + serverNotReachable: true; +} + +export interface NotFoundServer { serverNotFound: true; } -export type Server = RegularServer | NotFoundServer; +export type RegularServer = ReachableServer | NonReachableServer; + +export type SelectedServer = RegularServer | NotFoundServer | null; diff --git a/src/servers/helpers/ImportServersBtn.tsx b/src/servers/helpers/ImportServersBtn.tsx index 86ba43d6..95464210 100644 --- a/src/servers/helpers/ImportServersBtn.tsx +++ b/src/servers/helpers/ImportServersBtn.tsx @@ -1,7 +1,7 @@ import React, { useRef, RefObject, ChangeEvent, MutableRefObject } from 'react'; import { UncontrolledTooltip } from 'reactstrap'; import ServersImporter from '../services/ServersImporter'; -import { Server } from '../data'; +import { NewServerData } from '../data'; type Ref = RefObject | MutableRefObject; @@ -11,7 +11,7 @@ export interface ImportServersBtnProps { } interface ImportServersBtnConnectProps extends ImportServersBtnProps { - createServers: (servers: Server[]) => void; + createServers: (servers: NewServerData[]) => void; fileRef: Ref; } diff --git a/src/servers/reducers/selectedServer.js b/src/servers/reducers/selectedServer.ts similarity index 58% rename from src/servers/reducers/selectedServer.js rename to src/servers/reducers/selectedServer.ts index 07ca284d..2848280d 100644 --- a/src/servers/reducers/selectedServer.js +++ b/src/servers/reducers/selectedServer.ts @@ -1,7 +1,11 @@ import { createAction, handleActions } from 'redux-actions'; import { identity, memoizeWith, pipe } from 'ramda'; +import { Action, Dispatch } from 'redux'; import { resetShortUrlParams } from '../../short-urls/reducers/shortUrlsListParams'; import { versionToPrintable, versionToSemVer as toSemVer } from '../../utils/helpers/version'; +import { NonReachableServer, NotFoundServer, ReachableServer, SelectedServer } from '../data'; +import { GetState } from '../../container/types'; +import { ShlinkApiClientBuilder, ShlinkHealth } from '../../utils/services/types'; /* eslint-disable padding-line-between-statements */ export const SELECT_SERVER = 'shlink/selectedServer/SELECT_SERVER'; @@ -12,22 +16,30 @@ export const MAX_FALLBACK_VERSION = '999.999.999'; export const LATEST_VERSION_CONSTRAINT = 'latest'; /* eslint-enable padding-line-between-statements */ -const initialState = null; +const initialState: SelectedServer = null; const versionToSemVer = pipe( - (version) => version === LATEST_VERSION_CONSTRAINT ? MAX_FALLBACK_VERSION : version, + (version: string) => version === LATEST_VERSION_CONSTRAINT ? MAX_FALLBACK_VERSION : version, toSemVer(MIN_FALLBACK_VERSION), ); -const getServerVersion = memoizeWith(identity, (serverId, health) => health().then(({ version }) => ({ - version: versionToSemVer(version), - printableVersion: versionToPrintable(version), -}))); +const getServerVersion = memoizeWith( + identity, + async (_serverId: string, health: () => Promise) => health().then(({ version }) => ({ + version: versionToSemVer(version), + printableVersion: versionToPrintable(version), + })), +); export const resetSelectedServer = createAction(RESET_SELECTED_SERVER); -export const selectServer = (buildShlinkApiClient, loadMercureInfo) => (serverId) => async ( - dispatch, - getState, +export const selectServer = ( + buildShlinkApiClient: ShlinkApiClientBuilder, + loadMercureInfo: () => Action, +) => ( + serverId: string, +) => async ( + dispatch: Dispatch, + getState: GetState, ) => { dispatch(resetSelectedServer()); dispatch(resetShortUrlParams()); @@ -36,7 +48,7 @@ export const selectServer = (buildShlinkApiClient, loadMercureInfo) => (serverId const selectedServer = servers[serverId]; if (!selectedServer) { - dispatch({ + dispatch({ type: SELECT_SERVER, selectedServer: { serverNotFound: true }, }); @@ -48,7 +60,7 @@ export const selectServer = (buildShlinkApiClient, loadMercureInfo) => (serverId const { health } = buildShlinkApiClient(selectedServer); const { version, printableVersion } = await getServerVersion(serverId, health); - dispatch({ + dispatch({ type: SELECT_SERVER, selectedServer: { ...selectedServer, @@ -58,14 +70,14 @@ export const selectServer = (buildShlinkApiClient, loadMercureInfo) => (serverId }); dispatch(loadMercureInfo()); } catch (e) { - dispatch({ + dispatch({ type: SELECT_SERVER, selectedServer: { ...selectedServer, serverNotReachable: true }, }); } }; -export default handleActions({ +export default handleActions({ [RESET_SELECTED_SERVER]: () => initialState, - [SELECT_SERVER]: (state, { selectedServer }) => selectedServer, + [SELECT_SERVER]: (_, { selectedServer }: any) => selectedServer, }, initialState); diff --git a/src/servers/reducers/servers.ts b/src/servers/reducers/servers.ts index 31a66085..9d97dd8c 100644 --- a/src/servers/reducers/servers.ts +++ b/src/servers/reducers/servers.ts @@ -1,7 +1,7 @@ import { handleActions } from 'redux-actions'; import { pipe, assoc, map, reduce, dissoc } from 'ramda'; import { v4 as uuid } from 'uuid'; -import { RegularServer, NewServerData } from '../data'; +import { NewServerData, ServerWithId } from '../data'; /* eslint-disable padding-line-between-statements */ export const EDIT_SERVER = 'shlink/servers/EDIT_SERVER'; @@ -9,13 +9,13 @@ export const DELETE_SERVER = 'shlink/servers/DELETE_SERVER'; export const CREATE_SERVERS = 'shlink/servers/CREATE_SERVERS'; /* eslint-enable padding-line-between-statements */ -export type ServersMap = Record; +export type ServersMap = Record; const initialState: ServersMap = {}; -const serverWithId = (server: RegularServer | NewServerData): RegularServer => { - if ((server as RegularServer).id) { - return server as RegularServer; +const serverWithId = (server: ServerWithId | NewServerData): ServerWithId => { + if ((server as ServerWithId).id) { + return server as ServerWithId; } return assoc('id', uuid(), server); @@ -29,7 +29,7 @@ export default handleActions({ : assoc(serverId, { ...state[serverId], ...serverData }, state), }, initialState); -const serversListToMap = reduce((acc, server) => assoc(server.id, server, acc), {}); +const serversListToMap = reduce((acc, server) => assoc(server.id, server, acc), {}); export const createServers = pipe( map(serverWithId), @@ -37,7 +37,7 @@ export const createServers = pipe( (newServers: ServersMap) => ({ type: CREATE_SERVERS, newServers }), ); -export const createServer = (server: RegularServer) => createServers([ server ]); +export const createServer = (server: ServerWithId) => createServers([ server ]); export const editServer = (serverId: string, serverData: Partial) => ({ type: EDIT_SERVER, @@ -45,4 +45,4 @@ export const editServer = (serverId: string, serverData: Partial) serverData, }); -export const deleteServer = ({ id }: RegularServer) => ({ type: DELETE_SERVER, serverId: id }); +export const deleteServer = ({ id }: ServerWithId) => ({ type: DELETE_SERVER, serverId: id }); diff --git a/src/servers/services/ServersImporter.ts b/src/servers/services/ServersImporter.ts index 17ab38e3..2ffe8bda 100644 --- a/src/servers/services/ServersImporter.ts +++ b/src/servers/services/ServersImporter.ts @@ -1,12 +1,12 @@ import { CsvJson } from 'csvjson'; -import { RegularServer } from '../data'; +import { NewServerData } from '../data'; const CSV_MIME_TYPE = 'text/csv'; export default class ServersImporter { public constructor(private readonly csvjson: CsvJson, private readonly fileReaderFactory: () => FileReader) {} - public importServersFromFile = async (file?: File | null): Promise => { + public importServersFromFile = async (file?: File | null): Promise => { if (!file || file.type !== CSV_MIME_TYPE) { throw new Error('No file provided or file is not a CSV'); } @@ -16,7 +16,7 @@ export default class ServersImporter { return new Promise((resolve) => { reader.addEventListener('loadend', (e: ProgressEvent) => { const content = e.target?.result?.toString() ?? ''; - const servers = this.csvjson.toObject(content); + const servers = this.csvjson.toObject(content); resolve(servers); }); diff --git a/src/utils/services/types.ts b/src/utils/services/types.ts index c5f5a474..15e3972b 100644 --- a/src/utils/services/types.ts +++ b/src/utils/services/types.ts @@ -1,11 +1,16 @@ -import { RegularServer } from '../../servers/data'; +import { ServerWithId } from '../../servers/data'; import { GetState } from '../../container/types'; import ShlinkApiClient from './ShlinkApiClient'; // FIXME Move to ShlinkApiClientBuilder -export type ShlinkApiClientBuilder = (getStateOrSelectedServer: RegularServer | GetState) => ShlinkApiClient; +export type ShlinkApiClientBuilder = (getStateOrSelectedServer: ServerWithId | GetState) => ShlinkApiClient; export interface ShlinkMercureInfo { token: string; mercureHubUrl: string; } + +export interface ShlinkHealth { + status: 'pass' | 'fail'; + version: string; +} From 294888454df4005cfcf7da4383b4a4128694eefa Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 23 Aug 2020 10:52:37 +0200 Subject: [PATCH 16/59] Renamed NewServerData to ServerData, as it's used in other contexts too --- src/servers/CreateServer.tsx | 4 ++-- src/servers/data/index.ts | 2 +- src/servers/helpers/ImportServersBtn.tsx | 4 ++-- src/servers/reducers/servers.ts | 6 +++--- src/servers/services/ServersImporter.ts | 6 +++--- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/servers/CreateServer.tsx b/src/servers/CreateServer.tsx index dd140b3f..de171ac3 100644 --- a/src/servers/CreateServer.tsx +++ b/src/servers/CreateServer.tsx @@ -6,7 +6,7 @@ import NoMenuLayout from '../common/NoMenuLayout'; import { StateFlagTimeout } from '../utils/helpers/hooks'; import { ServerForm } from './helpers/ServerForm'; import { ImportServersBtnProps } from './helpers/ImportServersBtn'; -import { NewServerData, ServerWithId } from './data'; +import { ServerData, ServerWithId } from './data'; import './CreateServer.scss'; const SHOW_IMPORT_MSG_TIME = 4000; @@ -36,7 +36,7 @@ const CreateServer = (ImportServersBtn: FC, useStateFlagT ) => { const [ serversImported, setServersImported ] = useStateFlagTimeout(false, SHOW_IMPORT_MSG_TIME); const [ errorImporting, setErrorImporting ] = useStateFlagTimeout(false, SHOW_IMPORT_MSG_TIME); - const handleSubmit = (serverData: NewServerData) => { + const handleSubmit = (serverData: ServerData) => { const id = uuid(); createServer({ ...serverData, id }); diff --git a/src/servers/data/index.ts b/src/servers/data/index.ts index 97ade453..222b418e 100644 --- a/src/servers/data/index.ts +++ b/src/servers/data/index.ts @@ -1,4 +1,4 @@ -export interface NewServerData { +export interface ServerData { name: string; url: string; apiKey: string; diff --git a/src/servers/helpers/ImportServersBtn.tsx b/src/servers/helpers/ImportServersBtn.tsx index 95464210..41a7d465 100644 --- a/src/servers/helpers/ImportServersBtn.tsx +++ b/src/servers/helpers/ImportServersBtn.tsx @@ -1,7 +1,7 @@ import React, { useRef, RefObject, ChangeEvent, MutableRefObject } from 'react'; import { UncontrolledTooltip } from 'reactstrap'; import ServersImporter from '../services/ServersImporter'; -import { NewServerData } from '../data'; +import { ServerData } from '../data'; type Ref = RefObject | MutableRefObject; @@ -11,7 +11,7 @@ export interface ImportServersBtnProps { } interface ImportServersBtnConnectProps extends ImportServersBtnProps { - createServers: (servers: NewServerData[]) => void; + createServers: (servers: ServerData[]) => void; fileRef: Ref; } diff --git a/src/servers/reducers/servers.ts b/src/servers/reducers/servers.ts index 9d97dd8c..17fc71b2 100644 --- a/src/servers/reducers/servers.ts +++ b/src/servers/reducers/servers.ts @@ -1,7 +1,7 @@ import { handleActions } from 'redux-actions'; import { pipe, assoc, map, reduce, dissoc } from 'ramda'; import { v4 as uuid } from 'uuid'; -import { NewServerData, ServerWithId } from '../data'; +import { ServerData, ServerWithId } from '../data'; /* eslint-disable padding-line-between-statements */ export const EDIT_SERVER = 'shlink/servers/EDIT_SERVER'; @@ -13,7 +13,7 @@ export type ServersMap = Record; const initialState: ServersMap = {}; -const serverWithId = (server: ServerWithId | NewServerData): ServerWithId => { +const serverWithId = (server: ServerWithId | ServerData): ServerWithId => { if ((server as ServerWithId).id) { return server as ServerWithId; } @@ -39,7 +39,7 @@ export const createServers = pipe( export const createServer = (server: ServerWithId) => createServers([ server ]); -export const editServer = (serverId: string, serverData: Partial) => ({ +export const editServer = (serverId: string, serverData: Partial) => ({ type: EDIT_SERVER, serverId, serverData, diff --git a/src/servers/services/ServersImporter.ts b/src/servers/services/ServersImporter.ts index 2ffe8bda..8876d3fd 100644 --- a/src/servers/services/ServersImporter.ts +++ b/src/servers/services/ServersImporter.ts @@ -1,12 +1,12 @@ import { CsvJson } from 'csvjson'; -import { NewServerData } from '../data'; +import { ServerData } from '../data'; const CSV_MIME_TYPE = 'text/csv'; export default class ServersImporter { public constructor(private readonly csvjson: CsvJson, private readonly fileReaderFactory: () => FileReader) {} - public importServersFromFile = async (file?: File | null): Promise => { + public importServersFromFile = async (file?: File | null): Promise => { if (!file || file.type !== CSV_MIME_TYPE) { throw new Error('No file provided or file is not a CSV'); } @@ -16,7 +16,7 @@ export default class ServersImporter { return new Promise((resolve) => { reader.addEventListener('loadend', (e: ProgressEvent) => { const content = e.target?.result?.toString() ?? ''; - const servers = this.csvjson.toObject(content); + const servers = this.csvjson.toObject(content); resolve(servers); }); From 3e2fee0df5959be54cec3bb8f0db7509ff3bdf15 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 23 Aug 2020 10:58:43 +0200 Subject: [PATCH 17/59] Migrated selectedServer test to typescript --- ...tedServer.test.js => selectedServer.test.ts} | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) rename test/servers/reducers/{selectedServer.test.js => selectedServer.test.ts} (82%) diff --git a/test/servers/reducers/selectedServer.test.js b/test/servers/reducers/selectedServer.test.ts similarity index 82% rename from test/servers/reducers/selectedServer.test.js rename to test/servers/reducers/selectedServer.test.ts index 9307841f..57234462 100644 --- a/test/servers/reducers/selectedServer.test.js +++ b/test/servers/reducers/selectedServer.test.ts @@ -1,4 +1,5 @@ import { v4 as uuid } from 'uuid'; +import { Mock } from 'ts-mockery'; import reducer, { selectServer, resetSelectedServer, @@ -8,16 +9,18 @@ import reducer, { MIN_FALLBACK_VERSION, } from '../../../src/servers/reducers/selectedServer'; import { RESET_SHORT_URL_PARAMS } from '../../../src/short-urls/reducers/shortUrlsListParams'; +import { ShlinkState } from '../../../src/container/types'; +import { NonReachableServer, NotFoundServer } from '../../../src/servers/data'; describe('selectedServerReducer', () => { describe('reducer', () => { it('returns default when action is RESET_SELECTED_SERVER', () => - expect(reducer(null, { type: RESET_SELECTED_SERVER })).toEqual(null)); + expect(reducer(null, { type: RESET_SELECTED_SERVER } as any)).toEqual(null)); it('returns selected server when action is SELECT_SERVER', () => { const selectedServer = { id: 'abc123' }; - expect(reducer(null, { type: SELECT_SERVER, selectedServer })).toEqual(selectedServer); + expect(reducer(null, { type: SELECT_SERVER, selectedServer } as any)).toEqual(selectedServer); }); }); @@ -32,7 +35,7 @@ describe('selectedServerReducer', () => { id: 'abc123', }; const version = '1.19.0'; - const createGetStateMock = (id) => jest.fn().mockReturnValue({ servers: { [id]: selectedServer } }); + const createGetStateMock = (id: string) => jest.fn().mockReturnValue({ servers: { [id]: selectedServer } }); const apiClientMock = { health: jest.fn(), }; @@ -70,7 +73,7 @@ describe('selectedServerReducer', () => { const id = uuid(); const getState = createGetStateMock(id); - await selectServer(buildApiClient, loadMercureInfo)(id)(() => {}, getState); + await selectServer(buildApiClient, loadMercureInfo)(id)(jest.fn(), getState); expect(getState).toHaveBeenCalledTimes(1); expect(buildApiClient).toHaveBeenCalledTimes(1); @@ -79,7 +82,7 @@ describe('selectedServerReducer', () => { it('dispatches error when health endpoint fails', async () => { const id = uuid(); const getState = createGetStateMock(id); - const expectedSelectedServer = { ...selectedServer, serverNotReachable: true }; + const expectedSelectedServer: NonReachableServer = { ...selectedServer, serverNotReachable: true }; apiClientMock.health.mockRejectedValue({}); @@ -92,8 +95,8 @@ describe('selectedServerReducer', () => { it('dispatches error when server is not found', async () => { const id = uuid(); - const getState = jest.fn(() => ({ servers: {} })); - const expectedSelectedServer = { serverNotFound: true }; + const getState = jest.fn(() => Mock.of({ servers: {} })); + const expectedSelectedServer: NotFoundServer = { serverNotFound: true }; await selectServer(buildApiClient, loadMercureInfo)(id)(dispatch, getState); From 0b4a348969fe4c96f3f12fbe506f516c9b11d269 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 23 Aug 2020 11:58:43 +0200 Subject: [PATCH 18/59] Migrated remoteServers reducer to TS --- .../reducers/{remoteServers.js => remoteServers.ts} | 13 ++++++++----- ...{remoteServers.test.js => remoteServers.test.ts} | 9 ++++++--- 2 files changed, 14 insertions(+), 8 deletions(-) rename src/servers/reducers/{remoteServers.js => remoteServers.ts} (54%) rename test/servers/reducers/{remoteServers.test.js => remoteServers.test.ts} (86%) diff --git a/src/servers/reducers/remoteServers.js b/src/servers/reducers/remoteServers.ts similarity index 54% rename from src/servers/reducers/remoteServers.js rename to src/servers/reducers/remoteServers.ts index 675c36e7..316b03e9 100644 --- a/src/servers/reducers/remoteServers.js +++ b/src/servers/reducers/remoteServers.ts @@ -1,19 +1,22 @@ import { pipe, prop } from 'ramda'; +import { AxiosInstance } from 'axios'; +import { Dispatch } from 'redux'; import { homepage } from '../../../package.json'; +import { ServerData } from '../data'; import { createServers } from './servers'; const responseToServersList = pipe( - prop('data'), - (value) => { - if (!Array.isArray(value)) { + prop('data'), + (data: any): ServerData[] => { + if (!Array.isArray(data)) { throw new Error('Value is not an array'); } - return value; + return data as ServerData[]; }, ); -export const fetchServers = ({ get }) => () => async (dispatch) => { +export const fetchServers = ({ get }: AxiosInstance) => () => async (dispatch: Dispatch) => { const remoteList = await get(`${homepage}/servers.json`) .then(responseToServersList) .catch(() => []); diff --git a/test/servers/reducers/remoteServers.test.js b/test/servers/reducers/remoteServers.test.ts similarity index 86% rename from test/servers/reducers/remoteServers.test.js rename to test/servers/reducers/remoteServers.test.ts index ba4afc80..3468d1f2 100644 --- a/test/servers/reducers/remoteServers.test.js +++ b/test/servers/reducers/remoteServers.test.ts @@ -1,3 +1,5 @@ +import { Mock } from 'ts-mockery'; +import { AxiosInstance } from 'axios'; import { fetchServers } from '../../../src/servers/reducers/remoteServers'; import { CREATE_SERVERS } from '../../../src/servers/reducers/servers'; @@ -5,7 +7,8 @@ describe('remoteServersReducer', () => { afterEach(jest.resetAllMocks); describe('fetchServers', () => { - const axios = { get: jest.fn() }; + const get = jest.fn(); + const axios = Mock.of({ get }); const dispatch = jest.fn(); it.each([ @@ -44,12 +47,12 @@ describe('remoteServersReducer', () => { [ Promise.resolve(''), {}], [ Promise.reject({}), {}], ])('tries to fetch servers from remote', async (mockedValue, expectedList) => { - axios.get.mockReturnValue(mockedValue); + get.mockReturnValue(mockedValue); await fetchServers(axios)()(dispatch); expect(dispatch).toHaveBeenCalledWith({ type: CREATE_SERVERS, newServers: expectedList }); - expect(axios.get).toHaveBeenCalledTimes(1); + expect(get).toHaveBeenCalledTimes(1); }); }); }); From fefa4e78489e341e4421da70d739d82b27cff0d2 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Mon, 24 Aug 2020 17:32:20 +0200 Subject: [PATCH 19/59] Migrated settings module to TS --- src/container/types.ts | 3 ++- src/mercure/reducers/mercureInfo.ts | 4 +-- ...RealTimeUpdates.js => RealTimeUpdates.tsx} | 15 +++++------ src/settings/{Settings.js => Settings.tsx} | 4 +-- src/settings/reducers/settings.js | 25 ----------------- src/settings/reducers/settings.ts | 27 +++++++++++++++++++ .../{settings.test.js => settings.test.ts} | 2 +- 7 files changed, 40 insertions(+), 40 deletions(-) rename src/settings/{RealTimeUpdates.js => RealTimeUpdates.tsx} (68%) rename src/settings/{Settings.js => Settings.tsx} (62%) delete mode 100644 src/settings/reducers/settings.js create mode 100644 src/settings/reducers/settings.ts rename test/settings/reducers/{settings.test.js => settings.test.ts} (84%) diff --git a/src/container/types.ts b/src/container/types.ts index c8fd2ff7..7a644498 100644 --- a/src/container/types.ts +++ b/src/container/types.ts @@ -1,6 +1,7 @@ import { MercureInfo } from '../mercure/reducers/mercureInfo'; import { ServersMap } from '../servers/reducers/servers'; import { SelectedServer } from '../servers/data'; +import { Settings } from '../settings/reducers/settings'; export type ConnectDecorator = (props: string[], actions?: string[]) => any; @@ -21,7 +22,7 @@ export interface ShlinkState { tagDelete: any; tagEdit: any; mercureInfo: MercureInfo; - settings: any; + settings: Settings; } export type GetState = () => ShlinkState; diff --git a/src/mercure/reducers/mercureInfo.ts b/src/mercure/reducers/mercureInfo.ts index a5e251fc..cb7faa24 100644 --- a/src/mercure/reducers/mercureInfo.ts +++ b/src/mercure/reducers/mercureInfo.ts @@ -1,4 +1,4 @@ -import { handleActions } from 'redux-actions'; +import { Action, handleActions } from 'redux-actions'; import PropTypes from 'prop-types'; import { Dispatch } from 'redux'; import { ShlinkApiClientBuilder, ShlinkMercureInfo } from '../../utils/services/types'; @@ -52,7 +52,7 @@ export const loadMercureInfo = (buildShlinkApiClient: ShlinkApiClientBuilder) => try { const payload = await mercureInfo(); - dispatch({ type: GET_MERCURE_INFO, payload }); + dispatch>({ type: GET_MERCURE_INFO, payload }); } catch (e) { dispatch({ type: GET_MERCURE_INFO_ERROR }); } diff --git a/src/settings/RealTimeUpdates.js b/src/settings/RealTimeUpdates.tsx similarity index 68% rename from src/settings/RealTimeUpdates.js rename to src/settings/RealTimeUpdates.tsx index d2de544a..dc89f1d5 100644 --- a/src/settings/RealTimeUpdates.js +++ b/src/settings/RealTimeUpdates.tsx @@ -1,15 +1,14 @@ import React from 'react'; import { Card, CardBody, CardHeader } from 'reactstrap'; -import PropTypes from 'prop-types'; import ToggleSwitch from '../utils/ToggleSwitch'; -import { SettingsType } from './reducers/settings'; +import { Settings } from './reducers/settings'; -const propTypes = { - settings: SettingsType, - setRealTimeUpdates: PropTypes.func, -}; +interface RealTimeUpdatesProps { + settings: Settings; + setRealTimeUpdates: (enabled: boolean) => void; +} -const RealTimeUpdates = ({ settings: { realTimeUpdates }, setRealTimeUpdates }) => ( +const RealTimeUpdates = ({ settings: { realTimeUpdates }, setRealTimeUpdates }: RealTimeUpdatesProps) => ( Real-time updates @@ -20,6 +19,4 @@ const RealTimeUpdates = ({ settings: { realTimeUpdates }, setRealTimeUpdates }) ); -RealTimeUpdates.propTypes = propTypes; - export default RealTimeUpdates; diff --git a/src/settings/Settings.js b/src/settings/Settings.tsx similarity index 62% rename from src/settings/Settings.js rename to src/settings/Settings.tsx index 81b662bd..10871290 100644 --- a/src/settings/Settings.js +++ b/src/settings/Settings.tsx @@ -1,7 +1,7 @@ -import React from 'react'; +import React, { FC } from 'react'; import NoMenuLayout from '../common/NoMenuLayout'; -const Settings = (RealTimeUpdates) => () => ( +const Settings = (RealTimeUpdates: FC) => () => ( diff --git a/src/settings/reducers/settings.js b/src/settings/reducers/settings.js deleted file mode 100644 index 6771dbb5..00000000 --- a/src/settings/reducers/settings.js +++ /dev/null @@ -1,25 +0,0 @@ -import { handleActions } from 'redux-actions'; -import PropTypes from 'prop-types'; - -export const SET_REAL_TIME_UPDATES = 'shlink/realTimeUpdates/SET_REAL_TIME_UPDATES'; - -export const SettingsType = PropTypes.shape({ - realTimeUpdates: PropTypes.shape({ - enabled: PropTypes.bool.isRequired, - }), -}); - -const initialState = { - realTimeUpdates: { - enabled: true, - }, -}; - -export default handleActions({ - [SET_REAL_TIME_UPDATES]: (state, { realTimeUpdates }) => ({ ...state, realTimeUpdates }), -}, initialState); - -export const setRealTimeUpdates = (enabled) => ({ - type: SET_REAL_TIME_UPDATES, - realTimeUpdates: { enabled }, -}); diff --git a/src/settings/reducers/settings.ts b/src/settings/reducers/settings.ts new file mode 100644 index 00000000..e2409222 --- /dev/null +++ b/src/settings/reducers/settings.ts @@ -0,0 +1,27 @@ +import { handleActions } from 'redux-actions'; +import { Action } from 'redux'; + +export const SET_REAL_TIME_UPDATES = 'shlink/realTimeUpdates/SET_REAL_TIME_UPDATES'; + +interface RealTimeUpdates { + enabled: boolean; +} + +export interface Settings { + realTimeUpdates: RealTimeUpdates; +} + +const initialState: Settings = { + realTimeUpdates: { + enabled: true, + }, +}; + +export default handleActions({ + [SET_REAL_TIME_UPDATES]: (state, { realTimeUpdates }: any) => ({ ...state, realTimeUpdates }), +}, initialState); + +export const setRealTimeUpdates = (enabled: boolean): Action & Settings => ({ + type: SET_REAL_TIME_UPDATES, + realTimeUpdates: { enabled }, +}); diff --git a/test/settings/reducers/settings.test.js b/test/settings/reducers/settings.test.ts similarity index 84% rename from test/settings/reducers/settings.test.js rename to test/settings/reducers/settings.test.ts index 80f22c49..2d52bdd8 100644 --- a/test/settings/reducers/settings.test.js +++ b/test/settings/reducers/settings.test.ts @@ -5,7 +5,7 @@ describe('settingsReducer', () => { describe('reducer', () => { it('returns realTimeUpdates when action is SET_REAL_TIME_UPDATES', () => { - expect(reducer({}, { type: SET_REAL_TIME_UPDATES, realTimeUpdates })).toEqual({ realTimeUpdates }); + expect(reducer(undefined, { type: SET_REAL_TIME_UPDATES, realTimeUpdates } as any)).toEqual({ realTimeUpdates }); }); }); From d8f3952920144f5f2f9e69f5743a0786c7418a1a Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Mon, 24 Aug 2020 18:52:52 +0200 Subject: [PATCH 20/59] Migrated first short URL reducers to typescript --- .eslintrc | 3 +- src/container/types.ts | 6 ++- src/short-urls/data/index.ts | 29 ++++++++++++++ ...hortUrlCreation.js => shortUrlCreation.ts} | 24 +++++++++--- .../{shortUrlMeta.js => shortUrlMeta.ts} | 38 ++++++++++++++++--- src/short-urls/reducers/shortUrlsList.js | 9 ++++- src/utils/utils.ts | 4 ++ ...ation.test.js => shortUrlCreation.test.ts} | 33 +++++++++------- ...rtUrlMeta.test.js => shortUrlMeta.test.ts} | 23 +++++++---- 9 files changed, 134 insertions(+), 35 deletions(-) create mode 100644 src/short-urls/data/index.ts rename src/short-urls/reducers/{shortUrlCreation.js => shortUrlCreation.ts} (62%) rename src/short-urls/reducers/{shortUrlMeta.js => shortUrlMeta.ts} (55%) rename test/short-urls/reducers/{shortUrlCreation.test.js => shortUrlCreation.test.ts} (69%) rename test/short-urls/reducers/{shortUrlMeta.test.js => shortUrlMeta.test.ts} (78%) diff --git a/.eslintrc b/.eslintrc index f50c9716..ef7e0b47 100644 --- a/.eslintrc +++ b/.eslintrc @@ -64,7 +64,8 @@ "max-len": ["error", { "code": 120, "ignoreStrings": true, - "ignoreTemplateLiterals": true + "ignoreTemplateLiterals": true, + "ignoreTrailingComments": true }], "no-mixed-operators": "off", "react/display-name": "off" diff --git a/src/container/types.ts b/src/container/types.ts index 7a644498..618826dd 100644 --- a/src/container/types.ts +++ b/src/container/types.ts @@ -2,6 +2,8 @@ import { MercureInfo } from '../mercure/reducers/mercureInfo'; import { ServersMap } from '../servers/reducers/servers'; import { SelectedServer } from '../servers/data'; import { Settings } from '../settings/reducers/settings'; +import { ShortUrlMetaEdition } from '../short-urls/reducers/shortUrlMeta'; +import { ShortUrlCreation } from '../short-urls/reducers/shortUrlCreation'; export type ConnectDecorator = (props: string[], actions?: string[]) => any; @@ -10,10 +12,10 @@ export interface ShlinkState { selectedServer: SelectedServer; shortUrlsList: any; shortUrlsListParams: any; - shortUrlCreationResult: any; + shortUrlCreationResult: ShortUrlCreation; shortUrlDeletion: any; shortUrlTags: any; - shortUrlMeta: any; + shortUrlMeta: ShortUrlMetaEdition; shortUrlEdition: any; shortUrlVisits: any; tagVisits: any; diff --git a/src/short-urls/data/index.ts b/src/short-urls/data/index.ts new file mode 100644 index 00000000..7be9ac1a --- /dev/null +++ b/src/short-urls/data/index.ts @@ -0,0 +1,29 @@ +import { Nullable } from '../../utils/utils'; + +export interface ShortUrlData { + longUrl: string; + tags?: string[]; + customSlug?: string; + shortCodeLength?: number; + domain?: string; + validSince?: string; + validUntil?: string; + maxVisits?: number; + findIfExists?: boolean; +} + +export interface ShortUrl { + shortCode: string; + shortUrl: string; + longUrl: string; + visitsCount: number; + meta: Required>; + tags: string[]; + domain: string | null; +} + +export interface ShortUrlMeta { + validSince?: string; + validUntil?: string; + maxVisits?: number; +} diff --git a/src/short-urls/reducers/shortUrlCreation.js b/src/short-urls/reducers/shortUrlCreation.ts similarity index 62% rename from src/short-urls/reducers/shortUrlCreation.js rename to src/short-urls/reducers/shortUrlCreation.ts index 071e3d28..878dd143 100644 --- a/src/short-urls/reducers/shortUrlCreation.js +++ b/src/short-urls/reducers/shortUrlCreation.ts @@ -1,5 +1,9 @@ import PropTypes from 'prop-types'; import { createAction, handleActions } from 'redux-actions'; +import { Action, Dispatch } from 'redux'; +import { ShlinkApiClientBuilder } from '../../utils/services/types'; +import { GetState } from '../../container/types'; +import { ShortUrl, ShortUrlData } from '../data'; /* eslint-disable padding-line-between-statements */ export const CREATE_SHORT_URL_START = 'shlink/createShortUrl/CREATE_SHORT_URL_START'; @@ -8,6 +12,7 @@ export const CREATE_SHORT_URL = 'shlink/createShortUrl/CREATE_SHORT_URL'; export const RESET_CREATE_SHORT_URL = 'shlink/createShortUrl/RESET_CREATE_SHORT_URL'; /* eslint-enable padding-line-between-statements */ +/** @deprecated Use ShortUrlCreation interface instead */ export const createShortUrlResultType = PropTypes.shape({ result: PropTypes.shape({ shortUrl: PropTypes.string, @@ -16,27 +21,36 @@ export const createShortUrlResultType = PropTypes.shape({ error: PropTypes.bool, }); -const initialState = { +export interface ShortUrlCreation { + result: ShortUrl | null; + saving: boolean; + error: boolean; +} + +const initialState: ShortUrlCreation = { result: null, saving: false, error: false, }; -export default handleActions({ +export default handleActions({ [CREATE_SHORT_URL_START]: (state) => ({ ...state, saving: true, error: false }), [CREATE_SHORT_URL_ERROR]: (state) => ({ ...state, saving: false, error: true }), - [CREATE_SHORT_URL]: (state, { result }) => ({ result, saving: false, error: false }), + [CREATE_SHORT_URL]: (_, { result }: any) => ({ result, saving: false, error: false }), [RESET_CREATE_SHORT_URL]: () => initialState, }, initialState); -export const createShortUrl = (buildShlinkApiClient) => (data) => async (dispatch, getState) => { +export const createShortUrl = (buildShlinkApiClient: ShlinkApiClientBuilder) => (data: ShortUrlData) => async ( + dispatch: Dispatch, + getState: GetState, +) => { dispatch({ type: CREATE_SHORT_URL_START }); const { createShortUrl } = buildShlinkApiClient(getState); try { const result = await createShortUrl(data); - dispatch({ type: CREATE_SHORT_URL, result }); + dispatch({ type: CREATE_SHORT_URL, result }); } catch (e) { dispatch({ type: CREATE_SHORT_URL_ERROR }); diff --git a/src/short-urls/reducers/shortUrlMeta.js b/src/short-urls/reducers/shortUrlMeta.ts similarity index 55% rename from src/short-urls/reducers/shortUrlMeta.js rename to src/short-urls/reducers/shortUrlMeta.ts index 7582414f..aafdaab0 100644 --- a/src/short-urls/reducers/shortUrlMeta.js +++ b/src/short-urls/reducers/shortUrlMeta.ts @@ -1,5 +1,9 @@ -import { createAction, handleActions } from 'redux-actions'; +import { createAction, handleActions, Action } from 'redux-actions'; import PropTypes from 'prop-types'; +import { Dispatch } from 'redux'; +import { ShortUrlMeta } from '../data'; +import { ShlinkApiClientBuilder } from '../../utils/services/types'; +import { GetState } from '../../container/types'; /* eslint-disable padding-line-between-statements */ export const EDIT_SHORT_URL_META_START = 'shlink/shortUrlMeta/EDIT_SHORT_URL_META_START'; @@ -8,12 +12,14 @@ export const SHORT_URL_META_EDITED = 'shlink/shortUrlMeta/SHORT_URL_META_EDITED' export const RESET_EDIT_SHORT_URL_META = 'shlink/shortUrlMeta/RESET_EDIT_SHORT_URL_META'; /* eslint-enable padding-line-between-statements */ +/** @deprecated Use ShortUrlMeta interface instead */ export const shortUrlMetaType = PropTypes.shape({ validSince: PropTypes.string, validUntil: PropTypes.string, maxVisits: PropTypes.number, }); +/** @deprecated Use ShortUrlMetaEdition interface instead */ export const shortUrlEditMetaType = PropTypes.shape({ shortCode: PropTypes.string, meta: shortUrlMetaType.isRequired, @@ -21,27 +27,47 @@ export const shortUrlEditMetaType = PropTypes.shape({ error: PropTypes.bool.isRequired, }); -const initialState = { +export interface ShortUrlMetaEdition { + shortCode: string | null; + meta: ShortUrlMeta; + saving: boolean; + error: boolean; +} + +interface ShortUrlMetaEditedAction { + shortCode: string; + domain?: string | null; + meta: ShortUrlMeta; +} + +const initialState: ShortUrlMetaEdition = { shortCode: null, meta: {}, saving: false, error: false, }; -export default handleActions({ +export default handleActions({ [EDIT_SHORT_URL_META_START]: (state) => ({ ...state, saving: true, error: false }), [EDIT_SHORT_URL_META_ERROR]: (state) => ({ ...state, saving: false, error: true }), - [SHORT_URL_META_EDITED]: (state, { shortCode, meta }) => ({ shortCode, meta, saving: false, error: false }), + [SHORT_URL_META_EDITED]: (_, { payload }) => ({ ...payload, saving: false, error: false }), [RESET_EDIT_SHORT_URL_META]: () => initialState, }, initialState); -export const editShortUrlMeta = (buildShlinkApiClient) => (shortCode, domain, meta) => async (dispatch, getState) => { +export const editShortUrlMeta = (buildShlinkApiClient: ShlinkApiClientBuilder) => ( + shortCode: string, + domain: string | null | undefined, + meta: ShortUrlMeta, +) => async (dispatch: Dispatch, getState: GetState) => { dispatch({ type: EDIT_SHORT_URL_META_START }); const { updateShortUrlMeta } = buildShlinkApiClient(getState); try { await updateShortUrlMeta(shortCode, domain, meta); - dispatch({ shortCode, meta, domain, type: SHORT_URL_META_EDITED }); + dispatch>({ + type: SHORT_URL_META_EDITED, + payload: { shortCode, meta, domain }, + }); } catch (e) { dispatch({ type: EDIT_SHORT_URL_META_ERROR }); diff --git a/src/short-urls/reducers/shortUrlsList.js b/src/short-urls/reducers/shortUrlsList.js index 90d2b1c8..26f35aef 100644 --- a/src/short-urls/reducers/shortUrlsList.js +++ b/src/short-urls/reducers/shortUrlsList.js @@ -30,6 +30,7 @@ const initialState = { error: false, }; +// TODO Make all actions fetch shortCode, domain and prop from payload const setPropFromActionOnMatchingShortUrl = (prop) => (state, { shortCode, domain, [prop]: propValue }) => assocPath( [ 'shortUrls', 'data' ], state.shortUrls.data.map( @@ -48,7 +49,13 @@ export default handleActions({ state, ), [SHORT_URL_TAGS_EDITED]: setPropFromActionOnMatchingShortUrl('tags'), - [SHORT_URL_META_EDITED]: setPropFromActionOnMatchingShortUrl('meta'), + [SHORT_URL_META_EDITED]: (state, { payload: { shortCode, domain, meta } }) => assocPath( + [ 'shortUrls', 'data' ], + state.shortUrls.data.map( + (shortUrl) => shortUrlMatches(shortUrl, shortCode, domain) ? assoc('meta', meta, shortUrl) : shortUrl, + ), + state, + ), [SHORT_URL_EDITED]: setPropFromActionOnMatchingShortUrl('longUrl'), [CREATE_VISIT]: (state, { shortUrl: { shortCode, domain, visitsCount } }) => assocPath( [ 'shortUrls', 'data' ], diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 27521f1e..c968bf45 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -21,3 +21,7 @@ export const rangeOf = (size: number, mappingFn: (value: number) => T, startA export type Empty = null | undefined | '' | never[]; export const hasValue = (value: T | Empty): value is T => !isNil(value) && !isEmpty(value); + +export type Nullable = { + [P in keyof T]: T[P] | null +}; diff --git a/test/short-urls/reducers/shortUrlCreation.test.js b/test/short-urls/reducers/shortUrlCreation.test.ts similarity index 69% rename from test/short-urls/reducers/shortUrlCreation.test.js rename to test/short-urls/reducers/shortUrlCreation.test.ts index 0ebacbe5..22e24419 100644 --- a/test/short-urls/reducers/shortUrlCreation.test.js +++ b/test/short-urls/reducers/shortUrlCreation.test.ts @@ -1,3 +1,4 @@ +import { Mock } from 'ts-mockery'; import reducer, { CREATE_SHORT_URL_START, CREATE_SHORT_URL_ERROR, @@ -6,33 +7,40 @@ import reducer, { createShortUrl, resetCreateShortUrl, } from '../../../src/short-urls/reducers/shortUrlCreation'; +import { ShortUrl } from '../../../src/short-urls/data'; +import ShlinkApiClient from '../../../src/utils/services/ShlinkApiClient'; +import { ShlinkState } from '../../../src/container/types'; describe('shortUrlCreationReducer', () => { + const shortUrl = Mock.all(); + describe('reducer', () => { it('returns loading on CREATE_SHORT_URL_START', () => { - expect(reducer({}, { type: CREATE_SHORT_URL_START })).toEqual({ + expect(reducer(undefined, { type: CREATE_SHORT_URL_START } as any)).toEqual({ + result: null, saving: true, error: false, }); }); it('returns error on CREATE_SHORT_URL_ERROR', () => { - expect(reducer({}, { type: CREATE_SHORT_URL_ERROR })).toEqual({ + expect(reducer(undefined, { type: CREATE_SHORT_URL_ERROR } as any)).toEqual({ + result: null, saving: false, error: true, }); }); it('returns result on CREATE_SHORT_URL', () => { - expect(reducer({}, { type: CREATE_SHORT_URL, result: 'foo' })).toEqual({ + expect(reducer(undefined, { type: CREATE_SHORT_URL, result: shortUrl } as any)).toEqual({ + result: shortUrl, saving: false, error: false, - result: 'foo', }); }); it('returns default state on RESET_CREATE_SHORT_URL', () => { - expect(reducer({}, { type: RESET_CREATE_SHORT_URL })).toEqual({ + expect(reducer(undefined, { type: RESET_CREATE_SHORT_URL } as any)).toEqual({ result: null, saving: false, error: false, @@ -46,31 +54,30 @@ describe('shortUrlCreationReducer', () => { }); describe('createShortUrl', () => { - const createApiClientMock = (result) => ({ - createShortUrl: jest.fn(() => result), + const createApiClientMock = (result: Promise) => Mock.of({ + createShortUrl: jest.fn().mockReturnValue(result), }); const dispatch = jest.fn(); - const getState = () => ({}); + const getState = () => Mock.all(); afterEach(jest.resetAllMocks); it('calls API on success', async () => { - const result = 'foo'; - const apiClientMock = createApiClientMock(Promise.resolve(result)); - const dispatchable = createShortUrl(() => apiClientMock)({}); + const apiClientMock = createApiClientMock(Promise.resolve(shortUrl)); + const dispatchable = createShortUrl(() => apiClientMock)({ longUrl: 'foo' }); await dispatchable(dispatch, getState); expect(apiClientMock.createShortUrl).toHaveBeenCalledTimes(1); expect(dispatch).toHaveBeenCalledTimes(2); expect(dispatch).toHaveBeenNthCalledWith(1, { type: CREATE_SHORT_URL_START }); - expect(dispatch).toHaveBeenNthCalledWith(2, { type: CREATE_SHORT_URL, result }); + expect(dispatch).toHaveBeenNthCalledWith(2, { type: CREATE_SHORT_URL, result: shortUrl }); }); it('throws on error', async () => { const error = 'Error'; const apiClientMock = createApiClientMock(Promise.reject(error)); - const dispatchable = createShortUrl(() => apiClientMock)({}); + const dispatchable = createShortUrl(() => apiClientMock)({ longUrl: 'foo' }); expect.assertions(5); diff --git a/test/short-urls/reducers/shortUrlMeta.test.js b/test/short-urls/reducers/shortUrlMeta.test.ts similarity index 78% rename from test/short-urls/reducers/shortUrlMeta.test.js rename to test/short-urls/reducers/shortUrlMeta.test.ts index a02b385a..d7c6e409 100644 --- a/test/short-urls/reducers/shortUrlMeta.test.js +++ b/test/short-urls/reducers/shortUrlMeta.test.ts @@ -1,4 +1,5 @@ import moment from 'moment'; +import { Mock } from 'ts-mockery'; import reducer, { EDIT_SHORT_URL_META_START, EDIT_SHORT_URL_META_ERROR, @@ -7,6 +8,7 @@ import reducer, { editShortUrlMeta, resetShortUrlMeta, } from '../../../src/short-urls/reducers/shortUrlMeta'; +import { ShlinkState } from '../../../src/container/types'; describe('shortUrlMetaReducer', () => { const meta = { @@ -17,21 +19,25 @@ describe('shortUrlMetaReducer', () => { describe('reducer', () => { it('returns loading on EDIT_SHORT_URL_META_START', () => { - expect(reducer({}, { type: EDIT_SHORT_URL_META_START })).toEqual({ + expect(reducer(undefined, { type: EDIT_SHORT_URL_META_START } as any)).toEqual({ + meta: {}, + shortCode: null, saving: true, error: false, }); }); it('returns error on EDIT_SHORT_URL_META_ERROR', () => { - expect(reducer({}, { type: EDIT_SHORT_URL_META_ERROR })).toEqual({ + expect(reducer(undefined, { type: EDIT_SHORT_URL_META_ERROR } as any)).toEqual({ + meta: {}, + shortCode: null, saving: false, error: true, }); }); it('returns provided tags and shortCode on SHORT_URL_META_EDITED', () => { - expect(reducer({}, { type: SHORT_URL_META_EDITED, meta, shortCode })).toEqual({ + expect(reducer(undefined, { type: SHORT_URL_META_EDITED, payload: { meta, shortCode } })).toEqual({ meta, shortCode, saving: false, @@ -40,7 +46,7 @@ describe('shortUrlMetaReducer', () => { }); it('goes back to initial state on RESET_EDIT_SHORT_URL_META', () => { - expect(reducer({}, { type: RESET_EDIT_SHORT_URL_META })).toEqual({ + expect(reducer(undefined, { type: RESET_EDIT_SHORT_URL_META } as any)).toEqual({ meta: {}, shortCode: null, saving: false, @@ -53,18 +59,21 @@ describe('shortUrlMetaReducer', () => { const updateShortUrlMeta = jest.fn().mockResolvedValue({}); const buildShlinkApiClient = jest.fn().mockReturnValue({ updateShortUrlMeta }); const dispatch = jest.fn(); + const getState = () => Mock.all(); afterEach(jest.clearAllMocks); it.each([[ undefined ], [ null ], [ 'example.com' ]])('dispatches metadata on success', async (domain) => { - await editShortUrlMeta(buildShlinkApiClient)(shortCode, domain, meta)(dispatch); + const payload = { meta, shortCode, domain }; + + await editShortUrlMeta(buildShlinkApiClient)(shortCode, domain, meta)(dispatch, getState); expect(buildShlinkApiClient).toHaveBeenCalledTimes(1); expect(updateShortUrlMeta).toHaveBeenCalledTimes(1); expect(updateShortUrlMeta).toHaveBeenCalledWith(shortCode, domain, meta); expect(dispatch).toHaveBeenCalledTimes(2); expect(dispatch).toHaveBeenNthCalledWith(1, { type: EDIT_SHORT_URL_META_START }); - expect(dispatch).toHaveBeenNthCalledWith(2, { type: SHORT_URL_META_EDITED, meta, shortCode, domain }); + expect(dispatch).toHaveBeenNthCalledWith(2, { type: SHORT_URL_META_EDITED, payload }); }); it('dispatches error on failure', async () => { @@ -73,7 +82,7 @@ describe('shortUrlMetaReducer', () => { updateShortUrlMeta.mockRejectedValue(error); try { - await editShortUrlMeta(buildShlinkApiClient)(shortCode, undefined, meta)(dispatch); + await editShortUrlMeta(buildShlinkApiClient)(shortCode, undefined, meta)(dispatch, getState); } catch (e) { expect(e).toBe(error); } From f04aece7dfcd6782d37c559293a3ea302785fcd4 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Tue, 25 Aug 2020 19:41:48 +0200 Subject: [PATCH 21/59] Removed dependency on redux-actions for all reducers already migrated to typescript --- package-lock.json | 6 ---- package.json | 1 - src/mercure/reducers/mercureInfo.ts | 14 +++++---- src/servers/reducers/selectedServer.ts | 29 +++++++++++-------- src/servers/reducers/servers.ts | 11 +++++-- src/settings/reducers/settings.ts | 10 ++++--- src/short-urls/reducers/shortUrlCreation.ts | 14 +++++---- src/short-urls/reducers/shortUrlMeta.ts | 17 +++++------ src/short-urls/reducers/shortUrlsList.js | 8 +---- src/utils/helpers/redux.ts | 17 +++++++++++ test/mercure/reducers/mercureInfo.test.ts | 14 +++++---- test/servers/reducers/selectedServer.test.ts | 8 ++--- test/settings/reducers/settings.test.ts | 2 +- .../reducers/shortUrlCreation.test.ts | 16 ++++++---- test/short-urls/reducers/shortUrlMeta.test.ts | 6 ++-- 15 files changed, 99 insertions(+), 74 deletions(-) create mode 100644 src/utils/helpers/redux.ts diff --git a/package-lock.json b/package-lock.json index a644d0e7..e67e9e4e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3441,12 +3441,6 @@ "popper.js": "^1.14.1" } }, - "@types/redux-actions": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/@types/redux-actions/-/redux-actions-2.6.1.tgz", - "integrity": "sha512-zKgK+ATp3sswXs6sOYo1tk8xdXTy4CTaeeYrVQlClCjeOpag5vzPo0ASWiiBJ7vsiQRAdb3VkuFLnDoBimF67g==", - "dev": true - }, "@types/stack-utils": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz", diff --git a/package.json b/package.json index e82a37e8..8454ae72 100644 --- a/package.json +++ b/package.json @@ -86,7 +86,6 @@ "@types/react-redux": "^7.1.9", "@types/react-router-dom": "^5.1.5", "@types/reactstrap": "^8.5.1", - "@types/redux-actions": "^2.6.1", "@types/uuid": "^8.3.0", "adm-zip": "^0.4.13", "autoprefixer": "^9.6.3", diff --git a/src/mercure/reducers/mercureInfo.ts b/src/mercure/reducers/mercureInfo.ts index cb7faa24..396506c7 100644 --- a/src/mercure/reducers/mercureInfo.ts +++ b/src/mercure/reducers/mercureInfo.ts @@ -1,8 +1,8 @@ -import { Action, handleActions } from 'redux-actions'; import PropTypes from 'prop-types'; -import { Dispatch } from 'redux'; +import { Action, Dispatch } from 'redux'; import { ShlinkApiClientBuilder, ShlinkMercureInfo } from '../../utils/services/types'; import { GetState } from '../../container/types'; +import { buildReducer } from '../../utils/helpers/redux'; /* eslint-disable padding-line-between-statements */ export const GET_MERCURE_INFO_START = 'shlink/mercure/GET_MERCURE_INFO_START'; @@ -25,15 +25,17 @@ export interface MercureInfo { error: boolean; } +export type GetMercureInfoAction = Action & ShlinkMercureInfo; + const initialState: MercureInfo = { loading: true, error: false, }; -export default handleActions({ +export default buildReducer({ [GET_MERCURE_INFO_START]: (state) => ({ ...state, loading: true, error: false }), [GET_MERCURE_INFO_ERROR]: (state) => ({ ...state, loading: false, error: true }), - [GET_MERCURE_INFO]: (_, { payload }) => ({ ...payload, loading: false, error: false }), + [GET_MERCURE_INFO]: (_, { token, mercureHubUrl }) => ({ token, mercureHubUrl, loading: false, error: false }), }, initialState); export const loadMercureInfo = (buildShlinkApiClient: ShlinkApiClientBuilder) => @@ -50,9 +52,9 @@ export const loadMercureInfo = (buildShlinkApiClient: ShlinkApiClientBuilder) => } try { - const payload = await mercureInfo(); + const result = await mercureInfo(); - dispatch>({ type: GET_MERCURE_INFO, payload }); + dispatch>({ type: GET_MERCURE_INFO, ...result }); } catch (e) { dispatch({ type: GET_MERCURE_INFO_ERROR }); } diff --git a/src/servers/reducers/selectedServer.ts b/src/servers/reducers/selectedServer.ts index 2848280d..cf0583fd 100644 --- a/src/servers/reducers/selectedServer.ts +++ b/src/servers/reducers/selectedServer.ts @@ -1,11 +1,11 @@ -import { createAction, handleActions } from 'redux-actions'; import { identity, memoizeWith, pipe } from 'ramda'; import { Action, Dispatch } from 'redux'; import { resetShortUrlParams } from '../../short-urls/reducers/shortUrlsListParams'; import { versionToPrintable, versionToSemVer as toSemVer } from '../../utils/helpers/version'; -import { NonReachableServer, NotFoundServer, ReachableServer, SelectedServer } from '../data'; +import { SelectedServer } from '../data'; import { GetState } from '../../container/types'; import { ShlinkApiClientBuilder, ShlinkHealth } from '../../utils/services/types'; +import { buildActionCreator, buildReducer } from '../../utils/helpers/redux'; /* eslint-disable padding-line-between-statements */ export const SELECT_SERVER = 'shlink/selectedServer/SELECT_SERVER'; @@ -16,7 +16,10 @@ export const MAX_FALLBACK_VERSION = '999.999.999'; export const LATEST_VERSION_CONSTRAINT = 'latest'; /* eslint-enable padding-line-between-statements */ -const initialState: SelectedServer = null; +export interface SelectServerAction extends Action { + selectedServer: SelectedServer; +} + const versionToSemVer = pipe( (version: string) => version === LATEST_VERSION_CONSTRAINT ? MAX_FALLBACK_VERSION : version, toSemVer(MIN_FALLBACK_VERSION), @@ -30,7 +33,14 @@ const getServerVersion = memoizeWith( })), ); -export const resetSelectedServer = createAction(RESET_SELECTED_SERVER); +const initialState: SelectedServer = null; + +export default buildReducer({ + [RESET_SELECTED_SERVER]: () => initialState, + [SELECT_SERVER]: (_, { selectedServer }) => selectedServer, +}, initialState); + +export const resetSelectedServer = buildActionCreator(RESET_SELECTED_SERVER); export const selectServer = ( buildShlinkApiClient: ShlinkApiClientBuilder, @@ -48,7 +58,7 @@ export const selectServer = ( const selectedServer = servers[serverId]; if (!selectedServer) { - dispatch({ + dispatch({ type: SELECT_SERVER, selectedServer: { serverNotFound: true }, }); @@ -60,7 +70,7 @@ export const selectServer = ( const { health } = buildShlinkApiClient(selectedServer); const { version, printableVersion } = await getServerVersion(serverId, health); - dispatch({ + dispatch({ type: SELECT_SERVER, selectedServer: { ...selectedServer, @@ -70,14 +80,9 @@ export const selectServer = ( }); dispatch(loadMercureInfo()); } catch (e) { - dispatch({ + dispatch({ type: SELECT_SERVER, selectedServer: { ...selectedServer, serverNotReachable: true }, }); } }; - -export default handleActions({ - [RESET_SELECTED_SERVER]: () => initialState, - [SELECT_SERVER]: (_, { selectedServer }: any) => selectedServer, -}, initialState); diff --git a/src/servers/reducers/servers.ts b/src/servers/reducers/servers.ts index 17fc71b2..2312b0e5 100644 --- a/src/servers/reducers/servers.ts +++ b/src/servers/reducers/servers.ts @@ -1,7 +1,8 @@ -import { handleActions } from 'redux-actions'; import { pipe, assoc, map, reduce, dissoc } from 'ramda'; import { v4 as uuid } from 'uuid'; +import { Action } from 'redux'; import { ServerData, ServerWithId } from '../data'; +import { buildReducer } from '../../utils/helpers/redux'; /* eslint-disable padding-line-between-statements */ export const EDIT_SERVER = 'shlink/servers/EDIT_SERVER'; @@ -11,6 +12,10 @@ export const CREATE_SERVERS = 'shlink/servers/CREATE_SERVERS'; export type ServersMap = Record; +export interface CreateServersAction extends Action { + newServers: ServersMap; +} + const initialState: ServersMap = {}; const serverWithId = (server: ServerWithId | ServerData): ServerWithId => { @@ -21,8 +26,8 @@ const serverWithId = (server: ServerWithId | ServerData): ServerWithId => { return assoc('id', uuid(), server); }; -export default handleActions({ - [CREATE_SERVERS]: (state, { newServers }: any) => ({ ...state, ...newServers }), +export default buildReducer({ + [CREATE_SERVERS]: (state, { newServers }) => ({ ...state, ...newServers }), [DELETE_SERVER]: (state, { serverId }: any) => dissoc(serverId, state), [EDIT_SERVER]: (state, { serverId, serverData }: any) => !state[serverId] ? state diff --git a/src/settings/reducers/settings.ts b/src/settings/reducers/settings.ts index e2409222..a87455c8 100644 --- a/src/settings/reducers/settings.ts +++ b/src/settings/reducers/settings.ts @@ -1,5 +1,5 @@ -import { handleActions } from 'redux-actions'; import { Action } from 'redux'; +import { buildReducer } from '../../utils/helpers/redux'; export const SET_REAL_TIME_UPDATES = 'shlink/realTimeUpdates/SET_REAL_TIME_UPDATES'; @@ -17,11 +17,13 @@ const initialState: Settings = { }, }; -export default handleActions({ - [SET_REAL_TIME_UPDATES]: (state, { realTimeUpdates }: any) => ({ ...state, realTimeUpdates }), +type SettingsAction = Action & Settings; + +export default buildReducer({ + [SET_REAL_TIME_UPDATES]: (state, { realTimeUpdates }) => ({ ...state, realTimeUpdates }), }, initialState); -export const setRealTimeUpdates = (enabled: boolean): Action & Settings => ({ +export const setRealTimeUpdates = (enabled: boolean): SettingsAction => ({ type: SET_REAL_TIME_UPDATES, realTimeUpdates: { enabled }, }); diff --git a/src/short-urls/reducers/shortUrlCreation.ts b/src/short-urls/reducers/shortUrlCreation.ts index 878dd143..40753d4b 100644 --- a/src/short-urls/reducers/shortUrlCreation.ts +++ b/src/short-urls/reducers/shortUrlCreation.ts @@ -1,9 +1,9 @@ import PropTypes from 'prop-types'; -import { createAction, handleActions } from 'redux-actions'; import { Action, Dispatch } from 'redux'; import { ShlinkApiClientBuilder } from '../../utils/services/types'; import { GetState } from '../../container/types'; import { ShortUrl, ShortUrlData } from '../data'; +import { buildReducer, buildActionCreator } from '../../utils/helpers/redux'; /* eslint-disable padding-line-between-statements */ export const CREATE_SHORT_URL_START = 'shlink/createShortUrl/CREATE_SHORT_URL_START'; @@ -27,16 +27,20 @@ export interface ShortUrlCreation { error: boolean; } +export interface CreateShortUrlAction extends Action { + result: ShortUrl; +} + const initialState: ShortUrlCreation = { result: null, saving: false, error: false, }; -export default handleActions({ +export default buildReducer({ [CREATE_SHORT_URL_START]: (state) => ({ ...state, saving: true, error: false }), [CREATE_SHORT_URL_ERROR]: (state) => ({ ...state, saving: false, error: true }), - [CREATE_SHORT_URL]: (_, { result }: any) => ({ result, saving: false, error: false }), + [CREATE_SHORT_URL]: (_, { result }) => ({ result, saving: false, error: false }), [RESET_CREATE_SHORT_URL]: () => initialState, }, initialState); @@ -50,7 +54,7 @@ export const createShortUrl = (buildShlinkApiClient: ShlinkApiClientBuilder) => try { const result = await createShortUrl(data); - dispatch({ type: CREATE_SHORT_URL, result }); + dispatch({ type: CREATE_SHORT_URL, result }); } catch (e) { dispatch({ type: CREATE_SHORT_URL_ERROR }); @@ -58,4 +62,4 @@ export const createShortUrl = (buildShlinkApiClient: ShlinkApiClientBuilder) => } }; -export const resetCreateShortUrl = createAction(RESET_CREATE_SHORT_URL); +export const resetCreateShortUrl = buildActionCreator(RESET_CREATE_SHORT_URL); diff --git a/src/short-urls/reducers/shortUrlMeta.ts b/src/short-urls/reducers/shortUrlMeta.ts index aafdaab0..e21401af 100644 --- a/src/short-urls/reducers/shortUrlMeta.ts +++ b/src/short-urls/reducers/shortUrlMeta.ts @@ -1,9 +1,9 @@ -import { createAction, handleActions, Action } from 'redux-actions'; import PropTypes from 'prop-types'; -import { Dispatch } from 'redux'; +import { Dispatch, Action } from 'redux'; import { ShortUrlMeta } from '../data'; import { ShlinkApiClientBuilder } from '../../utils/services/types'; import { GetState } from '../../container/types'; +import { buildActionCreator, buildReducer } from '../../utils/helpers/redux'; /* eslint-disable padding-line-between-statements */ export const EDIT_SHORT_URL_META_START = 'shlink/shortUrlMeta/EDIT_SHORT_URL_META_START'; @@ -34,7 +34,7 @@ export interface ShortUrlMetaEdition { error: boolean; } -interface ShortUrlMetaEditedAction { +interface ShortUrlMetaEditedAction extends Action { shortCode: string; domain?: string | null; meta: ShortUrlMeta; @@ -47,10 +47,10 @@ const initialState: ShortUrlMetaEdition = { error: false, }; -export default handleActions({ +export default buildReducer({ [EDIT_SHORT_URL_META_START]: (state) => ({ ...state, saving: true, error: false }), [EDIT_SHORT_URL_META_ERROR]: (state) => ({ ...state, saving: false, error: true }), - [SHORT_URL_META_EDITED]: (_, { payload }) => ({ ...payload, saving: false, error: false }), + [SHORT_URL_META_EDITED]: (_, { shortCode, meta }) => ({ shortCode, meta, saving: false, error: false }), [RESET_EDIT_SHORT_URL_META]: () => initialState, }, initialState); @@ -64,10 +64,7 @@ export const editShortUrlMeta = (buildShlinkApiClient: ShlinkApiClientBuilder) = try { await updateShortUrlMeta(shortCode, domain, meta); - dispatch>({ - type: SHORT_URL_META_EDITED, - payload: { shortCode, meta, domain }, - }); + dispatch({ shortCode, meta, domain, type: SHORT_URL_META_EDITED }); } catch (e) { dispatch({ type: EDIT_SHORT_URL_META_ERROR }); @@ -75,4 +72,4 @@ export const editShortUrlMeta = (buildShlinkApiClient: ShlinkApiClientBuilder) = } }; -export const resetShortUrlMeta = createAction(RESET_EDIT_SHORT_URL_META); +export const resetShortUrlMeta = buildActionCreator(RESET_EDIT_SHORT_URL_META); diff --git a/src/short-urls/reducers/shortUrlsList.js b/src/short-urls/reducers/shortUrlsList.js index 26f35aef..d7ca6080 100644 --- a/src/short-urls/reducers/shortUrlsList.js +++ b/src/short-urls/reducers/shortUrlsList.js @@ -49,13 +49,7 @@ export default handleActions({ state, ), [SHORT_URL_TAGS_EDITED]: setPropFromActionOnMatchingShortUrl('tags'), - [SHORT_URL_META_EDITED]: (state, { payload: { shortCode, domain, meta } }) => assocPath( - [ 'shortUrls', 'data' ], - state.shortUrls.data.map( - (shortUrl) => shortUrlMatches(shortUrl, shortCode, domain) ? assoc('meta', meta, shortUrl) : shortUrl, - ), - state, - ), + [SHORT_URL_META_EDITED]: setPropFromActionOnMatchingShortUrl('meta'), [SHORT_URL_EDITED]: setPropFromActionOnMatchingShortUrl('longUrl'), [CREATE_VISIT]: (state, { shortUrl: { shortCode, domain, visitsCount } }) => assocPath( [ 'shortUrls', 'data' ], diff --git a/src/utils/helpers/redux.ts b/src/utils/helpers/redux.ts new file mode 100644 index 00000000..2eec915e --- /dev/null +++ b/src/utils/helpers/redux.ts @@ -0,0 +1,17 @@ +import { Action } from 'redux'; + +type ActionDispatcher = (currentState: State, action: AT) => State; +type ActionDispatcherMap = Record>; + +export const buildReducer = (map: ActionDispatcherMap, initialState: State) => ( + state: State | undefined, + action: AT, +): State => { + const { type } = action; + const actionDispatcher = map[type]; + const currentState = state ?? initialState; + + return actionDispatcher ? actionDispatcher(currentState, action) : currentState; +}; + +export const buildActionCreator = (type: T) => (): Action => ({ type }); diff --git a/test/mercure/reducers/mercureInfo.test.ts b/test/mercure/reducers/mercureInfo.test.ts index eb48e1f9..71954823 100644 --- a/test/mercure/reducers/mercureInfo.test.ts +++ b/test/mercure/reducers/mercureInfo.test.ts @@ -1,10 +1,10 @@ import { Mock } from 'ts-mockery'; -import { Action } from 'redux-actions'; import reducer, { GET_MERCURE_INFO_START, GET_MERCURE_INFO_ERROR, GET_MERCURE_INFO, loadMercureInfo, + GetMercureInfoAction, } from '../../../src/mercure/reducers/mercureInfo'; import { ShlinkMercureInfo } from '../../../src/utils/services/types'; import ShlinkApiClient from '../../../src/utils/services/ShlinkApiClient'; @@ -17,22 +17,26 @@ describe('mercureInfoReducer', () => { }; describe('reducer', () => { + const action = (type: string, args: Partial = {}) => Mock.of( + { type, ...args }, + ); + it('returns loading on GET_MERCURE_INFO_START', () => { - expect(reducer(undefined, { type: GET_MERCURE_INFO_START } as Action)).toEqual({ + expect(reducer(undefined, action(GET_MERCURE_INFO_START))).toEqual({ loading: true, error: false, }); }); it('returns error on GET_MERCURE_INFO_ERROR', () => { - expect(reducer(undefined, { type: GET_MERCURE_INFO_ERROR } as Action)).toEqual({ + expect(reducer(undefined, action(GET_MERCURE_INFO_ERROR))).toEqual({ loading: false, error: true, }); }); it('returns mercure info on GET_MERCURE_INFO', () => { - expect(reducer(undefined, { type: GET_MERCURE_INFO, payload: mercureInfo })).toEqual({ + expect(reducer(undefined, { type: GET_MERCURE_INFO, ...mercureInfo })).toEqual({ ...mercureInfo, loading: false, error: false, @@ -74,7 +78,7 @@ describe('mercureInfoReducer', () => { expect(apiClientMock.mercureInfo).toHaveBeenCalledTimes(1); expect(dispatch).toHaveBeenCalledTimes(2); expect(dispatch).toHaveBeenNthCalledWith(1, { type: GET_MERCURE_INFO_START }); - expect(dispatch).toHaveBeenNthCalledWith(2, { type: GET_MERCURE_INFO, payload: mercureInfo }); + expect(dispatch).toHaveBeenNthCalledWith(2, { type: GET_MERCURE_INFO, ...mercureInfo }); }); it('throws error on failure', async () => { diff --git a/test/servers/reducers/selectedServer.test.ts b/test/servers/reducers/selectedServer.test.ts index 57234462..8f8fd9f0 100644 --- a/test/servers/reducers/selectedServer.test.ts +++ b/test/servers/reducers/selectedServer.test.ts @@ -10,17 +10,17 @@ import reducer, { } from '../../../src/servers/reducers/selectedServer'; import { RESET_SHORT_URL_PARAMS } from '../../../src/short-urls/reducers/shortUrlsListParams'; import { ShlinkState } from '../../../src/container/types'; -import { NonReachableServer, NotFoundServer } from '../../../src/servers/data'; +import { NonReachableServer, NotFoundServer, RegularServer } from '../../../src/servers/data'; describe('selectedServerReducer', () => { describe('reducer', () => { it('returns default when action is RESET_SELECTED_SERVER', () => - expect(reducer(null, { type: RESET_SELECTED_SERVER } as any)).toEqual(null)); + expect(reducer(null, { type: RESET_SELECTED_SERVER, selectedServer: null })).toEqual(null)); it('returns selected server when action is SELECT_SERVER', () => { - const selectedServer = { id: 'abc123' }; + const selectedServer = Mock.of({ id: 'abc123' }); - expect(reducer(null, { type: SELECT_SERVER, selectedServer } as any)).toEqual(selectedServer); + expect(reducer(null, { type: SELECT_SERVER, selectedServer })).toEqual(selectedServer); }); }); diff --git a/test/settings/reducers/settings.test.ts b/test/settings/reducers/settings.test.ts index 2d52bdd8..c290f4f3 100644 --- a/test/settings/reducers/settings.test.ts +++ b/test/settings/reducers/settings.test.ts @@ -5,7 +5,7 @@ describe('settingsReducer', () => { describe('reducer', () => { it('returns realTimeUpdates when action is SET_REAL_TIME_UPDATES', () => { - expect(reducer(undefined, { type: SET_REAL_TIME_UPDATES, realTimeUpdates } as any)).toEqual({ realTimeUpdates }); + expect(reducer(undefined, { type: SET_REAL_TIME_UPDATES, realTimeUpdates })).toEqual({ realTimeUpdates }); }); }); diff --git a/test/short-urls/reducers/shortUrlCreation.test.ts b/test/short-urls/reducers/shortUrlCreation.test.ts index 22e24419..7347e5fb 100644 --- a/test/short-urls/reducers/shortUrlCreation.test.ts +++ b/test/short-urls/reducers/shortUrlCreation.test.ts @@ -6,6 +6,7 @@ import reducer, { RESET_CREATE_SHORT_URL, createShortUrl, resetCreateShortUrl, + CreateShortUrlAction, } from '../../../src/short-urls/reducers/shortUrlCreation'; import { ShortUrl } from '../../../src/short-urls/data'; import ShlinkApiClient from '../../../src/utils/services/ShlinkApiClient'; @@ -15,8 +16,12 @@ describe('shortUrlCreationReducer', () => { const shortUrl = Mock.all(); describe('reducer', () => { + const action = (type: string, args: Partial = {}) => Mock.of( + { type, ...args }, + ); + it('returns loading on CREATE_SHORT_URL_START', () => { - expect(reducer(undefined, { type: CREATE_SHORT_URL_START } as any)).toEqual({ + expect(reducer(undefined, action(CREATE_SHORT_URL_START))).toEqual({ result: null, saving: true, error: false, @@ -24,7 +29,7 @@ describe('shortUrlCreationReducer', () => { }); it('returns error on CREATE_SHORT_URL_ERROR', () => { - expect(reducer(undefined, { type: CREATE_SHORT_URL_ERROR } as any)).toEqual({ + expect(reducer(undefined, action(CREATE_SHORT_URL_ERROR))).toEqual({ result: null, saving: false, error: true, @@ -32,7 +37,7 @@ describe('shortUrlCreationReducer', () => { }); it('returns result on CREATE_SHORT_URL', () => { - expect(reducer(undefined, { type: CREATE_SHORT_URL, result: shortUrl } as any)).toEqual({ + expect(reducer(undefined, action(CREATE_SHORT_URL, { result: shortUrl }))).toEqual({ result: shortUrl, saving: false, error: false, @@ -40,7 +45,7 @@ describe('shortUrlCreationReducer', () => { }); it('returns default state on RESET_CREATE_SHORT_URL', () => { - expect(reducer(undefined, { type: RESET_CREATE_SHORT_URL } as any)).toEqual({ + expect(reducer(undefined, action(RESET_CREATE_SHORT_URL))).toEqual({ result: null, saving: false, error: false, @@ -49,8 +54,7 @@ describe('shortUrlCreationReducer', () => { }); describe('resetCreateShortUrl', () => { - it('returns proper action', () => - expect(resetCreateShortUrl()).toEqual({ type: RESET_CREATE_SHORT_URL })); + it('returns proper action', () => expect(resetCreateShortUrl()).toEqual({ type: RESET_CREATE_SHORT_URL })); }); describe('createShortUrl', () => { diff --git a/test/short-urls/reducers/shortUrlMeta.test.ts b/test/short-urls/reducers/shortUrlMeta.test.ts index d7c6e409..e3fff1ef 100644 --- a/test/short-urls/reducers/shortUrlMeta.test.ts +++ b/test/short-urls/reducers/shortUrlMeta.test.ts @@ -37,7 +37,7 @@ describe('shortUrlMetaReducer', () => { }); it('returns provided tags and shortCode on SHORT_URL_META_EDITED', () => { - expect(reducer(undefined, { type: SHORT_URL_META_EDITED, payload: { meta, shortCode } })).toEqual({ + expect(reducer(undefined, { type: SHORT_URL_META_EDITED, meta, shortCode })).toEqual({ meta, shortCode, saving: false, @@ -64,8 +64,6 @@ describe('shortUrlMetaReducer', () => { afterEach(jest.clearAllMocks); it.each([[ undefined ], [ null ], [ 'example.com' ]])('dispatches metadata on success', async (domain) => { - const payload = { meta, shortCode, domain }; - await editShortUrlMeta(buildShlinkApiClient)(shortCode, domain, meta)(dispatch, getState); expect(buildShlinkApiClient).toHaveBeenCalledTimes(1); @@ -73,7 +71,7 @@ describe('shortUrlMetaReducer', () => { expect(updateShortUrlMeta).toHaveBeenCalledWith(shortCode, domain, meta); expect(dispatch).toHaveBeenCalledTimes(2); expect(dispatch).toHaveBeenNthCalledWith(1, { type: EDIT_SHORT_URL_META_START }); - expect(dispatch).toHaveBeenNthCalledWith(2, { type: SHORT_URL_META_EDITED, payload }); + expect(dispatch).toHaveBeenNthCalledWith(2, { type: SHORT_URL_META_EDITED, meta, shortCode, domain }); }); it('dispatches error on failure', async () => { From 6696fb13d61c10959cdb761c891c20733228e7ab Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Tue, 25 Aug 2020 20:23:12 +0200 Subject: [PATCH 22/59] Created redux test --- src/utils/helpers/redux.ts | 10 +++--- test/utils/helpers/redux.test.ts | 61 ++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 5 deletions(-) create mode 100644 test/utils/helpers/redux.test.ts diff --git a/src/utils/helpers/redux.ts b/src/utils/helpers/redux.ts index 2eec915e..d2607707 100644 --- a/src/utils/helpers/redux.ts +++ b/src/utils/helpers/redux.ts @@ -1,17 +1,17 @@ import { Action } from 'redux'; -type ActionDispatcher = (currentState: State, action: AT) => State; -type ActionDispatcherMap = Record>; +type ActionHandler = (currentState: State, action: AT) => State; +type ActionHandlerMap = Record>; -export const buildReducer = (map: ActionDispatcherMap, initialState: State) => ( +export const buildReducer = (map: ActionHandlerMap, initialState: State) => ( state: State | undefined, action: AT, ): State => { const { type } = action; - const actionDispatcher = map[type]; + const actionHandler = map[type]; const currentState = state ?? initialState; - return actionDispatcher ? actionDispatcher(currentState, action) : currentState; + return actionHandler ? actionHandler(currentState, action) : currentState; }; export const buildActionCreator = (type: T) => (): Action => ({ type }); diff --git a/test/utils/helpers/redux.test.ts b/test/utils/helpers/redux.test.ts new file mode 100644 index 00000000..e34e9a3b --- /dev/null +++ b/test/utils/helpers/redux.test.ts @@ -0,0 +1,61 @@ +import { Action } from 'redux'; +import { buildActionCreator, buildReducer } from '../../../src/utils/helpers/redux'; + +describe('redux', () => { + beforeEach(jest.clearAllMocks); + + describe('buildActionCreator', () => { + it.each([ + [ 'foo', { type: 'foo' }], + [ 'bar', { type: 'bar' }], + [ 'something', { type: 'something' }], + ])('returns an action creator', (type, expected) => { + const actionCreator = buildActionCreator(type); + + expect(actionCreator).toBeInstanceOf(Function); + expect(actionCreator()).toEqual(expected); + }); + }); + + describe('buildReducer', () => { + const fooActionHandler = jest.fn(() => 'foo result'); + const barActionHandler = jest.fn(() => 'bar result'); + const initialState = 'initial state'; + let reducer: Function; + + beforeEach(() => { + reducer = buildReducer({ + foo: fooActionHandler, + bar: barActionHandler, + }, initialState); + }); + + it('returns a reducer which returns initial state when provided with unknown action', () => { + expect(reducer(undefined, { type: 'unknown action' })).toEqual(initialState); + expect(fooActionHandler).not.toHaveBeenCalled(); + expect(barActionHandler).not.toHaveBeenCalled(); + }); + + it.each([ + [ 'foo', 'foo result', fooActionHandler, barActionHandler ], + [ 'bar', 'bar result', barActionHandler, fooActionHandler ], + ])( + 'returns a reducer which calls corresponding action handler', + (type, expected, invokedActionHandler, notInvokedActionHandler) => { + expect(reducer(undefined, { type })).toEqual(expected); + expect(invokedActionHandler).toHaveBeenCalled(); + expect(notInvokedActionHandler).not.toHaveBeenCalled(); + }, + ); + + it.each([ + [ undefined, initialState ], + [ 'foo', 'foo' ], + [ 'something', 'something' ], + ])('returns a reducer which calls action handler with provided state or initial', (state, expected) => { + reducer(state, { type: 'foo' }); + + expect(fooActionHandler).toHaveBeenCalledWith(expected, expect.anything()); + }); + }); +}); From 1b03d043189fa10a4e51da1ebe5f0e2b2e230b6d Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Wed, 26 Aug 2020 18:55:40 +0200 Subject: [PATCH 23/59] Migrated more short-url reducers to TS --- .eslintrc | 5 ++- package-lock.json | 6 +-- package.json | 2 +- src/container/types.ts | 16 +++++--- src/short-urls/helpers/DeleteShortUrlModal.js | 2 +- ...hortUrlDeletion.js => shortUrlDeletion.ts} | 38 +++++++++++++++---- ...{shortUrlEdition.js => shortUrlEdition.ts} | 33 +++++++++++++--- .../{shortUrlTags.js => shortUrlTags.ts} | 35 +++++++++++++---- src/short-urls/reducers/shortUrlsList.js | 1 - ...lsListParams.js => shortUrlsListParams.ts} | 21 ++++++++-- src/utils/services/types.ts | 9 +++++ ...etion.test.js => shortUrlDeletion.test.ts} | 9 +++-- ...dition.test.js => shortUrlEdition.test.ts} | 18 ++++++--- ...rtUrlTags.test.js => shortUrlTags.test.ts} | 22 ++++++++--- ...ms.test.js => shortUrlsListParams.test.ts} | 9 +++-- 15 files changed, 169 insertions(+), 57 deletions(-) rename src/short-urls/reducers/{shortUrlDeletion.js => shortUrlDeletion.ts} (52%) rename src/short-urls/reducers/{shortUrlEdition.js => shortUrlEdition.ts} (50%) rename src/short-urls/reducers/{shortUrlTags.js => shortUrlTags.ts} (52%) rename src/short-urls/reducers/{shortUrlsListParams.js => shortUrlsListParams.ts} (51%) rename test/short-urls/reducers/{shortUrlDeletion.test.js => shortUrlDeletion.test.ts} (94%) rename test/short-urls/reducers/{shortUrlEdition.test.js => shortUrlEdition.test.ts} (78%) rename test/short-urls/reducers/{shortUrlTags.test.js => shortUrlTags.test.ts} (79%) rename test/short-urls/reducers/{shortUrlsListParams.test.js => shortUrlsListParams.test.ts} (66%) diff --git a/.eslintrc b/.eslintrc index ef7e0b47..dd2b27c9 100644 --- a/.eslintrc +++ b/.eslintrc @@ -31,7 +31,8 @@ "max-len": ["error", { "code": 120, "ignoreStrings": true, - "ignoreTemplateLiterals": true + "ignoreTemplateLiterals": true, + "ignoreComments": true }], "no-mixed-operators": "off", "comma-dangle": ["error", "always-multiline"], @@ -65,7 +66,7 @@ "code": 120, "ignoreStrings": true, "ignoreTemplateLiterals": true, - "ignoreTrailingComments": true + "ignoreComments": true }], "no-mixed-operators": "off", "react/display-name": "off" diff --git a/package-lock.json b/package-lock.json index e67e9e4e..27beeeee 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22452,9 +22452,9 @@ } }, "typescript": { - "version": "3.9.7", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.7.tgz", - "integrity": "sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.0.2.tgz", + "integrity": "sha512-e4ERvRV2wb+rRZ/IQeb3jm2VxBsirQLpQhdxplZ2MEzGvDkkMmPglecnNDfSUBivMjP93vRbngYYDQqQ/78bcQ==", "dev": true }, "uglify-es": { diff --git a/package.json b/package.json index 8454ae72..68dbdc47 100644 --- a/package.json +++ b/package.json @@ -141,7 +141,7 @@ "terser-webpack-plugin": "^2.1.2", "ts-jest": "^26.0.0", "ts-mockery": "^1.2.0", - "typescript": "^3.9.3", + "typescript": "^4.0.2", "url-loader": "^2.2.0", "webpack": "^4.41.0", "webpack-dev-server": "^3.8.2", diff --git a/src/container/types.ts b/src/container/types.ts index 618826dd..1018b5e9 100644 --- a/src/container/types.ts +++ b/src/container/types.ts @@ -4,19 +4,21 @@ import { SelectedServer } from '../servers/data'; import { Settings } from '../settings/reducers/settings'; import { ShortUrlMetaEdition } from '../short-urls/reducers/shortUrlMeta'; import { ShortUrlCreation } from '../short-urls/reducers/shortUrlCreation'; - -export type ConnectDecorator = (props: string[], actions?: string[]) => any; +import { ShortUrlDeletion } from '../short-urls/reducers/shortUrlDeletion'; +import { ShortUrlEdition } from '../short-urls/reducers/shortUrlEdition'; +import { ShortUrlsListParams } from '../short-urls/reducers/shortUrlsListParams'; +import { ShortUrlTags } from '../short-urls/reducers/shortUrlTags'; export interface ShlinkState { servers: ServersMap; selectedServer: SelectedServer; shortUrlsList: any; - shortUrlsListParams: any; + shortUrlsListParams: ShortUrlsListParams; shortUrlCreationResult: ShortUrlCreation; - shortUrlDeletion: any; - shortUrlTags: any; + shortUrlDeletion: ShortUrlDeletion; + shortUrlTags: ShortUrlTags; shortUrlMeta: ShortUrlMetaEdition; - shortUrlEdition: any; + shortUrlEdition: ShortUrlEdition; shortUrlVisits: any; tagVisits: any; shortUrlDetail: any; @@ -27,4 +29,6 @@ export interface ShlinkState { settings: Settings; } +export type ConnectDecorator = (props: string[], actions?: string[]) => any; + export type GetState = () => ShlinkState; diff --git a/src/short-urls/helpers/DeleteShortUrlModal.js b/src/short-urls/helpers/DeleteShortUrlModal.js index a41df3f9..dbc598ed 100644 --- a/src/short-urls/helpers/DeleteShortUrlModal.js +++ b/src/short-urls/helpers/DeleteShortUrlModal.js @@ -22,7 +22,7 @@ const DeleteShortUrlModal = ({ shortUrl, toggle, isOpen, shortUrlDeletion, reset useEffect(() => resetDeleteShortUrl, []); const { error, errorData } = shortUrlDeletion; - const errorCode = error && (errorData.type || errorData.error); + const errorCode = error && errorData && (errorData.type || errorData.error); const hasThresholdError = errorCode === THRESHOLD_REACHED; const hasErrorOtherThanThreshold = error && errorCode !== THRESHOLD_REACHED; const close = pipe(resetDeleteShortUrl, toggle); diff --git a/src/short-urls/reducers/shortUrlDeletion.js b/src/short-urls/reducers/shortUrlDeletion.ts similarity index 52% rename from src/short-urls/reducers/shortUrlDeletion.js rename to src/short-urls/reducers/shortUrlDeletion.ts index a878d07f..86b701cf 100644 --- a/src/short-urls/reducers/shortUrlDeletion.js +++ b/src/short-urls/reducers/shortUrlDeletion.ts @@ -1,6 +1,9 @@ -import { createAction, handleActions } from 'redux-actions'; import PropTypes from 'prop-types'; +import { Action, Dispatch } from 'redux'; import { apiErrorType } from '../../utils/services/ShlinkApiClient'; +import { buildActionCreator, buildReducer } from '../../utils/helpers/redux'; +import { ProblemDetailsError, ShlinkApiClientBuilder } from '../../utils/services/types'; +import { GetState } from '../../container/types'; /* eslint-disable padding-line-between-statements */ export const DELETE_SHORT_URL_START = 'shlink/deleteShortUrl/DELETE_SHORT_URL_START'; @@ -9,6 +12,7 @@ export const SHORT_URL_DELETED = 'shlink/deleteShortUrl/SHORT_URL_DELETED'; export const RESET_DELETE_SHORT_URL = 'shlink/deleteShortUrl/RESET_DELETE_SHORT_URL'; /* eslint-enable padding-line-between-statements */ +/** @deprecated Use ShortUrlDeletion interface */ export const shortUrlDeletionType = PropTypes.shape({ shortCode: PropTypes.string.isRequired, loading: PropTypes.bool.isRequired, @@ -16,32 +20,50 @@ export const shortUrlDeletionType = PropTypes.shape({ errorData: apiErrorType.isRequired, }); -const initialState = { +export interface ShortUrlDeletion { + shortCode: string; + loading: boolean; + error: boolean; + errorData?: ProblemDetailsError; +} + +interface DeleteShortUrlAction extends Action { + shortCode: string; + domain?: string | null; +} + +interface DeleteShortUrlErrorAction extends Action { + errorData: ProblemDetailsError; +} + +const initialState: ShortUrlDeletion = { shortCode: '', loading: false, error: false, - errorData: {}, }; -export default handleActions({ +export default buildReducer({ [DELETE_SHORT_URL_START]: (state) => ({ ...state, loading: true, error: false }), [DELETE_SHORT_URL_ERROR]: (state, { errorData }) => ({ ...state, errorData, loading: false, error: true }), [SHORT_URL_DELETED]: (state, { shortCode }) => ({ ...state, shortCode, loading: false, error: false }), [RESET_DELETE_SHORT_URL]: () => initialState, }, initialState); -export const deleteShortUrl = (buildShlinkApiClient) => (shortCode, domain) => async (dispatch, getState) => { +export const deleteShortUrl = (buildShlinkApiClient: ShlinkApiClientBuilder) => ( + shortCode: string, + domain?: string | null, +) => async (dispatch: Dispatch, getState: GetState) => { dispatch({ type: DELETE_SHORT_URL_START }); const { deleteShortUrl } = buildShlinkApiClient(getState); try { await deleteShortUrl(shortCode, domain); - dispatch({ type: SHORT_URL_DELETED, shortCode, domain }); + dispatch({ type: SHORT_URL_DELETED, shortCode, domain }); } catch (e) { - dispatch({ type: DELETE_SHORT_URL_ERROR, errorData: e.response.data }); + dispatch({ type: DELETE_SHORT_URL_ERROR, errorData: e.response.data }); throw e; } }; -export const resetDeleteShortUrl = createAction(RESET_DELETE_SHORT_URL); +export const resetDeleteShortUrl = buildActionCreator(RESET_DELETE_SHORT_URL); diff --git a/src/short-urls/reducers/shortUrlEdition.js b/src/short-urls/reducers/shortUrlEdition.ts similarity index 50% rename from src/short-urls/reducers/shortUrlEdition.js rename to src/short-urls/reducers/shortUrlEdition.ts index 4c545b6d..8b1a0634 100644 --- a/src/short-urls/reducers/shortUrlEdition.js +++ b/src/short-urls/reducers/shortUrlEdition.ts @@ -1,5 +1,8 @@ -import { handleActions } from 'redux-actions'; import PropTypes from 'prop-types'; +import { Action, Dispatch } from 'redux'; +import { buildReducer } from '../../utils/helpers/redux'; +import { ShlinkApiClientBuilder } from '../../utils/services/types'; +import { GetState } from '../../container/types'; /* eslint-disable padding-line-between-statements */ export const EDIT_SHORT_URL_START = 'shlink/shortUrlEdition/EDIT_SHORT_URL_START'; @@ -7,6 +10,7 @@ export const EDIT_SHORT_URL_ERROR = 'shlink/shortUrlEdition/EDIT_SHORT_URL_ERROR export const SHORT_URL_EDITED = 'shlink/shortUrlEdition/SHORT_URL_EDITED'; /* eslint-enable padding-line-between-statements */ +/** @deprecated Use ShortUrlEdition interface instead */ export const ShortUrlEditionType = PropTypes.shape({ shortCode: PropTypes.string, longUrl: PropTypes.string, @@ -14,26 +18,43 @@ export const ShortUrlEditionType = PropTypes.shape({ error: PropTypes.bool.isRequired, }); -const initialState = { +export interface ShortUrlEdition { + shortCode: string | null; + longUrl: string | null; + saving: boolean; + error: boolean; +} + +export interface ShortUrlEditedAction extends Action { + shortCode: string; + longUrl: string; + domain: string | undefined | null; +} + +const initialState: ShortUrlEdition = { shortCode: null, longUrl: null, saving: false, error: false, }; -export default handleActions({ +export default buildReducer({ [EDIT_SHORT_URL_START]: (state) => ({ ...state, saving: true, error: false }), [EDIT_SHORT_URL_ERROR]: (state) => ({ ...state, saving: false, error: true }), - [SHORT_URL_EDITED]: (state, { shortCode, longUrl }) => ({ shortCode, longUrl, saving: false, error: false }), + [SHORT_URL_EDITED]: (_, { shortCode, longUrl }) => ({ shortCode, longUrl, saving: false, error: false }), }, initialState); -export const editShortUrl = (buildShlinkApiClient) => (shortCode, domain, longUrl) => async (dispatch, getState) => { +export const editShortUrl = (buildShlinkApiClient: ShlinkApiClientBuilder) => ( + shortCode: string, + domain: string | undefined | null, + longUrl: string, +) => async (dispatch: Dispatch, getState: GetState) => { dispatch({ type: EDIT_SHORT_URL_START }); const { updateShortUrlMeta } = buildShlinkApiClient(getState); try { await updateShortUrlMeta(shortCode, domain, { longUrl }); - dispatch({ shortCode, longUrl, domain, type: SHORT_URL_EDITED }); + dispatch({ shortCode, longUrl, domain, type: SHORT_URL_EDITED }); } catch (e) { dispatch({ type: EDIT_SHORT_URL_ERROR }); diff --git a/src/short-urls/reducers/shortUrlTags.js b/src/short-urls/reducers/shortUrlTags.ts similarity index 52% rename from src/short-urls/reducers/shortUrlTags.js rename to src/short-urls/reducers/shortUrlTags.ts index 46c91d54..4890271d 100644 --- a/src/short-urls/reducers/shortUrlTags.js +++ b/src/short-urls/reducers/shortUrlTags.ts @@ -1,5 +1,8 @@ -import { createAction, handleActions } from 'redux-actions'; import PropTypes from 'prop-types'; +import { Action, Dispatch } from 'redux'; +import { buildActionCreator, buildReducer } from '../../utils/helpers/redux'; +import { ShlinkApiClientBuilder } from '../../utils/services/types'; +import { GetState } from '../../container/types'; /* eslint-disable padding-line-between-statements */ export const EDIT_SHORT_URL_TAGS_START = 'shlink/shortUrlTags/EDIT_SHORT_URL_TAGS_START'; @@ -8,6 +11,7 @@ export const SHORT_URL_TAGS_EDITED = 'shlink/shortUrlTags/SHORT_URL_TAGS_EDITED' export const RESET_EDIT_SHORT_URL_TAGS = 'shlink/shortUrlTags/RESET_EDIT_SHORT_URL_TAGS'; /* eslint-enable padding-line-between-statements */ +/** @deprecated Use ShortUrlTags interface */ export const shortUrlTagsType = PropTypes.shape({ shortCode: PropTypes.string, tags: PropTypes.arrayOf(PropTypes.string).isRequired, @@ -15,28 +19,45 @@ export const shortUrlTagsType = PropTypes.shape({ error: PropTypes.bool.isRequired, }); -const initialState = { +export interface ShortUrlTags { + shortCode: string | null; + tags: string[]; + saving: boolean; + error: boolean; +} + +export interface EditShortUrlTagsAction extends Action { + shortCode: string; + tags: string[]; + domain: string | null | undefined; +} + +const initialState: ShortUrlTags = { shortCode: null, tags: [], saving: false, error: false, }; -export default handleActions({ +export default buildReducer({ [EDIT_SHORT_URL_TAGS_START]: (state) => ({ ...state, saving: true, error: false }), [EDIT_SHORT_URL_TAGS_ERROR]: (state) => ({ ...state, saving: false, error: true }), - [SHORT_URL_TAGS_EDITED]: (state, { shortCode, tags }) => ({ shortCode, tags, saving: false, error: false }), + [SHORT_URL_TAGS_EDITED]: (_, { shortCode, tags }) => ({ shortCode, tags, saving: false, error: false }), [RESET_EDIT_SHORT_URL_TAGS]: () => initialState, }, initialState); -export const editShortUrlTags = (buildShlinkApiClient) => (shortCode, domain, tags) => async (dispatch, getState) => { +export const editShortUrlTags = (buildShlinkApiClient: ShlinkApiClientBuilder) => ( + shortCode: string, + domain: string | null | undefined, + tags: string[], +) => async (dispatch: Dispatch, getState: GetState) => { dispatch({ type: EDIT_SHORT_URL_TAGS_START }); const { updateShortUrlTags } = buildShlinkApiClient(getState); try { const normalizedTags = await updateShortUrlTags(shortCode, domain, tags); - dispatch({ tags: normalizedTags, shortCode, domain, type: SHORT_URL_TAGS_EDITED }); + dispatch({ tags: normalizedTags, shortCode, domain, type: SHORT_URL_TAGS_EDITED }); } catch (e) { dispatch({ type: EDIT_SHORT_URL_TAGS_ERROR }); @@ -44,4 +65,4 @@ export const editShortUrlTags = (buildShlinkApiClient) => (shortCode, domain, ta } }; -export const resetShortUrlsTags = createAction(RESET_EDIT_SHORT_URL_TAGS); +export const resetShortUrlsTags = buildActionCreator(RESET_EDIT_SHORT_URL_TAGS); diff --git a/src/short-urls/reducers/shortUrlsList.js b/src/short-urls/reducers/shortUrlsList.js index d7ca6080..90d2b1c8 100644 --- a/src/short-urls/reducers/shortUrlsList.js +++ b/src/short-urls/reducers/shortUrlsList.js @@ -30,7 +30,6 @@ const initialState = { error: false, }; -// TODO Make all actions fetch shortCode, domain and prop from payload const setPropFromActionOnMatchingShortUrl = (prop) => (state, { shortCode, domain, [prop]: propValue }) => assocPath( [ 'shortUrls', 'data' ], state.shortUrls.data.map( diff --git a/src/short-urls/reducers/shortUrlsListParams.js b/src/short-urls/reducers/shortUrlsListParams.ts similarity index 51% rename from src/short-urls/reducers/shortUrlsListParams.js rename to src/short-urls/reducers/shortUrlsListParams.ts index 6a6aa5e2..770f81f0 100644 --- a/src/short-urls/reducers/shortUrlsListParams.js +++ b/src/short-urls/reducers/shortUrlsListParams.ts @@ -1,9 +1,11 @@ -import { createAction, handleActions } from 'redux-actions'; import PropTypes from 'prop-types'; +import { Action } from 'redux'; +import { buildActionCreator, buildReducer } from '../../utils/helpers/redux'; import { LIST_SHORT_URLS } from './shortUrlsList'; export const RESET_SHORT_URL_PARAMS = 'shlink/shortUrlsListParams/RESET_SHORT_URL_PARAMS'; +/** @deprecated Use ShortUrlsListParams interface instead */ export const shortUrlsListParamsType = PropTypes.shape({ page: PropTypes.string, tags: PropTypes.arrayOf(PropTypes.string), @@ -13,11 +15,24 @@ export const shortUrlsListParamsType = PropTypes.shape({ orderBy: PropTypes.object, }); +export interface ShortUrlsListParams { + page: string; + tags?: string[]; + searchTerm?: string; + startDate?: string; + endDate?: string; + orderBy?: object; +} + +interface ListShortUrlsAction extends Action { + params: ShortUrlsListParams; +} + const initialState = { page: '1' }; -export default handleActions({ +export default buildReducer({ [LIST_SHORT_URLS]: (state, { params }) => ({ ...state, ...params }), [RESET_SHORT_URL_PARAMS]: () => initialState, }, initialState); -export const resetShortUrlParams = createAction(RESET_SHORT_URL_PARAMS); +export const resetShortUrlParams = buildActionCreator(RESET_SHORT_URL_PARAMS); diff --git a/src/utils/services/types.ts b/src/utils/services/types.ts index 15e3972b..048ce954 100644 --- a/src/utils/services/types.ts +++ b/src/utils/services/types.ts @@ -14,3 +14,12 @@ export interface ShlinkHealth { status: 'pass' | 'fail'; version: string; } + +export interface ProblemDetailsError { + type: string; + detail: string; + title: string; + status: number; + error?: string; // Deprecated + message?: string; // Deprecated +} diff --git a/test/short-urls/reducers/shortUrlDeletion.test.js b/test/short-urls/reducers/shortUrlDeletion.test.ts similarity index 94% rename from test/short-urls/reducers/shortUrlDeletion.test.js rename to test/short-urls/reducers/shortUrlDeletion.test.ts index c8437c25..d4fccee3 100644 --- a/test/short-urls/reducers/shortUrlDeletion.test.js +++ b/test/short-urls/reducers/shortUrlDeletion.test.ts @@ -1,3 +1,4 @@ +import { Mock } from 'ts-mockery'; import reducer, { DELETE_SHORT_URL_ERROR, DELETE_SHORT_URL_START, @@ -6,15 +7,17 @@ import reducer, { resetDeleteShortUrl, deleteShortUrl, } from '../../../src/short-urls/reducers/shortUrlDeletion'; +import { ProblemDetailsError } from '../../../src/utils/services/types'; describe('shortUrlDeletionReducer', () => { describe('reducer', () => { + + it('returns loading on DELETE_SHORT_URL_START', () => expect(reducer(undefined, { type: DELETE_SHORT_URL_START })).toEqual({ shortCode: '', loading: true, error: false, - errorData: {}, })); it('returns default on RESET_DELETE_SHORT_URL', () => @@ -22,7 +25,6 @@ describe('shortUrlDeletionReducer', () => { shortCode: '', loading: false, error: false, - errorData: {}, })); it('returns shortCode on SHORT_URL_DELETED', () => @@ -30,11 +32,10 @@ describe('shortUrlDeletionReducer', () => { shortCode: 'foo', loading: false, error: false, - errorData: {}, })); it('returns errorData on DELETE_SHORT_URL_ERROR', () => { - const errorData = { foo: 'bar' }; + const errorData = Mock.of({ type: 'bar' }); expect(reducer(undefined, { type: DELETE_SHORT_URL_ERROR, errorData })).toEqual({ shortCode: '', diff --git a/test/short-urls/reducers/shortUrlEdition.test.js b/test/short-urls/reducers/shortUrlEdition.test.ts similarity index 78% rename from test/short-urls/reducers/shortUrlEdition.test.js rename to test/short-urls/reducers/shortUrlEdition.test.ts index a64f5492..18500bab 100644 --- a/test/short-urls/reducers/shortUrlEdition.test.js +++ b/test/short-urls/reducers/shortUrlEdition.test.ts @@ -1,9 +1,12 @@ +import { Mock } from 'ts-mockery'; import reducer, { EDIT_SHORT_URL_START, EDIT_SHORT_URL_ERROR, SHORT_URL_EDITED, editShortUrl, + ShortUrlEditedAction, } from '../../../src/short-urls/reducers/shortUrlEdition'; +import { ShlinkState } from '../../../src/container/types'; describe('shortUrlEditionReducer', () => { const longUrl = 'https://shlink.io'; @@ -11,21 +14,25 @@ describe('shortUrlEditionReducer', () => { describe('reducer', () => { it('returns loading on EDIT_SHORT_URL_START', () => { - expect(reducer({}, { type: EDIT_SHORT_URL_START })).toEqual({ + expect(reducer(undefined, Mock.of({ type: EDIT_SHORT_URL_START }))).toEqual({ + longUrl: null, + shortCode: null, saving: true, error: false, }); }); it('returns error on EDIT_SHORT_URL_ERROR', () => { - expect(reducer({}, { type: EDIT_SHORT_URL_ERROR })).toEqual({ + expect(reducer(undefined, Mock.of({ type: EDIT_SHORT_URL_ERROR }))).toEqual({ + longUrl: null, + shortCode: null, saving: false, error: true, }); }); it('returns provided tags and shortCode on SHORT_URL_EDITED', () => { - expect(reducer({}, { type: SHORT_URL_EDITED, longUrl, shortCode })).toEqual({ + expect(reducer(undefined, { type: SHORT_URL_EDITED, longUrl, shortCode, domain: null })).toEqual({ longUrl, shortCode, saving: false, @@ -38,11 +45,12 @@ describe('shortUrlEditionReducer', () => { const updateShortUrlMeta = jest.fn().mockResolvedValue({}); const buildShlinkApiClient = jest.fn().mockReturnValue({ updateShortUrlMeta }); const dispatch = jest.fn(); + const getState = () => Mock.of(); afterEach(jest.clearAllMocks); it.each([[ undefined ], [ null ], [ 'example.com' ]])('dispatches long URL on success', async (domain) => { - await editShortUrl(buildShlinkApiClient)(shortCode, domain, longUrl)(dispatch); + await editShortUrl(buildShlinkApiClient)(shortCode, domain, longUrl)(dispatch, getState); expect(buildShlinkApiClient).toHaveBeenCalledTimes(1); expect(updateShortUrlMeta).toHaveBeenCalledTimes(1); @@ -58,7 +66,7 @@ describe('shortUrlEditionReducer', () => { updateShortUrlMeta.mockRejectedValue(error); try { - await editShortUrl(buildShlinkApiClient)(shortCode, undefined, longUrl)(dispatch); + await editShortUrl(buildShlinkApiClient)(shortCode, undefined, longUrl)(dispatch, getState); } catch (e) { expect(e).toBe(error); } diff --git a/test/short-urls/reducers/shortUrlTags.test.js b/test/short-urls/reducers/shortUrlTags.test.ts similarity index 79% rename from test/short-urls/reducers/shortUrlTags.test.js rename to test/short-urls/reducers/shortUrlTags.test.ts index 9be9f5ef..8542ac2d 100644 --- a/test/short-urls/reducers/shortUrlTags.test.js +++ b/test/short-urls/reducers/shortUrlTags.test.ts @@ -1,3 +1,4 @@ +import { Mock } from 'ts-mockery'; import reducer, { EDIT_SHORT_URL_TAGS_ERROR, EDIT_SHORT_URL_TAGS_START, @@ -5,29 +6,37 @@ import reducer, { resetShortUrlsTags, SHORT_URL_TAGS_EDITED, editShortUrlTags, + EditShortUrlTagsAction, } from '../../../src/short-urls/reducers/shortUrlTags'; +import { ShlinkState } from '../../../src/container/types'; describe('shortUrlTagsReducer', () => { const tags = [ 'foo', 'bar', 'baz' ]; const shortCode = 'abc123'; describe('reducer', () => { + const action = (type: string) => Mock.of({ type }); + it('returns loading on EDIT_SHORT_URL_TAGS_START', () => { - expect(reducer({}, { type: EDIT_SHORT_URL_TAGS_START })).toEqual({ + expect(reducer(undefined, action(EDIT_SHORT_URL_TAGS_START))).toEqual({ + tags: [], + shortCode: null, saving: true, error: false, }); }); it('returns error on EDIT_SHORT_URL_TAGS_ERROR', () => { - expect(reducer({}, { type: EDIT_SHORT_URL_TAGS_ERROR })).toEqual({ + expect(reducer(undefined, action(EDIT_SHORT_URL_TAGS_ERROR))).toEqual({ + tags: [], + shortCode: null, saving: false, error: true, }); }); it('returns provided tags and shortCode on SHORT_URL_TAGS_EDITED', () => { - expect(reducer({}, { type: SHORT_URL_TAGS_EDITED, tags, shortCode })).toEqual({ + expect(reducer(undefined, { type: SHORT_URL_TAGS_EDITED, tags, shortCode, domain: null })).toEqual({ tags, shortCode, saving: false, @@ -36,7 +45,7 @@ describe('shortUrlTagsReducer', () => { }); it('goes back to initial state on RESET_EDIT_SHORT_URL_TAGS', () => { - expect(reducer({}, { type: RESET_EDIT_SHORT_URL_TAGS })).toEqual({ + expect(reducer(undefined, action(RESET_EDIT_SHORT_URL_TAGS))).toEqual({ tags: [], shortCode: null, saving: false, @@ -53,6 +62,7 @@ describe('shortUrlTagsReducer', () => { const updateShortUrlTags = jest.fn(); const buildShlinkApiClient = jest.fn().mockReturnValue({ updateShortUrlTags }); const dispatch = jest.fn(); + const getState = () => Mock.all(); afterEach(jest.clearAllMocks); @@ -61,7 +71,7 @@ describe('shortUrlTagsReducer', () => { updateShortUrlTags.mockResolvedValue(normalizedTags); - await editShortUrlTags(buildShlinkApiClient)(shortCode, domain, tags)(dispatch); + await editShortUrlTags(buildShlinkApiClient)(shortCode, domain, tags)(dispatch, getState); expect(buildShlinkApiClient).toHaveBeenCalledTimes(1); expect(updateShortUrlTags).toHaveBeenCalledTimes(1); @@ -80,7 +90,7 @@ describe('shortUrlTagsReducer', () => { updateShortUrlTags.mockRejectedValue(error); try { - await editShortUrlTags(buildShlinkApiClient)(shortCode, undefined, tags)(dispatch); + await editShortUrlTags(buildShlinkApiClient)(shortCode, undefined, tags)(dispatch, getState); } catch (e) { expect(e).toBe(error); } diff --git a/test/short-urls/reducers/shortUrlsListParams.test.js b/test/short-urls/reducers/shortUrlsListParams.test.ts similarity index 66% rename from test/short-urls/reducers/shortUrlsListParams.test.js rename to test/short-urls/reducers/shortUrlsListParams.test.ts index 5c032e95..08778deb 100644 --- a/test/short-urls/reducers/shortUrlsListParams.test.js +++ b/test/short-urls/reducers/shortUrlsListParams.test.ts @@ -1,21 +1,22 @@ import reducer, { RESET_SHORT_URL_PARAMS, resetShortUrlParams, + ShortUrlsListParams, } from '../../../src/short-urls/reducers/shortUrlsListParams'; import { LIST_SHORT_URLS } from '../../../src/short-urls/reducers/shortUrlsList'; describe('shortUrlsListParamsReducer', () => { describe('reducer', () => { - const defaultState = { page: '1' }; + const defaultState: ShortUrlsListParams = { page: '1' }; it('returns params when action is LIST_SHORT_URLS', () => - expect(reducer(defaultState, { type: LIST_SHORT_URLS, params: { searchTerm: 'foo' } })).toEqual({ - ...defaultState, + expect(reducer(undefined, { type: LIST_SHORT_URLS, params: { searchTerm: 'foo', page: '2' } })).toEqual({ + page: '2', searchTerm: 'foo', })); it('returns default value when action is RESET_SHORT_URL_PARAMS', () => - expect(reducer(defaultState, { type: RESET_SHORT_URL_PARAMS })).toEqual(defaultState)); + expect(reducer(undefined, { type: RESET_SHORT_URL_PARAMS, params: defaultState })).toEqual(defaultState)); }); describe('resetShortUrlParams', () => { From b19bbee7fcabfe0e9f6eaa69b50e7921dea223a7 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Wed, 26 Aug 2020 20:03:23 +0200 Subject: [PATCH 24/59] More components migrated to TS --- package-lock.json | 19 ++++-- package.json | 3 +- .../{EditMetaModal.js => EditMetaModal.tsx} | 59 ++++++++++--------- src/short-urls/reducers/shortUrlEdition.ts | 5 +- src/short-urls/reducers/shortUrlMeta.ts | 11 +--- src/short-urls/reducers/shortUrlTags.ts | 5 +- src/utils/{DateInput.js => DateInput.tsx} | 21 +++---- src/utils/helpers/date.ts | 5 +- src/utils/utils.ts | 4 ++ ...taModal.test.js => EditMetaModal.test.tsx} | 39 ++++++------ .../{DateInput.test.js => DateInput.test.tsx} | 15 ++--- 11 files changed, 98 insertions(+), 88 deletions(-) rename src/short-urls/helpers/{EditMetaModal.js => EditMetaModal.tsx} (65%) rename src/utils/{DateInput.js => DateInput.tsx} (65%) rename test/short-urls/helpers/{EditMetaModal.test.js => EditMetaModal.test.tsx} (60%) rename test/utils/{DateInput.test.js => DateInput.test.tsx} (67%) diff --git a/package-lock.json b/package-lock.json index 27beeeee..f7749882 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1368,11 +1368,11 @@ } }, "@fortawesome/react-fontawesome": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.1.5.tgz", - "integrity": "sha512-WYDKTgyAWOncujWhhzhW7k8sgO5Eo2pZTUL51yNzSQNBUwwr6rNKg/JUSE3iebaU1XShHw74aKc1kJ+jvtRNew==", + "version": "0.1.11", + "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.1.11.tgz", + "integrity": "sha512-sClfojasRifQKI0OPqTy8Ln8iIhnxR/Pv/hukBhWnBz9kQRmqi6JSH3nghlhAY7SUeIIM7B5/D2G8WjX0iepVg==", "requires": { - "prop-types": "^15.5.10" + "prop-types": "^15.7.2" } }, "@hapi/address": { @@ -3378,6 +3378,17 @@ "csstype": "^3.0.2" } }, + "@types/react-datepicker": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@types/react-datepicker/-/react-datepicker-1.8.0.tgz", + "integrity": "sha512-QyHMOFCOFIkcyDCXUGnL7OyM+Gj2aG95d3t18wgrLTxQJseVQXeQFTVnUeXmmF2cZxeWtGTfRl1uYPTr3/rAFg==", + "dev": true, + "requires": { + "@types/react": "*", + "moment": ">=2.14.0", + "popper.js": "^1.14.1" + } + }, "@types/react-dom": { "version": "16.9.8", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.8.tgz", diff --git a/package.json b/package.json index 68dbdc47..95cd3fad 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "@fortawesome/fontawesome-svg-core": "^1.2.25", "@fortawesome/free-regular-svg-icons": "^5.11.2", "@fortawesome/free-solid-svg-icons": "^5.11.2", - "@fortawesome/react-fontawesome": "^0.1.5", + "@fortawesome/react-fontawesome": "^0.1.11", "array-filter": "^1.0.0", "array-map": "^0.0.0", "array-reduce": "^0.0.0", @@ -82,6 +82,7 @@ "@types/moment": "^2.13.0", "@types/ramda": "^0.27.14", "@types/react": "^16.9.46", + "@types/react-datepicker": "~1.8.0", "@types/react-dom": "^16.9.8", "@types/react-redux": "^7.1.9", "@types/react-router-dom": "^5.1.5", diff --git a/src/short-urls/helpers/EditMetaModal.js b/src/short-urls/helpers/EditMetaModal.tsx similarity index 65% rename from src/short-urls/helpers/EditMetaModal.js rename to src/short-urls/helpers/EditMetaModal.tsx index aa1cbcb6..84462ac1 100644 --- a/src/short-urls/helpers/EditMetaModal.js +++ b/src/short-urls/helpers/EditMetaModal.tsx @@ -1,41 +1,46 @@ -import React, { useState } from 'react'; -import PropTypes from 'prop-types'; +import React, { ChangeEvent, SyntheticEvent, useState } from 'react'; import { Modal, ModalBody, ModalFooter, ModalHeader, FormGroup, Input, UncontrolledTooltip } from 'reactstrap'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faInfoCircle as infoIcon } from '@fortawesome/free-solid-svg-icons'; import { ExternalLink } from 'react-external-link'; import moment from 'moment'; import { isEmpty, pipe } from 'ramda'; -import { shortUrlType } from '../reducers/shortUrlsList'; -import { shortUrlEditMetaType } from '../reducers/shortUrlMeta'; +import { ShortUrlMetaEdition } from '../reducers/shortUrlMeta'; import DateInput from '../../utils/DateInput'; import { formatIsoDate } from '../../utils/helpers/date'; +import { ShortUrl, ShortUrlMeta } from '../data'; +import { Nullable, OptionalString } from '../../utils/utils'; -const propTypes = { - isOpen: PropTypes.bool.isRequired, - toggle: PropTypes.func.isRequired, - shortUrl: shortUrlType.isRequired, - shortUrlMeta: shortUrlEditMetaType, - editShortUrlMeta: PropTypes.func, - resetShortUrlMeta: PropTypes.func, +export interface EditMetaModalProps { + shortUrl: ShortUrl; + isOpen: boolean; + toggle: () => void; +} + +interface EditMetaModalConnectProps extends EditMetaModalProps { + shortUrlMeta: ShortUrlMetaEdition; + resetShortUrlMeta: () => void; + editShortUrlMeta: (shortCode: string, domain: OptionalString, meta: Nullable) => Promise; +} + +const dateOrUndefined = (shortUrl: ShortUrl | undefined, dateName: 'validSince' | 'validUntil') => { + const date = shortUrl?.meta?.[dateName]; + + return date ? moment(date) : undefined; }; -const dateOrUndefined = (shortUrl, dateName) => { - const date = shortUrl && shortUrl.meta && shortUrl.meta[dateName]; - - return date && moment(date); -}; - -const EditMetaModal = ({ isOpen, toggle, shortUrl, shortUrlMeta, editShortUrlMeta, resetShortUrlMeta }) => { +const EditMetaModal = ( + { isOpen, toggle, shortUrl, shortUrlMeta, editShortUrlMeta, resetShortUrlMeta }: EditMetaModalConnectProps, +) => { const { saving, error } = shortUrlMeta; const url = shortUrl && (shortUrl.shortUrl || ''); const [ validSince, setValidSince ] = useState(dateOrUndefined(shortUrl, 'validSince')); const [ validUntil, setValidUntil ] = useState(dateOrUndefined(shortUrl, 'validUntil')); - const [ maxVisits, setMaxVisits ] = useState(shortUrl && shortUrl.meta && shortUrl.meta.maxVisits); + const [ maxVisits, setMaxVisits ] = useState(shortUrl?.meta?.maxVisits); const close = pipe(resetShortUrlMeta, toggle); - const doEdit = () => editShortUrlMeta(shortUrl.shortCode, shortUrl.domain, { - maxVisits: maxVisits && !isEmpty(maxVisits) ? parseInt(maxVisits) : null, + const doEdit = async () => editShortUrlMeta(shortUrl.shortCode, shortUrl.domain, { + maxVisits: maxVisits && !isEmpty(maxVisits) ? maxVisits : null, validSince: validSince && formatIsoDate(validSince), validUntil: validUntil && formatIsoDate(validUntil), }).then(close); @@ -49,7 +54,7 @@ const EditMetaModal = ({ isOpen, toggle, shortUrl, shortUrlMeta, editShortUrlMet

If any of the params is not met, the URL will behave as if it was an invalid short URL.

-
e.preventDefault() || doEdit()}> + e.preventDefault(), doEdit)}> @@ -66,7 +71,7 @@ const EditMetaModal = ({ isOpen, toggle, shortUrl, shortUrlMeta, editShortUrlMet selected={validUntil} minDate={validSince} isClearable - onChange={setValidUntil} + onChange={setValidUntil as any} /> @@ -74,8 +79,8 @@ const EditMetaModal = ({ isOpen, toggle, shortUrl, shortUrlMeta, editShortUrlMet type="number" placeholder="Maximum number of visits allowed" min={1} - value={maxVisits || ''} - onChange={(e) => setMaxVisits(e.target.value)} + value={maxVisits ?? ''} + onChange={(e: ChangeEvent) => setMaxVisits(Number(e.target.value))} /> {error && ( @@ -93,6 +98,4 @@ const EditMetaModal = ({ isOpen, toggle, shortUrl, shortUrlMeta, editShortUrlMet ); }; -EditMetaModal.propTypes = propTypes; - export default EditMetaModal; diff --git a/src/short-urls/reducers/shortUrlEdition.ts b/src/short-urls/reducers/shortUrlEdition.ts index 8b1a0634..fb429e44 100644 --- a/src/short-urls/reducers/shortUrlEdition.ts +++ b/src/short-urls/reducers/shortUrlEdition.ts @@ -3,6 +3,7 @@ import { Action, Dispatch } from 'redux'; import { buildReducer } from '../../utils/helpers/redux'; import { ShlinkApiClientBuilder } from '../../utils/services/types'; import { GetState } from '../../container/types'; +import { OptionalString } from '../../utils/utils'; /* eslint-disable padding-line-between-statements */ export const EDIT_SHORT_URL_START = 'shlink/shortUrlEdition/EDIT_SHORT_URL_START'; @@ -28,7 +29,7 @@ export interface ShortUrlEdition { export interface ShortUrlEditedAction extends Action { shortCode: string; longUrl: string; - domain: string | undefined | null; + domain: OptionalString; } const initialState: ShortUrlEdition = { @@ -46,7 +47,7 @@ export default buildReducer({ export const editShortUrl = (buildShlinkApiClient: ShlinkApiClientBuilder) => ( shortCode: string, - domain: string | undefined | null, + domain: OptionalString, longUrl: string, ) => async (dispatch: Dispatch, getState: GetState) => { dispatch({ type: EDIT_SHORT_URL_START }); diff --git a/src/short-urls/reducers/shortUrlMeta.ts b/src/short-urls/reducers/shortUrlMeta.ts index e21401af..a69c35de 100644 --- a/src/short-urls/reducers/shortUrlMeta.ts +++ b/src/short-urls/reducers/shortUrlMeta.ts @@ -4,6 +4,7 @@ import { ShortUrlMeta } from '../data'; import { ShlinkApiClientBuilder } from '../../utils/services/types'; import { GetState } from '../../container/types'; import { buildActionCreator, buildReducer } from '../../utils/helpers/redux'; +import { OptionalString } from '../../utils/utils'; /* eslint-disable padding-line-between-statements */ export const EDIT_SHORT_URL_META_START = 'shlink/shortUrlMeta/EDIT_SHORT_URL_META_START'; @@ -19,14 +20,6 @@ export const shortUrlMetaType = PropTypes.shape({ maxVisits: PropTypes.number, }); -/** @deprecated Use ShortUrlMetaEdition interface instead */ -export const shortUrlEditMetaType = PropTypes.shape({ - shortCode: PropTypes.string, - meta: shortUrlMetaType.isRequired, - saving: PropTypes.bool.isRequired, - error: PropTypes.bool.isRequired, -}); - export interface ShortUrlMetaEdition { shortCode: string | null; meta: ShortUrlMeta; @@ -56,7 +49,7 @@ export default buildReducer({ export const editShortUrlMeta = (buildShlinkApiClient: ShlinkApiClientBuilder) => ( shortCode: string, - domain: string | null | undefined, + domain: OptionalString, meta: ShortUrlMeta, ) => async (dispatch: Dispatch, getState: GetState) => { dispatch({ type: EDIT_SHORT_URL_META_START }); diff --git a/src/short-urls/reducers/shortUrlTags.ts b/src/short-urls/reducers/shortUrlTags.ts index 4890271d..89ecd15d 100644 --- a/src/short-urls/reducers/shortUrlTags.ts +++ b/src/short-urls/reducers/shortUrlTags.ts @@ -3,6 +3,7 @@ import { Action, Dispatch } from 'redux'; import { buildActionCreator, buildReducer } from '../../utils/helpers/redux'; import { ShlinkApiClientBuilder } from '../../utils/services/types'; import { GetState } from '../../container/types'; +import { OptionalString } from '../../utils/utils'; /* eslint-disable padding-line-between-statements */ export const EDIT_SHORT_URL_TAGS_START = 'shlink/shortUrlTags/EDIT_SHORT_URL_TAGS_START'; @@ -29,7 +30,7 @@ export interface ShortUrlTags { export interface EditShortUrlTagsAction extends Action { shortCode: string; tags: string[]; - domain: string | null | undefined; + domain: OptionalString; } const initialState: ShortUrlTags = { @@ -48,7 +49,7 @@ export default buildReducer({ export const editShortUrlTags = (buildShlinkApiClient: ShlinkApiClientBuilder) => ( shortCode: string, - domain: string | null | undefined, + domain: OptionalString, tags: string[], ) => async (dispatch: Dispatch, getState: GetState) => { dispatch({ type: EDIT_SHORT_URL_TAGS_START }); diff --git a/src/utils/DateInput.js b/src/utils/DateInput.tsx similarity index 65% rename from src/utils/DateInput.js rename to src/utils/DateInput.tsx index 52905c62..dd1f8220 100644 --- a/src/utils/DateInput.js +++ b/src/utils/DateInput.tsx @@ -1,21 +1,16 @@ -import React from 'react'; +import React, { Component, RefObject } from 'react'; import { isNil } from 'ramda'; -import DatePicker from 'react-datepicker'; +import DatePicker, { ReactDatePickerProps } from 'react-datepicker'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faCalendarAlt as calendarIcon } from '@fortawesome/free-regular-svg-icons'; -import * as PropTypes from 'prop-types'; import classNames from 'classnames'; import './DateInput.scss'; -const propTypes = { - className: PropTypes.string, - isClearable: PropTypes.bool, - selected: PropTypes.oneOfType([ PropTypes.string, PropTypes.object ]), - ref: PropTypes.object, - disabled: PropTypes.bool, -}; +export interface DateInputProps extends ReactDatePickerProps { + ref?: RefObject & { input: HTMLInputElement }>; +} -const DateInput = (props) => { +const DateInput = (props: DateInputProps) => { const { className, isClearable, selected, ref = React.createRef() } = props; const showCalendarIcon = !isClearable || isNil(selected); @@ -32,13 +27,11 @@ const DateInput = (props) => { ref.current.input.focus()} + onClick={() => ref.current?.input.focus()} /> )}
); }; -DateInput.propTypes = propTypes; - export default DateInput; diff --git a/src/utils/helpers/date.ts b/src/utils/helpers/date.ts index c4a44b3c..9d15c41f 100644 --- a/src/utils/helpers/date.ts +++ b/src/utils/helpers/date.ts @@ -1,11 +1,12 @@ import * as moment from 'moment'; +import { OptionalString } from '../utils'; type MomentOrString = moment.Moment | string; type NullableDate = MomentOrString | null; -const isMomentObject = (date: moment.Moment | string): date is moment.Moment => typeof (date as moment.Moment).format === 'function'; +const isMomentObject = (date: MomentOrString): date is moment.Moment => typeof (date as moment.Moment).format === 'function'; -const formatDateFromFormat = (date?: NullableDate, format?: string): NullableDate | undefined => +const formatDateFromFormat = (date?: NullableDate, format?: string): OptionalString => !date || !isMomentObject(date) ? date : date.format(format); export const formatDate = (format = 'YYYY-MM-DD') => (date?: NullableDate) => formatDateFromFormat(date, format); diff --git a/src/utils/utils.ts b/src/utils/utils.ts index c968bf45..ccaf6706 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -25,3 +25,7 @@ export const hasValue = (value: T | Empty): value is T => !isNil(value) && !i export type Nullable = { [P in keyof T]: T[P] | null }; + +type Optional = T | null | undefined; + +export type OptionalString = Optional; diff --git a/test/short-urls/helpers/EditMetaModal.test.js b/test/short-urls/helpers/EditMetaModal.test.tsx similarity index 60% rename from test/short-urls/helpers/EditMetaModal.test.js rename to test/short-urls/helpers/EditMetaModal.test.tsx index fb20f7ac..f1ec8b2a 100644 --- a/test/short-urls/helpers/EditMetaModal.test.js +++ b/test/short-urls/helpers/EditMetaModal.test.tsx @@ -1,19 +1,22 @@ import React from 'react'; -import { shallow } from 'enzyme'; -import { FormGroup, Modal, ModalHeader } from 'reactstrap'; +import { shallow, ShallowWrapper } from 'enzyme'; +import { FormGroup } from 'reactstrap'; +import { Mock } from 'ts-mockery'; import EditMetaModal from '../../../src/short-urls/helpers/EditMetaModal'; +import { ShortUrl } from '../../../src/short-urls/data'; +import { ShortUrlMetaEdition } from '../../../src/short-urls/reducers/shortUrlMeta'; describe('', () => { - let wrapper; - const editShortUrlMeta = jest.fn(() => Promise.resolve()); + let wrapper: ShallowWrapper; + const editShortUrlMeta = jest.fn(async () => Promise.resolve()); const resetShortUrlMeta = jest.fn(); const toggle = jest.fn(); - const createWrapper = (shortUrl, shortUrlMeta) => { + const createWrapper = (shortUrlMeta: Partial) => { wrapper = shallow( ()} + shortUrlMeta={Mock.of(shortUrlMeta)} toggle={toggle} editShortUrlMeta={editShortUrlMeta} resetShortUrlMeta={resetShortUrlMeta} @@ -23,13 +26,11 @@ describe('', () => { return wrapper; }; - afterEach(() => { - wrapper && wrapper.unmount(); - jest.clearAllMocks(); - }); + afterEach(() => wrapper?.unmount()); + afterEach(jest.clearAllMocks); it('properly renders form with components', () => { - const wrapper = createWrapper({}, { saving: false, error: false, meta: {} }); + const wrapper = createWrapper({ saving: false, error: false }); const error = wrapper.find('.bg-danger'); const form = wrapper.find('form'); const formGroup = form.find(FormGroup); @@ -43,7 +44,7 @@ describe('', () => { [ true, 'Saving...' ], [ false, 'Save' ], ])('renders submit button on expected state', (saving, expectedText) => { - const wrapper = createWrapper({}, { saving, error: false, meta: {} }); + const wrapper = createWrapper({ saving, error: false }); const button = wrapper.find('[type="submit"]'); expect(button.prop('disabled')).toEqual(saving); @@ -51,7 +52,7 @@ describe('', () => { }); it('renders error message on error', () => { - const wrapper = createWrapper({}, { saving: false, error: true, meta: {} }); + const wrapper = createWrapper({ saving: false, error: true }); const error = wrapper.find('.bg-danger'); expect(error).toHaveLength(1); @@ -59,7 +60,7 @@ describe('', () => { it('saves meta when form is submit', () => { const preventDefault = jest.fn(); - const wrapper = createWrapper({}, { saving: false, error: false, meta: {} }); + const wrapper = createWrapper({ saving: false, error: false }); const form = wrapper.find('form'); form.simulate('submit', { preventDefault }); @@ -70,13 +71,13 @@ describe('', () => { it.each([ [ '.btn-link', 'onClick' ], - [ Modal, 'toggle' ], - [ ModalHeader, 'toggle' ], + [ 'Modal', 'toggle' ], + [ 'ModalHeader', 'toggle' ], ])('resets meta when modal is toggled in any way', (componentToFind, propToCall) => { - const wrapper = createWrapper({}, { saving: false, error: false, meta: {} }); + const wrapper = createWrapper({ saving: false, error: false }); const component = wrapper.find(componentToFind); - component.prop(propToCall)(); + (component.prop(propToCall) as Function)(); // eslint-disable-line @typescript-eslint/no-unnecessary-type-assertion expect(resetShortUrlMeta).toHaveBeenCalled(); }); diff --git a/test/utils/DateInput.test.js b/test/utils/DateInput.test.tsx similarity index 67% rename from test/utils/DateInput.test.js rename to test/utils/DateInput.test.tsx index 995cd865..cb62f2a5 100644 --- a/test/utils/DateInput.test.js +++ b/test/utils/DateInput.test.tsx @@ -1,21 +1,22 @@ import React from 'react'; -import { shallow } from 'enzyme'; +import { shallow, ShallowWrapper } from 'enzyme'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import moment from 'moment'; -import DateInput from '../../src/utils/DateInput'; +import { Mock } from 'ts-mockery'; +import DateInput, { DateInputProps } from '../../src/utils/DateInput'; describe('', () => { - let wrapped; + let wrapped: ShallowWrapper; - const createComponent = (props = {}) => { - wrapped = shallow(); + const createComponent = (props: Partial = {}) => { + wrapped = shallow((props)} />); return wrapped; }; - afterEach(() => wrapped && wrapped.unmount()); + afterEach(() => wrapped?.unmount()); - it('wrapps a DatePicker', () => { + it('wraps a DatePicker', () => { wrapped = createComponent(); }); From f283dc8569bfd403ae6ec4ca44d2858eb75cc014 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Wed, 26 Aug 2020 20:37:36 +0200 Subject: [PATCH 25/59] Migrated short URL helper modal components to TS --- src/servers/helpers/ServerForm.js | 6 +-- src/short-urls/CreateShortUrl.js | 8 ++-- src/short-urls/data/index.ts | 6 +++ ...ortUrlModal.js => DeleteShortUrlModal.tsx} | 37 ++++++++---------- src/short-urls/helpers/EditMetaModal.tsx | 16 +++----- ...ShortUrlModal.js => EditShortUrlModal.tsx} | 28 ++++++------- src/short-urls/reducers/shortUrlDeletion.ts | 10 ----- src/short-urls/reducers/shortUrlEdition.ts | 9 ----- src/tags/helpers/EditTagModal.js | 13 +++---- src/utils/services/ShlinkApiClient.js | 10 ----- src/utils/services/types.ts | 1 + src/utils/utils.ts | 8 +++- ...l.test.js => DeleteShortUrlModal.test.tsx} | 39 +++++++++---------- ...dal.test.js => EditShortUrlModal.test.tsx} | 29 +++++++------- 14 files changed, 90 insertions(+), 130 deletions(-) rename src/short-urls/helpers/{DeleteShortUrlModal.js => DeleteShortUrlModal.tsx} (69%) rename src/short-urls/helpers/{EditShortUrlModal.js => EditShortUrlModal.tsx} (65%) rename test/short-urls/helpers/{DeleteShortUrlModal.test.js => DeleteShortUrlModal.test.tsx} (76%) rename test/short-urls/helpers/{EditShortUrlModal.test.js => EditShortUrlModal.test.tsx} (70%) diff --git a/src/servers/helpers/ServerForm.js b/src/servers/helpers/ServerForm.js index 57650f09..03c60868 100644 --- a/src/servers/helpers/ServerForm.js +++ b/src/servers/helpers/ServerForm.js @@ -1,6 +1,7 @@ import React, { useEffect, useState } from 'react'; import PropTypes from 'prop-types'; import { HorizontalFormGroup } from '../../utils/HorizontalFormGroup'; +import { handleEventPreventingDefault } from '../../utils/utils'; const propTypes = { onSubmit: PropTypes.func.isRequired, @@ -16,10 +17,7 @@ export const ServerForm = ({ onSubmit, initialValues, children }) => { const [ name, setName ] = useState(''); const [ url, setUrl ] = useState(''); const [ apiKey, setApiKey ] = useState(''); - const handleSubmit = (e) => { - e.preventDefault(); - onSubmit({ name, url, apiKey }); - }; + const handleSubmit = handleEventPreventingDefault(() => onSubmit({ name, url, apiKey })); useEffect(() => { initialValues && setName(initialValues.name); diff --git a/src/short-urls/CreateShortUrl.js b/src/short-urls/CreateShortUrl.js index 0759489b..e1591fd1 100644 --- a/src/short-urls/CreateShortUrl.js +++ b/src/short-urls/CreateShortUrl.js @@ -8,7 +8,7 @@ import DateInput from '../utils/DateInput'; import Checkbox from '../utils/Checkbox'; import { serverType } from '../servers/prop-types'; import { versionMatch } from '../utils/helpers/version'; -import { hasValue } from '../utils/utils'; +import { handleEventPreventingDefault, hasValue } from '../utils/utils'; import { useToggle } from '../utils/helpers/hooks'; import { createShortUrlResultType } from './reducers/shortUrlCreation'; import UseExistingIfFoundInfoIcon from './UseExistingIfFoundInfoIcon'; @@ -42,9 +42,7 @@ const CreateShortUrl = (TagsSelector, CreateShortUrlResult, ForServerVersion) => const changeTags = (tags) => setShortUrlCreation({ ...shortUrlCreation, tags: tags.map(normalizeTag) }); const reset = () => setShortUrlCreation(initialState); - const save = (e) => { - e.preventDefault(); - + const save = handleEventPreventingDefault(() => { const shortUrlData = { ...shortUrlCreation, validSince: formatDate(shortUrlCreation.validSince), @@ -52,7 +50,7 @@ const CreateShortUrl = (TagsSelector, CreateShortUrlResult, ForServerVersion) => }; createShortUrl(shortUrlData).then(reset).catch(() => {}); - }; + }); const renderOptionalInput = (id, placeholder, type = 'text', props = {}) => ( void; +} diff --git a/src/short-urls/helpers/DeleteShortUrlModal.js b/src/short-urls/helpers/DeleteShortUrlModal.tsx similarity index 69% rename from src/short-urls/helpers/DeleteShortUrlModal.js rename to src/short-urls/helpers/DeleteShortUrlModal.tsx index dbc598ed..55365af7 100644 --- a/src/short-urls/helpers/DeleteShortUrlModal.js +++ b/src/short-urls/helpers/DeleteShortUrlModal.tsx @@ -1,40 +1,37 @@ import React, { useEffect, useState } from 'react'; import { Modal, ModalBody, ModalFooter, ModalHeader } from 'reactstrap'; -import PropTypes from 'prop-types'; import { identity, pipe } from 'ramda'; -import { shortUrlType } from '../reducers/shortUrlsList'; -import { shortUrlDeletionType } from '../reducers/shortUrlDeletion'; +import { ShortUrlDeletion } from '../reducers/shortUrlDeletion'; +import { ShortUrlModalProps } from '../data'; +import { handleEventPreventingDefault, OptionalString } from '../../utils/utils'; const THRESHOLD_REACHED = 'INVALID_SHORTCODE_DELETION'; -const propTypes = { - shortUrl: shortUrlType, - toggle: PropTypes.func, - isOpen: PropTypes.bool, - shortUrlDeletion: shortUrlDeletionType, - deleteShortUrl: PropTypes.func, - resetDeleteShortUrl: PropTypes.func, -}; +interface DeleteShortUrlModalConnectProps extends ShortUrlModalProps { + shortUrlDeletion: ShortUrlDeletion; + deleteShortUrl: (shortCode: string, domain: OptionalString) => Promise; + resetDeleteShortUrl: () => void; +} -const DeleteShortUrlModal = ({ shortUrl, toggle, isOpen, shortUrlDeletion, resetDeleteShortUrl, deleteShortUrl }) => { +const DeleteShortUrlModal = ( + { shortUrl, toggle, isOpen, shortUrlDeletion, resetDeleteShortUrl, deleteShortUrl }: DeleteShortUrlModalConnectProps, +) => { const [ inputValue, setInputValue ] = useState(''); useEffect(() => resetDeleteShortUrl, []); const { error, errorData } = shortUrlDeletion; - const errorCode = error && errorData && (errorData.type || errorData.error); + const errorCode = error && (errorData?.type || errorData?.error); const hasThresholdError = errorCode === THRESHOLD_REACHED; const hasErrorOtherThanThreshold = error && errorCode !== THRESHOLD_REACHED; const close = pipe(resetDeleteShortUrl, toggle); - const handleDeleteUrl = (e) => { - e.preventDefault(); - + const handleDeleteUrl = handleEventPreventingDefault(() => { const { shortCode, domain } = shortUrl; deleteShortUrl(shortCode, domain) .then(toggle) .catch(identity); - }; + }); return ( @@ -56,8 +53,8 @@ const DeleteShortUrlModal = ({ shortUrl, toggle, isOpen, shortUrlDeletion, reset {hasThresholdError && (
- {errorData.threshold && `This short URL has received more than ${errorData.threshold} visits, and therefore, it cannot be deleted.`} - {!errorData.threshold && 'This short URL has received too many visits, and therefore, it cannot be deleted.'} + {errorData?.threshold && `This short URL has received more than ${errorData.threshold} visits, and therefore, it cannot be deleted.`} + {!errorData?.threshold && 'This short URL has received too many visits, and therefore, it cannot be deleted.'}
)} {hasErrorOtherThanThreshold && ( @@ -81,6 +78,4 @@ const DeleteShortUrlModal = ({ shortUrl, toggle, isOpen, shortUrlDeletion, reset ); }; -DeleteShortUrlModal.propTypes = propTypes; - export default DeleteShortUrlModal; diff --git a/src/short-urls/helpers/EditMetaModal.tsx b/src/short-urls/helpers/EditMetaModal.tsx index 84462ac1..58eaf42e 100644 --- a/src/short-urls/helpers/EditMetaModal.tsx +++ b/src/short-urls/helpers/EditMetaModal.tsx @@ -1,4 +1,4 @@ -import React, { ChangeEvent, SyntheticEvent, useState } from 'react'; +import React, { ChangeEvent, useState } from 'react'; import { Modal, ModalBody, ModalFooter, ModalHeader, FormGroup, Input, UncontrolledTooltip } from 'reactstrap'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faInfoCircle as infoIcon } from '@fortawesome/free-solid-svg-icons'; @@ -8,16 +8,10 @@ import { isEmpty, pipe } from 'ramda'; import { ShortUrlMetaEdition } from '../reducers/shortUrlMeta'; import DateInput from '../../utils/DateInput'; import { formatIsoDate } from '../../utils/helpers/date'; -import { ShortUrl, ShortUrlMeta } from '../data'; -import { Nullable, OptionalString } from '../../utils/utils'; +import { ShortUrl, ShortUrlMeta, ShortUrlModalProps } from '../data'; +import { handleEventPreventingDefault, Nullable, OptionalString } from '../../utils/utils'; -export interface EditMetaModalProps { - shortUrl: ShortUrl; - isOpen: boolean; - toggle: () => void; -} - -interface EditMetaModalConnectProps extends EditMetaModalProps { +interface EditMetaModalConnectProps extends ShortUrlModalProps { shortUrlMeta: ShortUrlMetaEdition; resetShortUrlMeta: () => void; editShortUrlMeta: (shortCode: string, domain: OptionalString, meta: Nullable) => Promise; @@ -54,7 +48,7 @@ const EditMetaModal = (

If any of the params is not met, the URL will behave as if it was an invalid short URL.

- e.preventDefault(), doEdit)}> + Promise; +} -const EditShortUrlModal = ({ isOpen, toggle, shortUrl, shortUrlEdition, editShortUrl }) => { +const EditShortUrlModal = ({ isOpen, toggle, shortUrl, shortUrlEdition, editShortUrl }: EditShortUrlModalProps) => { const { saving, error } = shortUrlEdition; - const url = shortUrl && (shortUrl.shortUrl || ''); + const url = shortUrl?.shortUrl ?? ''; const [ longUrl, setLongUrl ] = useState(shortUrl.longUrl); - const doEdit = () => editShortUrl(shortUrl.shortCode, shortUrl.domain, longUrl).then(toggle); + const doEdit = async () => editShortUrl(shortUrl.shortCode, shortUrl.domain, longUrl).then(toggle); return ( Edit long URL for - e.preventDefault() || doEdit()}> + { const [ newTagName, setNewTagName ] = useState(tag); const [ color, setColor ] = useState(getColorForKey(tag)); const [ showColorPicker, toggleColorPicker ] = useToggle(); - const saveTag = (e) => { - e.preventDefault(); - - editTag(tag, newTagName, color) - .then(() => tagEdited(tag, newTagName, color)) - .then(toggle) - .catch(() => {}); - }; + const saveTag = handleEventPreventingDefault(() => editTag(tag, newTagName, color) + .then(() => tagEdited(tag, newTagName, color)) + .then(toggle) + .catch(() => {})); return ( diff --git a/src/utils/services/ShlinkApiClient.js b/src/utils/services/ShlinkApiClient.js index 05a9c5bf..a26d7078 100644 --- a/src/utils/services/ShlinkApiClient.js +++ b/src/utils/services/ShlinkApiClient.js @@ -1,15 +1,5 @@ import qs from 'qs'; import { isEmpty, isNil, reject } from 'ramda'; -import PropTypes from 'prop-types'; - -export const apiErrorType = PropTypes.shape({ - type: PropTypes.string, - detail: PropTypes.string, - title: PropTypes.string, - status: PropTypes.number, - error: PropTypes.string, // Deprecated - message: PropTypes.string, // Deprecated -}); const buildShlinkBaseUrl = (url, apiVersion) => url ? `${url}/rest/v${apiVersion}` : ''; const rejectNilProps = reject(isNil); diff --git a/src/utils/services/types.ts b/src/utils/services/types.ts index 048ce954..b52a83ae 100644 --- a/src/utils/services/types.ts +++ b/src/utils/services/types.ts @@ -22,4 +22,5 @@ export interface ProblemDetailsError { status: number; error?: string; // Deprecated message?: string; // Deprecated + [extraProps: string]: any; } diff --git a/src/utils/utils.ts b/src/utils/utils.ts index ccaf6706..45592f26 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -1,4 +1,5 @@ -import { isEmpty, isNil, range } from 'ramda'; +import { isEmpty, isNil, pipe, range } from 'ramda'; +import { SyntheticEvent } from 'react'; export type OrderDir = 'ASC' | 'DESC' | undefined; @@ -22,6 +23,11 @@ export type Empty = null | undefined | '' | never[]; export const hasValue = (value: T | Empty): value is T => !isNil(value) && !isEmpty(value); +export const handleEventPreventingDefault = (handler: () => T) => pipe( + (e: SyntheticEvent) => e.preventDefault(), + handler, +); + export type Nullable = { [P in keyof T]: T[P] | null }; diff --git a/test/short-urls/helpers/DeleteShortUrlModal.test.js b/test/short-urls/helpers/DeleteShortUrlModal.test.tsx similarity index 76% rename from test/short-urls/helpers/DeleteShortUrlModal.test.js rename to test/short-urls/helpers/DeleteShortUrlModal.test.tsx index c950d642..a29395e5 100644 --- a/test/short-urls/helpers/DeleteShortUrlModal.test.js +++ b/test/short-urls/helpers/DeleteShortUrlModal.test.tsx @@ -1,35 +1,37 @@ import React from 'react'; -import { shallow } from 'enzyme'; +import { shallow, ShallowWrapper } from 'enzyme'; import { identity } from 'ramda'; +import { Mock } from 'ts-mockery'; import DeleteShortUrlModal from '../../../src/short-urls/helpers/DeleteShortUrlModal'; +import { ShortUrl } from '../../../src/short-urls/data'; +import { ShortUrlDeletion } from '../../../src/short-urls/reducers/shortUrlDeletion'; +import { ProblemDetailsError } from '../../../src/utils/services/types'; describe('', () => { - let wrapper; - const shortUrl = { + let wrapper: ShallowWrapper; + const shortUrl = Mock.of({ tags: [], shortCode: 'abc123', - originalUrl: 'https://long-domain.com/foo/bar', - }; - const deleteShortUrl = jest.fn(() => Promise.resolve()); - const createWrapper = (shortUrlDeletion) => { + longUrl: 'https://long-domain.com/foo/bar', + }); + const deleteShortUrl = jest.fn(async () => Promise.resolve()); + const createWrapper = (shortUrlDeletion: Partial) => { wrapper = shallow( (shortUrlDeletion)} + toggle={() => {}} deleteShortUrl={deleteShortUrl} - resetDeleteShortUrl={identity} + resetDeleteShortUrl={() => {}} />, ); return wrapper; }; - afterEach(() => { - wrapper && wrapper.unmount(); - deleteShortUrl.mockClear(); - }); + afterEach(() => wrapper?.unmount()); + afterEach(jest.clearAllMocks); it.each([ [ @@ -48,12 +50,12 @@ describe('', () => { { type: 'INVALID_SHORTCODE_DELETION', threshold: 8 }, 'This short URL has received more than 8 visits, and therefore, it cannot be deleted.', ], - ])('shows threshold error message when threshold error occurs', (errorData, expectedMessage) => { + ])('shows threshold error message when threshold error occurs', (errorData: Partial, expectedMessage) => { const wrapper = createWrapper({ loading: false, error: true, shortCode: 'abc123', - errorData, + errorData: Mock.of(errorData), }); const warning = wrapper.find('.bg-warning'); @@ -66,7 +68,7 @@ describe('', () => { loading: false, error: true, shortCode: 'abc123', - errorData: { error: 'OTHER_ERROR' }, + errorData: Mock.of({ error: 'OTHER_ERROR' }), }); const error = wrapper.find('.bg-danger'); @@ -79,7 +81,6 @@ describe('', () => { loading: true, error: false, shortCode: 'abc123', - errorData: {}, }); const submit = wrapper.find('.btn-danger'); @@ -94,7 +95,6 @@ describe('', () => { loading: false, error: false, shortCode, - errorData: {}, }); const input = wrapper.find('.form-control'); @@ -113,7 +113,6 @@ describe('', () => { loading: false, error: false, shortCode, - errorData: {}, }); const input = wrapper.find('.form-control'); diff --git a/test/short-urls/helpers/EditShortUrlModal.test.js b/test/short-urls/helpers/EditShortUrlModal.test.tsx similarity index 70% rename from test/short-urls/helpers/EditShortUrlModal.test.js rename to test/short-urls/helpers/EditShortUrlModal.test.tsx index a7a4ab15..2b723112 100644 --- a/test/short-urls/helpers/EditShortUrlModal.test.js +++ b/test/short-urls/helpers/EditShortUrlModal.test.tsx @@ -1,18 +1,21 @@ import React from 'react'; -import { shallow } from 'enzyme'; -import { FormGroup, Modal, ModalHeader } from 'reactstrap'; +import { shallow, ShallowWrapper } from 'enzyme'; +import { FormGroup } from 'reactstrap'; +import { Mock } from 'ts-mockery'; import EditShortUrlModal from '../../../src/short-urls/helpers/EditShortUrlModal'; +import { ShortUrl } from '../../../src/short-urls/data'; +import { ShortUrlEdition } from '../../../src/short-urls/reducers/shortUrlEdition'; describe('', () => { - let wrapper; - const editShortUrl = jest.fn(() => Promise.resolve()); + let wrapper: ShallowWrapper; + const editShortUrl = jest.fn(async () => Promise.resolve()); const toggle = jest.fn(); - const createWrapper = (shortUrl, shortUrlEdition) => { + const createWrapper = (shortUrl: Partial, shortUrlEdition: Partial) => { wrapper = shallow( (shortUrl)} + shortUrlEdition={Mock.of(shortUrlEdition)} toggle={toggle} editShortUrl={editShortUrl} />, @@ -21,10 +24,8 @@ describe('', () => { return wrapper; }; - afterEach(() => { - wrapper && wrapper.unmount(); - jest.clearAllMocks(); - }); + afterEach(() => wrapper?.unmount()); + afterEach(jest.clearAllMocks); it.each([ [ false, 0 ], @@ -66,13 +67,13 @@ describe('', () => { it.each([ [ '[color="link"]', 'onClick' ], - [ Modal, 'toggle' ], - [ ModalHeader, 'toggle' ], + [ 'Modal', 'toggle' ], + [ 'ModalHeader', 'toggle' ], ])('toggles modal with different mechanisms', (componentToFind, propToCall) => { const wrapper = createWrapper({}, { saving: false, error: false }); const component = wrapper.find(componentToFind); - component.prop(propToCall)(); + (component.prop(propToCall) as Function)(); // eslint-disable-line @typescript-eslint/no-unnecessary-type-assertion expect(toggle).toHaveBeenCalled(); }); From f3a2535e2fbf81d7caeb35d38fb6a2b726a7a787 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Thu, 27 Aug 2020 17:56:48 +0200 Subject: [PATCH 26/59] Defined visits TS types --- src/short-urls/reducers/shortUrlsList.js | 1 + src/visits/reducers/visitCreation.js | 3 -- src/visits/reducers/visitCreation.ts | 12 +++++ src/visits/types/index.js | 25 --------- src/visits/types/index.ts | 59 ++++++++++++++++++++++ test/visits/reducers/visitCreation.test.js | 10 ---- test/visits/reducers/visitCreation.test.ts | 16 ++++++ 7 files changed, 88 insertions(+), 38 deletions(-) delete mode 100644 src/visits/reducers/visitCreation.js create mode 100644 src/visits/reducers/visitCreation.ts delete mode 100644 src/visits/types/index.js create mode 100644 src/visits/types/index.ts delete mode 100644 test/visits/reducers/visitCreation.test.js create mode 100644 test/visits/reducers/visitCreation.test.ts diff --git a/src/short-urls/reducers/shortUrlsList.js b/src/short-urls/reducers/shortUrlsList.js index 90d2b1c8..f9a23e7f 100644 --- a/src/short-urls/reducers/shortUrlsList.js +++ b/src/short-urls/reducers/shortUrlsList.js @@ -14,6 +14,7 @@ export const LIST_SHORT_URLS_ERROR = 'shlink/shortUrlsList/LIST_SHORT_URLS_ERROR export const LIST_SHORT_URLS = 'shlink/shortUrlsList/LIST_SHORT_URLS'; /* eslint-enable padding-line-between-statements */ +/** @deprecated Use ShortUrl interface instead */ export const shortUrlType = PropTypes.shape({ shortCode: PropTypes.string, shortUrl: PropTypes.string, diff --git a/src/visits/reducers/visitCreation.js b/src/visits/reducers/visitCreation.js deleted file mode 100644 index e37890a1..00000000 --- a/src/visits/reducers/visitCreation.js +++ /dev/null @@ -1,3 +0,0 @@ -export const CREATE_VISIT = 'shlink/visitCreation/CREATE_VISIT'; - -export const createNewVisit = ({ shortUrl, visit }) => ({ shortUrl, visit, type: CREATE_VISIT }); diff --git a/src/visits/reducers/visitCreation.ts b/src/visits/reducers/visitCreation.ts new file mode 100644 index 00000000..735acf6d --- /dev/null +++ b/src/visits/reducers/visitCreation.ts @@ -0,0 +1,12 @@ +import { Action } from 'redux'; +import { CreateVisit } from '../types'; + +export const CREATE_VISIT = 'shlink/visitCreation/CREATE_VISIT'; + +type CreateVisitAction = Action & CreateVisit; + +export const createNewVisit = ({ shortUrl, visit }: CreateVisit): CreateVisitAction => ({ + type: CREATE_VISIT, + shortUrl, + visit, +}); diff --git a/src/visits/types/index.js b/src/visits/types/index.js deleted file mode 100644 index bd6d1383..00000000 --- a/src/visits/types/index.js +++ /dev/null @@ -1,25 +0,0 @@ -import PropTypes from 'prop-types'; - -export const VisitType = PropTypes.shape({ - referer: PropTypes.string, - date: PropTypes.string, - userAgent: PropTypes.string, - visitLocations: PropTypes.shape({ - countryCode: PropTypes.string, - countryName: PropTypes.string, - regionName: PropTypes.string, - cityName: PropTypes.string, - latitude: PropTypes.number, - longitude: PropTypes.number, - timezone: PropTypes.string, - isEmpty: PropTypes.bool, - }), -}); - -export const VisitsInfoType = PropTypes.shape({ - visits: PropTypes.arrayOf(VisitType), - loading: PropTypes.bool, - loadingLarge: PropTypes.bool, - error: PropTypes.bool, - progress: PropTypes.number, -}); diff --git a/src/visits/types/index.ts b/src/visits/types/index.ts new file mode 100644 index 00000000..7ade9fd9 --- /dev/null +++ b/src/visits/types/index.ts @@ -0,0 +1,59 @@ +import PropTypes from 'prop-types'; +import { ShortUrl } from '../../short-urls/data'; + +/** @deprecated Use Visit interface instead */ +export const VisitType = PropTypes.shape({ + referer: PropTypes.string, + date: PropTypes.string, + userAgent: PropTypes.string, + visitLocation: PropTypes.shape({ + countryCode: PropTypes.string, + countryName: PropTypes.string, + regionName: PropTypes.string, + cityName: PropTypes.string, + latitude: PropTypes.number, + longitude: PropTypes.number, + timezone: PropTypes.string, + isEmpty: PropTypes.bool, + }), +}); + +/** @deprecated Use VisitsInfo interface instead */ +export const VisitsInfoType = PropTypes.shape({ + visits: PropTypes.arrayOf(VisitType), + loading: PropTypes.bool, + loadingLarge: PropTypes.bool, + error: PropTypes.bool, + progress: PropTypes.number, +}); + +export interface VisitsInfo { + visits: Visit[]; + loading: boolean; + loadingLarge: boolean; + error: boolean; + progress: number; +} + +interface VisitLocation { + countryCode: string | null; + countryName: string | null; + regionName: string | null; + cityName: string | null; + latitude: number | null; + longitude: number | null; + timezone: string | null; + isEmpty: boolean; +} + +export interface Visit { + referer: string; + date: string; + userAgent: string; + visitLocation: VisitLocation | null; +} + +export interface CreateVisit { + shortUrl: ShortUrl; + visit: Visit; +} diff --git a/test/visits/reducers/visitCreation.test.js b/test/visits/reducers/visitCreation.test.js deleted file mode 100644 index 7f8dbe3b..00000000 --- a/test/visits/reducers/visitCreation.test.js +++ /dev/null @@ -1,10 +0,0 @@ -import { CREATE_VISIT, createNewVisit } from '../../../src/visits/reducers/visitCreation'; - -describe('visitCreationReducer', () => { - describe('createNewVisit', () => { - it('just returns the action with proper type', () => - expect(createNewVisit({ shortUrl: {}, visit: {} })).toEqual( - { type: CREATE_VISIT, shortUrl: {}, visit: {} }, - )); - }); -}); diff --git a/test/visits/reducers/visitCreation.test.ts b/test/visits/reducers/visitCreation.test.ts new file mode 100644 index 00000000..4149f99c --- /dev/null +++ b/test/visits/reducers/visitCreation.test.ts @@ -0,0 +1,16 @@ +import { Mock } from 'ts-mockery'; +import { CREATE_VISIT, createNewVisit } from '../../../src/visits/reducers/visitCreation'; +import { ShortUrl } from '../../../src/short-urls/data'; +import { Visit } from '../../../src/visits/types'; + +describe('visitCreationReducer', () => { + describe('createNewVisit', () => { + const shortUrl = Mock.all(); + const visit = Mock.all(); + + it('just returns the action with proper type', () => + expect(createNewVisit({ shortUrl, visit })).toEqual( + { type: CREATE_VISIT, shortUrl, visit }, + )); + }); +}); From 83531666deeba3d2123af2c641ac0a6e347e03a4 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Thu, 27 Aug 2020 18:31:56 +0200 Subject: [PATCH 27/59] Migrated to typescript the most complex reducer in the project --- src/container/types.ts | 3 +- src/short-urls/data/index.ts | 7 +- src/short-urls/helpers/{index.js => index.ts} | 4 +- src/short-urls/reducers/shortUrlEdition.ts | 5 +- src/short-urls/reducers/shortUrlMeta.ts | 6 +- src/short-urls/reducers/shortUrlTags.ts | 5 +- src/short-urls/reducers/shortUrlsList.js | 76 ------------ src/short-urls/reducers/shortUrlsList.ts | 108 ++++++++++++++++++ .../reducers/shortUrlsListParams.ts | 11 +- src/visits/reducers/visitCreation.ts | 2 +- ...UrlsList.test.js => shortUrlsList.test.ts} | 88 ++++++++------ 11 files changed, 182 insertions(+), 133 deletions(-) rename src/short-urls/helpers/{index.js => index.ts} (50%) delete mode 100644 src/short-urls/reducers/shortUrlsList.js create mode 100644 src/short-urls/reducers/shortUrlsList.ts rename test/short-urls/reducers/{shortUrlsList.test.js => shortUrlsList.test.ts} (62%) diff --git a/src/container/types.ts b/src/container/types.ts index 1018b5e9..ae26671d 100644 --- a/src/container/types.ts +++ b/src/container/types.ts @@ -8,11 +8,12 @@ import { ShortUrlDeletion } from '../short-urls/reducers/shortUrlDeletion'; import { ShortUrlEdition } from '../short-urls/reducers/shortUrlEdition'; import { ShortUrlsListParams } from '../short-urls/reducers/shortUrlsListParams'; import { ShortUrlTags } from '../short-urls/reducers/shortUrlTags'; +import { ShortUrlsList } from '../short-urls/reducers/shortUrlsList'; export interface ShlinkState { servers: ServersMap; selectedServer: SelectedServer; - shortUrlsList: any; + shortUrlsList: ShortUrlsList; shortUrlsListParams: ShortUrlsListParams; shortUrlCreationResult: ShortUrlCreation; shortUrlDeletion: ShortUrlDeletion; diff --git a/src/short-urls/data/index.ts b/src/short-urls/data/index.ts index e8b3d6d8..4997ce44 100644 --- a/src/short-urls/data/index.ts +++ b/src/short-urls/data/index.ts @@ -1,4 +1,4 @@ -import { Nullable } from '../../utils/utils'; +import { Nullable, OptionalString } from '../../utils/utils'; export interface ShortUrlData { longUrl: string; @@ -33,3 +33,8 @@ export interface ShortUrlModalProps { isOpen: boolean; toggle: () => void; } + +export interface ShortUrlIdentifier { + shortCode: string; + domain: OptionalString; +} diff --git a/src/short-urls/helpers/index.js b/src/short-urls/helpers/index.ts similarity index 50% rename from src/short-urls/helpers/index.js rename to src/short-urls/helpers/index.ts index 32a12ad9..e40fa4a3 100644 --- a/src/short-urls/helpers/index.js +++ b/src/short-urls/helpers/index.ts @@ -1,6 +1,8 @@ import { isNil } from 'ramda'; +import { ShortUrl } from '../data'; +import { OptionalString } from '../../utils/utils'; -export const shortUrlMatches = (shortUrl, shortCode, domain) => { +export const shortUrlMatches = (shortUrl: ShortUrl, shortCode: string, domain: OptionalString): boolean => { if (isNil(domain)) { return shortUrl.shortCode === shortCode && !shortUrl.domain; } diff --git a/src/short-urls/reducers/shortUrlEdition.ts b/src/short-urls/reducers/shortUrlEdition.ts index b10fc1ff..6179f98c 100644 --- a/src/short-urls/reducers/shortUrlEdition.ts +++ b/src/short-urls/reducers/shortUrlEdition.ts @@ -3,6 +3,7 @@ import { buildReducer } from '../../utils/helpers/redux'; import { ShlinkApiClientBuilder } from '../../utils/services/types'; import { GetState } from '../../container/types'; import { OptionalString } from '../../utils/utils'; +import { ShortUrlIdentifier } from '../data'; /* eslint-disable padding-line-between-statements */ export const EDIT_SHORT_URL_START = 'shlink/shortUrlEdition/EDIT_SHORT_URL_START'; @@ -17,10 +18,8 @@ export interface ShortUrlEdition { error: boolean; } -export interface ShortUrlEditedAction extends Action { - shortCode: string; +export interface ShortUrlEditedAction extends Action, ShortUrlIdentifier { longUrl: string; - domain: OptionalString; } const initialState: ShortUrlEdition = { diff --git a/src/short-urls/reducers/shortUrlMeta.ts b/src/short-urls/reducers/shortUrlMeta.ts index a69c35de..ec0a2bca 100644 --- a/src/short-urls/reducers/shortUrlMeta.ts +++ b/src/short-urls/reducers/shortUrlMeta.ts @@ -1,6 +1,6 @@ import PropTypes from 'prop-types'; import { Dispatch, Action } from 'redux'; -import { ShortUrlMeta } from '../data'; +import { ShortUrlIdentifier, ShortUrlMeta } from '../data'; import { ShlinkApiClientBuilder } from '../../utils/services/types'; import { GetState } from '../../container/types'; import { buildActionCreator, buildReducer } from '../../utils/helpers/redux'; @@ -27,9 +27,7 @@ export interface ShortUrlMetaEdition { error: boolean; } -interface ShortUrlMetaEditedAction extends Action { - shortCode: string; - domain?: string | null; +export interface ShortUrlMetaEditedAction extends Action, ShortUrlIdentifier { meta: ShortUrlMeta; } diff --git a/src/short-urls/reducers/shortUrlTags.ts b/src/short-urls/reducers/shortUrlTags.ts index 89ecd15d..823499aa 100644 --- a/src/short-urls/reducers/shortUrlTags.ts +++ b/src/short-urls/reducers/shortUrlTags.ts @@ -4,6 +4,7 @@ import { buildActionCreator, buildReducer } from '../../utils/helpers/redux'; import { ShlinkApiClientBuilder } from '../../utils/services/types'; import { GetState } from '../../container/types'; import { OptionalString } from '../../utils/utils'; +import { ShortUrlIdentifier } from '../data'; /* eslint-disable padding-line-between-statements */ export const EDIT_SHORT_URL_TAGS_START = 'shlink/shortUrlTags/EDIT_SHORT_URL_TAGS_START'; @@ -27,10 +28,8 @@ export interface ShortUrlTags { error: boolean; } -export interface EditShortUrlTagsAction extends Action { - shortCode: string; +export interface EditShortUrlTagsAction extends Action, ShortUrlIdentifier { tags: string[]; - domain: OptionalString; } const initialState: ShortUrlTags = { diff --git a/src/short-urls/reducers/shortUrlsList.js b/src/short-urls/reducers/shortUrlsList.js deleted file mode 100644 index f9a23e7f..00000000 --- a/src/short-urls/reducers/shortUrlsList.js +++ /dev/null @@ -1,76 +0,0 @@ -import { handleActions } from 'redux-actions'; -import { assoc, assocPath, reject } from 'ramda'; -import PropTypes from 'prop-types'; -import { shortUrlMatches } from '../helpers'; -import { CREATE_VISIT } from '../../visits/reducers/visitCreation'; -import { SHORT_URL_TAGS_EDITED } from './shortUrlTags'; -import { SHORT_URL_DELETED } from './shortUrlDeletion'; -import { SHORT_URL_META_EDITED, shortUrlMetaType } from './shortUrlMeta'; -import { SHORT_URL_EDITED } from './shortUrlEdition'; - -/* eslint-disable padding-line-between-statements */ -export const LIST_SHORT_URLS_START = 'shlink/shortUrlsList/LIST_SHORT_URLS_START'; -export const LIST_SHORT_URLS_ERROR = 'shlink/shortUrlsList/LIST_SHORT_URLS_ERROR'; -export const LIST_SHORT_URLS = 'shlink/shortUrlsList/LIST_SHORT_URLS'; -/* eslint-enable padding-line-between-statements */ - -/** @deprecated Use ShortUrl interface instead */ -export const shortUrlType = PropTypes.shape({ - shortCode: PropTypes.string, - shortUrl: PropTypes.string, - longUrl: PropTypes.string, - visitsCount: PropTypes.number, - meta: shortUrlMetaType, - tags: PropTypes.arrayOf(PropTypes.string), - domain: PropTypes.string, -}); - -const initialState = { - shortUrls: {}, - loading: true, - error: false, -}; - -const setPropFromActionOnMatchingShortUrl = (prop) => (state, { shortCode, domain, [prop]: propValue }) => assocPath( - [ 'shortUrls', 'data' ], - state.shortUrls.data.map( - (shortUrl) => shortUrlMatches(shortUrl, shortCode, domain) ? assoc(prop, propValue, shortUrl) : shortUrl, - ), - state, -); - -export default handleActions({ - [LIST_SHORT_URLS_START]: (state) => ({ ...state, loading: true, error: false }), - [LIST_SHORT_URLS]: (state, { shortUrls }) => ({ loading: false, error: false, shortUrls }), - [LIST_SHORT_URLS_ERROR]: () => ({ loading: false, error: true, shortUrls: {} }), - [SHORT_URL_DELETED]: (state, { shortCode, domain }) => assocPath( - [ 'shortUrls', 'data' ], - reject((shortUrl) => shortUrlMatches(shortUrl, shortCode, domain), state.shortUrls.data), - state, - ), - [SHORT_URL_TAGS_EDITED]: setPropFromActionOnMatchingShortUrl('tags'), - [SHORT_URL_META_EDITED]: setPropFromActionOnMatchingShortUrl('meta'), - [SHORT_URL_EDITED]: setPropFromActionOnMatchingShortUrl('longUrl'), - [CREATE_VISIT]: (state, { shortUrl: { shortCode, domain, visitsCount } }) => assocPath( - [ 'shortUrls', 'data' ], - state.shortUrls && state.shortUrls.data && state.shortUrls.data.map( - (shortUrl) => shortUrlMatches(shortUrl, shortCode, domain) - ? assoc('visitsCount', visitsCount, shortUrl) - : shortUrl, - ), - state, - ), -}, initialState); - -export const listShortUrls = (buildShlinkApiClient) => (params = {}) => async (dispatch, getState) => { - dispatch({ type: LIST_SHORT_URLS_START }); - const { listShortUrls } = buildShlinkApiClient(getState); - - try { - const shortUrls = await listShortUrls(params); - - dispatch({ type: LIST_SHORT_URLS, shortUrls, params }); - } catch (e) { - dispatch({ type: LIST_SHORT_URLS_ERROR, params }); - } -}; diff --git a/src/short-urls/reducers/shortUrlsList.ts b/src/short-urls/reducers/shortUrlsList.ts new file mode 100644 index 00000000..3e54ea6e --- /dev/null +++ b/src/short-urls/reducers/shortUrlsList.ts @@ -0,0 +1,108 @@ +import { assoc, assocPath, reject } from 'ramda'; +import PropTypes from 'prop-types'; +import { Action, Dispatch } from 'redux'; +import { shortUrlMatches } from '../helpers'; +import { CREATE_VISIT, CreateVisitAction } from '../../visits/reducers/visitCreation'; +import { ShortUrl, ShortUrlIdentifier } from '../data'; +import { buildReducer } from '../../utils/helpers/redux'; +import { GetState } from '../../container/types'; +import { ShlinkApiClientBuilder } from '../../utils/services/types'; +import { EditShortUrlTagsAction, SHORT_URL_TAGS_EDITED } from './shortUrlTags'; +import { SHORT_URL_DELETED } from './shortUrlDeletion'; +import { SHORT_URL_META_EDITED, ShortUrlMetaEditedAction, shortUrlMetaType } from './shortUrlMeta'; +import { SHORT_URL_EDITED, ShortUrlEditedAction } from './shortUrlEdition'; +import { ShortUrlsListParams } from './shortUrlsListParams'; + +/* eslint-disable padding-line-between-statements */ +export const LIST_SHORT_URLS_START = 'shlink/shortUrlsList/LIST_SHORT_URLS_START'; +export const LIST_SHORT_URLS_ERROR = 'shlink/shortUrlsList/LIST_SHORT_URLS_ERROR'; +export const LIST_SHORT_URLS = 'shlink/shortUrlsList/LIST_SHORT_URLS'; +/* eslint-enable padding-line-between-statements */ + +/** @deprecated Use ShortUrl interface instead */ +export const shortUrlType = PropTypes.shape({ + shortCode: PropTypes.string, + shortUrl: PropTypes.string, + longUrl: PropTypes.string, + visitsCount: PropTypes.number, + meta: shortUrlMetaType, + tags: PropTypes.arrayOf(PropTypes.string), + domain: PropTypes.string, +}); + +interface ShortUrlsData { + data: ShortUrl[]; +} + +export interface ShortUrlsList { + shortUrls: ShortUrlsData; + loading: boolean; + error: boolean; +} + +export interface ListShortUrlsAction extends Action { + shortUrls: ShortUrlsData; + params: ShortUrlsListParams; +} + +export type ListShortUrlsCombinedAction = ( + ListShortUrlsAction & EditShortUrlTagsAction & ShortUrlEditedAction & ShortUrlMetaEditedAction & CreateVisitAction +); + +const initialState: ShortUrlsList = { + shortUrls: { + data: [], + }, + loading: true, + error: false, +}; + +const setPropFromActionOnMatchingShortUrl = (prop: keyof T) => ( + state: ShortUrlsList, + { shortCode, domain, [prop]: propValue }: T, +): ShortUrlsList => assocPath( + [ 'shortUrls', 'data' ], + state.shortUrls.data.map( + (shortUrl: ShortUrl) => + shortUrlMatches(shortUrl, shortCode, domain) ? { ...shortUrl, [prop]: propValue } : shortUrl, + ), + state, +); + +export default buildReducer({ + [LIST_SHORT_URLS_START]: (state) => ({ ...state, loading: true, error: false }), + [LIST_SHORT_URLS_ERROR]: () => ({ loading: false, error: true, shortUrls: { data: [] } }), + [LIST_SHORT_URLS]: (_, { shortUrls }) => ({ loading: false, error: false, shortUrls }), + [SHORT_URL_DELETED]: (state, { shortCode, domain }) => assocPath( + [ 'shortUrls', 'data' ], + reject((shortUrl) => shortUrlMatches(shortUrl, shortCode, domain), state.shortUrls.data), + state, + ), + [SHORT_URL_TAGS_EDITED]: setPropFromActionOnMatchingShortUrl('tags'), + [SHORT_URL_META_EDITED]: setPropFromActionOnMatchingShortUrl('meta'), + [SHORT_URL_EDITED]: setPropFromActionOnMatchingShortUrl('longUrl'), + [CREATE_VISIT]: (state, { shortUrl: { shortCode, domain, visitsCount } }) => assocPath( + [ 'shortUrls', 'data' ], + state.shortUrls && state.shortUrls.data && state.shortUrls.data.map( + (shortUrl) => shortUrlMatches(shortUrl, shortCode, domain) + ? assoc('visitsCount', visitsCount, shortUrl) + : shortUrl, + ), + state, + ), +}, initialState); + +export const listShortUrls = (buildShlinkApiClient: ShlinkApiClientBuilder) => ( + params: ShortUrlsListParams = {}, +) => async (dispatch: Dispatch, getState: GetState) => { + dispatch({ type: LIST_SHORT_URLS_START }); + const { listShortUrls } = buildShlinkApiClient(getState); + + try { + const shortUrls = await listShortUrls(params); + + dispatch({ type: LIST_SHORT_URLS, shortUrls, params }); + } catch (e) { + dispatch({ type: LIST_SHORT_URLS_ERROR, params }); + } +}; diff --git a/src/short-urls/reducers/shortUrlsListParams.ts b/src/short-urls/reducers/shortUrlsListParams.ts index 770f81f0..9bc69594 100644 --- a/src/short-urls/reducers/shortUrlsListParams.ts +++ b/src/short-urls/reducers/shortUrlsListParams.ts @@ -1,7 +1,6 @@ import PropTypes from 'prop-types'; -import { Action } from 'redux'; import { buildActionCreator, buildReducer } from '../../utils/helpers/redux'; -import { LIST_SHORT_URLS } from './shortUrlsList'; +import { LIST_SHORT_URLS, ListShortUrlsAction } from './shortUrlsList'; export const RESET_SHORT_URL_PARAMS = 'shlink/shortUrlsListParams/RESET_SHORT_URL_PARAMS'; @@ -16,7 +15,7 @@ export const shortUrlsListParamsType = PropTypes.shape({ }); export interface ShortUrlsListParams { - page: string; + page?: string; tags?: string[]; searchTerm?: string; startDate?: string; @@ -24,11 +23,7 @@ export interface ShortUrlsListParams { orderBy?: object; } -interface ListShortUrlsAction extends Action { - params: ShortUrlsListParams; -} - -const initialState = { page: '1' }; +const initialState: ShortUrlsListParams = { page: '1' }; export default buildReducer({ [LIST_SHORT_URLS]: (state, { params }) => ({ ...state, ...params }), diff --git a/src/visits/reducers/visitCreation.ts b/src/visits/reducers/visitCreation.ts index 735acf6d..3b89318d 100644 --- a/src/visits/reducers/visitCreation.ts +++ b/src/visits/reducers/visitCreation.ts @@ -3,7 +3,7 @@ import { CreateVisit } from '../types'; export const CREATE_VISIT = 'shlink/visitCreation/CREATE_VISIT'; -type CreateVisitAction = Action & CreateVisit; +export type CreateVisitAction = Action & CreateVisit; export const createNewVisit = ({ shortUrl, visit }: CreateVisit): CreateVisitAction => ({ type: CREATE_VISIT, diff --git a/test/short-urls/reducers/shortUrlsList.test.js b/test/short-urls/reducers/shortUrlsList.test.ts similarity index 62% rename from test/short-urls/reducers/shortUrlsList.test.js rename to test/short-urls/reducers/shortUrlsList.test.ts index 08bb0a82..00aa1740 100644 --- a/test/short-urls/reducers/shortUrlsList.test.js +++ b/test/short-urls/reducers/shortUrlsList.test.ts @@ -1,3 +1,4 @@ +import { Mock } from 'ts-mockery'; import reducer, { LIST_SHORT_URLS, LIST_SHORT_URLS_ERROR, @@ -8,26 +9,32 @@ import { SHORT_URL_TAGS_EDITED } from '../../../src/short-urls/reducers/shortUrl import { SHORT_URL_DELETED } from '../../../src/short-urls/reducers/shortUrlDeletion'; import { SHORT_URL_META_EDITED } from '../../../src/short-urls/reducers/shortUrlMeta'; import { CREATE_VISIT } from '../../../src/visits/reducers/visitCreation'; +import { ShortUrl } from '../../../src/short-urls/data'; +import ShlinkApiClient from '../../../src/utils/services/ShlinkApiClient'; describe('shortUrlsListReducer', () => { describe('reducer', () => { it('returns loading on LIST_SHORT_URLS_START', () => - expect(reducer(undefined, { type: LIST_SHORT_URLS_START })).toEqual({ - shortUrls: {}, + expect(reducer(undefined, { type: LIST_SHORT_URLS_START } as any)).toEqual({ + shortUrls: { + data: [], + }, loading: true, error: false, })); it('returns short URLs on LIST_SHORT_URLS', () => - expect(reducer(undefined, { type: LIST_SHORT_URLS, shortUrls: { data: [], paginator: {} } })).toEqual({ - shortUrls: { data: [], paginator: {} }, + expect(reducer(undefined, { type: LIST_SHORT_URLS, shortUrls: { data: [] } } as any)).toEqual({ + shortUrls: { data: [] }, loading: false, error: false, })); it('returns error on LIST_SHORT_URLS_ERROR', () => - expect(reducer(undefined, { type: LIST_SHORT_URLS_ERROR })).toEqual({ - shortUrls: {}, + expect(reducer(undefined, { type: LIST_SHORT_URLS_ERROR } as any)).toEqual({ + shortUrls: { + data: [], + }, loading: false, error: true, })); @@ -38,14 +45,16 @@ describe('shortUrlsListReducer', () => { const state = { shortUrls: { data: [ - { shortCode, tags: [] }, - { shortCode, tags: [], domain: 'example.com' }, - { shortCode: 'foo', tags: [] }, + Mock.of({ shortCode, tags: [] }), + Mock.of({ shortCode, tags: [], domain: 'example.com' }), + Mock.of({ shortCode: 'foo', tags: [] }), ], }, + loading: false, + error: false, }; - expect(reducer(state, { type: SHORT_URL_TAGS_EDITED, shortCode, tags, domain: null })).toEqual({ + expect(reducer(state, { type: SHORT_URL_TAGS_EDITED, shortCode, tags, domain: null } as any)).toEqual({ shortUrls: { data: [ { shortCode, tags }, @@ -53,6 +62,8 @@ describe('shortUrlsListReducer', () => { { shortCode: 'foo', tags: [] }, ], }, + loading: false, + error: false, }); }); @@ -66,21 +77,25 @@ describe('shortUrlsListReducer', () => { const state = { shortUrls: { data: [ - { shortCode, meta: { maxVisits: 10 }, domain }, - { shortCode, meta: { maxVisits: 50 } }, - { shortCode: 'foo', meta: null }, + Mock.of({ shortCode, meta: { maxVisits: 10 }, domain }), + Mock.of({ shortCode, meta: { maxVisits: 50 } }), + Mock.of({ shortCode: 'foo', meta: {} }), ], }, + loading: false, + error: false, }; - expect(reducer(state, { type: SHORT_URL_META_EDITED, shortCode, meta, domain })).toEqual({ + expect(reducer(state, { type: SHORT_URL_META_EDITED, shortCode, meta, domain } as any)).toEqual({ shortUrls: { data: [ { shortCode, meta, domain: 'example.com' }, { shortCode, meta: { maxVisits: 50 } }, - { shortCode: 'foo', meta: null }, + { shortCode: 'foo', meta: {} }, ], }, + loading: false, + error: false, }); }); @@ -89,17 +104,21 @@ describe('shortUrlsListReducer', () => { const state = { shortUrls: { data: [ - { shortCode }, - { shortCode, domain: 'example.com' }, - { shortCode: 'foo' }, + Mock.of({ shortCode }), + Mock.of({ shortCode, domain: 'example.com' }), + Mock.of({ shortCode: 'foo' }), ], }, + loading: false, + error: false, }; - expect(reducer(state, { type: SHORT_URL_DELETED, shortCode })).toEqual({ + expect(reducer(state, { type: SHORT_URL_DELETED, shortCode } as any)).toEqual({ shortUrls: { data: [{ shortCode, domain: 'example.com' }, { shortCode: 'foo' }], }, + loading: false, + error: false, }); }); @@ -112,14 +131,16 @@ describe('shortUrlsListReducer', () => { const state = { shortUrls: { data: [ - { shortCode, domain: 'example.com', visitsCount: 5 }, - { shortCode, visitsCount: 10 }, - { shortCode: 'foo', visitsCount: 8 }, + Mock.of({ shortCode, domain: 'example.com', visitsCount: 5 }), + Mock.of({ shortCode, visitsCount: 10 }), + Mock.of({ shortCode: 'foo', visitsCount: 8 }), ], }, + loading: false, + error: false, }; - expect(reducer(state, { type: CREATE_VISIT, shortUrl })).toEqual({ + expect(reducer(state, { type: CREATE_VISIT, shortUrl } as any)).toEqual({ shortUrls: { data: [ { shortCode, domain: 'example.com', visitsCount: 5 }, @@ -127,6 +148,8 @@ describe('shortUrlsListReducer', () => { { shortCode: 'foo', visitsCount: 8 }, ], }, + loading: false, + error: false, }); }); }); @@ -135,15 +158,11 @@ describe('shortUrlsListReducer', () => { const dispatch = jest.fn(); const getState = jest.fn().mockReturnValue({ selectedServer: {} }); - afterEach(() => { - dispatch.mockReset(); - getState.mockClear(); - }); + afterEach(jest.clearAllMocks); it('dispatches proper actions if API client request succeeds', async () => { - const apiClientMock = { - listShortUrls: jest.fn().mockResolvedValue([]), - }; + const listShortUrlsMock = jest.fn().mockResolvedValue([]); + const apiClientMock = Mock.of({ listShortUrls: listShortUrlsMock }); await listShortUrls(() => apiClientMock)()(dispatch, getState); @@ -151,13 +170,12 @@ describe('shortUrlsListReducer', () => { expect(dispatch).toHaveBeenNthCalledWith(1, { type: LIST_SHORT_URLS_START }); expect(dispatch).toHaveBeenNthCalledWith(2, { type: LIST_SHORT_URLS, shortUrls: [], params: {} }); - expect(apiClientMock.listShortUrls).toHaveBeenCalledTimes(1); + expect(listShortUrlsMock).toHaveBeenCalledTimes(1); }); it('dispatches proper actions if API client request fails', async () => { - const apiClientMock = { - listShortUrls: jest.fn().mockRejectedValue(), - }; + const listShortUrlsMock = jest.fn().mockRejectedValue(undefined); + const apiClientMock = Mock.of({ listShortUrls: listShortUrlsMock }); await listShortUrls(() => apiClientMock)()(dispatch, getState); @@ -165,7 +183,7 @@ describe('shortUrlsListReducer', () => { expect(dispatch).toHaveBeenNthCalledWith(1, { type: LIST_SHORT_URLS_START }); expect(dispatch).toHaveBeenNthCalledWith(2, { type: LIST_SHORT_URLS_ERROR, params: {} }); - expect(apiClientMock.listShortUrls).toHaveBeenCalledTimes(1); + expect(listShortUrlsMock).toHaveBeenCalledTimes(1); }); }); }); From eb3775859a896bdb81f1f27fa396fd5612b809eb Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Thu, 27 Aug 2020 19:12:09 +0200 Subject: [PATCH 28/59] Migrated tags reducers to typescripts --- .eslintrc | 3 +- src/container/types.ts | 9 +- .../reducers/{tagDelete.js => tagDelete.ts} | 26 +++- src/tags/reducers/{tagEdit.js => tagEdit.ts} | 36 +++-- src/tags/reducers/tagsList.js | 99 -------------- src/tags/reducers/tagsList.ts | 125 ++++++++++++++++++ src/utils/services/types.ts | 11 ++ .../{tagDelete.test.js => tagDelete.test.ts} | 15 ++- .../{tagEdit.test.js => tagEdit.test.ts} | 42 +++--- .../{tagsList.test.js => tagsList.test.ts} | 40 +++--- 10 files changed, 246 insertions(+), 160 deletions(-) rename src/tags/reducers/{tagDelete.js => tagDelete.ts} (58%) rename src/tags/reducers/{tagEdit.js => tagEdit.ts} (53%) delete mode 100644 src/tags/reducers/tagsList.js create mode 100644 src/tags/reducers/tagsList.ts rename test/tags/reducers/{tagDelete.test.js => tagDelete.test.ts} (79%) rename test/tags/reducers/{tagEdit.test.js => tagEdit.test.ts} (72%) rename test/tags/reducers/{tagsList.test.js => tagsList.test.ts} (75%) diff --git a/.eslintrc b/.eslintrc index dd2b27c9..84103e15 100644 --- a/.eslintrc +++ b/.eslintrc @@ -69,7 +69,8 @@ "ignoreComments": true }], "no-mixed-operators": "off", - "react/display-name": "off" + "react/display-name": "off", + "@typescript-eslint/require-array-sort-compare": "off" } } ] diff --git a/src/container/types.ts b/src/container/types.ts index ae26671d..f5fc3ba8 100644 --- a/src/container/types.ts +++ b/src/container/types.ts @@ -9,6 +9,9 @@ import { ShortUrlEdition } from '../short-urls/reducers/shortUrlEdition'; import { ShortUrlsListParams } from '../short-urls/reducers/shortUrlsListParams'; import { ShortUrlTags } from '../short-urls/reducers/shortUrlTags'; import { ShortUrlsList } from '../short-urls/reducers/shortUrlsList'; +import { TagDeletion } from '../tags/reducers/tagDelete'; +import { TagEdition } from '../tags/reducers/tagEdit'; +import { TagsList } from '../tags/reducers/tagsList'; export interface ShlinkState { servers: ServersMap; @@ -23,9 +26,9 @@ export interface ShlinkState { shortUrlVisits: any; tagVisits: any; shortUrlDetail: any; - tagsList: any; - tagDelete: any; - tagEdit: any; + tagsList: TagsList; + tagDelete: TagDeletion; + tagEdit: TagEdition; mercureInfo: MercureInfo; settings: Settings; } diff --git a/src/tags/reducers/tagDelete.js b/src/tags/reducers/tagDelete.ts similarity index 58% rename from src/tags/reducers/tagDelete.js rename to src/tags/reducers/tagDelete.ts index 14486bea..8fa85414 100644 --- a/src/tags/reducers/tagDelete.js +++ b/src/tags/reducers/tagDelete.ts @@ -1,5 +1,8 @@ -import { handleActions } from 'redux-actions'; import PropTypes from 'prop-types'; +import { Action, Dispatch } from 'redux'; +import { buildReducer } from '../../utils/helpers/redux'; +import { GetState } from '../../container/types'; +import { ShlinkApiClientBuilder } from '../../utils/services/types'; /* eslint-disable padding-line-between-statements */ export const DELETE_TAG_START = 'shlink/deleteTag/DELETE_TAG_START'; @@ -8,23 +11,36 @@ export const DELETE_TAG = 'shlink/deleteTag/DELETE_TAG'; export const TAG_DELETED = 'shlink/deleteTag/TAG_DELETED'; /* eslint-enable padding-line-between-statements */ +/** @deprecated Use TagDeletion interface */ export const tagDeleteType = PropTypes.shape({ deleting: PropTypes.bool, error: PropTypes.bool, }); -const initialState = { +export interface TagDeletion { + deleting: boolean; + error: boolean; +} + +export interface DeleteTagAction extends Action { + tag: string; +} + +const initialState: TagDeletion = { deleting: false, error: false, }; -export default handleActions({ +export default buildReducer({ [DELETE_TAG_START]: () => ({ deleting: true, error: false }), [DELETE_TAG_ERROR]: () => ({ deleting: false, error: true }), [DELETE_TAG]: () => ({ deleting: false, error: false }), }, initialState); -export const deleteTag = (buildShlinkApiClient) => (tag) => async (dispatch, getState) => { +export const deleteTag = (buildShlinkApiClient: ShlinkApiClientBuilder) => (tag: string) => async ( + dispatch: Dispatch, + getState: GetState, +) => { dispatch({ type: DELETE_TAG_START }); const { deleteTags } = buildShlinkApiClient(getState); @@ -38,4 +54,4 @@ export const deleteTag = (buildShlinkApiClient) => (tag) => async (dispatch, get } }; -export const tagDeleted = (tag) => ({ type: TAG_DELETED, tag }); +export const tagDeleted = (tag: string): DeleteTagAction => ({ type: TAG_DELETED, tag }); diff --git a/src/tags/reducers/tagEdit.js b/src/tags/reducers/tagEdit.ts similarity index 53% rename from src/tags/reducers/tagEdit.js rename to src/tags/reducers/tagEdit.ts index 137f36df..b3e631c8 100644 --- a/src/tags/reducers/tagEdit.js +++ b/src/tags/reducers/tagEdit.ts @@ -1,5 +1,9 @@ import { pick } from 'ramda'; -import { handleActions } from 'redux-actions'; +import { Action, Dispatch } from 'redux'; +import { buildReducer } from '../../utils/helpers/redux'; +import { GetState } from '../../container/types'; +import { ShlinkApiClientBuilder } from '../../utils/services/types'; +import ColorGenerator from '../../utils/services/ColorGenerator'; /* eslint-disable padding-line-between-statements */ export const EDIT_TAG_START = 'shlink/editTag/EDIT_TAG_START'; @@ -9,27 +13,41 @@ export const EDIT_TAG = 'shlink/editTag/EDIT_TAG'; export const TAG_EDITED = 'shlink/editTag/TAG_EDITED'; -const initialState = { +export interface TagEdition { + oldName: string; + newName: string; + editing: boolean; + error: boolean; +} + +export interface EditTagAction extends Action { + oldName: string; + newName: string; + color: string; +} + +const initialState: TagEdition = { oldName: '', newName: '', editing: false, error: false, }; -export default handleActions({ +export default buildReducer({ [EDIT_TAG_START]: (state) => ({ ...state, editing: true, error: false }), [EDIT_TAG_ERROR]: (state) => ({ ...state, editing: false, error: true }), - [EDIT_TAG]: (state, action) => ({ + [EDIT_TAG]: (_, action) => ({ ...pick([ 'oldName', 'newName' ], action), editing: false, error: false, }), }, initialState); -export const editTag = (buildShlinkApiClient, colorGenerator) => (oldName, newName, color) => async ( - dispatch, - getState, -) => { +export const editTag = (buildShlinkApiClient: ShlinkApiClientBuilder, colorGenerator: ColorGenerator) => ( + oldName: string, + newName: string, + color: string, +) => async (dispatch: Dispatch, getState: GetState) => { dispatch({ type: EDIT_TAG_START }); const { editTag } = buildShlinkApiClient(getState); @@ -44,7 +62,7 @@ export const editTag = (buildShlinkApiClient, colorGenerator) => (oldName, newNa } }; -export const tagEdited = (oldName, newName, color) => ({ +export const tagEdited = (oldName: string, newName: string, color: string): EditTagAction => ({ type: TAG_EDITED, oldName, newName, diff --git a/src/tags/reducers/tagsList.js b/src/tags/reducers/tagsList.js deleted file mode 100644 index 643b3a94..00000000 --- a/src/tags/reducers/tagsList.js +++ /dev/null @@ -1,99 +0,0 @@ -import { handleActions } from 'redux-actions'; -import { isEmpty, reject } from 'ramda'; -import PropTypes from 'prop-types'; -import { CREATE_VISIT } from '../../visits/reducers/visitCreation'; -import { TAG_DELETED } from './tagDelete'; -import { TAG_EDITED } from './tagEdit'; - -/* eslint-disable padding-line-between-statements */ -export const LIST_TAGS_START = 'shlink/tagsList/LIST_TAGS_START'; -export const LIST_TAGS_ERROR = 'shlink/tagsList/LIST_TAGS_ERROR'; -export const LIST_TAGS = 'shlink/tagsList/LIST_TAGS'; -export const FILTER_TAGS = 'shlink/tagsList/FILTER_TAGS'; -/* eslint-enable padding-line-between-statements */ - -const TagStatsType = PropTypes.shape({ - shortUrlsCount: PropTypes.number, - visitsCount: PropTypes.number, -}); - -export const TagsListType = PropTypes.shape({ - tags: PropTypes.arrayOf(PropTypes.string), - filteredTags: PropTypes.arrayOf(PropTypes.string), - stats: PropTypes.objectOf(TagStatsType), // Record - loading: PropTypes.bool, - error: PropTypes.bool, -}); - -const initialState = { - tags: [], - filteredTags: [], - stats: {}, - loading: false, - error: false, -}; - -const renameTag = (oldName, newName) => (tag) => tag === oldName ? newName : tag; -const rejectTag = (tags, tagToReject) => reject((tag) => tag === tagToReject, tags); -const increaseVisitsForTags = (tags, stats) => tags.reduce((stats, tag) => { - if (!stats[tag]) { - return stats; - } - - const tagStats = stats[tag]; - - tagStats.visitsCount = tagStats.visitsCount + 1; - stats[tag] = tagStats; - - return stats; -}, { ...stats }); - -export default handleActions({ - [LIST_TAGS_START]: () => ({ ...initialState, loading: true }), - [LIST_TAGS_ERROR]: () => ({ ...initialState, error: true }), - [LIST_TAGS]: (state, { tags, stats }) => ({ ...initialState, stats, tags, filteredTags: tags }), - [TAG_DELETED]: (state, { tag }) => ({ - ...state, - tags: rejectTag(state.tags, tag), - filteredTags: rejectTag(state.filteredTags, tag), - }), - [TAG_EDITED]: (state, { oldName, newName }) => ({ - ...state, - tags: state.tags.map(renameTag(oldName, newName)).sort(), - filteredTags: state.filteredTags.map(renameTag(oldName, newName)).sort(), - }), - [FILTER_TAGS]: (state, { searchTerm }) => ({ - ...state, - filteredTags: state.tags.filter((tag) => tag.toLowerCase().match(searchTerm)), - }), - [CREATE_VISIT]: (state, { shortUrl }) => ({ - ...state, - stats: increaseVisitsForTags(shortUrl.tags, state.stats), - }), -}, initialState); - -export const listTags = (buildShlinkApiClient, force = true) => () => async (dispatch, getState) => { - const { tagsList } = getState(); - - if (!force && (tagsList.loading || !isEmpty(tagsList.tags))) { - return; - } - - dispatch({ type: LIST_TAGS_START }); - - try { - const { listTags } = buildShlinkApiClient(getState); - const { tags, stats = [] } = await listTags(); - const processedStats = stats.reduce((acc, { tag, shortUrlsCount, visitsCount }) => { - acc[tag] = { shortUrlsCount, visitsCount }; - - return acc; - }, {}); - - dispatch({ tags, stats: processedStats, type: LIST_TAGS }); - } catch (e) { - dispatch({ type: LIST_TAGS_ERROR }); - } -}; - -export const filterTags = (searchTerm) => ({ type: FILTER_TAGS, searchTerm }); diff --git a/src/tags/reducers/tagsList.ts b/src/tags/reducers/tagsList.ts new file mode 100644 index 00000000..207e64a8 --- /dev/null +++ b/src/tags/reducers/tagsList.ts @@ -0,0 +1,125 @@ +import { isEmpty, reject } from 'ramda'; +import PropTypes from 'prop-types'; +import { Action, Dispatch } from 'redux'; +import { CREATE_VISIT, CreateVisitAction } from '../../visits/reducers/visitCreation'; +import { buildReducer } from '../../utils/helpers/redux'; +import { ShlinkApiClientBuilder, ShlinkTags } from '../../utils/services/types'; +import { GetState } from '../../container/types'; +import { DeleteTagAction, TAG_DELETED } from './tagDelete'; +import { EditTagAction, TAG_EDITED } from './tagEdit'; + +/* eslint-disable padding-line-between-statements */ +export const LIST_TAGS_START = 'shlink/tagsList/LIST_TAGS_START'; +export const LIST_TAGS_ERROR = 'shlink/tagsList/LIST_TAGS_ERROR'; +export const LIST_TAGS = 'shlink/tagsList/LIST_TAGS'; +export const FILTER_TAGS = 'shlink/tagsList/FILTER_TAGS'; +/* eslint-enable padding-line-between-statements */ + +/** @deprecated Use TagsList interface instead */ +export const TagsListType = PropTypes.shape({ + tags: PropTypes.arrayOf(PropTypes.string), + filteredTags: PropTypes.arrayOf(PropTypes.string), + stats: PropTypes.objectOf(PropTypes.shape({ + shortUrlsCount: PropTypes.number, + visitsCount: PropTypes.number, + })), // Record + loading: PropTypes.bool, + error: PropTypes.bool, +}); + +type TagsStats = Record; + +export interface TagsList { + tags: string[]; + filteredTags: string[]; + stats: TagsStats; + loading: boolean; + error: boolean; +} + +interface ListTagsAction extends Action { + tags: string[]; + stats: TagsStats; +} + +interface FilterTagsAction extends Action { + searchTerm: string; +} + +type ListTagsCombinedAction = ListTagsAction & DeleteTagAction & CreateVisitAction & EditTagAction & FilterTagsAction; + +const initialState = { + tags: [], + filteredTags: [], + stats: {}, + loading: false, + error: false, +}; + +const renameTag = (oldName: string, newName: string) => (tag: string) => tag === oldName ? newName : tag; +const rejectTag = (tags: string[], tagToReject: string) => reject((tag) => tag === tagToReject, tags); +const increaseVisitsForTags = (tags: string[], stats: TagsStats) => tags.reduce((stats, tag) => { + if (!stats[tag]) { + return stats; + } + + const tagStats = stats[tag]; + + tagStats.visitsCount = tagStats.visitsCount + 1; + stats[tag] = tagStats; + + return stats; +}, { ...stats }); + +export default buildReducer({ + [LIST_TAGS_START]: () => ({ ...initialState, loading: true }), + [LIST_TAGS_ERROR]: () => ({ ...initialState, error: true }), + [LIST_TAGS]: (_, { tags, stats }) => ({ ...initialState, stats, tags, filteredTags: tags }), + [TAG_DELETED]: (state, { tag }) => ({ + ...state, + tags: rejectTag(state.tags, tag), + filteredTags: rejectTag(state.filteredTags, tag), + }), + [TAG_EDITED]: (state, { oldName, newName }) => ({ + ...state, + tags: state.tags.map(renameTag(oldName, newName)).sort(), + filteredTags: state.filteredTags.map(renameTag(oldName, newName)).sort(), + }), + [FILTER_TAGS]: (state, { searchTerm }) => ({ + ...state, + filteredTags: state.tags.filter((tag) => tag.toLowerCase().match(searchTerm)), + }), + [CREATE_VISIT]: (state, { shortUrl }) => ({ + ...state, + stats: increaseVisitsForTags(shortUrl.tags, state.stats), + }), +}, initialState); + +export const listTags = (buildShlinkApiClient: ShlinkApiClientBuilder, force = true) => () => async ( + dispatch: Dispatch, + getState: GetState, +) => { + const { tagsList } = getState(); + + if (!force && (tagsList.loading || !isEmpty(tagsList.tags))) { + return; + } + + dispatch({ type: LIST_TAGS_START }); + + try { + const { listTags } = buildShlinkApiClient(getState); + const { tags, stats = [] }: ShlinkTags = await listTags(); + const processedStats = stats.reduce((acc, { tag, shortUrlsCount, visitsCount }) => { + acc[tag] = { shortUrlsCount, visitsCount }; + + return acc; + }, {}); + + dispatch({ tags, stats: processedStats, type: LIST_TAGS }); + } catch (e) { + dispatch({ type: LIST_TAGS_ERROR }); + } +}; + +export const filterTags = (searchTerm: string): FilterTagsAction => ({ type: FILTER_TAGS, searchTerm }); diff --git a/src/utils/services/types.ts b/src/utils/services/types.ts index b52a83ae..a1c55880 100644 --- a/src/utils/services/types.ts +++ b/src/utils/services/types.ts @@ -15,6 +15,17 @@ export interface ShlinkHealth { version: string; } +interface ShlinkTagsStats { + tag: string; + shortUrlsCount: number; + visitsCount: number; +} + +export interface ShlinkTags { + tags: string[]; + stats?: ShlinkTagsStats[]; +} + export interface ProblemDetailsError { type: string; detail: string; diff --git a/test/tags/reducers/tagDelete.test.js b/test/tags/reducers/tagDelete.test.ts similarity index 79% rename from test/tags/reducers/tagDelete.test.js rename to test/tags/reducers/tagDelete.test.ts index 1978917a..e032f3da 100644 --- a/test/tags/reducers/tagDelete.test.js +++ b/test/tags/reducers/tagDelete.test.ts @@ -1,3 +1,4 @@ +import { Mock } from 'ts-mockery'; import reducer, { DELETE_TAG_START, DELETE_TAG_ERROR, @@ -6,25 +7,27 @@ import reducer, { tagDeleted, deleteTag, } from '../../../src/tags/reducers/tagDelete'; +import ShlinkApiClient from '../../../src/utils/services/ShlinkApiClient'; +import { ShlinkState } from '../../../src/container/types'; describe('tagDeleteReducer', () => { describe('reducer', () => { it('returns loading on DELETE_TAG_START', () => { - expect(reducer({}, { type: DELETE_TAG_START })).toEqual({ + expect(reducer(undefined, { type: DELETE_TAG_START })).toEqual({ deleting: true, error: false, }); }); it('returns error on DELETE_TAG_ERROR', () => { - expect(reducer({}, { type: DELETE_TAG_ERROR })).toEqual({ + expect(reducer(undefined, { type: DELETE_TAG_ERROR })).toEqual({ deleting: false, error: true, }); }); it('returns tag names on DELETE_TAG', () => { - expect(reducer({}, { type: DELETE_TAG })).toEqual({ + expect(reducer(undefined, { type: DELETE_TAG })).toEqual({ deleting: false, error: false, }); @@ -40,11 +43,11 @@ describe('tagDeleteReducer', () => { }); describe('deleteTag', () => { - const createApiClientMock = (result) => ({ - deleteTags: jest.fn(() => result), + const createApiClientMock = (result: Promise) => Mock.of({ + deleteTags: jest.fn(async () => result), }); const dispatch = jest.fn(); - const getState = () => ({}); + const getState = () => Mock.all(); afterEach(() => dispatch.mockReset()); diff --git a/test/tags/reducers/tagEdit.test.js b/test/tags/reducers/tagEdit.test.ts similarity index 72% rename from test/tags/reducers/tagEdit.test.js rename to test/tags/reducers/tagEdit.test.ts index f70a3fa7..d825ce42 100644 --- a/test/tags/reducers/tagEdit.test.js +++ b/test/tags/reducers/tagEdit.test.ts @@ -1,3 +1,4 @@ +import { Mock } from 'ts-mockery'; import reducer, { EDIT_TAG_START, EDIT_TAG_ERROR, @@ -5,26 +6,38 @@ import reducer, { TAG_EDITED, tagEdited, editTag, + EditTagAction, } from '../../../src/tags/reducers/tagEdit'; +import ShlinkApiClient from '../../../src/utils/services/ShlinkApiClient'; +import ColorGenerator from '../../../src/utils/services/ColorGenerator'; +import { ShlinkState } from '../../../src/container/types'; describe('tagEditReducer', () => { + const oldName = 'foo'; + const newName = 'bar'; + const color = '#ff0000'; + describe('reducer', () => { it('returns loading on EDIT_TAG_START', () => { - expect(reducer({}, { type: EDIT_TAG_START })).toEqual({ + expect(reducer(undefined, Mock.of({ type: EDIT_TAG_START }))).toEqual({ editing: true, error: false, + oldName: '', + newName: '', }); }); it('returns error on EDIT_TAG_ERROR', () => { - expect(reducer({}, { type: EDIT_TAG_ERROR })).toEqual({ + expect(reducer(undefined, Mock.of({ type: EDIT_TAG_ERROR }))).toEqual({ editing: false, error: true, + oldName: '', + newName: '', }); }); it('returns tag names on EDIT_TAG', () => { - expect(reducer({}, { type: EDIT_TAG, oldName: 'foo', newName: 'bar' })).toEqual({ + expect(reducer(undefined, { type: EDIT_TAG, oldName, newName, color })).toEqual({ editing: false, error: false, oldName: 'foo', @@ -44,24 +57,18 @@ describe('tagEditReducer', () => { }); describe('editTag', () => { - const createApiClientMock = (result) => ({ - editTag: jest.fn(() => result), + const createApiClientMock = (result: Promise) => Mock.of({ + editTag: jest.fn(async () => result), }); - const colorGenerator = { + const colorGenerator = Mock.of({ setColorForKey: jest.fn(), - }; - const dispatch = jest.fn(); - const getState = () => ({}); - - afterEach(() => { - colorGenerator.setColorForKey.mockReset(); - dispatch.mockReset(); }); + const dispatch = jest.fn(); + const getState = () => Mock.of(); + + afterEach(jest.clearAllMocks); it('calls API on success', async () => { - const oldName = 'foo'; - const newName = 'bar'; - const color = '#ff0000'; const apiClientMock = createApiClientMock(Promise.resolve()); const dispatchable = editTag(() => apiClientMock, colorGenerator)(oldName, newName, color); @@ -80,9 +87,6 @@ describe('tagEditReducer', () => { it('throws on error', async () => { const error = 'Error'; - const oldName = 'foo'; - const newName = 'bar'; - const color = '#ff0000'; const apiClientMock = createApiClientMock(Promise.reject(error)); const dispatchable = editTag(() => apiClientMock, colorGenerator)(oldName, newName, color); diff --git a/test/tags/reducers/tagsList.test.js b/test/tags/reducers/tagsList.test.ts similarity index 75% rename from test/tags/reducers/tagsList.test.js rename to test/tags/reducers/tagsList.test.ts index da86b564..884efc1b 100644 --- a/test/tags/reducers/tagsList.test.js +++ b/test/tags/reducers/tagsList.test.ts @@ -1,24 +1,30 @@ +import { Mock } from 'ts-mockery'; import reducer, { FILTER_TAGS, filterTags, LIST_TAGS, LIST_TAGS_ERROR, - LIST_TAGS_START, listTags, + LIST_TAGS_START, + listTags, + TagsList, } from '../../../src/tags/reducers/tagsList'; import { TAG_DELETED } from '../../../src/tags/reducers/tagDelete'; import { TAG_EDITED } from '../../../src/tags/reducers/tagEdit'; +import { ShlinkState } from '../../../src/container/types'; describe('tagsListReducer', () => { + const state = (props: Partial) => Mock.of(props); + describe('reducer', () => { it('returns loading on LIST_TAGS_START', () => { - expect(reducer({}, { type: LIST_TAGS_START })).toEqual(expect.objectContaining({ + expect(reducer(undefined, { type: LIST_TAGS_START } as any)).toEqual(expect.objectContaining({ loading: true, error: false, })); }); it('returns error on LIST_TAGS_ERROR', () => { - expect(reducer({}, { type: LIST_TAGS_ERROR })).toEqual(expect.objectContaining({ + expect(reducer(undefined, { type: LIST_TAGS_ERROR } as any)).toEqual(expect.objectContaining({ loading: false, error: true, })); @@ -27,7 +33,7 @@ describe('tagsListReducer', () => { it('returns provided tags as filtered and regular tags on LIST_TAGS', () => { const tags = [ 'foo', 'bar', 'baz' ]; - expect(reducer({}, { type: LIST_TAGS, tags })).toEqual({ + expect(reducer(undefined, { type: LIST_TAGS, tags } as any)).toEqual({ tags, filteredTags: tags, loading: false, @@ -40,7 +46,7 @@ describe('tagsListReducer', () => { const tag = 'foo'; const expectedTags = [ 'bar', 'baz' ]; - expect(reducer({ tags, filteredTags: tags }, { type: TAG_DELETED, tag })).toEqual({ + expect(reducer(state({ tags, filteredTags: tags }), { type: TAG_DELETED, tag } as any)).toEqual({ tags: expectedTags, filteredTags: expectedTags, }); @@ -52,7 +58,7 @@ describe('tagsListReducer', () => { const newName = 'renamed'; const expectedTags = [ 'foo', 'renamed', 'baz' ].sort(); - expect(reducer({ tags, filteredTags: tags }, { type: TAG_EDITED, oldName, newName })).toEqual({ + expect(reducer(state({ tags, filteredTags: tags }), { type: TAG_EDITED, oldName, newName } as any)).toEqual({ tags: expectedTags, filteredTags: expectedTags, }); @@ -63,7 +69,7 @@ describe('tagsListReducer', () => { const searchTerm = 'fo'; const filteredTags = [ 'foo', 'foo2', 'fo' ]; - expect(reducer({ tags }, { type: FILTER_TAGS, searchTerm })).toEqual({ + expect(reducer(state({ tags }), { type: FILTER_TAGS, searchTerm } as any)).toEqual({ tags, filteredTags, }); @@ -76,19 +82,14 @@ describe('tagsListReducer', () => { describe('listTags', () => { const dispatch = jest.fn(); - const getState = jest.fn(() => ({})); + const getState = jest.fn(() => Mock.all()); const buildShlinkApiClient = jest.fn(); const listTagsMock = jest.fn(); - afterEach(() => { - dispatch.mockReset(); - getState.mockClear(); - buildShlinkApiClient.mockReset(); - listTagsMock.mockReset(); - }); + afterEach(jest.clearAllMocks); - const assertNoAction = async (tagsList) => { - getState.mockReturnValue({ tagsList }); + const assertNoAction = async (tagsList: TagsList) => { + getState.mockReturnValue(Mock.of({ tagsList })); await listTags(buildShlinkApiClient, false)()(dispatch, getState); @@ -97,8 +98,11 @@ describe('tagsListReducer', () => { expect(getState).toHaveBeenCalledTimes(1); }; - it('does nothing when loading', async () => await assertNoAction({ loading: true })); - it('does nothing when list is not empty', async () => await assertNoAction({ loading: false, tags: [ 'foo', 'bar' ] })); + it('does nothing when loading', async () => assertNoAction(state({ loading: true }))); + it( + 'does nothing when list is not empty', + async () => assertNoAction(state({ loading: false, tags: [ 'foo', 'bar' ] })), + ); it('dispatches loaded lists when no error occurs', async () => { const tags = [ 'foo', 'bar', 'baz' ]; From 54290d4c9acc0facc84fbd6a333298b46a72f3b1 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Thu, 27 Aug 2020 22:09:16 +0200 Subject: [PATCH 29/59] Migrated ShlinkApiClientBuilder to TS --- src/mercure/reducers/mercureInfo.ts | 3 +- src/servers/data/index.ts | 5 ++- src/servers/reducers/selectedServer.ts | 3 +- src/short-urls/reducers/shortUrlCreation.ts | 2 +- src/short-urls/reducers/shortUrlDeletion.ts | 3 +- src/short-urls/reducers/shortUrlEdition.ts | 2 +- src/short-urls/reducers/shortUrlMeta.ts | 2 +- src/short-urls/reducers/shortUrlTags.ts | 2 +- src/short-urls/reducers/shortUrlsList.ts | 2 +- src/tags/reducers/tagDelete.ts | 2 +- src/tags/reducers/tagEdit.ts | 2 +- src/tags/reducers/tagsList.ts | 3 +- src/utils/services/ShlinkApiClientBuilder.js | 24 ------------- src/utils/services/ShlinkApiClientBuilder.ts | 36 +++++++++++++++++++ src/utils/services/types.ts | 7 ---- ...test.js => ShlinkApiClientBuilder.test.ts} | 23 +++++++----- 16 files changed, 70 insertions(+), 51 deletions(-) delete mode 100644 src/utils/services/ShlinkApiClientBuilder.js create mode 100644 src/utils/services/ShlinkApiClientBuilder.ts rename test/utils/services/{ShlinkApiClientBuilder.test.js => ShlinkApiClientBuilder.test.ts} (60%) diff --git a/src/mercure/reducers/mercureInfo.ts b/src/mercure/reducers/mercureInfo.ts index 396506c7..582f76b7 100644 --- a/src/mercure/reducers/mercureInfo.ts +++ b/src/mercure/reducers/mercureInfo.ts @@ -1,8 +1,9 @@ import PropTypes from 'prop-types'; import { Action, Dispatch } from 'redux'; -import { ShlinkApiClientBuilder, ShlinkMercureInfo } from '../../utils/services/types'; +import { ShlinkMercureInfo } from '../../utils/services/types'; import { GetState } from '../../container/types'; import { buildReducer } from '../../utils/helpers/redux'; +import { ShlinkApiClientBuilder } from '../../utils/services/ShlinkApiClientBuilder'; /* eslint-disable padding-line-between-statements */ export const GET_MERCURE_INFO_START = 'shlink/mercure/GET_MERCURE_INFO_START'; diff --git a/src/servers/data/index.ts b/src/servers/data/index.ts index 222b418e..57dbf52a 100644 --- a/src/servers/data/index.ts +++ b/src/servers/data/index.ts @@ -4,7 +4,7 @@ export interface ServerData { apiKey: string; } -export interface ServerWithId { +export interface ServerWithId extends ServerData { id: string; } @@ -24,3 +24,6 @@ export interface NotFoundServer { export type RegularServer = ReachableServer | NonReachableServer; export type SelectedServer = RegularServer | NotFoundServer | null; + +export const hasServerData = (server: ServerData | NotFoundServer | null): server is ServerData => + !!(server as ServerData)?.url && !!(server as ServerData)?.apiKey; diff --git a/src/servers/reducers/selectedServer.ts b/src/servers/reducers/selectedServer.ts index cf0583fd..3579a00e 100644 --- a/src/servers/reducers/selectedServer.ts +++ b/src/servers/reducers/selectedServer.ts @@ -4,8 +4,9 @@ import { resetShortUrlParams } from '../../short-urls/reducers/shortUrlsListPara import { versionToPrintable, versionToSemVer as toSemVer } from '../../utils/helpers/version'; import { SelectedServer } from '../data'; import { GetState } from '../../container/types'; -import { ShlinkApiClientBuilder, ShlinkHealth } from '../../utils/services/types'; +import { ShlinkHealth } from '../../utils/services/types'; import { buildActionCreator, buildReducer } from '../../utils/helpers/redux'; +import { ShlinkApiClientBuilder } from '../../utils/services/ShlinkApiClientBuilder'; /* eslint-disable padding-line-between-statements */ export const SELECT_SERVER = 'shlink/selectedServer/SELECT_SERVER'; diff --git a/src/short-urls/reducers/shortUrlCreation.ts b/src/short-urls/reducers/shortUrlCreation.ts index 40753d4b..f062b694 100644 --- a/src/short-urls/reducers/shortUrlCreation.ts +++ b/src/short-urls/reducers/shortUrlCreation.ts @@ -1,9 +1,9 @@ import PropTypes from 'prop-types'; import { Action, Dispatch } from 'redux'; -import { ShlinkApiClientBuilder } from '../../utils/services/types'; import { GetState } from '../../container/types'; import { ShortUrl, ShortUrlData } from '../data'; import { buildReducer, buildActionCreator } from '../../utils/helpers/redux'; +import { ShlinkApiClientBuilder } from '../../utils/services/ShlinkApiClientBuilder'; /* eslint-disable padding-line-between-statements */ export const CREATE_SHORT_URL_START = 'shlink/createShortUrl/CREATE_SHORT_URL_START'; diff --git a/src/short-urls/reducers/shortUrlDeletion.ts b/src/short-urls/reducers/shortUrlDeletion.ts index 316bd4b8..21347852 100644 --- a/src/short-urls/reducers/shortUrlDeletion.ts +++ b/src/short-urls/reducers/shortUrlDeletion.ts @@ -1,7 +1,8 @@ import { Action, Dispatch } from 'redux'; import { buildActionCreator, buildReducer } from '../../utils/helpers/redux'; -import { ProblemDetailsError, ShlinkApiClientBuilder } from '../../utils/services/types'; +import { ProblemDetailsError} from '../../utils/services/types'; import { GetState } from '../../container/types'; +import { ShlinkApiClientBuilder } from '../../utils/services/ShlinkApiClientBuilder'; /* eslint-disable padding-line-between-statements */ export const DELETE_SHORT_URL_START = 'shlink/deleteShortUrl/DELETE_SHORT_URL_START'; diff --git a/src/short-urls/reducers/shortUrlEdition.ts b/src/short-urls/reducers/shortUrlEdition.ts index 6179f98c..44feac7b 100644 --- a/src/short-urls/reducers/shortUrlEdition.ts +++ b/src/short-urls/reducers/shortUrlEdition.ts @@ -1,9 +1,9 @@ import { Action, Dispatch } from 'redux'; import { buildReducer } from '../../utils/helpers/redux'; -import { ShlinkApiClientBuilder } from '../../utils/services/types'; import { GetState } from '../../container/types'; import { OptionalString } from '../../utils/utils'; import { ShortUrlIdentifier } from '../data'; +import { ShlinkApiClientBuilder } from '../../utils/services/ShlinkApiClientBuilder'; /* eslint-disable padding-line-between-statements */ export const EDIT_SHORT_URL_START = 'shlink/shortUrlEdition/EDIT_SHORT_URL_START'; diff --git a/src/short-urls/reducers/shortUrlMeta.ts b/src/short-urls/reducers/shortUrlMeta.ts index ec0a2bca..71736ea0 100644 --- a/src/short-urls/reducers/shortUrlMeta.ts +++ b/src/short-urls/reducers/shortUrlMeta.ts @@ -1,10 +1,10 @@ import PropTypes from 'prop-types'; import { Dispatch, Action } from 'redux'; import { ShortUrlIdentifier, ShortUrlMeta } from '../data'; -import { ShlinkApiClientBuilder } from '../../utils/services/types'; import { GetState } from '../../container/types'; import { buildActionCreator, buildReducer } from '../../utils/helpers/redux'; import { OptionalString } from '../../utils/utils'; +import { ShlinkApiClientBuilder } from '../../utils/services/ShlinkApiClientBuilder'; /* eslint-disable padding-line-between-statements */ export const EDIT_SHORT_URL_META_START = 'shlink/shortUrlMeta/EDIT_SHORT_URL_META_START'; diff --git a/src/short-urls/reducers/shortUrlTags.ts b/src/short-urls/reducers/shortUrlTags.ts index 823499aa..cdaf9269 100644 --- a/src/short-urls/reducers/shortUrlTags.ts +++ b/src/short-urls/reducers/shortUrlTags.ts @@ -1,10 +1,10 @@ import PropTypes from 'prop-types'; import { Action, Dispatch } from 'redux'; import { buildActionCreator, buildReducer } from '../../utils/helpers/redux'; -import { ShlinkApiClientBuilder } from '../../utils/services/types'; import { GetState } from '../../container/types'; import { OptionalString } from '../../utils/utils'; import { ShortUrlIdentifier } from '../data'; +import { ShlinkApiClientBuilder } from '../../utils/services/ShlinkApiClientBuilder'; /* eslint-disable padding-line-between-statements */ export const EDIT_SHORT_URL_TAGS_START = 'shlink/shortUrlTags/EDIT_SHORT_URL_TAGS_START'; diff --git a/src/short-urls/reducers/shortUrlsList.ts b/src/short-urls/reducers/shortUrlsList.ts index 3e54ea6e..6294dd17 100644 --- a/src/short-urls/reducers/shortUrlsList.ts +++ b/src/short-urls/reducers/shortUrlsList.ts @@ -6,12 +6,12 @@ import { CREATE_VISIT, CreateVisitAction } from '../../visits/reducers/visitCrea import { ShortUrl, ShortUrlIdentifier } from '../data'; import { buildReducer } from '../../utils/helpers/redux'; import { GetState } from '../../container/types'; -import { ShlinkApiClientBuilder } from '../../utils/services/types'; import { EditShortUrlTagsAction, SHORT_URL_TAGS_EDITED } from './shortUrlTags'; import { SHORT_URL_DELETED } from './shortUrlDeletion'; import { SHORT_URL_META_EDITED, ShortUrlMetaEditedAction, shortUrlMetaType } from './shortUrlMeta'; import { SHORT_URL_EDITED, ShortUrlEditedAction } from './shortUrlEdition'; import { ShortUrlsListParams } from './shortUrlsListParams'; +import { ShlinkApiClientBuilder } from '../../utils/services/ShlinkApiClientBuilder'; /* eslint-disable padding-line-between-statements */ export const LIST_SHORT_URLS_START = 'shlink/shortUrlsList/LIST_SHORT_URLS_START'; diff --git a/src/tags/reducers/tagDelete.ts b/src/tags/reducers/tagDelete.ts index 8fa85414..e662a179 100644 --- a/src/tags/reducers/tagDelete.ts +++ b/src/tags/reducers/tagDelete.ts @@ -2,7 +2,7 @@ import PropTypes from 'prop-types'; import { Action, Dispatch } from 'redux'; import { buildReducer } from '../../utils/helpers/redux'; import { GetState } from '../../container/types'; -import { ShlinkApiClientBuilder } from '../../utils/services/types'; +import { ShlinkApiClientBuilder } from '../../utils/services/ShlinkApiClientBuilder'; /* eslint-disable padding-line-between-statements */ export const DELETE_TAG_START = 'shlink/deleteTag/DELETE_TAG_START'; diff --git a/src/tags/reducers/tagEdit.ts b/src/tags/reducers/tagEdit.ts index b3e631c8..44825200 100644 --- a/src/tags/reducers/tagEdit.ts +++ b/src/tags/reducers/tagEdit.ts @@ -2,8 +2,8 @@ import { pick } from 'ramda'; import { Action, Dispatch } from 'redux'; import { buildReducer } from '../../utils/helpers/redux'; import { GetState } from '../../container/types'; -import { ShlinkApiClientBuilder } from '../../utils/services/types'; import ColorGenerator from '../../utils/services/ColorGenerator'; +import { ShlinkApiClientBuilder } from '../../utils/services/ShlinkApiClientBuilder'; /* eslint-disable padding-line-between-statements */ export const EDIT_TAG_START = 'shlink/editTag/EDIT_TAG_START'; diff --git a/src/tags/reducers/tagsList.ts b/src/tags/reducers/tagsList.ts index 207e64a8..2e4cf1c0 100644 --- a/src/tags/reducers/tagsList.ts +++ b/src/tags/reducers/tagsList.ts @@ -3,10 +3,11 @@ import PropTypes from 'prop-types'; import { Action, Dispatch } from 'redux'; import { CREATE_VISIT, CreateVisitAction } from '../../visits/reducers/visitCreation'; import { buildReducer } from '../../utils/helpers/redux'; -import { ShlinkApiClientBuilder, ShlinkTags } from '../../utils/services/types'; +import { ShlinkTags } from '../../utils/services/types'; import { GetState } from '../../container/types'; import { DeleteTagAction, TAG_DELETED } from './tagDelete'; import { EditTagAction, TAG_EDITED } from './tagEdit'; +import { ShlinkApiClientBuilder } from '../../utils/services/ShlinkApiClientBuilder'; /* eslint-disable padding-line-between-statements */ export const LIST_TAGS_START = 'shlink/tagsList/LIST_TAGS_START'; diff --git a/src/utils/services/ShlinkApiClientBuilder.js b/src/utils/services/ShlinkApiClientBuilder.js deleted file mode 100644 index a436f698..00000000 --- a/src/utils/services/ShlinkApiClientBuilder.js +++ /dev/null @@ -1,24 +0,0 @@ -import ShlinkApiClient from './ShlinkApiClient'; - -const apiClients = {}; - -const getSelectedServerFromState = (getState) => { - const { selectedServer } = getState(); - - return selectedServer; -}; - -const buildShlinkApiClient = (axios) => (getStateOrSelectedServer) => { - const { url, apiKey } = typeof getStateOrSelectedServer === 'function' - ? getSelectedServerFromState(getStateOrSelectedServer) - : getStateOrSelectedServer; - const clientKey = `${url}_${apiKey}`; - - if (!apiClients[clientKey]) { - apiClients[clientKey] = new ShlinkApiClient(axios, url, apiKey); - } - - return apiClients[clientKey]; -}; - -export default buildShlinkApiClient; diff --git a/src/utils/services/ShlinkApiClientBuilder.ts b/src/utils/services/ShlinkApiClientBuilder.ts new file mode 100644 index 00000000..d2ba24cd --- /dev/null +++ b/src/utils/services/ShlinkApiClientBuilder.ts @@ -0,0 +1,36 @@ +import { AxiosInstance } from 'axios'; +import { prop } from 'ramda'; +import { hasServerData, SelectedServer, ServerWithId } from '../../servers/data'; +import { GetState } from '../../container/types'; +import ShlinkApiClient from './ShlinkApiClient'; + +const apiClients: Record = {}; + +const isGetState = (getStateOrSelectedServer: GetState | ServerWithId): getStateOrSelectedServer is GetState => + typeof getStateOrSelectedServer === 'function'; +const getSelectedServerFromState = (getState: GetState): SelectedServer => prop('selectedServer', getState()); + +export type ShlinkApiClientBuilder = (getStateOrSelectedServer: GetState | ServerWithId) => ShlinkApiClient; + +const buildShlinkApiClient = (axios: AxiosInstance): ShlinkApiClientBuilder => ( + getStateOrSelectedServer: GetState | ServerWithId, +) => { + const server = isGetState(getStateOrSelectedServer) + ? getSelectedServerFromState(getStateOrSelectedServer) + : getStateOrSelectedServer; + + if (!hasServerData(server)) { + throw new Error('There\'s no selected server or it is not found'); + } + + const { url, apiKey } = server; + const clientKey = `${url}_${apiKey}`; + + if (!apiClients[clientKey]) { + apiClients[clientKey] = new ShlinkApiClient(axios, url, apiKey); + } + + return apiClients[clientKey]; +}; + +export default buildShlinkApiClient; diff --git a/src/utils/services/types.ts b/src/utils/services/types.ts index a1c55880..5e446c55 100644 --- a/src/utils/services/types.ts +++ b/src/utils/services/types.ts @@ -1,10 +1,3 @@ -import { ServerWithId } from '../../servers/data'; -import { GetState } from '../../container/types'; -import ShlinkApiClient from './ShlinkApiClient'; - -// FIXME Move to ShlinkApiClientBuilder -export type ShlinkApiClientBuilder = (getStateOrSelectedServer: ServerWithId | GetState) => ShlinkApiClient; - export interface ShlinkMercureInfo { token: string; mercureHubUrl: string; diff --git a/test/utils/services/ShlinkApiClientBuilder.test.js b/test/utils/services/ShlinkApiClientBuilder.test.ts similarity index 60% rename from test/utils/services/ShlinkApiClientBuilder.test.js rename to test/utils/services/ShlinkApiClientBuilder.test.ts index 2490ca27..5fac09ae 100644 --- a/test/utils/services/ShlinkApiClientBuilder.test.js +++ b/test/utils/services/ShlinkApiClientBuilder.test.ts @@ -1,18 +1,25 @@ +import { Mock } from 'ts-mockery'; +import { AxiosInstance } from 'axios'; import buildShlinkApiClient from '../../../src/utils/services/ShlinkApiClientBuilder'; +import { ReachableServer, SelectedServer } from '../../../src/servers/data'; +import { ShlinkState } from '../../../src/container/types'; describe('ShlinkApiClientBuilder', () => { - const createBuilder = () => { - const builder = buildShlinkApiClient({}); + const axiosMock = Mock.all(); + const server = (data: Partial) => Mock.of(data); - return (selectedServer) => builder(() => ({ selectedServer })); + const createBuilder = () => { + const builder = buildShlinkApiClient(axiosMock); + + return (selectedServer: SelectedServer) => builder(() => Mock.of({ selectedServer })); }; it('creates new instances when provided params are different', async () => { const builder = createBuilder(); const [ firstApiClient, secondApiClient, thirdApiClient ] = await Promise.all([ - builder({ url: 'foo', apiKey: 'bar' }), - builder({ url: 'bar', apiKey: 'bar' }), - builder({ url: 'bar', apiKey: 'foo' }), + builder(server({ url: 'foo', apiKey: 'bar' })), + builder(server({ url: 'bar', apiKey: 'bar' })), + builder(server({ url: 'bar', apiKey: 'foo' })), ]); expect(firstApiClient).not.toBe(secondApiClient); @@ -22,7 +29,7 @@ describe('ShlinkApiClientBuilder', () => { it('returns existing instances when provided params are the same', async () => { const builder = createBuilder(); - const selectedServer = { url: 'foo', apiKey: 'bar' }; + const selectedServer = server({ url: 'foo', apiKey: 'bar' }); const [ firstApiClient, secondApiClient, thirdApiClient ] = await Promise.all([ builder(selectedServer), builder(selectedServer), @@ -37,7 +44,7 @@ describe('ShlinkApiClientBuilder', () => { it('does not fetch from state when provided param is already selected server', () => { const url = 'url'; const apiKey = 'apiKey'; - const apiClient = buildShlinkApiClient({})({ url, apiKey }); + const apiClient = buildShlinkApiClient(axiosMock)(server({ url, apiKey })); expect(apiClient._baseUrl).toEqual(url); expect(apiClient._apiKey).toEqual(apiKey); From dcf72e6818e2aa833a439acb67d8e16762c8d4c2 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Fri, 28 Aug 2020 18:33:37 +0200 Subject: [PATCH 30/59] Finished migrating remaining reducers to TS --- src/container/types.ts | 9 ++- src/utils/services/types.ts | 14 +++- src/visits/reducers/{common.js => common.ts} | 67 ++++++++++++------- src/visits/reducers/shortUrlDetail.js | 40 ----------- src/visits/reducers/shortUrlDetail.ts | 58 ++++++++++++++++ .../{shortUrlVisits.js => shortUrlVisits.ts} | 42 +++++++++--- .../reducers/{tagVisits.js => tagVisits.ts} | 38 ++++++++--- src/visits/types/index.ts | 6 ++ ...lDetail.test.js => shortUrlDetail.test.ts} | 25 ++++--- ...lVisits.test.js => shortUrlVisits.test.ts} | 62 ++++++++++------- .../{tagVisits.test.js => tagVisits.test.ts} | 54 +++++++++------ 11 files changed, 274 insertions(+), 141 deletions(-) rename src/visits/reducers/{common.js => common.ts} (57%) delete mode 100644 src/visits/reducers/shortUrlDetail.js create mode 100644 src/visits/reducers/shortUrlDetail.ts rename src/visits/reducers/{shortUrlVisits.js => shortUrlVisits.ts} (59%) rename src/visits/reducers/{tagVisits.js => tagVisits.ts} (59%) rename test/visits/reducers/{shortUrlDetail.test.js => shortUrlDetail.test.ts} (65%) rename test/visits/reducers/{shortUrlVisits.test.js => shortUrlVisits.test.ts} (66%) rename test/visits/reducers/{tagVisits.test.js => tagVisits.test.ts} (64%) diff --git a/src/container/types.ts b/src/container/types.ts index f5fc3ba8..9afa2de0 100644 --- a/src/container/types.ts +++ b/src/container/types.ts @@ -12,6 +12,9 @@ import { ShortUrlsList } from '../short-urls/reducers/shortUrlsList'; import { TagDeletion } from '../tags/reducers/tagDelete'; import { TagEdition } from '../tags/reducers/tagEdit'; import { TagsList } from '../tags/reducers/tagsList'; +import { ShortUrlDetail } from '../visits/reducers/shortUrlDetail'; +import { ShortUrlVisits } from '../visits/reducers/shortUrlVisits'; +import { TagVisits } from '../visits/reducers/tagVisits'; export interface ShlinkState { servers: ServersMap; @@ -23,9 +26,9 @@ export interface ShlinkState { shortUrlTags: ShortUrlTags; shortUrlMeta: ShortUrlMetaEdition; shortUrlEdition: ShortUrlEdition; - shortUrlVisits: any; - tagVisits: any; - shortUrlDetail: any; + shortUrlVisits: ShortUrlVisits; + tagVisits: TagVisits; + shortUrlDetail: ShortUrlDetail; tagsList: TagsList; tagDelete: TagDeletion; tagEdit: TagEdition; diff --git a/src/utils/services/types.ts b/src/utils/services/types.ts index 5e446c55..0f9062f7 100644 --- a/src/utils/services/types.ts +++ b/src/utils/services/types.ts @@ -1,3 +1,5 @@ +import { Visit } from '../../visits/types'; // FIXME Should be defined here + export interface ShlinkMercureInfo { token: string; mercureHubUrl: string; @@ -16,7 +18,17 @@ interface ShlinkTagsStats { export interface ShlinkTags { tags: string[]; - stats?: ShlinkTagsStats[]; + stats?: ShlinkTagsStats[]; // TODO Is only optional in old versions +} + +export interface ShlinkPaginator { + currentPage: number; + pagesCount: number; +} + +export interface ShlinkVisits { + data: Visit[]; + pagination?: ShlinkPaginator; // TODO Is only optional in old versions } export interface ProblemDetailsError { diff --git a/src/visits/reducers/common.js b/src/visits/reducers/common.ts similarity index 57% rename from src/visits/reducers/common.js rename to src/visits/reducers/common.ts index 3b150dcc..4270d12e 100644 --- a/src/visits/reducers/common.js +++ b/src/visits/reducers/common.ts @@ -1,15 +1,55 @@ import { flatten, prop, range, splitEvery } from 'ramda'; +import { Action, Dispatch } from 'redux'; +import { ShlinkPaginator, ShlinkVisits } from '../../utils/services/types'; +import { GetState } from '../../container/types'; +import { Visit } from '../types'; const ITEMS_PER_PAGE = 5000; const PARALLEL_REQUESTS_COUNT = 4; const PARALLEL_STARTING_PAGE = 2; -const isLastPage = ({ currentPage, pagesCount }) => currentPage >= pagesCount; -const calcProgress = (total, current) => current * 100 / total; +const isLastPage = ({ currentPage, pagesCount }: ShlinkPaginator): boolean => currentPage >= pagesCount; +const calcProgress = (total: number, current: number): number => current * 100 / total; -export const getVisitsWithLoader = async (visitsLoader, extraFinishActionData, actionMap, dispatch, getState) => { +type VisitsLoader = (page: number, itemsPerPage: number) => Promise; +interface ActionMap { + start: string; + large: string; + finish: string; + error: string; + progress: string; +} + +export const getVisitsWithLoader = async & { visits: Visit[] }>( + visitsLoader: VisitsLoader, + extraFinishActionData: Partial, + actionMap: ActionMap, + dispatch: Dispatch, + getState: GetState, +) => { dispatch({ type: actionMap.start }); + const loadVisitsInParallel = async (pages: number[]): Promise => + Promise.all(pages.map(async (page) => visitsLoader(page, ITEMS_PER_PAGE).then(prop('data')))).then(flatten); + + const loadPagesBlocks = async (pagesBlocks: number[][], index = 0): Promise => { + const { shortUrlVisits: { cancelLoad } } = getState(); + + if (cancelLoad) { + return []; + } + + const data = await loadVisitsInParallel(pagesBlocks[index]); + + dispatch({ type: actionMap.progress, progress: calcProgress(pagesBlocks.length, index + PARALLEL_STARTING_PAGE) }); + + if (index < pagesBlocks.length - 1) { + return data.concat(await loadPagesBlocks(pagesBlocks, index + 1)); + } + + return data; + }; + const loadVisits = async (page = 1) => { const { pagination, data } = await visitsLoader(page, ITEMS_PER_PAGE); @@ -29,27 +69,6 @@ export const getVisitsWithLoader = async (visitsLoader, extraFinishActionData, a return data.concat(await loadPagesBlocks(pagesBlocks)); }; - const loadPagesBlocks = async (pagesBlocks, index = 0) => { - const { shortUrlVisits: { cancelLoad } } = getState(); - - if (cancelLoad) { - return []; - } - - const data = await loadVisitsInParallel(pagesBlocks[index]); - - dispatch({ type: actionMap.progress, progress: calcProgress(pagesBlocks.length, index + PARALLEL_STARTING_PAGE) }); - - if (index < pagesBlocks.length - 1) { - return data.concat(await loadPagesBlocks(pagesBlocks, index + 1)); - } - - return data; - }; - - const loadVisitsInParallel = (pages) => - Promise.all(pages.map((page) => visitsLoader(page, ITEMS_PER_PAGE).then(prop('data')))).then(flatten); - try { const visits = await loadVisits(); diff --git a/src/visits/reducers/shortUrlDetail.js b/src/visits/reducers/shortUrlDetail.js deleted file mode 100644 index 7612b7ab..00000000 --- a/src/visits/reducers/shortUrlDetail.js +++ /dev/null @@ -1,40 +0,0 @@ -import { handleActions } from 'redux-actions'; -import PropTypes from 'prop-types'; -import { shortUrlType } from '../../short-urls/reducers/shortUrlsList'; - -/* eslint-disable padding-line-between-statements */ -export const GET_SHORT_URL_DETAIL_START = 'shlink/shortUrlDetail/GET_SHORT_URL_DETAIL_START'; -export const GET_SHORT_URL_DETAIL_ERROR = 'shlink/shortUrlDetail/GET_SHORT_URL_DETAIL_ERROR'; -export const GET_SHORT_URL_DETAIL = 'shlink/shortUrlDetail/GET_SHORT_URL_DETAIL'; -/* eslint-enable padding-line-between-statements */ - -export const shortUrlDetailType = PropTypes.shape({ - shortUrl: shortUrlType, - loading: PropTypes.bool, - error: PropTypes.bool, -}); - -const initialState = { - shortUrl: {}, - loading: false, - error: false, -}; - -export default handleActions({ - [GET_SHORT_URL_DETAIL_START]: () => ({ ...initialState, loading: true }), - [GET_SHORT_URL_DETAIL_ERROR]: () => ({ ...initialState, loading: false, error: true }), - [GET_SHORT_URL_DETAIL]: (state, { shortUrl }) => ({ ...initialState, shortUrl }), -}, initialState); - -export const getShortUrlDetail = (buildShlinkApiClient) => (shortCode, domain) => async (dispatch, getState) => { - dispatch({ type: GET_SHORT_URL_DETAIL_START }); - const { getShortUrl } = buildShlinkApiClient(getState); - - try { - const shortUrl = await getShortUrl(shortCode, domain); - - dispatch({ shortUrl, type: GET_SHORT_URL_DETAIL }); - } catch (e) { - dispatch({ type: GET_SHORT_URL_DETAIL_ERROR }); - } -}; diff --git a/src/visits/reducers/shortUrlDetail.ts b/src/visits/reducers/shortUrlDetail.ts new file mode 100644 index 00000000..177380df --- /dev/null +++ b/src/visits/reducers/shortUrlDetail.ts @@ -0,0 +1,58 @@ +import PropTypes from 'prop-types'; +import { Action, Dispatch } from 'redux'; +import { shortUrlType } from '../../short-urls/reducers/shortUrlsList'; +import { ShortUrl } from '../../short-urls/data'; +import { buildReducer } from '../../utils/helpers/redux'; +import { ShlinkApiClientBuilder } from '../../utils/services/ShlinkApiClientBuilder'; +import { OptionalString } from '../../utils/utils'; +import { GetState } from '../../container/types'; + +/* eslint-disable padding-line-between-statements */ +export const GET_SHORT_URL_DETAIL_START = 'shlink/shortUrlDetail/GET_SHORT_URL_DETAIL_START'; +export const GET_SHORT_URL_DETAIL_ERROR = 'shlink/shortUrlDetail/GET_SHORT_URL_DETAIL_ERROR'; +export const GET_SHORT_URL_DETAIL = 'shlink/shortUrlDetail/GET_SHORT_URL_DETAIL'; +/* eslint-enable padding-line-between-statements */ + +/** @deprecated Use ShortUrlDetail interface instead */ +export const shortUrlDetailType = PropTypes.shape({ + shortUrl: shortUrlType, + loading: PropTypes.bool, + error: PropTypes.bool, +}); + +export interface ShortUrlDetail { + shortUrl?: ShortUrl; + loading: boolean; + error: boolean; +} + +export interface ShortUrlDetailAction extends Action { + shortUrl: ShortUrl; +} + +const initialState: ShortUrlDetail = { + loading: false, + error: false, +}; + +export default buildReducer({ + [GET_SHORT_URL_DETAIL_START]: () => ({ loading: true, error: false }), + [GET_SHORT_URL_DETAIL_ERROR]: () => ({ loading: false, error: true }), + [GET_SHORT_URL_DETAIL]: (_, { shortUrl }) => ({ shortUrl, ...initialState }), +}, initialState); + +export const getShortUrlDetail = (buildShlinkApiClient: ShlinkApiClientBuilder) => ( + shortCode: string, + domain: OptionalString, +) => async (dispatch: Dispatch, getState: GetState) => { + dispatch({ type: GET_SHORT_URL_DETAIL_START }); + const { getShortUrl } = buildShlinkApiClient(getState); + + try { + const shortUrl = await getShortUrl(shortCode, domain); + + dispatch({ shortUrl, type: GET_SHORT_URL_DETAIL }); + } catch (e) { + dispatch({ type: GET_SHORT_URL_DETAIL_ERROR }); + } +}; diff --git a/src/visits/reducers/shortUrlVisits.js b/src/visits/reducers/shortUrlVisits.ts similarity index 59% rename from src/visits/reducers/shortUrlVisits.js rename to src/visits/reducers/shortUrlVisits.ts index cbbdd081..339f37a9 100644 --- a/src/visits/reducers/shortUrlVisits.js +++ b/src/visits/reducers/shortUrlVisits.ts @@ -1,9 +1,14 @@ -import { createAction, handleActions } from 'redux-actions'; import PropTypes from 'prop-types'; +import { Action, Dispatch } from 'redux'; import { shortUrlMatches } from '../../short-urls/helpers'; -import { VisitType } from '../types'; +import { Visit, VisitsInfo, VisitsLoadProgressChangedAction, VisitType } from '../types'; +import { ShortUrlIdentifier } from '../../short-urls/data'; +import { buildActionCreator, buildReducer } from '../../utils/helpers/redux'; +import { ShlinkApiClientBuilder } from '../../utils/services/ShlinkApiClientBuilder'; +import { GetState } from '../../container/types'; +import { OptionalString } from '../../utils/utils'; import { getVisitsWithLoader } from './common'; -import { CREATE_VISIT } from './visitCreation'; +import { CREATE_VISIT, CreateVisitAction } from './visitCreation'; /* eslint-disable padding-line-between-statements */ export const GET_SHORT_URL_VISITS_START = 'shlink/shortUrlVisits/GET_SHORT_URL_VISITS_START'; @@ -14,7 +19,8 @@ export const GET_SHORT_URL_VISITS_CANCEL = 'shlink/shortUrlVisits/GET_SHORT_URL_ export const GET_SHORT_URL_VISITS_PROGRESS_CHANGED = 'shlink/shortUrlVisits/GET_SHORT_URL_VISITS_PROGRESS_CHANGED'; /* eslint-enable padding-line-between-statements */ -export const shortUrlVisitsType = PropTypes.shape({ // TODO Should extend from VisitInfoType +/** @deprecated Use ShortUrlVisits interface instead */ +export const shortUrlVisitsType = PropTypes.shape({ visits: PropTypes.arrayOf(VisitType), shortCode: PropTypes.string, domain: PropTypes.string, @@ -24,7 +30,15 @@ export const shortUrlVisitsType = PropTypes.shape({ // TODO Should extend from V progress: PropTypes.number, }); -const initialState = { +export interface ShortUrlVisits extends VisitsInfo, ShortUrlIdentifier {} + +interface ShortUrlVisitsAction extends Action, ShortUrlIdentifier { + visits: Visit[]; +} + +type ShortUrlVisitsCombinedAction = ShortUrlVisitsAction & VisitsLoadProgressChangedAction & CreateVisitAction; + +const initialState: ShortUrlVisits = { visits: [], shortCode: '', domain: undefined, @@ -35,10 +49,10 @@ const initialState = { progress: 0, }; -export default handleActions({ +export default buildReducer({ [GET_SHORT_URL_VISITS_START]: () => ({ ...initialState, loading: true }), [GET_SHORT_URL_VISITS_ERROR]: () => ({ ...initialState, error: true }), - [GET_SHORT_URL_VISITS]: (state, { visits, shortCode, domain }) => ({ + [GET_SHORT_URL_VISITS]: (_, { visits, shortCode, domain }) => ({ ...initialState, visits, shortCode, @@ -58,10 +72,16 @@ export default handleActions({ }, }, initialState); -export const getShortUrlVisits = (buildShlinkApiClient) => (shortCode, query = {}) => (dispatch, getState) => { +export const getShortUrlVisits = (buildShlinkApiClient: ShlinkApiClientBuilder) => ( + shortCode: string, + query: { domain?: OptionalString } = {}, +) => async (dispatch: Dispatch, getState: GetState) => { const { getShortUrlVisits } = buildShlinkApiClient(getState); - const visitsLoader = (page, itemsPerPage) => getShortUrlVisits(shortCode, { ...query, page, itemsPerPage }); - const extraFinishActionData = { shortCode, domain: query.domain }; + const visitsLoader = (page: number, itemsPerPage: number) => getShortUrlVisits( + shortCode, + { ...query, page, itemsPerPage }, + ); + const extraFinishActionData: Partial = { shortCode, domain: query.domain }; const actionMap = { start: GET_SHORT_URL_VISITS_START, large: GET_SHORT_URL_VISITS_LARGE, @@ -73,4 +93,4 @@ export const getShortUrlVisits = (buildShlinkApiClient) => (shortCode, query = { return getVisitsWithLoader(visitsLoader, extraFinishActionData, actionMap, dispatch, getState); }; -export const cancelGetShortUrlVisits = createAction(GET_SHORT_URL_VISITS_CANCEL); +export const cancelGetShortUrlVisits = buildActionCreator(GET_SHORT_URL_VISITS_CANCEL); diff --git a/src/visits/reducers/tagVisits.js b/src/visits/reducers/tagVisits.ts similarity index 59% rename from src/visits/reducers/tagVisits.js rename to src/visits/reducers/tagVisits.ts index d149322b..ab1d0379 100644 --- a/src/visits/reducers/tagVisits.js +++ b/src/visits/reducers/tagVisits.ts @@ -1,8 +1,11 @@ -import { createAction, handleActions } from 'redux-actions'; import PropTypes from 'prop-types'; -import { VisitType } from '../types'; +import { Action, Dispatch } from 'redux'; +import { Visit, VisitsInfo, VisitsLoadProgressChangedAction, VisitType } from '../types'; +import { buildActionCreator, buildReducer } from '../../utils/helpers/redux'; +import { ShlinkApiClientBuilder } from '../../utils/services/ShlinkApiClientBuilder'; +import { GetState } from '../../container/types'; import { getVisitsWithLoader } from './common'; -import { CREATE_VISIT } from './visitCreation'; +import { CREATE_VISIT, CreateVisitAction } from './visitCreation'; /* eslint-disable padding-line-between-statements */ export const GET_TAG_VISITS_START = 'shlink/tagVisits/GET_TAG_VISITS_START'; @@ -13,7 +16,8 @@ export const GET_TAG_VISITS_CANCEL = 'shlink/tagVisits/GET_TAG_VISITS_CANCEL'; export const GET_TAG_VISITS_PROGRESS_CHANGED = 'shlink/tagVisits/GET_TAG_VISITS_PROGRESS_CHANGED'; /* eslint-enable padding-line-between-statements */ -export const TagVisitsType = PropTypes.shape({ // TODO Should extend from VisitInfoType +/** @deprecated Use TagVisits interface instead */ +export const TagVisitsType = PropTypes.shape({ visits: PropTypes.arrayOf(VisitType), tag: PropTypes.string, loading: PropTypes.bool, @@ -22,7 +26,16 @@ export const TagVisitsType = PropTypes.shape({ // TODO Should extend from VisitI progress: PropTypes.number, }); -const initialState = { +export interface TagVisits extends VisitsInfo { + tag: string; +} + +export interface TagVisitsAction extends Action { + visits: Visit[]; + tag: string; +} + +const initialState: TagVisits = { visits: [], tag: '', loading: false, @@ -32,10 +45,10 @@ const initialState = { progress: 0, }; -export default handleActions({ +export default buildReducer({ [GET_TAG_VISITS_START]: () => ({ ...initialState, loading: true }), [GET_TAG_VISITS_ERROR]: () => ({ ...initialState, error: true }), - [GET_TAG_VISITS]: (state, { visits, tag }) => ({ ...initialState, visits, tag }), + [GET_TAG_VISITS]: (_, { visits, tag }) => ({ ...initialState, visits, tag }), [GET_TAG_VISITS_LARGE]: (state) => ({ ...state, loadingLarge: true }), [GET_TAG_VISITS_CANCEL]: (state) => ({ ...state, cancelLoad: true }), [GET_TAG_VISITS_PROGRESS_CHANGED]: (state, { progress }) => ({ ...state, progress }), @@ -50,10 +63,13 @@ export default handleActions({ }, }, initialState); -export const getTagVisits = (buildShlinkApiClient) => (tag, query = {}) => (dispatch, getState) => { +export const getTagVisits = (buildShlinkApiClient: ShlinkApiClientBuilder) => (tag: string, query = {}) => async ( + dispatch: Dispatch, + getState: GetState, +) => { const { getTagVisits } = buildShlinkApiClient(getState); - const visitsLoader = (page, itemsPerPage) => getTagVisits(tag, { ...query, page, itemsPerPage }); - const extraFinishActionData = { tag }; + const visitsLoader = (page: number, itemsPerPage: number) => getTagVisits(tag, { ...query, page, itemsPerPage }); + const extraFinishActionData: Partial = { tag }; const actionMap = { start: GET_TAG_VISITS_START, large: GET_TAG_VISITS_LARGE, @@ -65,4 +81,4 @@ export const getTagVisits = (buildShlinkApiClient) => (tag, query = {}) => (disp return getVisitsWithLoader(visitsLoader, extraFinishActionData, actionMap, dispatch, getState); }; -export const cancelGetTagVisits = createAction(GET_TAG_VISITS_CANCEL); +export const cancelGetTagVisits = buildActionCreator(GET_TAG_VISITS_CANCEL); diff --git a/src/visits/types/index.ts b/src/visits/types/index.ts index 7ade9fd9..b66ee1c4 100644 --- a/src/visits/types/index.ts +++ b/src/visits/types/index.ts @@ -1,5 +1,6 @@ import PropTypes from 'prop-types'; import { ShortUrl } from '../../short-urls/data'; +import { Action } from 'redux'; /** @deprecated Use Visit interface instead */ export const VisitType = PropTypes.shape({ @@ -33,6 +34,11 @@ export interface VisitsInfo { loadingLarge: boolean; error: boolean; progress: number; + cancelLoad: boolean; +} + +export interface VisitsLoadProgressChangedAction extends Action { + progress: number; } interface VisitLocation { diff --git a/test/visits/reducers/shortUrlDetail.test.js b/test/visits/reducers/shortUrlDetail.test.ts similarity index 65% rename from test/visits/reducers/shortUrlDetail.test.js rename to test/visits/reducers/shortUrlDetail.test.ts index fb0555c9..582dd2d1 100644 --- a/test/visits/reducers/shortUrlDetail.test.js +++ b/test/visits/reducers/shortUrlDetail.test.ts @@ -1,21 +1,28 @@ +import { Mock } from 'ts-mockery'; import reducer, { getShortUrlDetail, GET_SHORT_URL_DETAIL_START, GET_SHORT_URL_DETAIL_ERROR, GET_SHORT_URL_DETAIL, + ShortUrlDetailAction, } from '../../../src/visits/reducers/shortUrlDetail'; +import { ShortUrl } from '../../../src/short-urls/data'; +import ShlinkApiClient from '../../../src/utils/services/ShlinkApiClient'; +import { ShlinkState } from '../../../src/container/types'; describe('shortUrlDetailReducer', () => { describe('reducer', () => { + const action = (type: string) => Mock.of({ type }); + it('returns loading on GET_SHORT_URL_DETAIL_START', () => { - const state = reducer({ loading: false }, { type: GET_SHORT_URL_DETAIL_START }); + const state = reducer({ loading: false, error: false }, action(GET_SHORT_URL_DETAIL_START)); const { loading } = state; expect(loading).toEqual(true); }); it('stops loading and returns error on GET_SHORT_URL_DETAIL_ERROR', () => { - const state = reducer({ loading: true, error: false }, { type: GET_SHORT_URL_DETAIL_ERROR }); + const state = reducer({ loading: true, error: false }, action(GET_SHORT_URL_DETAIL_ERROR)); const { loading, error } = state; expect(loading).toEqual(false); @@ -23,7 +30,7 @@ describe('shortUrlDetailReducer', () => { }); it('return short URL on GET_SHORT_URL_DETAIL', () => { - const actionShortUrl = { longUrl: 'foo', shortCode: 'bar' }; + const actionShortUrl = Mock.of({ longUrl: 'foo', shortCode: 'bar' }); const state = reducer({ loading: true, error: false }, { type: GET_SHORT_URL_DETAIL, shortUrl: actionShortUrl }); const { loading, error, shortUrl } = state; @@ -34,18 +41,18 @@ describe('shortUrlDetailReducer', () => { }); describe('getShortUrlDetail', () => { - const buildApiClientMock = (returned) => ({ - getShortUrl: jest.fn(() => returned), + const buildApiClientMock = (returned: Promise) => Mock.of({ + getShortUrl: jest.fn(async () => returned), }); const dispatchMock = jest.fn(); - const getState = () => ({}); + const getState = () => Mock.of(); beforeEach(() => dispatchMock.mockReset()); it('dispatches start and error when promise is rejected', async () => { const ShlinkApiClient = buildApiClientMock(Promise.reject()); - await getShortUrlDetail(() => ShlinkApiClient)('abc123')(dispatchMock, getState); + await getShortUrlDetail(() => ShlinkApiClient)('abc123', '')(dispatchMock, getState); expect(dispatchMock).toHaveBeenCalledTimes(2); expect(dispatchMock).toHaveBeenNthCalledWith(1, { type: GET_SHORT_URL_DETAIL_START }); @@ -54,10 +61,10 @@ describe('shortUrlDetailReducer', () => { }); it('dispatches start and success when promise is resolved', async () => { - const resolvedShortUrl = { longUrl: 'foo', shortCode: 'bar' }; + const resolvedShortUrl = Mock.of({ longUrl: 'foo', shortCode: 'bar' }); const ShlinkApiClient = buildApiClientMock(Promise.resolve(resolvedShortUrl)); - await getShortUrlDetail(() => ShlinkApiClient)('abc123')(dispatchMock, getState); + await getShortUrlDetail(() => ShlinkApiClient)('abc123', '')(dispatchMock, getState); expect(dispatchMock).toHaveBeenCalledTimes(2); expect(dispatchMock).toHaveBeenNthCalledWith(1, { type: GET_SHORT_URL_DETAIL_START }); diff --git a/test/visits/reducers/shortUrlVisits.test.js b/test/visits/reducers/shortUrlVisits.test.ts similarity index 66% rename from test/visits/reducers/shortUrlVisits.test.js rename to test/visits/reducers/shortUrlVisits.test.ts index 0c295a12..e71bc38c 100644 --- a/test/visits/reducers/shortUrlVisits.test.js +++ b/test/visits/reducers/shortUrlVisits.test.ts @@ -1,3 +1,4 @@ +import { Mock } from 'ts-mockery'; import reducer, { getShortUrlVisits, cancelGetShortUrlVisits, @@ -7,34 +8,44 @@ import reducer, { GET_SHORT_URL_VISITS_LARGE, GET_SHORT_URL_VISITS_CANCEL, GET_SHORT_URL_VISITS_PROGRESS_CHANGED, + ShortUrlVisits, } from '../../../src/visits/reducers/shortUrlVisits'; import { CREATE_VISIT } from '../../../src/visits/reducers/visitCreation'; +import { rangeOf } from '../../../src/utils/utils'; +import { Visit } from '../../../src/visits/types'; +import { ShlinkVisits } from '../../../src/utils/services/types'; +import ShlinkApiClient from '../../../src/utils/services/ShlinkApiClient'; +import { ShlinkState } from '../../../src/container/types'; describe('shortUrlVisitsReducer', () => { + const visitsMocks = rangeOf(2, () => Mock.all()); + describe('reducer', () => { + const buildState = (data: Partial) => Mock.of(data); + it('returns loading on GET_SHORT_URL_VISITS_START', () => { - const state = reducer({ loading: false }, { type: GET_SHORT_URL_VISITS_START }); + const state = reducer(buildState({ loading: false }), { type: GET_SHORT_URL_VISITS_START } as any); const { loading } = state; expect(loading).toEqual(true); }); it('returns loadingLarge on GET_SHORT_URL_VISITS_LARGE', () => { - const state = reducer({ loadingLarge: false }, { type: GET_SHORT_URL_VISITS_LARGE }); + const state = reducer(buildState({ loadingLarge: false }), { type: GET_SHORT_URL_VISITS_LARGE } as any); const { loadingLarge } = state; expect(loadingLarge).toEqual(true); }); it('returns cancelLoad on GET_SHORT_URL_VISITS_CANCEL', () => { - const state = reducer({ cancelLoad: false }, { type: GET_SHORT_URL_VISITS_CANCEL }); + const state = reducer(buildState({ cancelLoad: false }), { type: GET_SHORT_URL_VISITS_CANCEL } as any); const { cancelLoad } = state; expect(cancelLoad).toEqual(true); }); it('stops loading and returns error on GET_SHORT_URL_VISITS_ERROR', () => { - const state = reducer({ loading: true, error: false }, { type: GET_SHORT_URL_VISITS_ERROR }); + const state = reducer(buildState({ loading: true, error: false }), { type: GET_SHORT_URL_VISITS_ERROR } as any); const { loading, error } = state; expect(loading).toEqual(false); @@ -43,7 +54,10 @@ describe('shortUrlVisitsReducer', () => { it('return visits on GET_SHORT_URL_VISITS', () => { const actionVisits = [{}, {}]; - const state = reducer({ loading: true, error: false }, { type: GET_SHORT_URL_VISITS, visits: actionVisits }); + const state = reducer( + buildState({ loading: true, error: false }), + { type: GET_SHORT_URL_VISITS, visits: actionVisits } as any, + ); const { loading, error, visits } = state; expect(loading).toEqual(false); @@ -52,42 +66,44 @@ describe('shortUrlVisitsReducer', () => { }); it.each([ - [{ shortCode: 'abc123' }, [{}, {}, {}]], - [{ shortCode: 'def456' }, [{}, {}]], + [{ shortCode: 'abc123' }, [ ...visitsMocks, {}]], + [{ shortCode: 'def456' }, visitsMocks ], ])('appends a new visit on CREATE_VISIT', (state, expectedVisits) => { const shortUrl = { shortCode: 'abc123', }; - const prevState = { + const prevState = buildState({ ...state, - visits: [{}, {}], - }; + visits: visitsMocks, + }); - const { visits } = reducer(prevState, { type: CREATE_VISIT, shortUrl, visit: {} }); + const { visits } = reducer(prevState, { type: CREATE_VISIT, shortUrl, visit: {} } as any); expect(visits).toEqual(expectedVisits); }); it('returns new progress on GET_SHORT_URL_VISITS_PROGRESS_CHANGED', () => { - const state = reducer({}, { type: GET_SHORT_URL_VISITS_PROGRESS_CHANGED, progress: 85 }); + const state = reducer(undefined, { type: GET_SHORT_URL_VISITS_PROGRESS_CHANGED, progress: 85 } as any); - expect(state).toEqual({ progress: 85 }); + expect(state).toEqual(expect.objectContaining({ progress: 85 })); }); }); describe('getShortUrlVisits', () => { - const buildApiClientMock = (returned) => ({ - getShortUrlVisits: jest.fn(typeof returned === 'function' ? returned : () => returned), + type GetVisitsReturn = Promise | ((shortCode: string, query: any) => Promise); + + const buildApiClientMock = (returned: GetVisitsReturn) => Mock.of({ + getShortUrlVisits: jest.fn(typeof returned === 'function' ? returned : async () => returned), }); const dispatchMock = jest.fn(); - const getState = () => ({ - shortUrlVisits: { cancelVisits: false }, + const getState = () => Mock.of({ + shortUrlVisits: Mock.of({ cancelLoad: false }), }); beforeEach(() => dispatchMock.mockReset()); it('dispatches start and error when promise is rejected', async () => { - const ShlinkApiClient = buildApiClientMock(Promise.reject()); + const ShlinkApiClient = buildApiClientMock(Promise.reject() as any); await getShortUrlVisits(() => ShlinkApiClient)('abc123')(dispatchMock, getState); @@ -102,10 +118,10 @@ describe('shortUrlVisitsReducer', () => { [{}, undefined ], [{ domain: 'foobar.com' }, 'foobar.com' ], ])('dispatches start and success when promise is resolved', async (query, domain) => { - const visits = [{}, {}]; + const visits = visitsMocks; const shortCode = 'abc123'; const ShlinkApiClient = buildApiClientMock(Promise.resolve({ - data: visits, + data: visitsMocks, pagination: { currentPage: 1, pagesCount: 1, @@ -122,9 +138,9 @@ describe('shortUrlVisitsReducer', () => { it('performs multiple API requests when response contains more pages', async () => { const expectedRequests = 3; - const ShlinkApiClient = buildApiClientMock((shortCode, { page }) => + const ShlinkApiClient = buildApiClientMock(async (_, { page }) => Promise.resolve({ - data: [{}, {}], + data: visitsMocks, pagination: { currentPage: page, pagesCount: expectedRequests, @@ -135,7 +151,7 @@ describe('shortUrlVisitsReducer', () => { expect(ShlinkApiClient.getShortUrlVisits).toHaveBeenCalledTimes(expectedRequests); expect(dispatchMock).toHaveBeenNthCalledWith(3, expect.objectContaining({ - visits: [{}, {}, {}, {}, {}, {}], + visits: [ ...visitsMocks, ...visitsMocks, ...visitsMocks ], })); }); }); diff --git a/test/visits/reducers/tagVisits.test.js b/test/visits/reducers/tagVisits.test.ts similarity index 64% rename from test/visits/reducers/tagVisits.test.js rename to test/visits/reducers/tagVisits.test.ts index b39444eb..d7fc93c6 100644 --- a/test/visits/reducers/tagVisits.test.js +++ b/test/visits/reducers/tagVisits.test.ts @@ -1,3 +1,4 @@ +import { Mock } from 'ts-mockery'; import reducer, { getTagVisits, cancelGetTagVisits, @@ -7,34 +8,44 @@ import reducer, { GET_TAG_VISITS_LARGE, GET_TAG_VISITS_CANCEL, GET_TAG_VISITS_PROGRESS_CHANGED, + TagVisits, } from '../../../src/visits/reducers/tagVisits'; import { CREATE_VISIT } from '../../../src/visits/reducers/visitCreation'; +import { rangeOf } from '../../../src/utils/utils'; +import { Visit } from '../../../src/visits/types'; +import { ShlinkVisits } from '../../../src/utils/services/types'; +import ShlinkApiClient from '../../../src/utils/services/ShlinkApiClient'; +import { ShlinkState } from '../../../src/container/types'; describe('tagVisitsReducer', () => { + const visitsMocks = rangeOf(2, () => Mock.all()); + describe('reducer', () => { + const buildState = (data: Partial) => Mock.of(data); + it('returns loading on GET_TAG_VISITS_START', () => { - const state = reducer({ loading: false }, { type: GET_TAG_VISITS_START }); + const state = reducer(buildState({ loading: false }), { type: GET_TAG_VISITS_START } as any); const { loading } = state; expect(loading).toEqual(true); }); it('returns loadingLarge on GET_TAG_VISITS_LARGE', () => { - const state = reducer({ loadingLarge: false }, { type: GET_TAG_VISITS_LARGE }); + const state = reducer(buildState({ loadingLarge: false }), { type: GET_TAG_VISITS_LARGE } as any); const { loadingLarge } = state; expect(loadingLarge).toEqual(true); }); it('returns cancelLoad on GET_TAG_VISITS_CANCEL', () => { - const state = reducer({ cancelLoad: false }, { type: GET_TAG_VISITS_CANCEL }); + const state = reducer(buildState({ cancelLoad: false }), { type: GET_TAG_VISITS_CANCEL } as any); const { cancelLoad } = state; expect(cancelLoad).toEqual(true); }); it('stops loading and returns error on GET_TAG_VISITS_ERROR', () => { - const state = reducer({ loading: true, error: false }, { type: GET_TAG_VISITS_ERROR }); + const state = reducer(buildState({ loading: true, error: false }), { type: GET_TAG_VISITS_ERROR } as any); const { loading, error } = state; expect(loading).toEqual(false); @@ -43,7 +54,10 @@ describe('tagVisitsReducer', () => { it('return visits on GET_TAG_VISITS', () => { const actionVisits = [{}, {}]; - const state = reducer({ loading: true, error: false }, { type: GET_TAG_VISITS, visits: actionVisits }); + const state = reducer( + buildState({ loading: true, error: false }), + { type: GET_TAG_VISITS, visits: actionVisits } as any, + ); const { loading, error, visits } = state; expect(loading).toEqual(false); @@ -52,36 +66,38 @@ describe('tagVisitsReducer', () => { }); it.each([ - [{ tag: 'foo' }, [{}, {}, {}]], - [{ tag: 'bar' }, [{}, {}]], + [{ tag: 'foo' }, [ ...visitsMocks, {}]], + [{ tag: 'bar' }, visitsMocks ], ])('appends a new visit on CREATE_VISIT', (state, expectedVisits) => { const shortUrl = { tags: [ 'foo', 'baz' ], }; - const prevState = { + const prevState = buildState({ ...state, - visits: [{}, {}], - }; + visits: visitsMocks, + }); - const { visits } = reducer(prevState, { type: CREATE_VISIT, shortUrl, visit: {} }); + const { visits } = reducer(prevState, { type: CREATE_VISIT, shortUrl, visit: {} } as any); expect(visits).toEqual(expectedVisits); }); it('returns new progress on GET_TAG_VISITS_PROGRESS_CHANGED', () => { - const state = reducer({}, { type: GET_TAG_VISITS_PROGRESS_CHANGED, progress: 85 }); + const state = reducer(undefined, { type: GET_TAG_VISITS_PROGRESS_CHANGED, progress: 85 } as any); - expect(state).toEqual({ progress: 85 }); + expect(state).toEqual(expect.objectContaining({ progress: 85 })); }); }); describe('getTagVisits', () => { - const buildApiClientMock = (returned) => ({ - getTagVisits: jest.fn(typeof returned === 'function' ? returned : () => returned), + type GetVisitsReturn = Promise | ((shortCode: string, query: any) => Promise); + + const buildApiClientMock = (returned: GetVisitsReturn) => Mock.of({ + getTagVisits: jest.fn(typeof returned === 'function' ? returned : async () => returned), }); const dispatchMock = jest.fn(); - const getState = () => ({ - tagVisits: { cancelVisits: false }, + const getState = () => Mock.of({ + tagVisits: { cancelLoad: false }, }); beforeEach(jest.resetAllMocks); @@ -101,10 +117,10 @@ describe('tagVisitsReducer', () => { [ undefined ], [{}], ])('dispatches start and success when promise is resolved', async (query) => { - const visits = [{}, {}]; + const visits = visitsMocks; const tag = 'foo'; const ShlinkApiClient = buildApiClientMock(Promise.resolve({ - data: visits, + data: visitsMocks, pagination: { currentPage: 1, pagesCount: 1, From a96539129db063dd865e7649176e70d65f58a46b Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Fri, 28 Aug 2020 20:05:01 +0200 Subject: [PATCH 31/59] Migrated more common components to TS --- package-lock.json | 6 +-- package.json | 2 +- .../{ShlinkVersions.js => ShlinkVersions.tsx} | 38 +++++++------- ...SimplePaginator.js => SimplePaginator.tsx} | 25 +++++----- src/servers/data/index.ts | 3 ++ src/short-urls/Paginator.js | 4 +- src/utils/helpers/pagination.ts | 19 ++++--- test/common/ShlinkVersions.test.js | 32 ------------ test/common/ShlinkVersions.test.tsx | 49 +++++++++++++++++++ ...nator.test.js => SimplePaginator.test.tsx} | 13 ++--- 10 files changed, 105 insertions(+), 86 deletions(-) rename src/common/{ShlinkVersions.js => ShlinkVersions.tsx} (50%) rename src/common/{SimplePaginator.js => SimplePaginator.tsx} (64%) delete mode 100644 test/common/ShlinkVersions.test.js create mode 100644 test/common/ShlinkVersions.test.tsx rename test/common/{SimplePaginator.test.js => SimplePaginator.test.tsx} (83%) diff --git a/package-lock.json b/package-lock.json index f7749882..5e1b06d6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18293,9 +18293,9 @@ "dev": true }, "react-external-link": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/react-external-link/-/react-external-link-1.0.0.tgz", - "integrity": "sha512-KkEozBNo4OI+zdNgGX6ua5+w68wEu2RLdnMGF7KIod6+heDMLfK52Xeqtb0GBO/JvC+HTcj5Kdz8ol0oORYIPA==" + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/react-external-link/-/react-external-link-1.1.1.tgz", + "integrity": "sha512-e2WnTWkg81cuqxmDfjOalliAE20+Y/uD+lserN4uuwkwu+ciGLB3BMz4m7GnXh2+TowIi4sLtCL7zr7aDnIaqA==" }, "react-is": { "version": "16.7.0", diff --git a/package.json b/package.json index 95cd3fad..7d8e84be 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "react-copy-to-clipboard": "^5.0.1", "react-datepicker": "~1.5.0", "react-dom": "^16.13.1", - "react-external-link": "^1.0.0", + "react-external-link": "^1.1.1", "react-leaflet": "^2.4.0", "react-moment": "^0.9.5", "react-redux": "^7.1.1", diff --git a/src/common/ShlinkVersions.js b/src/common/ShlinkVersions.tsx similarity index 50% rename from src/common/ShlinkVersions.js rename to src/common/ShlinkVersions.tsx index ae083806..547b447e 100644 --- a/src/common/ShlinkVersions.js +++ b/src/common/ShlinkVersions.tsx @@ -1,45 +1,43 @@ import React from 'react'; -import PropTypes from 'prop-types'; import classNames from 'classnames'; import { pipe } from 'ramda'; import { ExternalLink } from 'react-external-link'; -import { serverType } from '../servers/prop-types'; import { versionToPrintable, versionToSemVer } from '../utils/helpers/version'; +import { isReachableServer, SelectedServer } from '../servers/data'; const SHLINK_WEB_CLIENT_VERSION = '%_VERSION_%'; const normalizeVersion = pipe(versionToSemVer(), versionToPrintable); -const propTypes = { - selectedServer: serverType, - className: PropTypes.string, - clientVersion: PropTypes.string, -}; +export interface ShlinkVersionsProps { + selectedServer: SelectedServer; + clientVersion?: string; + className?: string; +} -const versionLinkPropTypes = { - project: PropTypes.oneOf([ 'shlink', 'shlink-web-client' ]).isRequired, - version: PropTypes.string.isRequired, -}; +interface VersionLinkProps { + project: 'shlink' | 'shlink-web-client'; + version: string; +} -const VersionLink = ({ project, version }) => ( +const VersionLink = ({ project, version }: VersionLinkProps) => ( {version} ); -VersionLink.propTypes = versionLinkPropTypes; - -const ShlinkVersions = ({ selectedServer, className, clientVersion = SHLINK_WEB_CLIENT_VERSION }) => { - const { printableVersion: serverVersion } = selectedServer; +const ShlinkVersions = ( + { selectedServer, className, clientVersion = SHLINK_WEB_CLIENT_VERSION }: ShlinkVersionsProps, +) => { const normalizedClientVersion = normalizeVersion(clientVersion); return ( - Client: - - Server: + {isReachableServer(selectedServer) && + Server: - + } + Client: ); }; -ShlinkVersions.propTypes = propTypes; - export default ShlinkVersions; diff --git a/src/common/SimplePaginator.js b/src/common/SimplePaginator.tsx similarity index 64% rename from src/common/SimplePaginator.js rename to src/common/SimplePaginator.tsx index 0859e4ff..2b648962 100644 --- a/src/common/SimplePaginator.js +++ b/src/common/SimplePaginator.tsx @@ -1,23 +1,22 @@ -import React from 'react'; -import PropTypes from 'prop-types'; +import React, { FC } from 'react'; import classNames from 'classnames'; import { Pagination, PaginationItem, PaginationLink } from 'reactstrap'; -import { isPageDisabled, keyForPage, progressivePagination } from '../utils/helpers/pagination'; +import { pageIsEllipsis, keyForPage, NumberOrEllipsis, progressivePagination } from '../utils/helpers/pagination'; import './SimplePaginator.scss'; -const propTypes = { - pagesCount: PropTypes.number.isRequired, - currentPage: PropTypes.number.isRequired, - setCurrentPage: PropTypes.func.isRequired, - centered: PropTypes.bool, -}; +interface SimplePaginatorProps { + pagesCount: number; + currentPage: number; + setCurrentPage: (currentPage: number) => void; + centered?: boolean; +} -const SimplePaginator = ({ pagesCount, currentPage, setCurrentPage, centered = true }) => { +const SimplePaginator: FC = ({ pagesCount, currentPage, setCurrentPage, centered = true }) => { if (pagesCount < 2) { return null; } - const onClick = (page) => () => setCurrentPage(page); + const onClick = (page: NumberOrEllipsis) => () => !pageIsEllipsis(page) && setCurrentPage(page); return ( @@ -27,7 +26,7 @@ const SimplePaginator = ({ pagesCount, currentPage, setCurrentPage, centered = t {progressivePagination(currentPage, pagesCount).map((pageNumber, index) => ( {pageNumber} @@ -40,6 +39,4 @@ const SimplePaginator = ({ pagesCount, currentPage, setCurrentPage, centered = t ); }; -SimplePaginator.propTypes = propTypes; - export default SimplePaginator; diff --git a/src/servers/data/index.ts b/src/servers/data/index.ts index 57dbf52a..3c918cbb 100644 --- a/src/servers/data/index.ts +++ b/src/servers/data/index.ts @@ -27,3 +27,6 @@ export type SelectedServer = RegularServer | NotFoundServer | null; export const hasServerData = (server: ServerData | NotFoundServer | null): server is ServerData => !!(server as ServerData)?.url && !!(server as ServerData)?.apiKey; + +export const isReachableServer = (server: SelectedServer): server is ReachableServer => + !!server?.hasOwnProperty('printableVersion'); diff --git a/src/short-urls/Paginator.js b/src/short-urls/Paginator.js index dd976954..4b051811 100644 --- a/src/short-urls/Paginator.js +++ b/src/short-urls/Paginator.js @@ -2,7 +2,7 @@ import React from 'react'; import { Link } from 'react-router-dom'; import { Pagination, PaginationItem, PaginationLink } from 'reactstrap'; import PropTypes from 'prop-types'; -import { isPageDisabled, keyForPage, progressivePagination } from '../utils/helpers/pagination'; +import { pageIsEllipsis, keyForPage, progressivePagination } from '../utils/helpers/pagination'; import './Paginator.scss'; const propTypes = { @@ -24,7 +24,7 @@ const Paginator = ({ paginator = {}, serverId }) => { progressivePagination(currentPage, pagesCount).map((pageNumber, index) => ( { - const delta = 2; const pages: NumberOrEllipsis[] = range( - max(delta, currentPage - delta), - min(pageCount - 1, currentPage + delta) + 1, + max(DELTA, currentPage - DELTA), + min(pageCount - 1, currentPage + DELTA) + 1, ); - if (currentPage - delta > delta) { + if (currentPage - DELTA > DELTA) { pages.unshift(ELLIPSIS); } - if (currentPage + delta < pageCount - 1) { + if (currentPage + DELTA < pageCount - 1) { pages.push(ELLIPSIS); } @@ -24,6 +27,6 @@ export const progressivePagination = (currentPage: number, pageCount: number): N return pages; }; -export const keyForPage = (pageNumber: NumberOrEllipsis, index: number) => pageNumber !== ELLIPSIS ? pageNumber : `${pageNumber}_${index}`; +export const pageIsEllipsis = (pageNumber: NumberOrEllipsis): pageNumber is Ellipsis => pageNumber === ELLIPSIS; -export const isPageDisabled = (pageNumber: NumberOrEllipsis) => pageNumber === ELLIPSIS; +export const keyForPage = (pageNumber: NumberOrEllipsis, index: number) => !pageIsEllipsis(pageNumber) ? `${pageNumber}` : `${pageNumber}_${index}`; diff --git a/test/common/ShlinkVersions.test.js b/test/common/ShlinkVersions.test.js deleted file mode 100644 index 020f50dd..00000000 --- a/test/common/ShlinkVersions.test.js +++ /dev/null @@ -1,32 +0,0 @@ -import React from 'react'; -import { shallow } from 'enzyme'; -import ShlinkVersions from '../../src/common/ShlinkVersions'; - -describe('', () => { - let wrapper; - const createWrapper = (props) => { - wrapper = shallow(); - - return wrapper; - }; - - afterEach(() => wrapper && wrapper.unmount()); - - it.each([ - [ '1.2.3', 'foo', 'v1.2.3', 'foo' ], - [ 'foo', '1.2.3', 'latest', '1.2.3' ], - [ 'latest', 'latest', 'latest', 'latest' ], - [ '5.5.0', '0.2.8', 'v5.5.0', '0.2.8' ], - [ 'not-semver', 'something', 'latest', 'something' ], - ])('displays expected versions', (clientVersion, printableVersion, expectedClientVersion, expectedServerVersion) => { - const wrapper = createWrapper({ clientVersion, selectedServer: { printableVersion } }); - const links = wrapper.find('VersionLink'); - const clientLink = links.at(0); - const serverLink = links.at(1); - - expect(clientLink.prop('project')).toEqual('shlink-web-client'); - expect(clientLink.prop('version')).toEqual(expectedClientVersion); - expect(serverLink.prop('project')).toEqual('shlink'); - expect(serverLink.prop('version')).toEqual(expectedServerVersion); - }); -}); diff --git a/test/common/ShlinkVersions.test.tsx b/test/common/ShlinkVersions.test.tsx new file mode 100644 index 00000000..75d0600a --- /dev/null +++ b/test/common/ShlinkVersions.test.tsx @@ -0,0 +1,49 @@ +import React from 'react'; +import { shallow, ShallowWrapper } from 'enzyme'; +import { Mock } from 'ts-mockery'; +import ShlinkVersions, { ShlinkVersionsProps } from '../../src/common/ShlinkVersions'; +import { NonReachableServer, NotFoundServer, ReachableServer } from '../../src/servers/data'; + +describe('', () => { + let wrapper: ShallowWrapper; + const createWrapper = (props: ShlinkVersionsProps) => { + wrapper = shallow(); + + return wrapper; + }; + + afterEach(() => wrapper?.unmount()); + + it.each([ + [ '1.2.3', Mock.of({ printableVersion: 'foo' }), 'v1.2.3', 'foo' ], + [ 'foo', Mock.of({ printableVersion: '1.2.3' }), 'latest', '1.2.3' ], + [ 'latest', Mock.of({ printableVersion: 'latest' }), 'latest', 'latest' ], + [ '5.5.0', Mock.of({ printableVersion: '0.2.8' }), 'v5.5.0', '0.2.8' ], + [ 'not-semver', Mock.of({ printableVersion: 'something' }), 'latest', 'something' ], + ])( + 'displays expected versions when selected server is reachable', + (clientVersion, selectedServer, expectedClientVersion, expectedServerVersion) => { + const wrapper = createWrapper({ clientVersion, selectedServer }); + const links = wrapper.find('VersionLink'); + const serverLink = links.at(0); + const clientLink = links.at(1); + + expect(serverLink.prop('project')).toEqual('shlink'); + expect(serverLink.prop('version')).toEqual(expectedServerVersion); + expect(clientLink.prop('project')).toEqual('shlink-web-client'); + expect(clientLink.prop('version')).toEqual(expectedClientVersion); + }, + ); + + it.each([ + [ '1.2.3', null ], + [ '1.2.3', Mock.of({ serverNotFound: true }) ], + [ '1.2.3', Mock.of({ serverNotReachable: true }) ], + ])('displays only client version when selected server is not reachable', (clientVersion, selectedServer) => { + const wrapper = createWrapper({ clientVersion, selectedServer }); + const links = wrapper.find('VersionLink'); + + expect(links).toHaveLength(1); + expect(links.at(0).prop('project')).toEqual('shlink-web-client'); + }); +}); diff --git a/test/common/SimplePaginator.test.js b/test/common/SimplePaginator.test.tsx similarity index 83% rename from test/common/SimplePaginator.test.js rename to test/common/SimplePaginator.test.tsx index ac91dd01..a8408f56 100644 --- a/test/common/SimplePaginator.test.js +++ b/test/common/SimplePaginator.test.tsx @@ -1,29 +1,30 @@ import React from 'react'; -import { shallow } from 'enzyme'; +import { shallow, ShallowWrapper } from 'enzyme'; import { identity } from 'ramda'; import { PaginationItem } from 'reactstrap'; import SimplePaginator from '../../src/common/SimplePaginator'; import { ELLIPSIS } from '../../src/utils/helpers/pagination'; describe('', () => { - let wrapper; - const createWrapper = (pagesCount, currentPage = 1) => { + let wrapper: ShallowWrapper; + const createWrapper = (pagesCount: number, currentPage = 1) => { + // @ts-expect-error wrapper = shallow(); return wrapper; }; - afterEach(() => wrapper && wrapper.unmount()); + afterEach(() => wrapper?.unmount()); it.each([ -3, -2, 0, 1 ])('renders empty when the amount of pages is smaller than 2', (pagesCount) => { expect(createWrapper(pagesCount).text()).toEqual(''); }); describe('ELLIPSIS are rendered where expected', () => { - const getItemsForPages = (pagesCount, currentPage) => { + const getItemsForPages = (pagesCount: number, currentPage: number) => { const paginator = createWrapper(pagesCount, currentPage); const items = paginator.find(PaginationItem); - const itemsWithEllipsis = items.filterWhere((item) => item.key() && item.key().includes(ELLIPSIS)); + const itemsWithEllipsis = items.filterWhere((item) => item?.key()?.includes(ELLIPSIS)); return { items, itemsWithEllipsis }; }; From f40ad91ea9bd2b7da844c7a7347b2596a1b9baed Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 29 Aug 2020 09:19:15 +0200 Subject: [PATCH 32/59] Migrated some common components and their dependencies to TS --- shlink-web-client.d.ts | 2 + src/common/AsideMenu.js | 81 ------------------- src/common/AsideMenu.tsx | 77 ++++++++++++++++++ src/common/{Home.js => Home.tsx} | 16 ++-- src/common/MainHeader.js | 61 -------------- src/common/MainHeader.tsx | 51 ++++++++++++ src/common/NoMenuLayout.js | 13 --- src/common/NoMenuLayout.tsx | 6 ++ src/common/{NotFound.js => NotFound.tsx} | 14 ++-- src/common/ScrollToTop.js | 23 ------ src/common/ScrollToTop.tsx | 12 +++ src/common/services/provideServices.ts | 2 +- src/servers/DeleteServerButton.js | 36 --------- src/servers/DeleteServerButton.tsx | 31 +++++++ ...teServerModal.js => DeleteServerModal.tsx} | 26 +++--- ...rversListGroup.js => ServersListGroup.tsx} | 23 ++---- src/utils/helpers/hooks.ts | 2 +- .../{AsideMenu.test.js => AsideMenu.test.tsx} | 10 ++- test/common/{Home.test.js => Home.test.tsx} | 16 ++-- .../{NotFound.test.js => NotFound.test.tsx} | 6 +- test/common/ScrollToTop.test.js | 23 ------ test/common/ScrollToTop.test.tsx | 19 +++++ ...on.test.js => DeleteServerButton.test.tsx} | 10 ++- ...dal.test.js => DeleteServerModal.test.tsx} | 22 ++--- ...roup.test.js => ServersListGroup.test.tsx} | 14 ++-- 25 files changed, 274 insertions(+), 322 deletions(-) delete mode 100644 src/common/AsideMenu.js create mode 100644 src/common/AsideMenu.tsx rename src/common/{Home.js => Home.tsx} (77%) delete mode 100644 src/common/MainHeader.js create mode 100644 src/common/MainHeader.tsx delete mode 100644 src/common/NoMenuLayout.js create mode 100644 src/common/NoMenuLayout.tsx rename src/common/{NotFound.js => NotFound.tsx} (63%) delete mode 100644 src/common/ScrollToTop.js create mode 100644 src/common/ScrollToTop.tsx delete mode 100644 src/servers/DeleteServerButton.js create mode 100644 src/servers/DeleteServerButton.tsx rename src/servers/{DeleteServerModal.js => DeleteServerModal.tsx} (72%) rename src/servers/{ServersListGroup.js => ServersListGroup.tsx} (66%) rename test/common/{AsideMenu.test.js => AsideMenu.test.tsx} (65%) rename test/common/{Home.test.js => Home.test.tsx} (61%) rename test/common/{NotFound.test.js => NotFound.test.tsx} (92%) delete mode 100644 test/common/ScrollToTop.test.js create mode 100644 test/common/ScrollToTop.test.tsx rename test/servers/{DeleteServerButton.test.js => DeleteServerButton.test.tsx} (70%) rename test/servers/{DeleteServerModal.test.js => DeleteServerModal.test.tsx} (77%) rename test/servers/{ServersListGroup.test.js => ServersListGroup.test.tsx} (66%) diff --git a/shlink-web-client.d.ts b/shlink-web-client.d.ts index bbe9db86..52526860 100644 --- a/shlink-web-client.d.ts +++ b/shlink-web-client.d.ts @@ -1,4 +1,6 @@ export declare global { + declare module '*.png' + interface Window { __REDUX_DEVTOOLS_EXTENSION_COMPOSE__?: Function; } diff --git a/src/common/AsideMenu.js b/src/common/AsideMenu.js deleted file mode 100644 index dca91132..00000000 --- a/src/common/AsideMenu.js +++ /dev/null @@ -1,81 +0,0 @@ -import { - faList as listIcon, - faLink as createIcon, - faTags as tagsIcon, - faPen as editIcon, -} from '@fortawesome/free-solid-svg-icons'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import React from 'react'; -import { NavLink } from 'react-router-dom'; -import PropTypes from 'prop-types'; -import classNames from 'classnames'; -import { serverType } from '../servers/prop-types'; -import './AsideMenu.scss'; - -const AsideMenuItem = ({ children, to, className, ...rest }) => ( - - {children} - -); - -AsideMenuItem.propTypes = { - children: PropTypes.node.isRequired, - to: PropTypes.string.isRequired, - className: PropTypes.string, -}; - -const propTypes = { - selectedServer: serverType, - className: PropTypes.string, - showOnMobile: PropTypes.bool, -}; - -const AsideMenu = (DeleteServerButton) => { - const AsideMenu = ({ selectedServer, className, showOnMobile }) => { - const serverId = selectedServer ? selectedServer.id : ''; - const asideClass = classNames('aside-menu', className, { - 'aside-menu--hidden': !showOnMobile, - }); - const shortUrlsIsActive = (match, location) => location.pathname.match('/list-short-urls'); - const buildPath = (suffix) => `/server/${serverId}${suffix}`; - - return ( - - ); - }; - - AsideMenu.propTypes = propTypes; - - return AsideMenu; -}; - -export default AsideMenu; diff --git a/src/common/AsideMenu.tsx b/src/common/AsideMenu.tsx new file mode 100644 index 00000000..75647382 --- /dev/null +++ b/src/common/AsideMenu.tsx @@ -0,0 +1,77 @@ +import { + faList as listIcon, + faLink as createIcon, + faTags as tagsIcon, + faPen as editIcon, +} from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import React, { FC } from 'react'; +import { NavLink, NavLinkProps } from 'react-router-dom'; +import classNames from 'classnames'; +import { Location } from 'history'; +import { DeleteServerButtonProps } from '../servers/DeleteServerButton'; +import { ServerWithId } from '../servers/data'; +import './AsideMenu.scss'; + +interface AsideMenuProps { + selectedServer: ServerWithId; + className?: string; + showOnMobile?: boolean; +} + +interface AsideMenuItemItemProps extends NavLinkProps { + to: string; + className?: string; +} + +const AsideMenuItem: FC = ({ children, to, className, ...rest }) => ( + + {children} + +); + +const AsideMenu = (DeleteServerButton: FC) => ( + { selectedServer, className, showOnMobile = false }: AsideMenuProps, +) => { + const serverId = selectedServer ? selectedServer.id : ''; + const asideClass = classNames('aside-menu', className, { + 'aside-menu--hidden': !showOnMobile, + }); + const shortUrlsIsActive = (_: null, location: Location) => location.pathname.match('/list-short-urls') !== null; + const buildPath = (suffix: string) => `/server/${serverId}${suffix}`; + + return ( + + ); +}; + +export default AsideMenu; diff --git a/src/common/Home.js b/src/common/Home.tsx similarity index 77% rename from src/common/Home.js rename to src/common/Home.tsx index 63fbc80a..4c5623f3 100644 --- a/src/common/Home.js +++ b/src/common/Home.tsx @@ -1,16 +1,16 @@ import React, { useEffect } from 'react'; import { isEmpty, values } from 'ramda'; import { Link } from 'react-router-dom'; -import PropTypes from 'prop-types'; -import './Home.scss'; import ServersListGroup from '../servers/ServersListGroup'; +import { ServersMap } from '../servers/reducers/servers'; +import './Home.scss'; -const propTypes = { - resetSelectedServer: PropTypes.func, - servers: PropTypes.object, -}; +export interface HomeProps { + resetSelectedServer: Function; + servers: ServersMap; +} -const Home = ({ resetSelectedServer, servers }) => { +const Home = ({ resetSelectedServer, servers }: HomeProps) => { const serversList = values(servers); const hasServers = !isEmpty(serversList); @@ -29,6 +29,4 @@ const Home = ({ resetSelectedServer, servers }) => { ); }; -Home.propTypes = propTypes; - export default Home; diff --git a/src/common/MainHeader.js b/src/common/MainHeader.js deleted file mode 100644 index eecaf464..00000000 --- a/src/common/MainHeader.js +++ /dev/null @@ -1,61 +0,0 @@ -import { faPlus as plusIcon, faChevronDown as arrowIcon, faCogs as cogsIcon } from '@fortawesome/free-solid-svg-icons'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import React, { useEffect } from 'react'; -import { Link } from 'react-router-dom'; -import { Collapse, Nav, Navbar, NavbarBrand, NavbarToggler, NavItem, NavLink } from 'reactstrap'; -import classNames from 'classnames'; -import PropTypes from 'prop-types'; -import { useToggle } from '../utils/helpers/hooks'; -import shlinkLogo from './shlink-logo-white.png'; -import './MainHeader.scss'; - -const propTypes = { - location: PropTypes.object, -}; - -const MainHeader = (ServersDropdown) => { - const MainHeaderComp = ({ location }) => { - const [ isOpen, toggleOpen, , close ] = useToggle(); - const { pathname } = location; - - useEffect(close, [ location ]); - - const createServerPath = '/server/create'; - const settingsPath = '/settings'; - const toggleClass = classNames('main-header__toggle-icon', { 'main-header__toggle-icon--opened': isOpen }); - - return ( - - - Shlink Shlink - - - - - - - - - - - ); - }; - - MainHeaderComp.propTypes = propTypes; - - return MainHeaderComp; -}; - -export default MainHeader; diff --git a/src/common/MainHeader.tsx b/src/common/MainHeader.tsx new file mode 100644 index 00000000..e4a8c163 --- /dev/null +++ b/src/common/MainHeader.tsx @@ -0,0 +1,51 @@ +import { faPlus as plusIcon, faChevronDown as arrowIcon, faCogs as cogsIcon } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import React, { FC, useEffect } from 'react'; +import { Link } from 'react-router-dom'; +import { Collapse, Nav, Navbar, NavbarBrand, NavbarToggler, NavItem, NavLink } from 'reactstrap'; +import classNames from 'classnames'; +import { RouteChildrenProps } from 'react-router'; +import { useToggle } from '../utils/helpers/hooks'; +import shlinkLogo from './shlink-logo-white.png'; +import './MainHeader.scss'; + +const MainHeader = (ServersDropdown: FC) => ({ location }: RouteChildrenProps) => { + const [ isOpen, toggleOpen, , close ] = useToggle(); + const { pathname } = location; + + useEffect(close, [ location ]); + + const createServerPath = '/server/create'; + const settingsPath = '/settings'; + const toggleClass = classNames('main-header__toggle-icon', { 'main-header__toggle-icon--opened': isOpen }); + + return ( + + + Shlink Shlink + + + + + + + + + + + ); +}; + +export default MainHeader; diff --git a/src/common/NoMenuLayout.js b/src/common/NoMenuLayout.js deleted file mode 100644 index f63ca0cd..00000000 --- a/src/common/NoMenuLayout.js +++ /dev/null @@ -1,13 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import './NoMenuLayout.scss'; - -const propTypes = { - children: PropTypes.node, -}; - -const NoMenuLayout = ({ children }) =>
{children}
; - -NoMenuLayout.propTypes = propTypes; - -export default NoMenuLayout; diff --git a/src/common/NoMenuLayout.tsx b/src/common/NoMenuLayout.tsx new file mode 100644 index 00000000..1cbe2533 --- /dev/null +++ b/src/common/NoMenuLayout.tsx @@ -0,0 +1,6 @@ +import React, { FC } from 'react'; +import './NoMenuLayout.scss'; + +const NoMenuLayout: FC = ({ children }) =>
{children}
; + +export default NoMenuLayout; diff --git a/src/common/NotFound.js b/src/common/NotFound.tsx similarity index 63% rename from src/common/NotFound.js rename to src/common/NotFound.tsx index 7a2355e9..4ec67f71 100644 --- a/src/common/NotFound.js +++ b/src/common/NotFound.tsx @@ -1,13 +1,11 @@ -import React from 'react'; +import React, { FC } from 'react'; import { Link } from 'react-router-dom'; -import * as PropTypes from 'prop-types'; -const propTypes = { - to: PropTypes.string, - children: PropTypes.node, -}; +interface NotFoundProps { + to?: string; +} -const NotFound = ({ to = '/', children = 'Home' }) => ( +const NotFound: FC = ({ to = '/', children = 'Home' }) => (

Oops! We could not find requested route.

@@ -19,6 +17,4 @@ const NotFound = ({ to = '/', children = 'Home' }) => (

); -NotFound.propTypes = propTypes; - export default NotFound; diff --git a/src/common/ScrollToTop.js b/src/common/ScrollToTop.js deleted file mode 100644 index 7de10d6d..00000000 --- a/src/common/ScrollToTop.js +++ /dev/null @@ -1,23 +0,0 @@ -import { useEffect } from 'react'; -import PropTypes from 'prop-types'; - -const propTypes = { - location: PropTypes.object, - children: PropTypes.node, -}; - -const ScrollToTop = () => { - const ScrollToTopComp = ({ location, children }) => { - useEffect(() => { - scrollTo(0, 0); - }, [ location ]); - - return children; - }; - - ScrollToTopComp.propTypes = propTypes; - - return ScrollToTopComp; -}; - -export default ScrollToTop; diff --git a/src/common/ScrollToTop.tsx b/src/common/ScrollToTop.tsx new file mode 100644 index 00000000..48bf2a04 --- /dev/null +++ b/src/common/ScrollToTop.tsx @@ -0,0 +1,12 @@ +import React, { PropsWithChildren, useEffect } from 'react'; +import { RouteChildrenProps } from 'react-router'; + +const ScrollToTop = () => ({ location, children }: PropsWithChildren) => { + useEffect(() => { + scrollTo(0, 0); + }, [ location ]); + + return {children}; +}; + +export default ScrollToTop; diff --git a/src/common/services/provideServices.ts b/src/common/services/provideServices.ts index ad3f1bb3..68d978af 100644 --- a/src/common/services/provideServices.ts +++ b/src/common/services/provideServices.ts @@ -12,7 +12,7 @@ const provideServices = (bottle: Bottle, connect: ConnectDecorator, withRouter: bottle.constant('window', (global as any).window); bottle.constant('console', global.console); - bottle.serviceFactory('ScrollToTop', ScrollToTop, 'window'); + bottle.serviceFactory('ScrollToTop', ScrollToTop); bottle.decorator('ScrollToTop', withRouter); bottle.serviceFactory('MainHeader', MainHeader, 'ServersDropdown'); diff --git a/src/servers/DeleteServerButton.js b/src/servers/DeleteServerButton.js deleted file mode 100644 index 23cab6e4..00000000 --- a/src/servers/DeleteServerButton.js +++ /dev/null @@ -1,36 +0,0 @@ -import React from 'react'; -import { faMinusCircle as deleteIcon } from '@fortawesome/free-solid-svg-icons'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import PropTypes from 'prop-types'; -import { useToggle } from '../utils/helpers/hooks'; -import { serverType } from './prop-types'; - -const propTypes = { - server: serverType, - className: PropTypes.string, - textClassName: PropTypes.string, - children: PropTypes.node, -}; - -const DeleteServerButton = (DeleteServerModal) => { - const DeleteServerButtonComp = ({ server, className, children, textClassName }) => { - const [ isModalOpen, , showModal, hideModal ] = useToggle(); - - return ( - - - {!children && } - {children || 'Remove this server'} - - - - - ); - }; - - DeleteServerButtonComp.propTypes = propTypes; - - return DeleteServerButtonComp; -}; - -export default DeleteServerButton; diff --git a/src/servers/DeleteServerButton.tsx b/src/servers/DeleteServerButton.tsx new file mode 100644 index 00000000..152434a4 --- /dev/null +++ b/src/servers/DeleteServerButton.tsx @@ -0,0 +1,31 @@ +import React, { FC } from 'react'; +import { faMinusCircle as deleteIcon } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { useToggle } from '../utils/helpers/hooks'; +import { DeleteServerModalProps } from './DeleteServerModal'; +import { ServerWithId } from './data'; + +export interface DeleteServerButtonProps { + server: ServerWithId; + className?: string; + textClassName?: string; +} + +const DeleteServerButton = (DeleteServerModal: FC): FC => ( + { server, className, children, textClassName }, +) => { + const [ isModalOpen, , showModal, hideModal ] = useToggle(); + + return ( + + + {!children && } + {children ?? 'Remove this server'} + + + + + ); +}; + +export default DeleteServerButton; diff --git a/src/servers/DeleteServerModal.js b/src/servers/DeleteServerModal.tsx similarity index 72% rename from src/servers/DeleteServerModal.js rename to src/servers/DeleteServerModal.tsx index 756c9c8e..bc757859 100644 --- a/src/servers/DeleteServerModal.js +++ b/src/servers/DeleteServerModal.tsx @@ -1,19 +1,19 @@ -import PropTypes from 'prop-types'; import React from 'react'; import { Modal, ModalBody, ModalFooter, ModalHeader } from 'reactstrap'; -import { serverType } from './prop-types'; +import { RouterProps } from 'react-router'; +import { ServerWithId } from './data'; -const propTypes = { - toggle: PropTypes.func.isRequired, - isOpen: PropTypes.bool.isRequired, - server: serverType, - deleteServer: PropTypes.func, - history: PropTypes.shape({ - push: PropTypes.func, - }), -}; +export interface DeleteServerModalProps { + server: ServerWithId; + toggle: () => void; + isOpen: boolean; +} -const DeleteServerModal = ({ server, toggle, isOpen, deleteServer, history }) => { +interface DeleteServerModalConnectProps extends DeleteServerModalProps, RouterProps { + deleteServer: (server: ServerWithId) => void; +} + +const DeleteServerModal = ({ server, toggle, isOpen, deleteServer, history }: DeleteServerModalConnectProps) => { const closeModal = () => { deleteServer(server); toggle(); @@ -40,6 +40,4 @@ const DeleteServerModal = ({ server, toggle, isOpen, deleteServer, history }) => ); }; -DeleteServerModal.propTypes = propTypes; - export default DeleteServerModal; diff --git a/src/servers/ServersListGroup.js b/src/servers/ServersListGroup.tsx similarity index 66% rename from src/servers/ServersListGroup.js rename to src/servers/ServersListGroup.tsx index 55f8784a..133a95ca 100644 --- a/src/servers/ServersListGroup.js +++ b/src/servers/ServersListGroup.tsx @@ -1,30 +1,23 @@ -import React from 'react'; -import PropTypes from 'prop-types'; +import React, { FC } from 'react'; import { ListGroup, ListGroupItem } from 'reactstrap'; import { Link } from 'react-router-dom'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faChevronRight as chevronIcon } from '@fortawesome/free-solid-svg-icons'; -import { serverType } from './prop-types'; import './ServersListGroup.scss'; +import { ServerWithId } from './data'; -const propTypes = { - servers: PropTypes.arrayOf(serverType).isRequired, - children: PropTypes.node.isRequired, -}; +interface ServersListGroup { + servers: ServerWithId[]; +} -const ServerListItem = ({ id, name }) => ( +const ServerListItem = ({ id, name }: { id: string; name: string }) => ( {name} ); -ServerListItem.propTypes = { - id: PropTypes.string, - name: PropTypes.string, -}; - -const ServersListGroup = ({ servers, children }) => ( +const ServersListGroup: FC = ({ servers, children }) => (
{children}
@@ -37,6 +30,4 @@ const ServersListGroup = ({ servers, children }) => ( ); -ServersListGroup.propTypes = propTypes; - export default ServersListGroup; diff --git a/src/utils/helpers/hooks.ts b/src/utils/helpers/hooks.ts index 6e2c986c..3eadd866 100644 --- a/src/utils/helpers/hooks.ts +++ b/src/utils/helpers/hooks.ts @@ -23,7 +23,7 @@ export const useStateFlagTimeout = ( return [ flag, callback ]; }; -type ToggleResult = [ boolean, (flag: boolean) => void, () => void, () => void ]; +type ToggleResult = [ boolean, () => void, () => void, () => void ]; export const useToggle = (initialValue = false): ToggleResult => { const [ flag, setFlag ] = useState(initialValue); diff --git a/test/common/AsideMenu.test.js b/test/common/AsideMenu.test.tsx similarity index 65% rename from test/common/AsideMenu.test.js rename to test/common/AsideMenu.test.tsx index 03ab5097..c1145bca 100644 --- a/test/common/AsideMenu.test.js +++ b/test/common/AsideMenu.test.tsx @@ -1,15 +1,17 @@ -import { shallow } from 'enzyme'; +import { shallow, ShallowWrapper } from 'enzyme'; import React from 'react'; +import { Mock } from 'ts-mockery'; import asideMenuCreator from '../../src/common/AsideMenu'; +import { ServerWithId } from '../../src/servers/data'; describe('', () => { - let wrapped; - const DeleteServerButton = () => ''; + let wrapped: ShallowWrapper; + const DeleteServerButton = () => null; beforeEach(() => { const AsideMenu = asideMenuCreator(DeleteServerButton); - wrapped = shallow(); + wrapped = shallow(({ id: 'abc123' })} />); }); afterEach(() => wrapped.unmount()); diff --git a/test/common/Home.test.js b/test/common/Home.test.tsx similarity index 61% rename from test/common/Home.test.js rename to test/common/Home.test.tsx index 0af589b2..99eb3be8 100644 --- a/test/common/Home.test.js +++ b/test/common/Home.test.tsx @@ -1,14 +1,16 @@ -import { shallow } from 'enzyme'; +import { shallow, ShallowWrapper } from 'enzyme'; import React from 'react'; -import Home from '../../src/common/Home'; +import { Mock } from 'ts-mockery'; +import Home, { HomeProps } from '../../src/common/Home'; +import { ServerWithId } from '../../src/servers/data'; describe('', () => { - let wrapped; + let wrapped: ShallowWrapper; const defaultProps = { resetSelectedServer: jest.fn(), servers: {}, }; - const createComponent = (props) => { + const createComponent = (props: Partial = {}) => { const actualProps = { ...defaultProps, ...props }; wrapped = shallow(); @@ -16,7 +18,7 @@ describe('', () => { return wrapped; }; - afterEach(() => wrapped && wrapped.unmount()); + afterEach(() => wrapped?.unmount()); it('shows link to create server when no servers exist', () => { const wrapped = createComponent(); @@ -26,8 +28,8 @@ describe('', () => { it('asks to select a server when servers exist', () => { const servers = { - 1: { name: 'foo', id: '1' }, - 2: { name: 'bar', id: '2' }, + '1a': Mock.of({ name: 'foo', id: '1' }), + '2b': Mock.of({ name: 'bar', id: '2' }), }; const wrapped = createComponent({ servers }); const span = wrapped.find('span'); diff --git a/test/common/NotFound.test.js b/test/common/NotFound.test.tsx similarity index 92% rename from test/common/NotFound.test.js rename to test/common/NotFound.test.tsx index 036db8a6..a194711c 100644 --- a/test/common/NotFound.test.js +++ b/test/common/NotFound.test.tsx @@ -1,10 +1,10 @@ import React from 'react'; -import { shallow } from 'enzyme'; +import { shallow, ShallowWrapper } from 'enzyme'; import { Link } from 'react-router-dom'; import NotFound from '../../src/common/NotFound'; describe('', () => { - let wrapper; + let wrapper: ShallowWrapper; const createWrapper = (props = {}) => { wrapper = shallow(); const content = wrapper.text(); @@ -12,7 +12,7 @@ describe('', () => { return { wrapper, content }; }; - afterEach(() => wrapper && wrapper.unmount()); + afterEach(() => wrapper?.unmount()); it('shows expected error title', () => { const { content } = createWrapper(); diff --git a/test/common/ScrollToTop.test.js b/test/common/ScrollToTop.test.js deleted file mode 100644 index a0264182..00000000 --- a/test/common/ScrollToTop.test.js +++ /dev/null @@ -1,23 +0,0 @@ -import React from 'react'; -import { shallow } from 'enzyme'; -import createScrollToTop from '../../src/common/ScrollToTop'; - -describe('', () => { - let wrapper; - const window = { - scrollTo: jest.fn(), - }; - - beforeEach(() => { - const ScrollToTop = createScrollToTop(window); - - wrapper = shallow(Foobar); - }); - - afterEach(() => { - wrapper.unmount(); - window.scrollTo.mockReset(); - }); - - it('just renders children', () => expect(wrapper.text()).toEqual('Foobar')); -}); diff --git a/test/common/ScrollToTop.test.tsx b/test/common/ScrollToTop.test.tsx new file mode 100644 index 00000000..ff925292 --- /dev/null +++ b/test/common/ScrollToTop.test.tsx @@ -0,0 +1,19 @@ +import React from 'react'; +import { shallow, ShallowWrapper } from 'enzyme'; +import { Mock } from 'ts-mockery'; +import { RouteChildrenProps } from 'react-router'; +import createScrollToTop from '../../src/common/ScrollToTop'; + +describe('', () => { + let wrapper: ShallowWrapper; + + beforeEach(() => { + const ScrollToTop = createScrollToTop(); + + wrapper = shallow(()}>Foobar); + }); + + afterEach(() => wrapper.unmount()); + + it('just renders children', () => expect(wrapper.text()).toEqual('Foobar')); +}); diff --git a/test/servers/DeleteServerButton.test.js b/test/servers/DeleteServerButton.test.tsx similarity index 70% rename from test/servers/DeleteServerButton.test.js rename to test/servers/DeleteServerButton.test.tsx index a377cf83..d5541662 100644 --- a/test/servers/DeleteServerButton.test.js +++ b/test/servers/DeleteServerButton.test.tsx @@ -1,15 +1,17 @@ import React from 'react'; -import { shallow } from 'enzyme'; +import { shallow, ShallowWrapper } from 'enzyme'; +import { Mock } from 'ts-mockery'; import deleteServerButtonConstruct from '../../src/servers/DeleteServerButton'; -import DeleteServerModal from '../../src/servers/DeleteServerModal'; +import { ServerWithId } from '../../src/servers/data'; describe('', () => { - let wrapper; + let wrapper: ShallowWrapper; + const DeleteServerModal = () => null; beforeEach(() => { const DeleteServerButton = deleteServerButtonConstruct(DeleteServerModal); - wrapper = shallow(); + wrapper = shallow(()} className="button" />); }); afterEach(() => wrapper.unmount()); diff --git a/test/servers/DeleteServerModal.test.js b/test/servers/DeleteServerModal.test.tsx similarity index 77% rename from test/servers/DeleteServerModal.test.js rename to test/servers/DeleteServerModal.test.tsx index 1114fd8f..cc5d881f 100644 --- a/test/servers/DeleteServerModal.test.js +++ b/test/servers/DeleteServerModal.test.tsx @@ -1,31 +1,31 @@ import React from 'react'; -import { shallow } from 'enzyme'; +import { shallow, ShallowWrapper } from 'enzyme'; import { Modal, ModalBody, ModalFooter, ModalHeader } from 'reactstrap'; +import { History } from 'history'; +import { Mock } from 'ts-mockery'; import DeleteServerModal from '../../src/servers/DeleteServerModal'; +import { ServerWithId } from '../../src/servers/data'; describe('', () => { - let wrapper; + let wrapper: ShallowWrapper; const deleteServerMock = jest.fn(); - const historyMock = { push: jest.fn() }; + const push = jest.fn(); const toggleMock = jest.fn(); const serverName = 'the_server_name'; beforeEach(() => { - deleteServerMock.mockReset(); - toggleMock.mockReset(); - historyMock.push.mockReset(); - wrapper = shallow( ({ name: serverName })} toggle={toggleMock} isOpen={true} deleteServer={deleteServerMock} - history={historyMock} + history={Mock.of({ push })} />, ); }); afterEach(() => wrapper.unmount()); + afterEach(jest.clearAllMocks); it('renders a modal window', () => { expect(wrapper.find(Modal)).toHaveLength(1); @@ -49,7 +49,7 @@ describe('', () => { expect(toggleMock).toHaveBeenCalledTimes(1); expect(deleteServerMock).not.toHaveBeenCalled(); - expect(historyMock.push).not.toHaveBeenCalled(); + expect(push).not.toHaveBeenCalled(); }); it('deletes server when clicking accept button', () => { @@ -59,6 +59,6 @@ describe('', () => { expect(toggleMock).toHaveBeenCalledTimes(1); expect(deleteServerMock).toHaveBeenCalledTimes(1); - expect(historyMock.push).toHaveBeenCalledTimes(1); + expect(push).toHaveBeenCalledTimes(1); }); }); diff --git a/test/servers/ServersListGroup.test.js b/test/servers/ServersListGroup.test.tsx similarity index 66% rename from test/servers/ServersListGroup.test.js rename to test/servers/ServersListGroup.test.tsx index b2871bbe..2f6ba24b 100644 --- a/test/servers/ServersListGroup.test.js +++ b/test/servers/ServersListGroup.test.tsx @@ -1,17 +1,19 @@ -import { shallow } from 'enzyme'; +import { shallow, ShallowWrapper } from 'enzyme'; import React from 'react'; import { ListGroup } from 'reactstrap'; +import { Mock } from 'ts-mockery'; import ServersListGroup from '../../src/servers/ServersListGroup'; +import { ServerWithId } from '../../src/servers/data'; describe('', () => { - let wrapped; - const createComponent = (servers) => { + let wrapped: ShallowWrapper; + const createComponent = (servers: ServerWithId[]) => { wrapped = shallow(The list of servers); return wrapped; }; - afterEach(() => wrapped && wrapped.unmount()); + afterEach(() => wrapped?.unmount()); it('Renders title', () => { const wrapped = createComponent([]); @@ -23,8 +25,8 @@ describe('', () => { it('shows servers list', () => { const servers = [ - { name: 'foo', id: '123' }, - { name: 'bar', id: '456' }, + Mock.of({ name: 'foo', id: '123' }), + Mock.of({ name: 'bar', id: '456' }), ]; const wrapped = createComponent(servers); From 8cc0695ee913271e53b02aed943aeac517db3f72 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 29 Aug 2020 10:53:02 +0200 Subject: [PATCH 33/59] Refactored ServerError to infer error message based on provided server type guards --- src/common/AsideMenu.tsx | 6 +- .../{ErrorHandler.js => ErrorHandler.tsx} | 25 ++--- src/common/Home.tsx | 2 +- src/common/MenuLayout.js | 98 ------------------- src/common/MenuLayout.tsx | 89 +++++++++++++++++ src/container/types.ts | 3 +- src/servers/data/index.ts | 5 + src/servers/helpers/ServerError.js | 50 ---------- src/servers/helpers/ServerError.tsx | 45 +++++++++ src/servers/helpers/withSelectedServer.js | 2 +- src/servers/reducers/servers.ts | 6 +- src/visits/ShortUrlVisitsHeader.js | 2 +- ...rHandler.test.js => ErrorHandler.test.tsx} | 11 ++- ...rverError.test.js => ServerError.test.tsx} | 19 ++-- 14 files changed, 177 insertions(+), 186 deletions(-) rename src/common/{ErrorHandler.js => ErrorHandler.tsx} (61%) delete mode 100644 src/common/MenuLayout.js create mode 100644 src/common/MenuLayout.tsx delete mode 100644 src/servers/helpers/ServerError.js create mode 100644 src/servers/helpers/ServerError.tsx rename test/common/{ErrorHandler.test.js => ErrorHandler.test.tsx} (81%) rename test/servers/helpers/{ServerError.test.js => ServerError.test.tsx} (70%) diff --git a/src/common/AsideMenu.tsx b/src/common/AsideMenu.tsx index 75647382..6283ff49 100644 --- a/src/common/AsideMenu.tsx +++ b/src/common/AsideMenu.tsx @@ -13,18 +13,18 @@ import { DeleteServerButtonProps } from '../servers/DeleteServerButton'; import { ServerWithId } from '../servers/data'; import './AsideMenu.scss'; -interface AsideMenuProps { +export interface AsideMenuProps { selectedServer: ServerWithId; className?: string; showOnMobile?: boolean; } -interface AsideMenuItemItemProps extends NavLinkProps { +interface AsideMenuItemProps extends NavLinkProps { to: string; className?: string; } -const AsideMenuItem: FC = ({ children, to, className, ...rest }) => ( +const AsideMenuItem: FC = ({ children, to, className, ...rest }) => ( class ErrorHandler extends React.Component { - static propTypes = { - children: PropTypes.node.isRequired, - }; +interface ErrorHandlerState { + hasError: boolean; +} - constructor(props) { +const ErrorHandler = ( + { location }: Window, + { error }: Console, +) => class ErrorHandler extends React.Component { + public constructor(props: object) { super(props); this.state = { hasError: false }; } - static getDerivedStateFromError() { + public static getDerivedStateFromError(): ErrorHandlerState { return { hasError: true }; } - componentDidCatch(e) { + public componentDidCatch(e: Error): void { if (process.env.NODE_ENV !== 'development') { error(e); } } - render() { + public render(): ReactNode | undefined { if (this.state.hasError) { return (
diff --git a/src/common/Home.tsx b/src/common/Home.tsx index 4c5623f3..252e1d62 100644 --- a/src/common/Home.tsx +++ b/src/common/Home.tsx @@ -2,8 +2,8 @@ import React, { useEffect } from 'react'; import { isEmpty, values } from 'ramda'; import { Link } from 'react-router-dom'; import ServersListGroup from '../servers/ServersListGroup'; -import { ServersMap } from '../servers/reducers/servers'; import './Home.scss'; +import { ServersMap } from '../servers/data'; export interface HomeProps { resetSelectedServer: Function; diff --git a/src/common/MenuLayout.js b/src/common/MenuLayout.js deleted file mode 100644 index f890223d..00000000 --- a/src/common/MenuLayout.js +++ /dev/null @@ -1,98 +0,0 @@ -import React, { useEffect } from 'react'; -import { Route, Switch } from 'react-router-dom'; -import { Swipeable } from 'react-swipeable'; -import { faBars as burgerIcon } from '@fortawesome/free-solid-svg-icons'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import classNames from 'classnames'; -import * as PropTypes from 'prop-types'; -import { serverType } from '../servers/prop-types'; -import { withSelectedServer } from '../servers/helpers/withSelectedServer'; -import { useToggle } from '../utils/helpers/hooks'; -import { versionMatch } from '../utils/helpers/version'; -import NotFound from './NotFound'; -import './MenuLayout.scss'; - -const propTypes = { - match: PropTypes.object, - location: PropTypes.object, - selectedServer: serverType, -}; - -const MenuLayout = ( - TagsList, - ShortUrls, - AsideMenu, - CreateShortUrl, - ShortUrlVisits, - TagVisits, - ShlinkVersions, - ServerError, -) => { - const MenuLayoutComp = ({ match, location, selectedServer }) => { - const [ sidebarVisible, toggleSidebar, showSidebar, hideSidebar ] = useToggle(); - const { params: { serverId } } = match; - - useEffect(() => hideSidebar(), [ location ]); - - if (selectedServer.serverNotReachable) { - return ; - } - - const addTagsVisitsRoute = versionMatch(selectedServer.version, { minVersion: '2.2.0' }); - const burgerClasses = classNames('menu-layout__burger-icon', { - 'menu-layout__burger-icon--active': sidebarVisible, - }); - const swipeMenuIfNoModalExists = (callback) => (e) => { - const swippedOnVisitsTable = e.event.path.some( - ({ classList }) => classList && classList.contains('visits-table'), - ); - - if (swippedOnVisitsTable || document.querySelector('.modal')) { - return; - } - - callback(); - }; - - return ( - - - - -
- -
hideSidebar()}> -
- - - - - {addTagsVisitsRoute && } - - List short URLs} - /> - -
- -
- -
-
-
-
-
- ); - }; - - MenuLayoutComp.propTypes = propTypes; - - return withSelectedServer(MenuLayoutComp, ServerError); -}; - -export default MenuLayout; diff --git a/src/common/MenuLayout.tsx b/src/common/MenuLayout.tsx new file mode 100644 index 00000000..79b5a043 --- /dev/null +++ b/src/common/MenuLayout.tsx @@ -0,0 +1,89 @@ +import React, { FC, useEffect } from 'react'; +import { Route, RouteChildrenProps, Switch } from 'react-router-dom'; +import { EventData, Swipeable } from 'react-swipeable'; +import { faBars as burgerIcon } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import classNames from 'classnames'; +import { withSelectedServer } from '../servers/helpers/withSelectedServer'; +import { useToggle } from '../utils/helpers/hooks'; +import { versionMatch } from '../utils/helpers/version'; +import { isReachableServer, SelectedServer } from '../servers/data'; +import NotFound from './NotFound'; +import { AsideMenuProps } from './AsideMenu'; +import './MenuLayout.scss'; + +interface MenuLayoutProps extends RouteChildrenProps { + selectedServer: SelectedServer; +} + +const MenuLayout = ( + TagsList: FC, + ShortUrls: FC, + AsideMenu: FC, + CreateShortUrl: FC, + ShortUrlVisits: FC, + TagVisits: FC, + ShlinkVersions: FC, + ServerError: FC, +) => withSelectedServer(({ location, selectedServer }: MenuLayoutProps) => { + const [ sidebarVisible, toggleSidebar, showSidebar, hideSidebar ] = useToggle(); + + useEffect(() => hideSidebar(), [ location ]); + + if (!isReachableServer(selectedServer)) { + return ; + } + + const addTagsVisitsRoute = versionMatch(selectedServer.version, { minVersion: '2.2.0' }); + const burgerClasses = classNames('menu-layout__burger-icon', { + 'menu-layout__burger-icon--active': sidebarVisible, + }); + const swipeMenuIfNoModalExists = (callback: () => void) => (e: EventData) => { + const swippedOnVisitsTable = (e.event.composedPath() as HTMLElement[]).some( + ({ classList }) => classList?.contains('visits-table'), + ); + + if (swippedOnVisitsTable || document.querySelector('.modal')) { + return; + } + + callback(); + }; + + return ( + + + + +
+ +
hideSidebar()}> +
+ + + + + {addTagsVisitsRoute && } + + List short URLs} + /> + +
+ +
+ +
+
+
+
+
+ ); +}, ServerError); + +export default MenuLayout; diff --git a/src/container/types.ts b/src/container/types.ts index 9afa2de0..8b04e6fb 100644 --- a/src/container/types.ts +++ b/src/container/types.ts @@ -1,6 +1,5 @@ import { MercureInfo } from '../mercure/reducers/mercureInfo'; -import { ServersMap } from '../servers/reducers/servers'; -import { SelectedServer } from '../servers/data'; +import { SelectedServer, ServersMap } from '../servers/data'; import { Settings } from '../settings/reducers/settings'; import { ShortUrlMetaEdition } from '../short-urls/reducers/shortUrlMeta'; import { ShortUrlCreation } from '../short-urls/reducers/shortUrlCreation'; diff --git a/src/servers/data/index.ts b/src/servers/data/index.ts index 3c918cbb..61f86009 100644 --- a/src/servers/data/index.ts +++ b/src/servers/data/index.ts @@ -25,8 +25,13 @@ export type RegularServer = ReachableServer | NonReachableServer; export type SelectedServer = RegularServer | NotFoundServer | null; +export type ServersMap = Record; + export const hasServerData = (server: ServerData | NotFoundServer | null): server is ServerData => !!(server as ServerData)?.url && !!(server as ServerData)?.apiKey; export const isReachableServer = (server: SelectedServer): server is ReachableServer => !!server?.hasOwnProperty('printableVersion'); + +export const isServerWithId = (server: SelectedServer | ServerWithId): server is ServerWithId => + !!server?.hasOwnProperty('id'); diff --git a/src/servers/helpers/ServerError.js b/src/servers/helpers/ServerError.js deleted file mode 100644 index 14983987..00000000 --- a/src/servers/helpers/ServerError.js +++ /dev/null @@ -1,50 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { Link } from 'react-router-dom'; -import Message from '../../utils/Message'; -import ServersListGroup from '../ServersListGroup'; -import { serverType } from '../prop-types'; -import './ServerError.scss'; - -const propTypes = { - servers: PropTypes.object, - selectedServer: serverType, - type: PropTypes.oneOf([ 'not-found', 'not-reachable' ]).isRequired, -}; - -export const ServerError = (DeleteServerButton) => { - const ServerErrorComp = ({ type, servers, selectedServer }) => ( -
-
- - {type === 'not-found' && 'Could not find this Shlink server.'} - {type === 'not-reachable' && ( - -

Oops! Could not connect to this Shlink server.

- Make sure you have internet connection, and the server is properly configured and on-line. -
- )} -
-
- - - These are the Shlink servers currently configured. Choose one of - them or add a new one. - - - {type === 'not-reachable' && ( -
-
- Alternatively, if you think you may have miss-configured this server, you - can remove it or  - edit it. -
-
- )} -
- ); - - ServerErrorComp.propTypes = propTypes; - - return ServerErrorComp; -}; diff --git a/src/servers/helpers/ServerError.tsx b/src/servers/helpers/ServerError.tsx new file mode 100644 index 00000000..f4d1dc0f --- /dev/null +++ b/src/servers/helpers/ServerError.tsx @@ -0,0 +1,45 @@ +import React, { FC } from 'react'; +import { Link } from 'react-router-dom'; +import Message from '../../utils/Message'; +import ServersListGroup from '../ServersListGroup'; +import { DeleteServerButtonProps } from '../DeleteServerButton'; +import { isServerWithId, SelectedServer, ServersMap } from '../data'; +import './ServerError.scss'; + +interface ServerErrorProps { + servers: ServersMap; + selectedServer: SelectedServer; +} + +export const ServerError = (DeleteServerButton: FC): FC => ( + { servers, selectedServer }, +) => ( +
+
+ + {!isServerWithId(selectedServer) && 'Could not find this Shlink server.'} + {isServerWithId(selectedServer) && ( + +

Oops! Could not connect to this Shlink server.

+ Make sure you have internet connection, and the server is properly configured and on-line. +
+ )} +
+
+ + + These are the Shlink servers currently configured. Choose one of + them or add a new one. + + + {isServerWithId(selectedServer) && ( +
+
+ Alternatively, if you think you may have miss-configured this server, you + can remove it or  + edit it. +
+
+ )} +
+); diff --git a/src/servers/helpers/withSelectedServer.js b/src/servers/helpers/withSelectedServer.js index 3c524e61..737f9894 100644 --- a/src/servers/helpers/withSelectedServer.js +++ b/src/servers/helpers/withSelectedServer.js @@ -23,7 +23,7 @@ export const withSelectedServer = (WrappedComponent, ServerError) => { } if (selectedServer.serverNotFound) { - return ; + return ; } return ; diff --git a/src/servers/reducers/servers.ts b/src/servers/reducers/servers.ts index 2312b0e5..057f5531 100644 --- a/src/servers/reducers/servers.ts +++ b/src/servers/reducers/servers.ts @@ -1,7 +1,7 @@ -import { pipe, assoc, map, reduce, dissoc } from 'ramda'; +import { assoc, dissoc, map, pipe, reduce } from 'ramda'; import { v4 as uuid } from 'uuid'; import { Action } from 'redux'; -import { ServerData, ServerWithId } from '../data'; +import { ServerData, ServersMap, ServerWithId } from '../data'; import { buildReducer } from '../../utils/helpers/redux'; /* eslint-disable padding-line-between-statements */ @@ -10,8 +10,6 @@ export const DELETE_SERVER = 'shlink/servers/DELETE_SERVER'; export const CREATE_SERVERS = 'shlink/servers/CREATE_SERVERS'; /* eslint-enable padding-line-between-statements */ -export type ServersMap = Record; - export interface CreateServersAction extends Action { newServers: ServersMap; } diff --git a/src/visits/ShortUrlVisitsHeader.js b/src/visits/ShortUrlVisitsHeader.js index a3b100a3..1d901cbc 100644 --- a/src/visits/ShortUrlVisitsHeader.js +++ b/src/visits/ShortUrlVisitsHeader.js @@ -20,7 +20,7 @@ const ShortUrlVisitsHeader = ({ shortUrlDetail, shortUrlVisits, goBack }) => { const shortLink = shortUrl && shortUrl.shortUrl ? shortUrl.shortUrl : ''; const longLink = shortUrl && shortUrl.longUrl ? shortUrl.longUrl : ''; - const renderDate = () => ( + const renderDate = () => !shortUrl ? Loading... : ( {shortUrl.dateCreated} diff --git a/test/common/ErrorHandler.test.js b/test/common/ErrorHandler.test.tsx similarity index 81% rename from test/common/ErrorHandler.test.js rename to test/common/ErrorHandler.test.tsx index a98437d0..06402637 100644 --- a/test/common/ErrorHandler.test.js +++ b/test/common/ErrorHandler.test.tsx @@ -1,16 +1,17 @@ import React from 'react'; -import { shallow } from 'enzyme'; +import { shallow, ShallowWrapper } from 'enzyme'; import { Button } from 'reactstrap'; +import { Mock } from 'ts-mockery'; import createErrorHandler from '../../src/common/ErrorHandler'; describe('', () => { - const window = { + const window = Mock.of({ location: { reload: jest.fn(), }, - }; - const console = { error: jest.fn() }; - let wrapper; + }); + const console = Mock.of({ error: jest.fn() }); + let wrapper: ShallowWrapper; beforeEach(() => { const ErrorHandler = createErrorHandler(window, console); diff --git a/test/servers/helpers/ServerError.test.js b/test/servers/helpers/ServerError.test.tsx similarity index 70% rename from test/servers/helpers/ServerError.test.js rename to test/servers/helpers/ServerError.test.tsx index 0847afe2..bd31dbf6 100644 --- a/test/servers/helpers/ServerError.test.js +++ b/test/servers/helpers/ServerError.test.tsx @@ -1,18 +1,19 @@ import React from 'react'; -import { shallow } from 'enzyme'; +import { shallow, ShallowWrapper } from 'enzyme'; import { BrowserRouter } from 'react-router-dom'; +import { Mock } from 'ts-mockery'; import { ServerError as createServerError } from '../../../src/servers/helpers/ServerError'; +import { NonReachableServer, NotFoundServer } from '../../../src/servers/data'; describe('', () => { - let wrapper; - const selectedServer = { id: '' }; - const ServerError = createServerError(() => ''); + let wrapper: ShallowWrapper; + const ServerError = createServerError(() => null); - afterEach(() => wrapper && wrapper.unmount()); + afterEach(() => wrapper?.unmount()); it.each([ [ - 'not-found', + Mock.all(), { 'Could not find this Shlink server.': true, 'Oops! Could not connect to this Shlink server.': false, @@ -21,7 +22,7 @@ describe('', () => { }, ], [ - 'not-reachable', + Mock.of({ id: 'abc123' }), { 'Could not find this Shlink server.': false, 'Oops! Could not connect to this Shlink server.': true, @@ -29,10 +30,10 @@ describe('', () => { 'Alternatively, if you think you may have miss-configured this server': true, }, ], - ])('renders expected information for type "%s"', (type, textsToFind) => { + ])('renders expected information based on provided server type', (selectedServer, textsToFind) => { wrapper = shallow( - + , ); const wrapperText = wrapper.html(); From aee4c2d02f7b89f41342e36f60ab1a12f79e3bce Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 29 Aug 2020 13:51:53 +0200 Subject: [PATCH 34/59] Migrated to TS all servers helpers --- jest.config.js | 8 ++--- shlink-web-client.d.ts | 10 +++--- src/container/store.ts | 2 +- src/mercure/helpers/index.js | 28 --------------- src/mercure/helpers/index.ts | 34 ++++++++++++++++++ src/servers/helpers/ForServerVersion.js | 30 ---------------- src/servers/helpers/ForServerVersion.tsx | 24 +++++++++++++ .../helpers/{ServerForm.js => ServerForm.tsx} | 21 ++++------- src/servers/helpers/withSelectedServer.js | 35 ------------------- src/servers/helpers/withSelectedServer.tsx | 29 +++++++++++++++ .../helpers/{index.test.js => index.test.tsx} | 17 +++++---- test/servers/EditServer.test.js | 2 ++ ...sion.test.js => ForServerVersion.test.tsx} | 16 +++++---- ...ServerForm.test.js => ServerForm.test.tsx} | 10 +++--- 14 files changed, 129 insertions(+), 137 deletions(-) delete mode 100644 src/mercure/helpers/index.js create mode 100644 src/mercure/helpers/index.ts delete mode 100644 src/servers/helpers/ForServerVersion.js create mode 100644 src/servers/helpers/ForServerVersion.tsx rename src/servers/helpers/{ServerForm.js => ServerForm.tsx} (67%) delete mode 100644 src/servers/helpers/withSelectedServer.js create mode 100644 src/servers/helpers/withSelectedServer.tsx rename test/mercure/helpers/{index.test.js => index.test.tsx} (65%) rename test/servers/helpers/{ForServerVersion.test.js => ForServerVersion.test.tsx} (64%) rename test/servers/helpers/{ServerForm.test.js => ServerForm.test.tsx} (84%) diff --git a/jest.config.js b/jest.config.js index 0387f86d..1460edba 100644 --- a/jest.config.js +++ b/jest.config.js @@ -3,10 +3,10 @@ module.exports = { collectCoverageFrom: [ 'src/**/*.{js,jsx,ts,tsx}', '!src/registerServiceWorker.js', - '!src/index.js', - '!src/reducers/index.js', - '!src/**/provideServices.js', - '!src/container/*.js', + '!src/index.ts', + '!src/reducers/index.ts', + '!src/**/provideServices.ts', + '!src/container/*.ts', ], resolver: 'jest-pnp-resolver', setupFiles: [ diff --git a/shlink-web-client.d.ts b/shlink-web-client.d.ts index 52526860..06f04f6c 100644 --- a/shlink-web-client.d.ts +++ b/shlink-web-client.d.ts @@ -1,7 +1,5 @@ -export declare global { - declare module '*.png' - - interface Window { - __REDUX_DEVTOOLS_EXTENSION_COMPOSE__?: Function; - } +declare module 'event-source-polyfill' { + export const EventSourcePolyfill: any; } + +declare module '*.png' diff --git a/src/container/store.ts b/src/container/store.ts index 70ea604e..3196d6c2 100644 --- a/src/container/store.ts +++ b/src/container/store.ts @@ -4,7 +4,7 @@ import { save, load, RLSOptions } from 'redux-localstorage-simple'; import reducers from '../reducers'; const isProduction = process.env.NODE_ENV !== 'production'; -const composeEnhancers: Function = !isProduction && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; +const composeEnhancers: Function = !isProduction && (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; const localStorageConfig: RLSOptions = { states: [ 'settings', 'servers' ], diff --git a/src/mercure/helpers/index.js b/src/mercure/helpers/index.js deleted file mode 100644 index ac824cc5..00000000 --- a/src/mercure/helpers/index.js +++ /dev/null @@ -1,28 +0,0 @@ -import { useEffect } from 'react'; -import { EventSourcePolyfill as EventSource } from 'event-source-polyfill'; - -export const bindToMercureTopic = (mercureInfo, topic, onMessage, onTokenExpired) => () => { - const { mercureHubUrl, token, loading, error } = mercureInfo; - - if (loading || error) { - return undefined; - } - - const hubUrl = new URL(mercureHubUrl); - - hubUrl.searchParams.append('topic', topic); - const es = new EventSource(hubUrl, { - headers: { - Authorization: `Bearer ${token}`, - }, - }); - - es.onmessage = ({ data }) => onMessage(JSON.parse(data)); - es.onerror = ({ status }) => status === 401 && onTokenExpired(); - - return () => es.close(); -}; - -export const useMercureTopicBinding = (mercureInfo, topic, onMessage, onTokenExpired) => { - useEffect(bindToMercureTopic(mercureInfo, topic, onMessage, onTokenExpired), [ mercureInfo ]); -}; diff --git a/src/mercure/helpers/index.ts b/src/mercure/helpers/index.ts new file mode 100644 index 00000000..8f6eec78 --- /dev/null +++ b/src/mercure/helpers/index.ts @@ -0,0 +1,34 @@ +import { useEffect } from 'react'; +import { EventSourcePolyfill as EventSource } from 'event-source-polyfill'; +import { MercureInfo } from '../reducers/mercureInfo'; + +export const bindToMercureTopic = (mercureInfo: MercureInfo, topic: string, onMessage: (message: T) => void, onTokenExpired: Function) => () => { // eslint-disable-line max-len + const { mercureHubUrl, token, loading, error } = mercureInfo; + + if (loading || error || !mercureHubUrl) { + return undefined; + } + + const hubUrl = new URL(mercureHubUrl); + + hubUrl.searchParams.append('topic', topic); + const es = new EventSource(hubUrl, { + headers: { + Authorization: `Bearer ${token}`, + }, + }); + + es.onmessage = ({ data }: { data: string }) => onMessage(JSON.parse(data) as T); + es.onerror = ({ status }: { status: number }) => status === 401 && onTokenExpired(); + + return () => es.close(); +}; + +export const useMercureTopicBinding = ( + mercureInfo: MercureInfo, + topic: string, + onMessage: (message: T) => void, + onTokenExpired: Function, +) => { + useEffect(bindToMercureTopic(mercureInfo, topic, onMessage, onTokenExpired), [ mercureInfo ]); +}; diff --git a/src/servers/helpers/ForServerVersion.js b/src/servers/helpers/ForServerVersion.js deleted file mode 100644 index 0b3c6fba..00000000 --- a/src/servers/helpers/ForServerVersion.js +++ /dev/null @@ -1,30 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { serverType } from '../prop-types'; -import { versionMatch } from '../../utils/helpers/version'; - -const propTypes = { - minVersion: PropTypes.string, - maxVersion: PropTypes.string, - selectedServer: serverType, - children: PropTypes.node.isRequired, -}; - -const ForServerVersion = ({ minVersion, maxVersion, selectedServer, children }) => { - if (!selectedServer) { - return null; - } - - const { version } = selectedServer; - const matchesVersion = versionMatch(version, { maxVersion, minVersion }); - - if (!matchesVersion) { - return null; - } - - return {children}; -}; - -ForServerVersion.propTypes = propTypes; - -export default ForServerVersion; diff --git a/src/servers/helpers/ForServerVersion.tsx b/src/servers/helpers/ForServerVersion.tsx new file mode 100644 index 00000000..3fe61e1a --- /dev/null +++ b/src/servers/helpers/ForServerVersion.tsx @@ -0,0 +1,24 @@ +import React, { FC } from 'react'; +import { versionMatch, Versions } from '../../utils/helpers/version'; +import { isReachableServer, SelectedServer } from '../data'; + +interface ForServerVersionProps extends Versions { + selectedServer: SelectedServer; +} + +const ForServerVersion: FC = ({ minVersion, maxVersion, selectedServer, children }) => { + if (!isReachableServer(selectedServer)) { + return null; + } + + const { version } = selectedServer; + const matchesVersion = versionMatch(version, { maxVersion, minVersion }); + + if (!matchesVersion) { + return null; + } + + return {children}; +}; + +export default ForServerVersion; diff --git a/src/servers/helpers/ServerForm.js b/src/servers/helpers/ServerForm.tsx similarity index 67% rename from src/servers/helpers/ServerForm.js rename to src/servers/helpers/ServerForm.tsx index 03c60868..cf9afa1f 100644 --- a/src/servers/helpers/ServerForm.js +++ b/src/servers/helpers/ServerForm.tsx @@ -1,19 +1,14 @@ -import React, { useEffect, useState } from 'react'; -import PropTypes from 'prop-types'; +import React, { FC, useEffect, useState } from 'react'; import { HorizontalFormGroup } from '../../utils/HorizontalFormGroup'; import { handleEventPreventingDefault } from '../../utils/utils'; +import { ServerData } from '../data'; -const propTypes = { - onSubmit: PropTypes.func.isRequired, - initialValues: PropTypes.shape({ - name: PropTypes.string.isRequired, - url: PropTypes.string.isRequired, - apiKey: PropTypes.string.isRequired, - }), - children: PropTypes.node.isRequired, -}; +interface ServerFormProps { + onSubmit: (server: ServerData) => void; + initialValues?: ServerData; +} -export const ServerForm = ({ onSubmit, initialValues, children }) => { +export const ServerForm: FC = ({ onSubmit, initialValues, children }) => { const [ name, setName ] = useState(''); const [ url, setUrl ] = useState(''); const [ apiKey, setApiKey ] = useState(''); @@ -35,5 +30,3 @@ export const ServerForm = ({ onSubmit, initialValues, children }) => { ); }; - -ServerForm.propTypes = propTypes; diff --git a/src/servers/helpers/withSelectedServer.js b/src/servers/helpers/withSelectedServer.js deleted file mode 100644 index 737f9894..00000000 --- a/src/servers/helpers/withSelectedServer.js +++ /dev/null @@ -1,35 +0,0 @@ -import React, { useEffect } from 'react'; -import PropTypes from 'prop-types'; -import Message from '../../utils/Message'; -import { serverType } from '../prop-types'; - -const propTypes = { - selectServer: PropTypes.func, - selectedServer: serverType, - match: PropTypes.object, -}; - -export const withSelectedServer = (WrappedComponent, ServerError) => { - const Component = (props) => { - const { selectServer, selectedServer, match } = props; - const { params: { serverId } } = match; - - useEffect(() => { - selectServer(serverId); - }, [ serverId ]); - - if (!selectedServer) { - return ; - } - - if (selectedServer.serverNotFound) { - return ; - } - - return ; - }; - - Component.propTypes = propTypes; - - return Component; -}; diff --git a/src/servers/helpers/withSelectedServer.tsx b/src/servers/helpers/withSelectedServer.tsx new file mode 100644 index 00000000..2215ded3 --- /dev/null +++ b/src/servers/helpers/withSelectedServer.tsx @@ -0,0 +1,29 @@ +import React, { FC, useEffect } from 'react'; +import { RouteChildrenProps } from 'react-router'; +import Message from '../../utils/Message'; +import { isReachableServer, SelectedServer } from '../data'; + +interface WithSelectedServerProps extends RouteChildrenProps<{ serverId: string }> { + selectServer: (serverId: string) => void; + selectedServer: SelectedServer; +} + +export const withSelectedServer = (WrappedComponent: FC, ServerError: FC) => ( + props: WithSelectedServerProps, +) => { + const { selectServer, selectedServer, match } = props; + + useEffect(() => { + match?.params?.serverId && selectServer(match?.params.serverId); + }, [ match?.params.serverId ]); + + if (!selectedServer) { + return ; + } + + if (!isReachableServer(selectedServer)) { + return ; + } + + return ; +}; diff --git a/test/mercure/helpers/index.test.js b/test/mercure/helpers/index.test.tsx similarity index 65% rename from test/mercure/helpers/index.test.js rename to test/mercure/helpers/index.test.tsx index 1e6fd3df..3a7f897f 100644 --- a/test/mercure/helpers/index.test.js +++ b/test/mercure/helpers/index.test.tsx @@ -1,5 +1,8 @@ import { EventSourcePolyfill as EventSource } from 'event-source-polyfill'; +import { Mock } from 'ts-mockery'; +import { identity } from 'ramda'; import { bindToMercureTopic } from '../../../src/mercure/helpers'; +import { MercureInfo } from '../../../src/mercure/reducers/mercureInfo'; jest.mock('event-source-polyfill'); @@ -11,11 +14,13 @@ describe('helpers', () => { const onTokenExpired = jest.fn(); it.each([ - [{ loading: true, error: false }], - [{ loading: false, error: true }], - [{ loading: true, error: true }], - ])('does not bind an EventSource when loading or error', (mercureInfo) => { - bindToMercureTopic(mercureInfo)(); + [ Mock.of({ loading: true, error: false, mercureHubUrl: 'foo' }) ], + [ Mock.of({ loading: false, error: true, mercureHubUrl: 'foo' }) ], + [ Mock.of({ loading: true, error: true, mercureHubUrl: 'foo' }) ], + [ Mock.of({ loading: false, error: false, mercureHubUrl: undefined }) ], + [ Mock.of({ loading: true, error: true, mercureHubUrl: undefined }) ], + ])('does not bind an EventSource when loading, error or no hub URL', (mercureInfo) => { + bindToMercureTopic(mercureInfo, '', identity, identity)(); expect(EventSource).not.toHaveBeenCalled(); expect(onMessage).not.toHaveBeenCalled(); @@ -50,7 +55,7 @@ describe('helpers', () => { expect(onMessage).toHaveBeenCalledWith({ foo: 'bar' }); expect(onTokenExpired).toHaveBeenCalled(); - callback(); + callback?.(); expect(es.close).toHaveBeenCalled(); }); }); diff --git a/test/servers/EditServer.test.js b/test/servers/EditServer.test.js index 366dd24f..0dd0f155 100644 --- a/test/servers/EditServer.test.js +++ b/test/servers/EditServer.test.js @@ -16,6 +16,8 @@ describe('', () => { name: 'name', url: 'url', apiKey: 'apiKey', + printableVersion: 'v1.2.0', + version: '1.2.0' }; beforeEach(() => { diff --git a/test/servers/helpers/ForServerVersion.test.js b/test/servers/helpers/ForServerVersion.test.tsx similarity index 64% rename from test/servers/helpers/ForServerVersion.test.js rename to test/servers/helpers/ForServerVersion.test.tsx index 171d2feb..daaadc24 100644 --- a/test/servers/helpers/ForServerVersion.test.js +++ b/test/servers/helpers/ForServerVersion.test.tsx @@ -1,11 +1,13 @@ import React from 'react'; -import { mount } from 'enzyme'; +import { mount, ReactWrapper } from 'enzyme'; +import { Mock } from 'ts-mockery'; import ForServerVersion from '../../../src/servers/helpers/ForServerVersion'; +import { ReachableServer, SelectedServer } from '../../../src/servers/data'; describe('', () => { - let wrapped; + let wrapped: ReactWrapper; - const renderComponent = (minVersion, maxVersion, selectedServer) => { + const renderComponent = (selectedServer: SelectedServer, minVersion?: string, maxVersion?: string) => { wrapped = mount( Hello @@ -15,10 +17,10 @@ describe('', () => { return wrapped; }; - afterEach(() => wrapped && wrapped.unmount()); + afterEach(() => wrapped?.unmount()); it('does not render children when current server is empty', () => { - const wrapped = renderComponent('1'); + const wrapped = renderComponent(null, '1'); expect(wrapped.html()).toBeNull(); }); @@ -28,7 +30,7 @@ describe('', () => { [ undefined, '1.8.0', '1.8.3' ], [ '1.7.0', '1.8.0', '1.8.3' ], ])('does not render children when current version does not match requirements', (min, max, version) => { - const wrapped = renderComponent(min, max, { version }); + const wrapped = renderComponent(Mock.of({ version, printableVersion: version }), min, max); expect(wrapped.html()).toBeNull(); }); @@ -40,7 +42,7 @@ describe('', () => { [ undefined, '1.8.0', '1.7.1' ], [ '1.7.0', '1.8.0', '1.7.3' ], ])('renders children when current version matches requirements', (min, max, version) => { - const wrapped = renderComponent(min, max, { version }); + const wrapped = renderComponent(Mock.of({ version, printableVersion: version }), min, max); expect(wrapped.html()).toContain('Hello'); }); diff --git a/test/servers/helpers/ServerForm.test.js b/test/servers/helpers/ServerForm.test.tsx similarity index 84% rename from test/servers/helpers/ServerForm.test.js rename to test/servers/helpers/ServerForm.test.tsx index 855e8c1e..52020f5c 100644 --- a/test/servers/helpers/ServerForm.test.js +++ b/test/servers/helpers/ServerForm.test.tsx @@ -1,20 +1,18 @@ import React from 'react'; -import { shallow } from 'enzyme'; +import { shallow, ShallowWrapper } from 'enzyme'; import { ServerForm } from '../../../src/servers/helpers/ServerForm'; import { HorizontalFormGroup } from '../../../src/utils/HorizontalFormGroup'; describe('', () => { - let wrapper; + let wrapper: ShallowWrapper; const onSubmit = jest.fn(); beforeEach(() => { wrapper = shallow(Something); }); - afterEach(() => { - jest.resetAllMocks(); - wrapper && wrapper.unmount(); - }); + afterEach(() => wrapper?.unmount()); + afterEach(jest.resetAllMocks); it('renders components', () => { expect(wrapper.find(HorizontalFormGroup)).toHaveLength(3); From 64a968711c33d7a5e5ce4003239afd88ae94dfc3 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 29 Aug 2020 14:16:37 +0200 Subject: [PATCH 35/59] Migrated all servers services to TS --- csvjson.d.ts | 5 --- shlink-web-client.d.ts | 7 ++++ src/container/index.ts | 2 +- src/container/types.ts | 2 +- ...{ServersExporter.js => ServersExporter.ts} | 19 ++++++---- src/servers/services/ServersImporter.ts | 2 +- ...{provideServices.js => provideServices.ts} | 4 +- src/utils/services/LocalStorage.ts | 14 +++++++ src/utils/services/Storage.js | 16 -------- src/utils/services/provideServices.ts | 4 +- ...porter.test.js => ServersExporter.test.ts} | 38 +++++++++++-------- test/utils/services/Storage.test.js | 6 +-- 12 files changed, 66 insertions(+), 53 deletions(-) delete mode 100644 csvjson.d.ts rename src/servers/services/{ServersExporter.js => ServersExporter.ts} (66%) rename src/servers/services/{provideServices.js => provideServices.ts} (93%) create mode 100644 src/utils/services/LocalStorage.ts delete mode 100644 src/utils/services/Storage.js rename test/servers/services/{ServersExporter.test.js => ServersExporter.test.ts} (60%) diff --git a/csvjson.d.ts b/csvjson.d.ts deleted file mode 100644 index dbe9abde..00000000 --- a/csvjson.d.ts +++ /dev/null @@ -1,5 +0,0 @@ -declare module 'csvjson' { - export declare class CsvJson { - public toObject(content: string): T[]; - } -} diff --git a/shlink-web-client.d.ts b/shlink-web-client.d.ts index 06f04f6c..10678f09 100644 --- a/shlink-web-client.d.ts +++ b/shlink-web-client.d.ts @@ -2,4 +2,11 @@ declare module 'event-source-polyfill' { export const EventSourcePolyfill: any; } +declare module 'csvjson' { + export declare class CsvJson { + public toObject(content: string): T[]; + public toCSV(data: T[], options: { headers: string }): string; + } +} + declare module '*.png' diff --git a/src/container/index.ts b/src/container/index.ts index 9b9a7c14..22271324 100644 --- a/src/container/index.ts +++ b/src/container/index.ts @@ -24,7 +24,7 @@ const mapActionService = (map: ActionMap, actionName: string): ActionMap => ({ // Wrap actual action service in a function so that it is lazily created the first time it is called [actionName]: lazyService(container, actionName), }); -const connect: ConnectDecorator = (propsFromState: string[], actionServiceNames: string[] = []) => +const connect: ConnectDecorator = (propsFromState: string[] | null, actionServiceNames: string[] = []) => reduxConnect( propsFromState ? pick(propsFromState) : null, actionServiceNames.reduce(mapActionService, {}), diff --git a/src/container/types.ts b/src/container/types.ts index 8b04e6fb..a9ebbde3 100644 --- a/src/container/types.ts +++ b/src/container/types.ts @@ -35,6 +35,6 @@ export interface ShlinkState { settings: Settings; } -export type ConnectDecorator = (props: string[], actions?: string[]) => any; +export type ConnectDecorator = (props: string[] | null, actions?: string[]) => any; export type GetState = () => ShlinkState; diff --git a/src/servers/services/ServersExporter.js b/src/servers/services/ServersExporter.ts similarity index 66% rename from src/servers/services/ServersExporter.js rename to src/servers/services/ServersExporter.ts index 01a33316..fb610276 100644 --- a/src/servers/services/ServersExporter.js +++ b/src/servers/services/ServersExporter.ts @@ -1,6 +1,9 @@ import { dissoc, head, keys, values } from 'ramda'; +import { CsvJson } from 'csvjson'; +import LocalStorage from '../../utils/services/LocalStorage'; +import { ServersMap } from '../data'; -const saveCsv = (window, csv) => { +const saveCsv = (window: Window, csv: string) => { const { navigator, document } = window; const filename = 'shlink-servers.csv'; const blob = new Blob([ csv ], { type: 'text/csv;charset=utf-8;' }); @@ -25,14 +28,14 @@ const saveCsv = (window, csv) => { }; export default class ServersExporter { - constructor(storage, window, csvjson) { - this.storage = storage; - this.window = window; - this.csvjson = csvjson; - } + public constructor( + private readonly storage: LocalStorage, + private readonly window: Window, + private readonly csvjson: CsvJson, + ) {} - exportServers = async () => { - const servers = values(this.storage.get('servers') || {}).map(dissoc('id')); + public readonly exportServers = async () => { + const servers = values(this.storage.get('servers') || {}).map(dissoc('id')); try { const csv = this.csvjson.toCSV(servers, { diff --git a/src/servers/services/ServersImporter.ts b/src/servers/services/ServersImporter.ts index 8876d3fd..62de8f53 100644 --- a/src/servers/services/ServersImporter.ts +++ b/src/servers/services/ServersImporter.ts @@ -6,7 +6,7 @@ const CSV_MIME_TYPE = 'text/csv'; export default class ServersImporter { public constructor(private readonly csvjson: CsvJson, private readonly fileReaderFactory: () => FileReader) {} - public importServersFromFile = async (file?: File | null): Promise => { + public readonly importServersFromFile = async (file?: File | null): Promise => { if (!file || file.type !== CSV_MIME_TYPE) { throw new Error('No file provided or file is not a CSV'); } diff --git a/src/servers/services/provideServices.js b/src/servers/services/provideServices.ts similarity index 93% rename from src/servers/services/provideServices.js rename to src/servers/services/provideServices.ts index 7b8e7d99..b71bab9c 100644 --- a/src/servers/services/provideServices.js +++ b/src/servers/services/provideServices.ts @@ -1,4 +1,5 @@ import csvjson from 'csvjson'; +import Bottle, { Decorator } from 'bottlejs'; import CreateServer from '../CreateServer'; import ServersDropdown from '../ServersDropdown'; import DeleteServerModal from '../DeleteServerModal'; @@ -10,10 +11,11 @@ import { createServer, createServers, deleteServer, editServer } from '../reduce import { fetchServers } from '../reducers/remoteServers'; import ForServerVersion from '../helpers/ForServerVersion'; import { ServerError } from '../helpers/ServerError'; +import { ConnectDecorator } from '../../container/types'; import ServersImporter from './ServersImporter'; import ServersExporter from './ServersExporter'; -const provideServices = (bottle, connect, withRouter) => { +const provideServices = (bottle: Bottle, connect: ConnectDecorator, withRouter: Decorator) => { // Components bottle.serviceFactory('CreateServer', CreateServer, 'ImportServersBtn', 'useStateFlagTimeout'); bottle.decorator('CreateServer', connect([ 'selectedServer' ], [ 'createServer', 'resetSelectedServer' ])); diff --git a/src/utils/services/LocalStorage.ts b/src/utils/services/LocalStorage.ts new file mode 100644 index 00000000..20bbb491 --- /dev/null +++ b/src/utils/services/LocalStorage.ts @@ -0,0 +1,14 @@ +const PREFIX = 'shlink'; +const buildPath = (path: string) => `${PREFIX}.${path}`; + +export default class LocalStorage { + public constructor(private readonly localStorage: Storage) {} + + public readonly get = (key: string): T => { + const item = this.localStorage.getItem(buildPath(key)); + + return item ? JSON.parse(item) : undefined; + }; + + public readonly set = (key: string, value: any) => this.localStorage.setItem(buildPath(key), JSON.stringify(value)); +} diff --git a/src/utils/services/Storage.js b/src/utils/services/Storage.js deleted file mode 100644 index 35f9eb74..00000000 --- a/src/utils/services/Storage.js +++ /dev/null @@ -1,16 +0,0 @@ -const PREFIX = 'shlink'; -const buildPath = (path) => `${PREFIX}.${path}`; - -export default class Storage { - constructor(localStorage) { - this.localStorage = localStorage; - } - - get = (key) => { - const item = this.localStorage.getItem(buildPath(key)); - - return item ? JSON.parse(item) : undefined; - }; - - set = (key, value) => this.localStorage.setItem(buildPath(key), JSON.stringify(value)); -} diff --git a/src/utils/services/provideServices.ts b/src/utils/services/provideServices.ts index 458cb680..34574b6e 100644 --- a/src/utils/services/provideServices.ts +++ b/src/utils/services/provideServices.ts @@ -1,13 +1,13 @@ import axios from 'axios'; import Bottle from 'bottlejs'; import { useStateFlagTimeout } from '../helpers/hooks'; -import Storage from './Storage'; +import LocalStorage from './LocalStorage'; import ColorGenerator from './ColorGenerator'; import buildShlinkApiClient from './ShlinkApiClientBuilder'; const provideServices = (bottle: Bottle) => { bottle.constant('localStorage', (global as any).localStorage); - bottle.service('Storage', Storage, 'localStorage'); + bottle.service('Storage', LocalStorage, 'localStorage'); bottle.service('ColorGenerator', ColorGenerator, 'Storage'); bottle.constant('axios', axios); diff --git a/test/servers/services/ServersExporter.test.js b/test/servers/services/ServersExporter.test.ts similarity index 60% rename from test/servers/services/ServersExporter.test.js rename to test/servers/services/ServersExporter.test.ts index 1dc2156e..f4e689ff 100644 --- a/test/servers/services/ServersExporter.test.js +++ b/test/servers/services/ServersExporter.test.ts @@ -1,4 +1,7 @@ +import { Mock } from 'ts-mockery'; +import { CsvJson } from 'csvjson'; import ServersExporter from '../../../src/servers/services/ServersExporter'; +import LocalStorage from '../../../src/utils/services/LocalStorage'; describe('ServersExporter', () => { const createLinkMock = () => ({ @@ -6,7 +9,7 @@ describe('ServersExporter', () => { click: jest.fn(), style: {}, }); - const createWindowMock = (isIe10 = true) => ({ + const createWindowMock = (isIe10 = true) => Mock.of({ navigator: { msSaveBlob: isIe10 ? jest.fn() : undefined, }, @@ -18,7 +21,7 @@ describe('ServersExporter', () => { }, }, }); - const storageMock = { + const storageMock = Mock.of({ get: jest.fn(() => ({ abc123: { id: 'abc123', @@ -29,8 +32,8 @@ describe('ServersExporter', () => { name: 'bar', }, })), - }; - const createCsvjsonMock = (throwError = false) => ({ + }); + const createCsvjsonMock = (throwError = false) => Mock.of({ toCSV: jest.fn(() => { if (throwError) { throw new Error(''); @@ -41,13 +44,14 @@ describe('ServersExporter', () => { }); describe('exportServers', () => { - let originalConsole; + let originalConsole: Console; + const error = jest.fn(); beforeEach(() => { originalConsole = global.console; - global.console = { error: jest.fn() }; - global.Blob = class Blob {}; - global.URL = { createObjectURL: () => '' }; + global.console = Mock.of({ error }); + (global as any).Blob = class Blob {}; // eslint-disable-line @typescript-eslint/no-extraneous-class + (global as any).URL = { createObjectURL: () => '' }; }); afterEach(() => { global.console = originalConsole; @@ -57,34 +61,38 @@ describe('ServersExporter', () => { it('logs an error if something fails', () => { const csvjsonMock = createCsvjsonMock(true); const exporter = new ServersExporter(storageMock, createWindowMock(), csvjsonMock); + const { toCSV } = csvjsonMock; exporter.exportServers(); - expect(global.console.error).toHaveBeenCalledTimes(1); - expect(csvjsonMock.toCSV).toHaveBeenCalledTimes(1); + expect(error).toHaveBeenCalledTimes(1); + expect(toCSV).toHaveBeenCalledTimes(1); }); it('makes use of msSaveBlob API when available', () => { const windowMock = createWindowMock(); const exporter = new ServersExporter(storageMock, windowMock, createCsvjsonMock()); + const { navigator: { msSaveBlob }, document: { createElement } } = windowMock; exporter.exportServers(); expect(storageMock.get).toHaveBeenCalledTimes(1); - expect(windowMock.navigator.msSaveBlob).toHaveBeenCalledTimes(1); - expect(windowMock.document.createElement).not.toHaveBeenCalled(); + expect(msSaveBlob).toHaveBeenCalledTimes(1); + expect(createElement).not.toHaveBeenCalled(); }); it('makes use of download link API when available', () => { const windowMock = createWindowMock(false); const exporter = new ServersExporter(storageMock, windowMock, createCsvjsonMock()); + const { document: { createElement, body } } = windowMock; + const { appendChild, removeChild } = body; exporter.exportServers(); expect(storageMock.get).toHaveBeenCalledTimes(1); - expect(windowMock.document.createElement).toHaveBeenCalledTimes(1); - expect(windowMock.document.body.appendChild).toHaveBeenCalledTimes(1); - expect(windowMock.document.body.removeChild).toHaveBeenCalledTimes(1); + expect(createElement).toHaveBeenCalledTimes(1); + expect(appendChild).toHaveBeenCalledTimes(1); + expect(removeChild).toHaveBeenCalledTimes(1); }); }); }); diff --git a/test/utils/services/Storage.test.js b/test/utils/services/Storage.test.js index 290a5c09..ec427015 100644 --- a/test/utils/services/Storage.test.js +++ b/test/utils/services/Storage.test.js @@ -1,6 +1,6 @@ -import Storage from '../../../src/utils/services/Storage'; +import LocalStorage from '../../../src/utils/services/LocalStorage'; -describe('Storage', () => { +describe('LocalStorage', () => { const localStorageMock = { getItem: jest.fn((key) => key === 'shlink.foo' ? JSON.stringify({ foo: 'bar' }) : null), setItem: jest.fn(), @@ -11,7 +11,7 @@ describe('Storage', () => { localStorageMock.getItem.mockClear(); localStorageMock.setItem.mockReset(); - storage = new Storage(localStorageMock); + storage = new LocalStorage(localStorageMock); }); describe('set', () => { From ebd7a768963e5700ab9d7f9d0e77049df09138d4 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 29 Aug 2020 18:51:03 +0200 Subject: [PATCH 36/59] Migrated to TS main services except ShlinkApiClient --- src/servers/data/index.ts | 3 +++ src/servers/helpers/withSelectedServer.tsx | 4 ++-- .../{ColorGenerator.js => ColorGenerator.ts} | 18 +++++++++-------- test/servers/EditServer.test.js | 2 -- ...nerator.test.js => ColorGenerator.test.ts} | 12 +++++------ .../{Storage.test.js => Storage.test.ts} | 20 +++++++++---------- 6 files changed, 30 insertions(+), 29 deletions(-) rename src/utils/services/{ColorGenerator.js => ColorGenerator.ts} (56%) rename test/utils/services/{ColorGenerator.test.js => ColorGenerator.test.ts} (87%) rename test/utils/services/{Storage.test.js => Storage.test.ts} (64%) diff --git a/src/servers/data/index.ts b/src/servers/data/index.ts index 61f86009..0a89ae2c 100644 --- a/src/servers/data/index.ts +++ b/src/servers/data/index.ts @@ -35,3 +35,6 @@ export const isReachableServer = (server: SelectedServer): server is ReachableSe export const isServerWithId = (server: SelectedServer | ServerWithId): server is ServerWithId => !!server?.hasOwnProperty('id'); + +export const isNotFoundServer = (server: SelectedServer): server is NotFoundServer => + !!server?.hasOwnProperty('serverNotFound'); diff --git a/src/servers/helpers/withSelectedServer.tsx b/src/servers/helpers/withSelectedServer.tsx index 2215ded3..f308d6e0 100644 --- a/src/servers/helpers/withSelectedServer.tsx +++ b/src/servers/helpers/withSelectedServer.tsx @@ -1,7 +1,7 @@ import React, { FC, useEffect } from 'react'; import { RouteChildrenProps } from 'react-router'; import Message from '../../utils/Message'; -import { isReachableServer, SelectedServer } from '../data'; +import { isNotFoundServer, SelectedServer } from '../data'; interface WithSelectedServerProps extends RouteChildrenProps<{ serverId: string }> { selectServer: (serverId: string) => void; @@ -21,7 +21,7 @@ export const withSelectedServer = (WrappedComponent: FC return ; } - if (!isReachableServer(selectedServer)) { + if (isNotFoundServer(selectedServer)) { return ; } diff --git a/src/utils/services/ColorGenerator.js b/src/utils/services/ColorGenerator.ts similarity index 56% rename from src/utils/services/ColorGenerator.js rename to src/utils/services/ColorGenerator.ts index 1a15f2a4..5f79b0b8 100644 --- a/src/utils/services/ColorGenerator.js +++ b/src/utils/services/ColorGenerator.ts @@ -1,20 +1,21 @@ import PropTypes from 'prop-types'; import { rangeOf } from '../utils'; +import LocalStorage from './LocalStorage'; const HEX_COLOR_LENGTH = 6; const { floor, random } = Math; const letters = '0123456789ABCDEF'; -const buildRandomColor = () => - `#${rangeOf(HEX_COLOR_LENGTH, () => letters[floor(random() * letters.length)]).join('')}`; -const normalizeKey = (key) => key.toLowerCase().trim(); +const buildRandomColor = () => `#${rangeOf(HEX_COLOR_LENGTH, () => letters[floor(random() * letters.length)]).join('')}`; +const normalizeKey = (key: string) => key.toLowerCase().trim(); export default class ColorGenerator { - constructor(storage) { - this.storage = storage; - this.colors = this.storage.get('colors') || {}; + private readonly colors: Record; + + public constructor(private readonly storage: LocalStorage) { + this.colors = this.storage.get>('colors') || {}; } - getColorForKey = (key) => { + public readonly getColorForKey = (key: string) => { const normalizedKey = normalizeKey(key); const color = this.colors[normalizedKey]; @@ -26,7 +27,7 @@ export default class ColorGenerator { return color; }; - setColorForKey = (key, color) => { + public readonly setColorForKey = (key: string, color: string) => { const normalizedKey = normalizeKey(key); this.colors[normalizedKey] = color; @@ -36,6 +37,7 @@ export default class ColorGenerator { }; } +/** @deprecated Use ColorGenerator class instead */ export const colorGeneratorType = PropTypes.shape({ getColorForKey: PropTypes.func, setColorForKey: PropTypes.func, diff --git a/test/servers/EditServer.test.js b/test/servers/EditServer.test.js index 0dd0f155..366dd24f 100644 --- a/test/servers/EditServer.test.js +++ b/test/servers/EditServer.test.js @@ -16,8 +16,6 @@ describe('', () => { name: 'name', url: 'url', apiKey: 'apiKey', - printableVersion: 'v1.2.0', - version: '1.2.0' }; beforeEach(() => { diff --git a/test/utils/services/ColorGenerator.test.js b/test/utils/services/ColorGenerator.test.ts similarity index 87% rename from test/utils/services/ColorGenerator.test.js rename to test/utils/services/ColorGenerator.test.ts index 71546d17..990b268e 100644 --- a/test/utils/services/ColorGenerator.test.js +++ b/test/utils/services/ColorGenerator.test.ts @@ -1,16 +1,16 @@ +import { Mock } from 'ts-mockery'; import ColorGenerator from '../../../src/utils/services/ColorGenerator'; +import LocalStorage from '../../../src/utils/services/LocalStorage'; describe('ColorGenerator', () => { - let colorGenerator; - const storageMock = { + let colorGenerator: ColorGenerator; + const storageMock = Mock.of({ set: jest.fn(), get: jest.fn(), - }; + }); beforeEach(() => { - storageMock.set.mockReset(); - storageMock.get.mockReset(); - + jest.clearAllMocks(); colorGenerator = new ColorGenerator(storageMock); }); diff --git a/test/utils/services/Storage.test.js b/test/utils/services/Storage.test.ts similarity index 64% rename from test/utils/services/Storage.test.js rename to test/utils/services/Storage.test.ts index ec427015..e7de6771 100644 --- a/test/utils/services/Storage.test.js +++ b/test/utils/services/Storage.test.ts @@ -1,16 +1,14 @@ +import { Mock } from 'ts-mockery'; import LocalStorage from '../../../src/utils/services/LocalStorage'; describe('LocalStorage', () => { - const localStorageMock = { - getItem: jest.fn((key) => key === 'shlink.foo' ? JSON.stringify({ foo: 'bar' }) : null), - setItem: jest.fn(), - }; - let storage; + const getItem = jest.fn((key) => key === 'shlink.foo' ? JSON.stringify({ foo: 'bar' }) : null); + const setItem = jest.fn(); + const localStorageMock = Mock.of({ getItem, setItem }); + let storage: LocalStorage; beforeEach(() => { - localStorageMock.getItem.mockClear(); - localStorageMock.setItem.mockReset(); - + jest.clearAllMocks(); storage = new LocalStorage(localStorageMock); }); @@ -20,15 +18,15 @@ describe('LocalStorage', () => { storage.set('foo', value); - expect(localStorageMock.setItem).toHaveBeenCalledTimes(1); - expect(localStorageMock.setItem).toHaveBeenCalledWith('shlink.foo', JSON.stringify(value)); + expect(setItem).toHaveBeenCalledTimes(1); + expect(setItem).toHaveBeenCalledWith('shlink.foo', JSON.stringify(value)); }); }); describe('get', () => { it('fetches item from local storage', () => { storage.get('foo'); - expect(localStorageMock.getItem).toHaveBeenCalledTimes(1); + expect(getItem).toHaveBeenCalledTimes(1); }); it('returns parsed value when requested value is found in local storage', () => { From ef630af15490890ebe5dbb626ac8f5453b57933e Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 29 Aug 2020 19:51:14 +0200 Subject: [PATCH 37/59] Migrated ShlinkApiClient to TS --- package-lock.json | 6 + package.json | 1 + src/mercure/reducers/mercureInfo.ts | 2 +- .../reducers/shortUrlsListParams.ts | 2 +- src/utils/services/ShlinkApiClient.js | 97 ------------- src/utils/services/ShlinkApiClient.ts | 127 ++++++++++++++++++ src/utils/services/types.ts | 26 +++- ...Client.test.js => ShlinkApiClient.test.ts} | 67 ++++----- .../services/ShlinkApiClientBuilder.test.ts | 4 +- 9 files changed, 198 insertions(+), 134 deletions(-) delete mode 100644 src/utils/services/ShlinkApiClient.js create mode 100644 src/utils/services/ShlinkApiClient.ts rename test/utils/services/{ShlinkApiClient.test.js => ShlinkApiClient.test.ts} (77%) diff --git a/package-lock.json b/package-lock.json index 5e1b06d6..26892ac8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3359,6 +3359,12 @@ "integrity": "sha512-ce5d3q03Ex0sy4R14722Rmt6MT07Ua+k4FwDfdcToYJcMKNtRVQvJ6JCAPdAmAnbRb6CsX6aYb9m96NGod9uTw==", "dev": true }, + "@types/qs": { + "version": "6.9.4", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.4.tgz", + "integrity": "sha512-+wYo+L6ZF6BMoEjtf8zB2esQsqdV6WsjRK/GP9WOgLPrq87PbNWgIxS76dS5uvl/QXtHGakZmwTznIfcPXcKlQ==", + "dev": true + }, "@types/ramda": { "version": "0.27.14", "resolved": "https://registry.npmjs.org/@types/ramda/-/ramda-0.27.14.tgz", diff --git a/package.json b/package.json index 7d8e84be..b4ec1ce3 100644 --- a/package.json +++ b/package.json @@ -80,6 +80,7 @@ "@types/jest": "^26.0.10", "@types/leaflet": "^1.5.17", "@types/moment": "^2.13.0", + "@types/qs": "^6.9.4", "@types/ramda": "^0.27.14", "@types/react": "^16.9.46", "@types/react-datepicker": "~1.8.0", diff --git a/src/mercure/reducers/mercureInfo.ts b/src/mercure/reducers/mercureInfo.ts index 582f76b7..768d8acf 100644 --- a/src/mercure/reducers/mercureInfo.ts +++ b/src/mercure/reducers/mercureInfo.ts @@ -55,7 +55,7 @@ export const loadMercureInfo = (buildShlinkApiClient: ShlinkApiClientBuilder) => try { const result = await mercureInfo(); - dispatch>({ type: GET_MERCURE_INFO, ...result }); + dispatch({ type: GET_MERCURE_INFO, ...result }); } catch (e) { dispatch({ type: GET_MERCURE_INFO_ERROR }); } diff --git a/src/short-urls/reducers/shortUrlsListParams.ts b/src/short-urls/reducers/shortUrlsListParams.ts index 9bc69594..e5ed91d5 100644 --- a/src/short-urls/reducers/shortUrlsListParams.ts +++ b/src/short-urls/reducers/shortUrlsListParams.ts @@ -20,7 +20,7 @@ export interface ShortUrlsListParams { searchTerm?: string; startDate?: string; endDate?: string; - orderBy?: object; + orderBy?: string | Record; } const initialState: ShortUrlsListParams = { page: '1' }; diff --git a/src/utils/services/ShlinkApiClient.js b/src/utils/services/ShlinkApiClient.js deleted file mode 100644 index a26d7078..00000000 --- a/src/utils/services/ShlinkApiClient.js +++ /dev/null @@ -1,97 +0,0 @@ -import qs from 'qs'; -import { isEmpty, isNil, reject } from 'ramda'; - -const buildShlinkBaseUrl = (url, apiVersion) => url ? `${url}/rest/v${apiVersion}` : ''; -const rejectNilProps = reject(isNil); - -export default class ShlinkApiClient { - constructor(axios, baseUrl, apiKey) { - this.axios = axios; - this._apiVersion = 2; - this._baseUrl = baseUrl; - this._apiKey = apiKey || ''; - } - - listShortUrls = (options = {}) => - this._performRequest('/short-urls', 'GET', options).then((resp) => resp.data.shortUrls); - - createShortUrl = (options) => { - const filteredOptions = reject((value) => isEmpty(value) || isNil(value), options); - - return this._performRequest('/short-urls', 'POST', {}, filteredOptions) - .then((resp) => resp.data); - }; - - getShortUrlVisits = (shortCode, query) => - this._performRequest(`/short-urls/${shortCode}/visits`, 'GET', query) - .then((resp) => resp.data.visits); - - getTagVisits = (tag, query) => - this._performRequest(`/tags/${tag}/visits`, 'GET', query) - .then((resp) => resp.data.visits); - - getShortUrl = (shortCode, domain) => - this._performRequest(`/short-urls/${shortCode}`, 'GET', { domain }) - .then((resp) => resp.data); - - deleteShortUrl = (shortCode, domain) => - this._performRequest(`/short-urls/${shortCode}`, 'DELETE', { domain }) - .then(() => ({})); - - updateShortUrlTags = (shortCode, domain, tags) => - this._performRequest(`/short-urls/${shortCode}/tags`, 'PUT', { domain }, { tags }) - .then((resp) => resp.data.tags); - - updateShortUrlMeta = (shortCode, domain, meta) => - this._performRequest(`/short-urls/${shortCode}`, 'PATCH', { domain }, meta) - .then(() => meta); - - listTags = () => - this._performRequest('/tags', 'GET', { withStats: 'true' }) - .then((resp) => resp.data.tags) - .then(({ data, stats }) => ({ tags: data, stats })); - - deleteTags = (tags) => - this._performRequest('/tags', 'DELETE', { tags }) - .then(() => ({ tags })); - - editTag = (oldName, newName) => - this._performRequest('/tags', 'PUT', {}, { oldName, newName }) - .then(() => ({ oldName, newName })); - - health = () => this._performRequest('/health', 'GET').then((resp) => resp.data); - - mercureInfo = () => this._performRequest('/mercure-info', 'GET').then((resp) => resp.data); - - _performRequest = async (url, method = 'GET', query = {}, body = {}) => { - try { - return await this.axios({ - method, - url: `${buildShlinkBaseUrl(this._baseUrl, this._apiVersion)}${url}`, - headers: { 'X-Api-Key': this._apiKey }, - params: rejectNilProps(query), - data: body, - paramsSerializer: (params) => qs.stringify(params, { arrayFormat: 'brackets' }), - }); - } catch (e) { - const { response } = e; - - // Due to a bug on all previous Shlink versions, requests to non-matching URLs will always result on a CORS error - // when performed from the browser (due to the preflight request not returning a 2xx status. - // See https://github.com/shlinkio/shlink/issues/614), which will make the "response" prop not to be set here. - // The bug will be fixed on upcoming Shlink patches, but for other versions, we can consider this situation as - // if a request has been performed to a not supported API version. - const apiVersionIsNotSupported = !response; - - // When the request is not invalid or we have already tried both API versions, throw the error and let the - // caller handle it - if (!apiVersionIsNotSupported || this._apiVersion === 1) { - throw e; - } - - this._apiVersion = 1; - - return await this._performRequest(url, method, query, body); - } - } -} diff --git a/src/utils/services/ShlinkApiClient.ts b/src/utils/services/ShlinkApiClient.ts new file mode 100644 index 00000000..84b5bc9e --- /dev/null +++ b/src/utils/services/ShlinkApiClient.ts @@ -0,0 +1,127 @@ +import qs from 'qs'; +import { isEmpty, isNil, reject } from 'ramda'; +import { AxiosInstance, AxiosResponse, Method } from 'axios'; +import { ShortUrlsListParams } from '../../short-urls/reducers/shortUrlsListParams'; +import { ShortUrl } from '../../short-urls/data'; +import { OptionalString } from '../utils'; +import { + ShlinkHealth, + ShlinkMercureInfo, + ShlinkShortUrlsResponse, + ShlinkTags, + ShlinkTagsResponse, + ShlinkVisits, + ShlinkVisitsParams, + ShlinkShortUrlMeta, +} from './types'; + +const buildShlinkBaseUrl = (url: string, apiVersion: number) => url ? `${url}/rest/v${apiVersion}` : ''; +const rejectNilProps = reject(isNil); + +export default class ShlinkApiClient { + private apiVersion: number; + + public constructor( + private readonly axios: AxiosInstance, + private readonly baseUrl: string, + private readonly apiKey: string, + ) { + this.apiVersion = 2; + } + + public readonly listShortUrls = async (params: ShortUrlsListParams = {}): Promise => + this.performRequest<{ shortUrls: ShlinkShortUrlsResponse }>('/short-urls', 'GET', params) + .then(({ data }) => data.shortUrls); + + public readonly createShortUrl = async (options: any): Promise => { // TODO CreateShortUrl interface + const filteredOptions = reject((value) => isEmpty(value) || isNil(value), options); + + return this.performRequest('/short-urls', 'POST', {}, filteredOptions) + .then((resp) => resp.data); + }; + + public readonly getShortUrlVisits = async (shortCode: string, query?: ShlinkVisitsParams): Promise => + this.performRequest<{ visits: ShlinkVisits }>(`/short-urls/${shortCode}/visits`, 'GET', query) + .then(({ data }) => data.visits); + + public readonly getTagVisits = async (tag: string, query?: Omit): Promise => + this.performRequest<{ visits: ShlinkVisits }>(`/tags/${tag}/visits`, 'GET', query) + .then(({ data }) => data.visits); + + public readonly getShortUrl = async (shortCode: string, domain?: OptionalString): Promise => + this.performRequest(`/short-urls/${shortCode}`, 'GET', { domain }) + .then(({ data }) => data); + + public readonly deleteShortUrl = async (shortCode: string, domain?: OptionalString): Promise => + this.performRequest(`/short-urls/${shortCode}`, 'DELETE', { domain }) + .then(() => {}); + + public readonly updateShortUrlTags = async ( + shortCode: string, + domain: OptionalString, + tags: string[], + ): Promise => + this.performRequest<{ tags: string[] }>(`/short-urls/${shortCode}/tags`, 'PUT', { domain }, { tags }) + .then(({ data }) => data.tags); + + public readonly updateShortUrlMeta = async ( + shortCode: string, + domain: OptionalString, + meta: ShlinkShortUrlMeta, + ): Promise => + this.performRequest(`/short-urls/${shortCode}`, 'PATCH', { domain }, meta) + .then(() => meta); + + public readonly listTags = async (): Promise => + this.performRequest<{ tags: ShlinkTagsResponse }>('/tags', 'GET', { withStats: 'true' }) + .then((resp) => resp.data.tags) + .then(({ data, stats }) => ({ tags: data, stats })); + + public readonly deleteTags = async (tags: string[]): Promise<{ tags: string[] }> => + this.performRequest('/tags', 'DELETE', { tags }) + .then(() => ({ tags })); + + public readonly editTag = async (oldName: string, newName: string): Promise<{ oldName: string; newName: string }> => + this.performRequest('/tags', 'PUT', {}, { oldName, newName }) + .then(() => ({ oldName, newName })); + + public readonly health = async (): Promise => + this.performRequest('/health', 'GET') + .then((resp) => resp.data); + + public readonly mercureInfo = async (): Promise => + this.performRequest('/mercure-info', 'GET') + .then((resp) => resp.data); + + private readonly performRequest = async (url: string, method: Method = 'GET', query = {}, body = {}): Promise> => { + try { + return await this.axios({ + method, + url: `${buildShlinkBaseUrl(this.baseUrl, this.apiVersion)}${url}`, + headers: { 'X-Api-Key': this.apiKey }, + params: rejectNilProps(query), + data: body, + paramsSerializer: (params) => qs.stringify(params, { arrayFormat: 'brackets' }), + }); + } catch (e) { + const { response } = e; + + // Due to a bug on all previous Shlink versions, requests to non-matching URLs will always result on a CORS error + // when performed from the browser (due to the preflight request not returning a 2xx status. + // See https://github.com/shlinkio/shlink/issues/614), which will make the "response" prop not to be set here. + // The bug will be fixed on upcoming Shlink patches, but for other versions, we can consider this situation as + // if a request has been performed to a not supported API version. + const apiVersionIsNotSupported = !response; + + // When the request is not invalid or we have already tried both API versions, throw the error and let the + // caller handle it + if (!apiVersionIsNotSupported || this.apiVersion === 1) { + throw e; + } + + this.apiVersion = this.apiVersion - 1; + + return await this.performRequest(url, method, query, body); + } + }; +} diff --git a/src/utils/services/types.ts b/src/utils/services/types.ts index 0f9062f7..8c4c53d3 100644 --- a/src/utils/services/types.ts +++ b/src/utils/services/types.ts @@ -1,4 +1,11 @@ -import { Visit } from '../../visits/types'; // FIXME Should be defined here +import { Visit } from '../../visits/types'; // FIXME Should be defined as part of this module +import { ShortUrl, ShortUrlMeta } from '../../short-urls/data'; // FIXME Should be defined as part of this module +import { OptionalString } from '../utils'; + +export interface ShlinkShortUrlsResponse { + data: ShortUrl[]; + pagination: ShlinkPaginator; +} export interface ShlinkMercureInfo { token: string; @@ -21,6 +28,11 @@ export interface ShlinkTags { stats?: ShlinkTagsStats[]; // TODO Is only optional in old versions } +export interface ShlinkTagsResponse { + data: string[]; + stats?: ShlinkTagsStats[]; // TODO Is only optional in old versions +} + export interface ShlinkPaginator { currentPage: number; pagesCount: number; @@ -31,6 +43,18 @@ export interface ShlinkVisits { pagination?: ShlinkPaginator; // TODO Is only optional in old versions } +export interface ShlinkVisitsParams { + domain?: OptionalString; + page?: number; + itemsPerPage?: number; + startDate?: string; + endDate?: string; +} + +export interface ShlinkShortUrlMeta extends ShortUrlMeta { + longUrl?: string; +} + export interface ProblemDetailsError { type: string; detail: string; diff --git a/test/utils/services/ShlinkApiClient.test.js b/test/utils/services/ShlinkApiClient.test.ts similarity index 77% rename from test/utils/services/ShlinkApiClient.test.js rename to test/utils/services/ShlinkApiClient.test.ts index 55a4fd3b..db9193b3 100644 --- a/test/utils/services/ShlinkApiClient.test.js +++ b/test/utils/services/ShlinkApiClient.test.ts @@ -1,9 +1,12 @@ +import { AxiosInstance, AxiosRequestConfig } from 'axios'; import ShlinkApiClient from '../../../src/utils/services/ShlinkApiClient'; +import { OptionalString } from '../../../src/utils/utils'; describe('ShlinkApiClient', () => { - const createAxiosMock = (data) => () => Promise.resolve(data); - const createApiClient = (data) => new ShlinkApiClient(createAxiosMock(data)); - const shortCodesWithDomainCombinations = [ + const createAxios = (data: AxiosRequestConfig) => (async () => Promise.resolve(data)) as unknown as AxiosInstance; + const createAxiosMock = (data: AxiosRequestConfig = {}) => jest.fn(createAxios(data)) as unknown as AxiosInstance; + const createApiClient = (data: AxiosRequestConfig) => new ShlinkApiClient(createAxios(data), '', ''); + const shortCodesWithDomainCombinations: [ string, OptionalString ][] = [ [ 'abc123', null ], [ 'abc123', undefined ], [ 'abc123', 'example.com' ], @@ -38,8 +41,8 @@ describe('ShlinkApiClient', () => { }); it('removes all empty options', async () => { - const axiosSpy = jest.fn(createAxiosMock({ data: shortUrl })); - const { createShortUrl } = new ShlinkApiClient(axiosSpy); + const axiosSpy = createAxiosMock({ data: shortUrl }); + const { createShortUrl } = new ShlinkApiClient(axiosSpy, '', ''); await createShortUrl( { foo: 'bar', empty: undefined, anotherEmpty: null }, @@ -52,14 +55,14 @@ describe('ShlinkApiClient', () => { describe('getShortUrlVisits', () => { it('properly returns short URL visits', async () => { const expectedVisits = [ 'foo', 'bar' ]; - const axiosSpy = jest.fn(createAxiosMock({ + const axiosSpy = createAxiosMock({ data: { visits: { data: expectedVisits, }, }, - })); - const { getShortUrlVisits } = new ShlinkApiClient(axiosSpy); + }); + const { getShortUrlVisits } = new ShlinkApiClient(axiosSpy, '', ''); const actualVisits = await getShortUrlVisits('abc123', {}); @@ -74,14 +77,14 @@ describe('ShlinkApiClient', () => { describe('getTagVisits', () => { it('properly returns tag visits', async () => { const expectedVisits = [ 'foo', 'bar' ]; - const axiosSpy = jest.fn(createAxiosMock({ + const axiosSpy = createAxiosMock({ data: { visits: { data: expectedVisits, }, }, - })); - const { getTagVisits } = new ShlinkApiClient(axiosSpy); + }); + const { getTagVisits } = new ShlinkApiClient(axiosSpy, '', ''); const actualVisits = await getTagVisits('foo', {}); @@ -96,10 +99,10 @@ describe('ShlinkApiClient', () => { describe('getShortUrl', () => { it.each(shortCodesWithDomainCombinations)('properly returns short URL', async (shortCode, domain) => { const expectedShortUrl = { foo: 'bar' }; - const axiosSpy = jest.fn(createAxiosMock({ + const axiosSpy = createAxiosMock({ data: expectedShortUrl, - })); - const { getShortUrl } = new ShlinkApiClient(axiosSpy); + }); + const { getShortUrl } = new ShlinkApiClient(axiosSpy, '', ''); const result = await getShortUrl(shortCode, domain); @@ -115,10 +118,10 @@ describe('ShlinkApiClient', () => { describe('updateShortUrlTags', () => { it.each(shortCodesWithDomainCombinations)('properly updates short URL tags', async (shortCode, domain) => { const expectedTags = [ 'foo', 'bar' ]; - const axiosSpy = jest.fn(createAxiosMock({ + const axiosSpy = createAxiosMock({ data: { tags: expectedTags }, - })); - const { updateShortUrlTags } = new ShlinkApiClient(axiosSpy); + }); + const { updateShortUrlTags } = new ShlinkApiClient(axiosSpy, '', ''); const result = await updateShortUrlTags(shortCode, domain, expectedTags); @@ -137,8 +140,8 @@ describe('ShlinkApiClient', () => { maxVisits: 50, validSince: '2025-01-01T10:00:00+01:00', }; - const axiosSpy = jest.fn(createAxiosMock()); - const { updateShortUrlMeta } = new ShlinkApiClient(axiosSpy); + const axiosSpy = createAxiosMock(); + const { updateShortUrlMeta } = new ShlinkApiClient(axiosSpy, '', ''); const result = await updateShortUrlMeta(shortCode, domain, expectedMeta); @@ -154,12 +157,12 @@ describe('ShlinkApiClient', () => { describe('listTags', () => { it('properly returns list of tags', async () => { const expectedTags = [ 'foo', 'bar' ]; - const axiosSpy = jest.fn(createAxiosMock({ + const axiosSpy = createAxiosMock({ data: { tags: { data: expectedTags }, }, - })); - const { listTags } = new ShlinkApiClient(axiosSpy); + }); + const { listTags } = new ShlinkApiClient(axiosSpy, '', ''); const result = await listTags(); @@ -171,8 +174,8 @@ describe('ShlinkApiClient', () => { describe('deleteTags', () => { it('properly deletes provided tags', async () => { const tags = [ 'foo', 'bar' ]; - const axiosSpy = jest.fn(createAxiosMock({})); - const { deleteTags } = new ShlinkApiClient(axiosSpy); + const axiosSpy = createAxiosMock(); + const { deleteTags } = new ShlinkApiClient(axiosSpy, '', ''); await deleteTags(tags); @@ -188,8 +191,8 @@ describe('ShlinkApiClient', () => { it('properly edits provided tag', async () => { const oldName = 'foo'; const newName = 'bar'; - const axiosSpy = jest.fn(createAxiosMock({})); - const { editTag } = new ShlinkApiClient(axiosSpy); + const axiosSpy = createAxiosMock(); + const { editTag } = new ShlinkApiClient(axiosSpy, '', ''); await editTag(oldName, newName); @@ -203,8 +206,8 @@ describe('ShlinkApiClient', () => { describe('deleteShortUrl', () => { it.each(shortCodesWithDomainCombinations)('properly deletes provided short URL', async (shortCode, domain) => { - const axiosSpy = jest.fn(createAxiosMock({})); - const { deleteShortUrl } = new ShlinkApiClient(axiosSpy); + const axiosSpy = createAxiosMock({}); + const { deleteShortUrl } = new ShlinkApiClient(axiosSpy, '', ''); await deleteShortUrl(shortCode, domain); @@ -222,8 +225,8 @@ describe('ShlinkApiClient', () => { status: 'pass', version: '1.19.0', }; - const axiosSpy = jest.fn(createAxiosMock({ data: expectedData })); - const { health } = new ShlinkApiClient(axiosSpy); + const axiosSpy = createAxiosMock({ data: expectedData }); + const { health } = new ShlinkApiClient(axiosSpy, '', ''); const result = await health(); @@ -238,8 +241,8 @@ describe('ShlinkApiClient', () => { token: 'abc.123.def', mercureHubUrl: 'http://example.com/.well-known/mercure', }; - const axiosSpy = jest.fn(createAxiosMock({ data: expectedData })); - const { mercureInfo } = new ShlinkApiClient(axiosSpy); + const axiosSpy = createAxiosMock({ data: expectedData }); + const { mercureInfo } = new ShlinkApiClient(axiosSpy, '', ''); const result = await mercureInfo(); diff --git a/test/utils/services/ShlinkApiClientBuilder.test.ts b/test/utils/services/ShlinkApiClientBuilder.test.ts index 5fac09ae..0cd31204 100644 --- a/test/utils/services/ShlinkApiClientBuilder.test.ts +++ b/test/utils/services/ShlinkApiClientBuilder.test.ts @@ -46,7 +46,7 @@ describe('ShlinkApiClientBuilder', () => { const apiKey = 'apiKey'; const apiClient = buildShlinkApiClient(axiosMock)(server({ url, apiKey })); - expect(apiClient._baseUrl).toEqual(url); - expect(apiClient._apiKey).toEqual(apiKey); + expect(apiClient['baseUrl']).toEqual(url); // eslint-disable-line dot-notation + expect(apiClient['apiKey']).toEqual(apiKey); // eslint-disable-line dot-notation }); }); From c0f5d9c12c22ee79c09fa5d4a237707bbf461d71 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 29 Aug 2020 20:20:45 +0200 Subject: [PATCH 38/59] Finished migrating servers module to TS --- src/common/MenuLayout.tsx | 10 +--- src/servers/EditServer.js | 38 ------------- src/servers/EditServer.tsx | 32 +++++++++++ src/servers/ServersDropdown.js | 55 ------------------- src/servers/ServersDropdown.tsx | 49 +++++++++++++++++ src/servers/helpers/withSelectedServer.tsx | 30 +++++----- ...EditServer.test.js => EditServer.test.tsx} | 28 ++++++---- ...pdown.test.js => ServersDropdown.test.tsx} | 30 +++++----- 8 files changed, 130 insertions(+), 142 deletions(-) delete mode 100644 src/servers/EditServer.js create mode 100644 src/servers/EditServer.tsx delete mode 100644 src/servers/ServersDropdown.js create mode 100644 src/servers/ServersDropdown.tsx rename test/servers/{EditServer.test.js => EditServer.test.tsx} (60%) rename test/servers/{ServersDropdown.test.js => ServersDropdown.test.tsx} (53%) diff --git a/src/common/MenuLayout.tsx b/src/common/MenuLayout.tsx index 79b5a043..07553820 100644 --- a/src/common/MenuLayout.tsx +++ b/src/common/MenuLayout.tsx @@ -1,5 +1,5 @@ import React, { FC, useEffect } from 'react'; -import { Route, RouteChildrenProps, Switch } from 'react-router-dom'; +import { Route, Switch } from 'react-router-dom'; import { EventData, Swipeable } from 'react-swipeable'; import { faBars as burgerIcon } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; @@ -7,15 +7,11 @@ import classNames from 'classnames'; import { withSelectedServer } from '../servers/helpers/withSelectedServer'; import { useToggle } from '../utils/helpers/hooks'; import { versionMatch } from '../utils/helpers/version'; -import { isReachableServer, SelectedServer } from '../servers/data'; +import { isReachableServer } from '../servers/data'; import NotFound from './NotFound'; import { AsideMenuProps } from './AsideMenu'; import './MenuLayout.scss'; -interface MenuLayoutProps extends RouteChildrenProps { - selectedServer: SelectedServer; -} - const MenuLayout = ( TagsList: FC, ShortUrls: FC, @@ -25,7 +21,7 @@ const MenuLayout = ( TagVisits: FC, ShlinkVersions: FC, ServerError: FC, -) => withSelectedServer(({ location, selectedServer }: MenuLayoutProps) => { +) => withSelectedServer(({ location, selectedServer }) => { const [ sidebarVisible, toggleSidebar, showSidebar, hideSidebar ] = useToggle(); useEffect(() => hideSidebar(), [ location ]); diff --git a/src/servers/EditServer.js b/src/servers/EditServer.js deleted file mode 100644 index 33560631..00000000 --- a/src/servers/EditServer.js +++ /dev/null @@ -1,38 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { Button } from 'reactstrap'; -import NoMenuLayout from '../common/NoMenuLayout'; -import { ServerForm } from './helpers/ServerForm'; -import { withSelectedServer } from './helpers/withSelectedServer'; -import { serverType } from './prop-types'; - -const propTypes = { - editServer: PropTypes.func, - selectedServer: serverType, - history: PropTypes.shape({ - push: PropTypes.func, - goBack: PropTypes.func, - }), -}; - -export const EditServer = (ServerError) => { - const EditServerComp = ({ editServer, selectedServer, history: { push, goBack } }) => { - const handleSubmit = (serverData) => { - editServer(selectedServer.id, serverData); - push(`/server/${selectedServer.id}/list-short-urls/1`); - }; - - return ( - - - - - - - ); - }; - - EditServerComp.propTypes = propTypes; - - return withSelectedServer(EditServerComp, ServerError); -}; diff --git a/src/servers/EditServer.tsx b/src/servers/EditServer.tsx new file mode 100644 index 00000000..7aa28b6c --- /dev/null +++ b/src/servers/EditServer.tsx @@ -0,0 +1,32 @@ +import React, { FC } from 'react'; +import { Button } from 'reactstrap'; +import NoMenuLayout from '../common/NoMenuLayout'; +import { ServerForm } from './helpers/ServerForm'; +import { withSelectedServer } from './helpers/withSelectedServer'; +import { isServerWithId, ServerData } from './data'; + +interface EditServerProps { + editServer: (serverId: string, serverData: ServerData) => void; +} + +export const EditServer = (ServerError: FC) => withSelectedServer(( + { editServer, selectedServer, history: { push, goBack } }, +) => { + if (!isServerWithId(selectedServer)) { + return null; + } + + const handleSubmit = (serverData: ServerData) => { + editServer(selectedServer.id, serverData); + push(`/server/${selectedServer.id}/list-short-urls/1`); + }; + + return ( + + + + + + + ); +}, ServerError); diff --git a/src/servers/ServersDropdown.js b/src/servers/ServersDropdown.js deleted file mode 100644 index c4200e5d..00000000 --- a/src/servers/ServersDropdown.js +++ /dev/null @@ -1,55 +0,0 @@ -import { isEmpty, values } from 'ramda'; -import React from 'react'; -import { DropdownItem, DropdownMenu, DropdownToggle, UncontrolledDropdown } from 'reactstrap'; -import PropTypes from 'prop-types'; -import { Link } from 'react-router-dom'; -import { serverType } from './prop-types'; - -const propTypes = { - servers: PropTypes.object, - selectedServer: serverType, -}; - -const ServersDropdown = (serversExporter) => { - const ServersDropdownComp = ({ servers, selectedServer }) => { - const serversList = values(servers); - - const renderServers = () => { - if (isEmpty(serversList)) { - return Add a server first...; - } - - return ( - - {serversList.map(({ name, id }) => ( - - {name} - - ))} - - serversExporter.exportServers()}> - Export servers - - - ); - }; - - return ( - - Servers - {renderServers()} - - ); - }; - - ServersDropdownComp.propTypes = propTypes; - - return ServersDropdownComp; -}; - -export default ServersDropdown; diff --git a/src/servers/ServersDropdown.tsx b/src/servers/ServersDropdown.tsx new file mode 100644 index 00000000..80be5e61 --- /dev/null +++ b/src/servers/ServersDropdown.tsx @@ -0,0 +1,49 @@ +import { isEmpty, values } from 'ramda'; +import React from 'react'; +import { DropdownItem, DropdownMenu, DropdownToggle, UncontrolledDropdown } from 'reactstrap'; +import { Link } from 'react-router-dom'; +import ServersExporter from './services/ServersExporter'; +import { isServerWithId, SelectedServer, ServersMap } from './data'; + +export interface ServersDropdownProps { + servers: ServersMap; + selectedServer: SelectedServer; +} + +const ServersDropdown = (serversExporter: ServersExporter) => ({ servers, selectedServer }: ServersDropdownProps) => { + const serversList = values(servers); + + const renderServers = () => { + if (isEmpty(serversList)) { + return Add a server first...; + } + + return ( + + {serversList.map(({ name, id }) => ( + + {name} + + ))} + + serversExporter.exportServers()}> + Export servers + + + ); + }; + + return ( + + Servers + {renderServers()} + + ); +}; + +export default ServersDropdown; diff --git a/src/servers/helpers/withSelectedServer.tsx b/src/servers/helpers/withSelectedServer.tsx index f308d6e0..41f8a45c 100644 --- a/src/servers/helpers/withSelectedServer.tsx +++ b/src/servers/helpers/withSelectedServer.tsx @@ -8,22 +8,22 @@ interface WithSelectedServerProps extends RouteChildrenProps<{ serverId: string selectedServer: SelectedServer; } -export const withSelectedServer = (WrappedComponent: FC, ServerError: FC) => ( - props: WithSelectedServerProps, -) => { - const { selectServer, selectedServer, match } = props; +export function withSelectedServer(WrappedComponent: FC, ServerError: FC) { + return (props: WithSelectedServerProps & T) => { + const { selectServer, selectedServer, match } = props; - useEffect(() => { - match?.params?.serverId && selectServer(match?.params.serverId); - }, [ match?.params.serverId ]); + useEffect(() => { + match?.params?.serverId && selectServer(match?.params.serverId); + }, [ match?.params.serverId ]); - if (!selectedServer) { - return ; - } + if (!selectedServer) { + return ; + } - if (isNotFoundServer(selectedServer)) { - return ; - } + if (isNotFoundServer(selectedServer)) { + return ; + } - return ; -}; + return ; + }; +} diff --git a/test/servers/EditServer.test.js b/test/servers/EditServer.test.tsx similarity index 60% rename from test/servers/EditServer.test.js rename to test/servers/EditServer.test.tsx index 366dd24f..844b4e5b 100644 --- a/test/servers/EditServer.test.js +++ b/test/servers/EditServer.test.tsx @@ -1,22 +1,27 @@ import React from 'react'; -import { mount } from 'enzyme'; +import { mount, ReactWrapper } from 'enzyme'; +import { Mock } from 'ts-mockery'; +import { History, Location } from 'history'; +import { match } from 'react-router'; // eslint-disable-line @typescript-eslint/no-unused-vars import { EditServer as editServerConstruct } from '../../src/servers/EditServer'; import { ServerForm } from '../../src/servers/helpers/ServerForm'; +import { ReachableServer } from '../../src/servers/data'; describe('', () => { - let wrapper; + let wrapper: ReactWrapper; const ServerError = jest.fn(); const editServerMock = jest.fn(); - const historyMock = { push: jest.fn() }; - const match = { + const push = jest.fn(); + const historyMock = Mock.of({ push }); + const match = Mock.of>({ params: { serverId: 'abc123' }, - }; - const selectedServer = { + }); + const selectedServer = Mock.of({ id: 'abc123', name: 'name', url: 'url', apiKey: 'apiKey', - }; + }); beforeEach(() => { const EditServer = editServerConstruct(ServerError); @@ -26,16 +31,15 @@ describe('', () => { editServer={editServerMock} history={historyMock} match={match} + location={Mock.all()} selectedServer={selectedServer} selectServer={jest.fn()} />, ); }); - afterEach(() => { - jest.resetAllMocks(); - wrapper && wrapper.unmount(); - }); + afterEach(jest.resetAllMocks); + afterEach(() => wrapper?.unmount()); it('renders components', () => { expect(wrapper.find(ServerForm)).toHaveLength(1); @@ -47,6 +51,6 @@ describe('', () => { form.simulate('submit', {}); expect(editServerMock).toHaveBeenCalledTimes(1); - expect(historyMock.push).toHaveBeenCalledTimes(1); + expect(push).toHaveBeenCalledTimes(1); }); }); diff --git a/test/servers/ServersDropdown.test.js b/test/servers/ServersDropdown.test.tsx similarity index 53% rename from test/servers/ServersDropdown.test.js rename to test/servers/ServersDropdown.test.tsx index f2e6bd51..dd6a8c63 100644 --- a/test/servers/ServersDropdown.test.js +++ b/test/servers/ServersDropdown.test.tsx @@ -1,24 +1,24 @@ -import { identity, values } from 'ramda'; -import React from 'react'; -import { shallow } from 'enzyme'; +import { values } from 'ramda'; +import { Mock } from 'ts-mockery'; +import React, { FC } from 'react'; +import { shallow, ShallowWrapper } from 'enzyme'; import { DropdownItem, DropdownToggle } from 'reactstrap'; -import serversDropdownCreator from '../../src/servers/ServersDropdown'; +import serversDropdownCreator, { ServersDropdownProps } from '../../src/servers/ServersDropdown'; +import { ServerWithId } from '../../src/servers/data'; +import ServersExporter from '../../src/servers/services/ServersExporter'; describe('', () => { - let wrapped; - let ServersDropdown; + let wrapped: ShallowWrapper; + let ServersDropdown: FC; const servers = { - '1a': { name: 'foo', id: 1 }, - '2b': { name: 'bar', id: 2 }, - '3c': { name: 'baz', id: 3 }, - }; - const history = { - push: jest.fn(), + '1a': Mock.of({ name: 'foo', id: '1a' }), + '2b': Mock.of({ name: 'bar', id: '2b' }), + '3c': Mock.of({ name: 'baz', id: '3c' }), }; beforeEach(() => { - ServersDropdown = serversDropdownCreator({}); - wrapped = shallow(); + ServersDropdown = serversDropdownCreator(Mock.of()); + wrapped = shallow(); }); afterEach(() => wrapped.unmount()); @@ -37,7 +37,7 @@ describe('', () => { it('shows a message when no servers exist yet', () => { wrapped = shallow( - , + , ); const item = wrapped.find(DropdownItem); From 4b33d39d445342c1218f76fb15e750f72e8df70a Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 30 Aug 2020 09:59:14 +0200 Subject: [PATCH 39/59] Finished migrating ll short-url helpers to TS --- package-lock.json | 9 ++ package.json | 1 + src/servers/data/index.ts | 8 +- src/servers/prop-types/index.js | 1 + src/short-urls/data/index.ts | 1 + .../helpers/CreateShortUrlResult.js | 67 ------------- .../helpers/CreateShortUrlResult.tsx | 61 ++++++++++++ src/short-urls/helpers/EditTagsModal.js | 56 ----------- src/short-urls/helpers/EditTagsModal.tsx | 49 ++++++++++ .../{PreviewModal.js => PreviewModal.tsx} | 16 +-- .../{QrCodeModal.js => QrCodeModal.tsx} | 16 +-- ...VisitsCount.js => ShortUrlVisitsCount.tsx} | 25 ++--- src/short-urls/helpers/ShortUrlsRow.js | 98 ------------------- src/short-urls/helpers/ShortUrlsRow.tsx | 94 ++++++++++++++++++ src/short-urls/helpers/ShortUrlsRowMenu.js | 95 ------------------ src/short-urls/helpers/ShortUrlsRowMenu.tsx | 96 ++++++++++++++++++ src/short-urls/helpers/VisitStatsLink.js | 29 ------ src/short-urls/helpers/VisitStatsLink.tsx | 27 +++++ src/short-urls/reducers/shortUrlTags.ts | 9 -- src/short-urls/reducers/shortUrlsList.ts | 2 +- src/utils/helpers/{leaflet.js => leaflet.ts} | 4 +- ....test.js => CreateShortUrlResult.test.tsx} | 29 +++--- ...gsModal.test.js => EditTagsModal.test.tsx} | 33 ++++--- ...iewModal.test.js => PreviewModal.test.tsx} | 14 +-- ...CodeModal.test.js => QrCodeModal.test.tsx} | 14 +-- ...t.test.js => ShortUrlVisitsCount.test.tsx} | 14 +-- ...tUrlsRow.test.js => ShortUrlsRow.test.tsx} | 37 ++++--- ...Menu.test.js => ShortUrlsRowMenu.test.tsx} | 47 ++++----- ...tsLink.test.js => VisitStatsLink.test.tsx} | 30 +++--- .../{leaflet.test.js => leaflet.test.ts} | 0 30 files changed, 483 insertions(+), 499 deletions(-) delete mode 100644 src/short-urls/helpers/CreateShortUrlResult.js create mode 100644 src/short-urls/helpers/CreateShortUrlResult.tsx delete mode 100644 src/short-urls/helpers/EditTagsModal.js create mode 100644 src/short-urls/helpers/EditTagsModal.tsx rename src/short-urls/helpers/{PreviewModal.js => PreviewModal.tsx} (55%) rename src/short-urls/helpers/{QrCodeModal.js => QrCodeModal.tsx} (51%) rename src/short-urls/helpers/{ShortUrlVisitsCount.js => ShortUrlVisitsCount.tsx} (70%) delete mode 100644 src/short-urls/helpers/ShortUrlsRow.js create mode 100644 src/short-urls/helpers/ShortUrlsRow.tsx delete mode 100644 src/short-urls/helpers/ShortUrlsRowMenu.js create mode 100644 src/short-urls/helpers/ShortUrlsRowMenu.tsx delete mode 100644 src/short-urls/helpers/VisitStatsLink.js create mode 100644 src/short-urls/helpers/VisitStatsLink.tsx rename src/utils/helpers/{leaflet.js => leaflet.ts} (80%) rename test/short-urls/helpers/{CreateShortUrlResult.test.js => CreateShortUrlResult.test.tsx} (61%) rename test/short-urls/helpers/{EditTagsModal.test.js => EditTagsModal.test.tsx} (74%) rename test/short-urls/helpers/{PreviewModal.test.js => PreviewModal.test.tsx} (55%) rename test/short-urls/helpers/{QrCodeModal.test.js => QrCodeModal.test.tsx} (55%) rename test/short-urls/helpers/{ShortUrlVisitsCount.test.js => ShortUrlVisitsCount.test.tsx} (74%) rename test/short-urls/helpers/{ShortUrlsRow.test.js => ShortUrlsRow.test.tsx} (75%) rename test/short-urls/helpers/{ShortUrlsRowMenu.test.js => ShortUrlsRowMenu.test.tsx} (67%) rename test/short-urls/helpers/{VisitStatsLink.test.js => VisitStatsLink.test.tsx} (51%) rename test/utils/helpers/{leaflet.test.js => leaflet.test.ts} (100%) diff --git a/package-lock.json b/package-lock.json index 26892ac8..ee22ba94 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3384,6 +3384,15 @@ "csstype": "^3.0.2" } }, + "@types/react-copy-to-clipboard": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@types/react-copy-to-clipboard/-/react-copy-to-clipboard-4.3.0.tgz", + "integrity": "sha512-iideNPRyroENqsOFh1i2Dv3zkviYS9r/9qD9Uh3Z9NNoAAqqa2x53i7iGndGNnJFIo20wIu7Hgh77tx1io8bgw==", + "dev": true, + "requires": { + "@types/react": "*" + } + }, "@types/react-datepicker": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/@types/react-datepicker/-/react-datepicker-1.8.0.tgz", diff --git a/package.json b/package.json index b4ec1ce3..4139b384 100644 --- a/package.json +++ b/package.json @@ -83,6 +83,7 @@ "@types/qs": "^6.9.4", "@types/ramda": "^0.27.14", "@types/react": "^16.9.46", + "@types/react-copy-to-clipboard": "^4.3.0", "@types/react-datepicker": "~1.8.0", "@types/react-dom": "^16.9.8", "@types/react-redux": "^7.1.9", diff --git a/src/servers/data/index.ts b/src/servers/data/index.ts index 0a89ae2c..22294143 100644 --- a/src/servers/data/index.ts +++ b/src/servers/data/index.ts @@ -27,14 +27,14 @@ export type SelectedServer = RegularServer | NotFoundServer | null; export type ServersMap = Record; -export const hasServerData = (server: ServerData | NotFoundServer | null): server is ServerData => +export const hasServerData = (server: SelectedServer | ServerData): server is ServerData => !!(server as ServerData)?.url && !!(server as ServerData)?.apiKey; -export const isReachableServer = (server: SelectedServer): server is ReachableServer => - !!server?.hasOwnProperty('printableVersion'); - export const isServerWithId = (server: SelectedServer | ServerWithId): server is ServerWithId => !!server?.hasOwnProperty('id'); +export const isReachableServer = (server: SelectedServer): server is ReachableServer => + !!server?.hasOwnProperty('printableVersion'); + export const isNotFoundServer = (server: SelectedServer): server is NotFoundServer => !!server?.hasOwnProperty('serverNotFound'); diff --git a/src/servers/prop-types/index.js b/src/servers/prop-types/index.js index 221d946a..f86d1744 100644 --- a/src/servers/prop-types/index.js +++ b/src/servers/prop-types/index.js @@ -14,6 +14,7 @@ const notFoundServerType = PropTypes.shape({ serverNotFound: PropTypes.bool.isRequired, }); +/** @deprecated Use SelectedServer type instead */ export const serverType = PropTypes.oneOfType([ regularServerType, notFoundServerType, diff --git a/src/short-urls/data/index.ts b/src/short-urls/data/index.ts index 4997ce44..f0045e83 100644 --- a/src/short-urls/data/index.ts +++ b/src/short-urls/data/index.ts @@ -16,6 +16,7 @@ export interface ShortUrl { shortCode: string; shortUrl: string; longUrl: string; + dateCreated: string; visitsCount: number; meta: Required>; tags: string[]; diff --git a/src/short-urls/helpers/CreateShortUrlResult.js b/src/short-urls/helpers/CreateShortUrlResult.js deleted file mode 100644 index f7cd3f5d..00000000 --- a/src/short-urls/helpers/CreateShortUrlResult.js +++ /dev/null @@ -1,67 +0,0 @@ -import { faCopy as copyIcon } from '@fortawesome/free-regular-svg-icons'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { isNil } from 'ramda'; -import React, { useEffect } from 'react'; -import { CopyToClipboard } from 'react-copy-to-clipboard'; -import { Card, CardBody, Tooltip } from 'reactstrap'; -import PropTypes from 'prop-types'; -import { createShortUrlResultType } from '../reducers/shortUrlCreation'; -import './CreateShortUrlResult.scss'; - -const propTypes = { - resetCreateShortUrl: PropTypes.func, - error: PropTypes.bool, - result: createShortUrlResultType, -}; - -const CreateShortUrlResult = (useStateFlagTimeout) => { - const CreateShortUrlResultComp = ({ error, result, resetCreateShortUrl }) => { - const [ showCopyTooltip, setShowCopyTooltip ] = useStateFlagTimeout(); - - useEffect(() => { - resetCreateShortUrl(); - }, []); - - if (error) { - return ( - - An error occurred while creating the URL :( - - ); - } - - if (isNil(result)) { - return null; - } - - const { shortUrl } = result; - - return ( - - - Great! The short URL is {shortUrl} - - - - - - - Copied! - - - - ); - }; - - CreateShortUrlResultComp.propTypes = propTypes; - - return CreateShortUrlResultComp; -}; - -export default CreateShortUrlResult; diff --git a/src/short-urls/helpers/CreateShortUrlResult.tsx b/src/short-urls/helpers/CreateShortUrlResult.tsx new file mode 100644 index 00000000..83701cf8 --- /dev/null +++ b/src/short-urls/helpers/CreateShortUrlResult.tsx @@ -0,0 +1,61 @@ +import { faCopy as copyIcon } from '@fortawesome/free-regular-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { isNil } from 'ramda'; +import React, { useEffect } from 'react'; +import CopyToClipboard from 'react-copy-to-clipboard'; +import { Card, CardBody, Tooltip } from 'reactstrap'; +import { ShortUrlCreation } from '../reducers/shortUrlCreation'; +import './CreateShortUrlResult.scss'; +import { StateFlagTimeout } from '../../utils/helpers/hooks'; + +interface CreateShortUrlResultProps extends ShortUrlCreation { + resetCreateShortUrl: Function; +} + +const CreateShortUrlResult = (useStateFlagTimeout: StateFlagTimeout) => ( + { error, result, resetCreateShortUrl }: CreateShortUrlResultProps, +) => { + const [ showCopyTooltip, setShowCopyTooltip ] = useStateFlagTimeout(); + + useEffect(() => { + resetCreateShortUrl(); + }, []); + + if (error) { + return ( + + An error occurred while creating the URL :( + + ); + } + + if (isNil(result)) { + return null; + } + + const { shortUrl } = result; + + return ( + + + Great! The short URL is {shortUrl} + + + + + + + Copied! + + + + ); +}; + +export default CreateShortUrlResult; diff --git a/src/short-urls/helpers/EditTagsModal.js b/src/short-urls/helpers/EditTagsModal.js deleted file mode 100644 index ed4c5055..00000000 --- a/src/short-urls/helpers/EditTagsModal.js +++ /dev/null @@ -1,56 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import { Modal, ModalBody, ModalFooter, ModalHeader } from 'reactstrap'; -import PropTypes from 'prop-types'; -import { ExternalLink } from 'react-external-link'; -import { shortUrlTagsType } from '../reducers/shortUrlTags'; -import { shortUrlType } from '../reducers/shortUrlsList'; - -const propTypes = { - isOpen: PropTypes.bool.isRequired, - toggle: PropTypes.func.isRequired, - shortUrl: shortUrlType.isRequired, - shortUrlTags: shortUrlTagsType, - editShortUrlTags: PropTypes.func, - resetShortUrlsTags: PropTypes.func, -}; - -const EditTagsModal = (TagsSelector) => { - const EditTagsModalComp = ({ isOpen, toggle, shortUrl, shortUrlTags, editShortUrlTags, resetShortUrlsTags }) => { - const [ selectedTags, setSelectedTags ] = useState(shortUrl.tags || []); - - useEffect(() => resetShortUrlsTags, []); - - const url = shortUrl && (shortUrl.shortUrl || ''); - const saveTags = () => editShortUrlTags(shortUrl.shortCode, shortUrl.domain, selectedTags) - .then(toggle) - .catch(() => {}); - - return ( - - - Edit tags for - - - setSelectedTags(tags)} /> - {shortUrlTags.error && ( -
- Something went wrong while saving the tags :( -
- )} -
- - - - -
- ); - }; - - EditTagsModalComp.propTypes = propTypes; - - return EditTagsModalComp; -}; - -export default EditTagsModal; diff --git a/src/short-urls/helpers/EditTagsModal.tsx b/src/short-urls/helpers/EditTagsModal.tsx new file mode 100644 index 00000000..be7cf4ee --- /dev/null +++ b/src/short-urls/helpers/EditTagsModal.tsx @@ -0,0 +1,49 @@ +import React, { FC, useEffect, useState } from 'react'; +import { Modal, ModalBody, ModalFooter, ModalHeader } from 'reactstrap'; +import { ExternalLink } from 'react-external-link'; +import { ShortUrlTags } from '../reducers/shortUrlTags'; +import { ShortUrlModalProps } from '../data'; +import { OptionalString } from '../../utils/utils'; + +interface EditTagsModalProps extends ShortUrlModalProps { + shortUrlTags: ShortUrlTags; + editShortUrlTags: (shortCode: string, domain: OptionalString, tags: string[]) => Promise; + resetShortUrlsTags: () => void; +} + +const EditTagsModal = (TagsSelector: FC) => ( // TODO Use TagsSelector type when available + { isOpen, toggle, shortUrl, shortUrlTags, editShortUrlTags, resetShortUrlsTags }: EditTagsModalProps, +) => { + const [ selectedTags, setSelectedTags ] = useState(shortUrl.tags || []); + + useEffect(() => resetShortUrlsTags, []); + + const url = shortUrl?.shortUrl ?? ''; + const saveTags = async () => editShortUrlTags(shortUrl.shortCode, shortUrl.domain, selectedTags) + .then(toggle) + .catch(() => {}); + + return ( + + + Edit tags for + + + + {shortUrlTags.error && ( +
+ Something went wrong while saving the tags :( +
+ )} +
+ + + + +
+ ); +}; + +export default EditTagsModal; diff --git a/src/short-urls/helpers/PreviewModal.js b/src/short-urls/helpers/PreviewModal.tsx similarity index 55% rename from src/short-urls/helpers/PreviewModal.js rename to src/short-urls/helpers/PreviewModal.tsx index 9570ec75..b4c85878 100644 --- a/src/short-urls/helpers/PreviewModal.js +++ b/src/short-urls/helpers/PreviewModal.tsx @@ -1,29 +1,21 @@ import React from 'react'; import { Modal, ModalBody, ModalHeader } from 'reactstrap'; -import PropTypes from 'prop-types'; import { ExternalLink } from 'react-external-link'; +import { ShortUrlModalProps } from '../data'; import './PreviewModal.scss'; -const propTypes = { - url: PropTypes.string, - toggle: PropTypes.func, - isOpen: PropTypes.bool, -}; - -const PreviewModal = ({ url, toggle, isOpen }) => ( +const PreviewModal = ({ shortUrl: { shortUrl }, toggle, isOpen }: ShortUrlModalProps) => ( - Preview for {url} + Preview for {shortUrl}

Loading...

- Preview + Preview
); -PreviewModal.propTypes = propTypes; - export default PreviewModal; diff --git a/src/short-urls/helpers/QrCodeModal.js b/src/short-urls/helpers/QrCodeModal.tsx similarity index 51% rename from src/short-urls/helpers/QrCodeModal.js rename to src/short-urls/helpers/QrCodeModal.tsx index dcc8b323..f050f662 100644 --- a/src/short-urls/helpers/QrCodeModal.js +++ b/src/short-urls/helpers/QrCodeModal.tsx @@ -1,28 +1,20 @@ import React from 'react'; import { Modal, ModalBody, ModalHeader } from 'reactstrap'; -import PropTypes from 'prop-types'; import { ExternalLink } from 'react-external-link'; +import { ShortUrlModalProps } from '../data'; import './QrCodeModal.scss'; -const propTypes = { - url: PropTypes.string, - toggle: PropTypes.func, - isOpen: PropTypes.bool, -}; - -const QrCodeModal = ({ url, toggle, isOpen }) => ( +const QrCodeModal = ({ shortUrl: { shortUrl }, toggle, isOpen }: ShortUrlModalProps) => ( - QR code for {url} + QR code for {shortUrl}
- QR code + QR code
); -QrCodeModal.propTypes = propTypes; - export default QrCodeModal; diff --git a/src/short-urls/helpers/ShortUrlVisitsCount.js b/src/short-urls/helpers/ShortUrlVisitsCount.tsx similarity index 70% rename from src/short-urls/helpers/ShortUrlVisitsCount.js rename to src/short-urls/helpers/ShortUrlVisitsCount.tsx index e33ae1c0..b87af090 100644 --- a/src/short-urls/helpers/ShortUrlVisitsCount.js +++ b/src/short-urls/helpers/ShortUrlVisitsCount.tsx @@ -1,24 +1,19 @@ import React, { useRef } from 'react'; -import PropTypes from 'prop-types'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faInfoCircle as infoIcon } from '@fortawesome/free-solid-svg-icons'; import { UncontrolledTooltip } from 'reactstrap'; import classNames from 'classnames'; -import { serverType } from '../../servers/prop-types'; import { prettify } from '../../utils/helpers/numbers'; -import { shortUrlType } from '../reducers/shortUrlsList'; -import VisitStatsLink from './VisitStatsLink'; +import VisitStatsLink, { VisitStatsLinkProps } from './VisitStatsLink'; import './ShortUrlVisitsCount.scss'; -const propTypes = { - visitsCount: PropTypes.number.isRequired, - shortUrl: shortUrlType, - selectedServer: serverType, - active: PropTypes.bool, -}; +export interface ShortUrlVisitsCount extends VisitStatsLinkProps { + visitsCount: number; + active?: boolean; +} -const ShortUrlVisitsCount = ({ visitsCount, shortUrl, selectedServer, active = false }) => { - const maxVisits = shortUrl && shortUrl.meta && shortUrl.meta.maxVisits; +const ShortUrlVisitsCount = ({ visitsCount, shortUrl, selectedServer, active = false }: ShortUrlVisitsCount) => { + const maxVisits = shortUrl?.meta?.maxVisits; const visitsLink = ( (); return ( @@ -52,13 +47,11 @@ const ShortUrlVisitsCount = ({ visitsCount, shortUrl, selectedServer, active = f
- tooltipRef.current} placement="bottom"> + tooltipRef.current) as any} placement="bottom"> This short URL will not accept more than {prettifiedMaxVisits} visits. ); }; -ShortUrlVisitsCount.propTypes = propTypes; - export default ShortUrlVisitsCount; diff --git a/src/short-urls/helpers/ShortUrlsRow.js b/src/short-urls/helpers/ShortUrlsRow.js deleted file mode 100644 index f2762a9f..00000000 --- a/src/short-urls/helpers/ShortUrlsRow.js +++ /dev/null @@ -1,98 +0,0 @@ -import { isEmpty } from 'ramda'; -import React, { useEffect, useRef } from 'react'; -import Moment from 'react-moment'; -import PropTypes from 'prop-types'; -import { ExternalLink } from 'react-external-link'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { faCopy as copyIcon } from '@fortawesome/free-regular-svg-icons'; -import { CopyToClipboard } from 'react-copy-to-clipboard'; -import { shortUrlsListParamsType } from '../reducers/shortUrlsListParams'; -import { serverType } from '../../servers/prop-types'; -import { shortUrlType } from '../reducers/shortUrlsList'; -import Tag from '../../tags/helpers/Tag'; -import ShortUrlVisitsCount from './ShortUrlVisitsCount'; -import './ShortUrlsRow.scss'; - -const propTypes = { - refreshList: PropTypes.func, - shortUrlsListParams: shortUrlsListParamsType, - selectedServer: serverType, - shortUrl: shortUrlType, -}; - -const ShortUrlsRow = ( - ShortUrlsRowMenu, - colorGenerator, - useStateFlagTimeout, -) => { - const ShortUrlsRowComp = ({ shortUrl, selectedServer, refreshList, shortUrlsListParams }) => { - const [ copiedToClipboard, setCopiedToClipboard ] = useStateFlagTimeout(); - const [ active, setActive ] = useStateFlagTimeout(false, 500); - const isFirstRun = useRef(true); - - const renderTags = (tags) => { - if (isEmpty(tags)) { - return No tags; - } - - const selectedTags = shortUrlsListParams.tags || []; - - return tags.map((tag) => ( - refreshList({ tags: [ ...selectedTags, tag ] })} - /> - )); - }; - - useEffect(() => { - if (isFirstRun.current) { - isFirstRun.current = false; - } else { - setActive(true); - } - }, [ shortUrl.visitsCount ]); - - return ( - - - {shortUrl.dateCreated} - - - - - - - - - - - - - - {renderTags(shortUrl.tags)} - - - - - - - - ); - }; - - ShortUrlsRowComp.propTypes = propTypes; - - return ShortUrlsRowComp; -}; - -export default ShortUrlsRow; diff --git a/src/short-urls/helpers/ShortUrlsRow.tsx b/src/short-urls/helpers/ShortUrlsRow.tsx new file mode 100644 index 00000000..be524892 --- /dev/null +++ b/src/short-urls/helpers/ShortUrlsRow.tsx @@ -0,0 +1,94 @@ +import { isEmpty } from 'ramda'; +import React, { FC, useEffect, useRef } from 'react'; +import Moment from 'react-moment'; +import { ExternalLink } from 'react-external-link'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faCopy as copyIcon } from '@fortawesome/free-regular-svg-icons'; +import CopyToClipboard from 'react-copy-to-clipboard'; +import { ShortUrlsListParams } from '../reducers/shortUrlsListParams'; +import ColorGenerator from '../../utils/services/ColorGenerator'; +import { StateFlagTimeout } from '../../utils/helpers/hooks'; +import Tag from '../../tags/helpers/Tag'; +import { SelectedServer } from '../../servers/data'; +import { ShortUrl } from '../data'; +import ShortUrlVisitsCount from './ShortUrlVisitsCount'; +import { ShortUrlsRowMenuProps } from './ShortUrlsRowMenu'; +import './ShortUrlsRow.scss'; + +export interface ShortUrlsRowProps { + refreshList: Function; + shortUrlsListParams: ShortUrlsListParams; + selectedServer: SelectedServer; + shortUrl: ShortUrl; +} + +const ShortUrlsRow = ( + ShortUrlsRowMenu: FC, + colorGenerator: ColorGenerator, + useStateFlagTimeout: StateFlagTimeout, +) => ({ shortUrl, selectedServer, refreshList, shortUrlsListParams }: ShortUrlsRowProps) => { + const [ copiedToClipboard, setCopiedToClipboard ] = useStateFlagTimeout(); + const [ active, setActive ] = useStateFlagTimeout(false, 500); + const isFirstRun = useRef(true); + + const renderTags = (tags: string[]) => { + if (isEmpty(tags)) { + return No tags; + } + + const selectedTags = shortUrlsListParams.tags ?? []; + + return tags.map((tag) => ( + refreshList({ tags: [ ...selectedTags, tag ] })} + /> + )); + }; + + useEffect(() => { + if (isFirstRun.current) { + isFirstRun.current = false; + } else { + setActive(); + } + }, [ shortUrl.visitsCount ]); + + return ( + + + {shortUrl.dateCreated} + + + + + + + + + + + + + + {renderTags(shortUrl.tags)} + + + + + + + + ); +}; + +export default ShortUrlsRow; diff --git a/src/short-urls/helpers/ShortUrlsRowMenu.js b/src/short-urls/helpers/ShortUrlsRowMenu.js deleted file mode 100644 index ae6dc1e7..00000000 --- a/src/short-urls/helpers/ShortUrlsRowMenu.js +++ /dev/null @@ -1,95 +0,0 @@ -import { faImage as pictureIcon } from '@fortawesome/free-regular-svg-icons'; -import { - faTags as tagsIcon, - faChartPie as pieChartIcon, - faEllipsisV as menuIcon, - faQrcode as qrIcon, - faMinusCircle as deleteIcon, - faEdit as editIcon, - faLink as linkIcon, -} from '@fortawesome/free-solid-svg-icons'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import React from 'react'; -import { ButtonDropdown, DropdownItem, DropdownMenu, DropdownToggle } from 'reactstrap'; -import { serverType } from '../../servers/prop-types'; -import { shortUrlType } from '../reducers/shortUrlsList'; -import { useToggle } from '../../utils/helpers/hooks'; -import PreviewModal from './PreviewModal'; -import QrCodeModal from './QrCodeModal'; -import VisitStatsLink from './VisitStatsLink'; -import './ShortUrlsRowMenu.scss'; - -const propTypes = { - selectedServer: serverType, - shortUrl: shortUrlType, -}; - -const ShortUrlsRowMenu = (DeleteShortUrlModal, EditTagsModal, EditMetaModal, EditShortUrlModal, ForServerVersion) => { - const ShortUrlsRowMenuComp = ({ shortUrl, selectedServer }) => { - const [ isOpen, toggle ] = useToggle(); - const [ isQrModalOpen, toggleQrCode ] = useToggle(); - const [ isPreviewModalOpen, togglePreview ] = useToggle(); - const [ isTagsModalOpen, toggleTags ] = useToggle(); - const [ isMetaModalOpen, toggleMeta ] = useToggle(); - const [ isDeleteModalOpen, toggleDelete ] = useToggle(); - const [ isEditModalOpen, toggleEdit ] = useToggle(); - const completeShortUrl = shortUrl && shortUrl.shortUrl ? shortUrl.shortUrl : ''; - - return ( - - -    - - - - Visit stats - - - - Edit tags - - - - - - Edit metadata - - - - - - - Edit long URL - - - - - - QR code - - - - - - Preview - - - - - - - - Delete short URL - - - - - ); - }; - - ShortUrlsRowMenuComp.propTypes = propTypes; - - return ShortUrlsRowMenuComp; -}; - -export default ShortUrlsRowMenu; diff --git a/src/short-urls/helpers/ShortUrlsRowMenu.tsx b/src/short-urls/helpers/ShortUrlsRowMenu.tsx new file mode 100644 index 00000000..141a5947 --- /dev/null +++ b/src/short-urls/helpers/ShortUrlsRowMenu.tsx @@ -0,0 +1,96 @@ +import { faImage as pictureIcon } from '@fortawesome/free-regular-svg-icons'; +import { + faTags as tagsIcon, + faChartPie as pieChartIcon, + faEllipsisV as menuIcon, + faQrcode as qrIcon, + faMinusCircle as deleteIcon, + faEdit as editIcon, + faLink as linkIcon, +} from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import React, { FC } from 'react'; +import { ButtonDropdown, DropdownItem, DropdownMenu, DropdownToggle } from 'reactstrap'; +import { useToggle } from '../../utils/helpers/hooks'; +import { ShortUrl, ShortUrlModalProps } from '../data'; +import { Versions } from '../../utils/helpers/version'; +import { SelectedServer } from '../../servers/data'; +import PreviewModal from './PreviewModal'; +import QrCodeModal from './QrCodeModal'; +import VisitStatsLink from './VisitStatsLink'; +import './ShortUrlsRowMenu.scss'; + +export interface ShortUrlsRowMenuProps { + selectedServer: SelectedServer; + shortUrl: ShortUrl; +} +type ShortUrlModal = FC; + +const ShortUrlsRowMenu = ( + DeleteShortUrlModal: ShortUrlModal, + EditTagsModal: ShortUrlModal, + EditMetaModal: ShortUrlModal, + EditShortUrlModal: ShortUrlModal, + ForServerVersion: FC, +) => ({ shortUrl, selectedServer }: ShortUrlsRowMenuProps) => { + const [ isOpen, toggle ] = useToggle(); + const [ isQrModalOpen, toggleQrCode ] = useToggle(); + const [ isPreviewModalOpen, togglePreview ] = useToggle(); + const [ isTagsModalOpen, toggleTags ] = useToggle(); + const [ isMetaModalOpen, toggleMeta ] = useToggle(); + const [ isDeleteModalOpen, toggleDelete ] = useToggle(); + const [ isEditModalOpen, toggleEdit ] = useToggle(); + + return ( + + +    + + + + Visit stats + + + + Edit tags + + + + + + Edit metadata + + + + + + + Edit long URL + + + + + + QR code + + + + + + Preview + + + + + + + + Delete short URL + + + + + ); +}; + +export default ShortUrlsRowMenu; diff --git a/src/short-urls/helpers/VisitStatsLink.js b/src/short-urls/helpers/VisitStatsLink.js deleted file mode 100644 index f500e580..00000000 --- a/src/short-urls/helpers/VisitStatsLink.js +++ /dev/null @@ -1,29 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { Link } from 'react-router-dom'; -import { serverType } from '../../servers/prop-types'; -import { shortUrlType } from '../reducers/shortUrlsList'; - -const propTypes = { - shortUrl: shortUrlType, - selectedServer: serverType, - children: PropTypes.node.isRequired, -}; - -const buildVisitsUrl = ({ id }, { shortCode, domain }) => { - const query = domain ? `?domain=${domain}` : ''; - - return `/server/${id}/short-code/${shortCode}/visits${query}`; -}; - -const VisitStatsLink = ({ selectedServer, shortUrl, children, ...rest }) => { - if (!selectedServer || !shortUrl) { - return {children}; - } - - return {children}; -}; - -VisitStatsLink.propTypes = propTypes; - -export default VisitStatsLink; diff --git a/src/short-urls/helpers/VisitStatsLink.tsx b/src/short-urls/helpers/VisitStatsLink.tsx new file mode 100644 index 00000000..7138533a --- /dev/null +++ b/src/short-urls/helpers/VisitStatsLink.tsx @@ -0,0 +1,27 @@ +import React, { FC } from 'react'; +import { Link } from 'react-router-dom'; +import { isServerWithId, SelectedServer, ServerWithId } from '../../servers/data'; +import { ShortUrl } from '../data'; + +export interface VisitStatsLinkProps { + shortUrl?: ShortUrl | null; + selectedServer?: SelectedServer; +} + +const buildVisitsUrl = ({ id }: ServerWithId, { shortCode, domain }: ShortUrl) => { + const query = domain ? `?domain=${domain}` : ''; + + return `/server/${id}/short-code/${shortCode}/visits${query}`; +}; + +const VisitStatsLink: FC> = ( + { selectedServer, shortUrl, children, ...rest }, +) => { + if (!selectedServer || !isServerWithId(selectedServer) || !shortUrl) { + return {children}; + } + + return {children}; +}; + +export default VisitStatsLink; diff --git a/src/short-urls/reducers/shortUrlTags.ts b/src/short-urls/reducers/shortUrlTags.ts index cdaf9269..b81380f1 100644 --- a/src/short-urls/reducers/shortUrlTags.ts +++ b/src/short-urls/reducers/shortUrlTags.ts @@ -1,4 +1,3 @@ -import PropTypes from 'prop-types'; import { Action, Dispatch } from 'redux'; import { buildActionCreator, buildReducer } from '../../utils/helpers/redux'; import { GetState } from '../../container/types'; @@ -13,14 +12,6 @@ export const SHORT_URL_TAGS_EDITED = 'shlink/shortUrlTags/SHORT_URL_TAGS_EDITED' export const RESET_EDIT_SHORT_URL_TAGS = 'shlink/shortUrlTags/RESET_EDIT_SHORT_URL_TAGS'; /* eslint-enable padding-line-between-statements */ -/** @deprecated Use ShortUrlTags interface */ -export const shortUrlTagsType = PropTypes.shape({ - shortCode: PropTypes.string, - tags: PropTypes.arrayOf(PropTypes.string).isRequired, - saving: PropTypes.bool.isRequired, - error: PropTypes.bool.isRequired, -}); - export interface ShortUrlTags { shortCode: string | null; tags: string[]; diff --git a/src/short-urls/reducers/shortUrlsList.ts b/src/short-urls/reducers/shortUrlsList.ts index 6294dd17..de4a2370 100644 --- a/src/short-urls/reducers/shortUrlsList.ts +++ b/src/short-urls/reducers/shortUrlsList.ts @@ -6,12 +6,12 @@ import { CREATE_VISIT, CreateVisitAction } from '../../visits/reducers/visitCrea import { ShortUrl, ShortUrlIdentifier } from '../data'; import { buildReducer } from '../../utils/helpers/redux'; import { GetState } from '../../container/types'; +import { ShlinkApiClientBuilder } from '../../utils/services/ShlinkApiClientBuilder'; import { EditShortUrlTagsAction, SHORT_URL_TAGS_EDITED } from './shortUrlTags'; import { SHORT_URL_DELETED } from './shortUrlDeletion'; import { SHORT_URL_META_EDITED, ShortUrlMetaEditedAction, shortUrlMetaType } from './shortUrlMeta'; import { SHORT_URL_EDITED, ShortUrlEditedAction } from './shortUrlEdition'; import { ShortUrlsListParams } from './shortUrlsListParams'; -import { ShlinkApiClientBuilder } from '../../utils/services/ShlinkApiClientBuilder'; /* eslint-disable padding-line-between-statements */ export const LIST_SHORT_URLS_START = 'shlink/shortUrlsList/LIST_SHORT_URLS_START'; diff --git a/src/utils/helpers/leaflet.js b/src/utils/helpers/leaflet.ts similarity index 80% rename from src/utils/helpers/leaflet.js rename to src/utils/helpers/leaflet.ts index 8b9ac82f..2c1f621b 100644 --- a/src/utils/helpers/leaflet.js +++ b/src/utils/helpers/leaflet.ts @@ -1,12 +1,10 @@ -// TODO Migrate this file to Typescript - import L from 'leaflet'; import marker2x from 'leaflet/dist/images/marker-icon-2x.png'; import marker from 'leaflet/dist/images/marker-icon.png'; import markerShadow from 'leaflet/dist/images/marker-shadow.png'; export const fixLeafletIcons = () => { - delete L.Icon.Default.prototype._getIconUrl; + delete (L.Icon.Default.prototype as any)._getIconUrl; L.Icon.Default.mergeOptions({ iconRetinaUrl: marker2x, diff --git a/test/short-urls/helpers/CreateShortUrlResult.test.js b/test/short-urls/helpers/CreateShortUrlResult.test.tsx similarity index 61% rename from test/short-urls/helpers/CreateShortUrlResult.test.js rename to test/short-urls/helpers/CreateShortUrlResult.test.tsx index 09e41ddf..88173622 100644 --- a/test/short-urls/helpers/CreateShortUrlResult.test.js +++ b/test/short-urls/helpers/CreateShortUrlResult.test.tsx @@ -1,28 +1,31 @@ import React from 'react'; -import { shallow } from 'enzyme'; +import { shallow, ShallowWrapper } from 'enzyme'; import { identity } from 'ramda'; -import { CopyToClipboard } from 'react-copy-to-clipboard'; +import CopyToClipboard from 'react-copy-to-clipboard'; import { Tooltip } from 'reactstrap'; +import { Mock } from 'ts-mockery'; import createCreateShortUrlResult from '../../../src/short-urls/helpers/CreateShortUrlResult'; +import { ShortUrl } from '../../../src/short-urls/data'; +import { StateFlagTimeout } from '../../../src/utils/helpers/hooks'; describe('', () => { - let wrapper; + let wrapper: ShallowWrapper; const copyToClipboard = jest.fn(); - const useStateFlagTimeout = jest.fn(() => [ false, copyToClipboard ]); + const useStateFlagTimeout = jest.fn(() => [ false, copyToClipboard ]) as StateFlagTimeout; const CreateShortUrlResult = createCreateShortUrlResult(useStateFlagTimeout); - const createWrapper = (result, error = false) => { - wrapper = shallow(); + const createWrapper = (result: ShortUrl | null = null, error = false) => { + wrapper = shallow( + , + ); return wrapper; }; - afterEach(() => { - jest.clearAllMocks(); - wrapper && wrapper.unmount(); - }); + afterEach(jest.clearAllMocks); + afterEach(() => wrapper?.unmount()); it('renders an error when error is true', () => { - const wrapper = createWrapper({}, true); + const wrapper = createWrapper(Mock.all(), true); const errorCard = wrapper.find('.bg-danger'); expect(errorCard).toHaveLength(1); @@ -36,7 +39,7 @@ describe('', () => { }); it('renders a result message when result is provided', () => { - const wrapper = createWrapper({ shortUrl: 'https://doma.in/abc123' }); + const wrapper = createWrapper(Mock.of({ shortUrl: 'https://doma.in/abc123' })); expect(wrapper.html()).toContain('Great! The short URL is https://doma.in/abc123'); expect(wrapper.find(CopyToClipboard)).toHaveLength(1); @@ -44,7 +47,7 @@ describe('', () => { }); it('Invokes tooltip timeout when copy to clipboard button is clicked', () => { - const wrapper = createWrapper({ shortUrl: 'https://doma.in/abc123' }); + const wrapper = createWrapper(Mock.of({ shortUrl: 'https://doma.in/abc123' })); const copyBtn = wrapper.find(CopyToClipboard); expect(copyToClipboard).not.toHaveBeenCalled(); diff --git a/test/short-urls/helpers/EditTagsModal.test.js b/test/short-urls/helpers/EditTagsModal.test.tsx similarity index 74% rename from test/short-urls/helpers/EditTagsModal.test.js rename to test/short-urls/helpers/EditTagsModal.test.tsx index ceef068b..50e94c97 100644 --- a/test/short-urls/helpers/EditTagsModal.test.js +++ b/test/short-urls/helpers/EditTagsModal.test.tsx @@ -1,27 +1,31 @@ import React from 'react'; -import { shallow } from 'enzyme'; +import { shallow, ShallowWrapper } from 'enzyme'; import { Modal } from 'reactstrap'; +import { Mock } from 'ts-mockery'; import createEditTagsModal from '../../../src/short-urls/helpers/EditTagsModal'; +import { ShortUrl } from '../../../src/short-urls/data'; +import { ShortUrlTags } from '../../../src/short-urls/reducers/shortUrlTags'; +import { OptionalString } from '../../../src/utils/utils'; describe('', () => { - let wrapper; + let wrapper: ShallowWrapper; const shortCode = 'abc123'; - const TagsSelector = () => ''; - const editShortUrlTags = jest.fn(() => Promise.resolve()); + const TagsSelector = () => null; + const editShortUrlTags = jest.fn(async () => Promise.resolve()); const resetShortUrlsTags = jest.fn(); const toggle = jest.fn(); - const createWrapper = (shortUrlTags, domain) => { + const createWrapper = (shortUrlTags: ShortUrlTags, domain?: OptionalString) => { const EditTagsModal = createEditTagsModal(TagsSelector); wrapper = shallow( ({ tags: [], shortCode, domain, - originalUrl: 'https://long-domain.com/foo/bar', - }} + longUrl: 'https://long-domain.com/foo/bar', + })} shortUrlTags={shortUrlTags} toggle={toggle} editShortUrlTags={editShortUrlTags} @@ -32,10 +36,8 @@ describe('', () => { return wrapper; }; - afterEach(() => { - wrapper && wrapper.unmount(); - jest.clearAllMocks(); - }); + afterEach(() => wrapper?.unmount()); + afterEach(jest.clearAllMocks); it('renders tags selector and save button when loaded', () => { const wrapper = createWrapper({ @@ -64,7 +66,12 @@ describe('', () => { expect(saveBtn.text()).toEqual('Saving tags...'); }); - it.each([[ undefined ], [ null ], [ 'example.com' ]])('saves tags when save button is clicked', (domain, done) => { + it.each([ + [ undefined ], + [ null ], + [ 'example.com' ], + // @ts-expect-error + ])('saves tags when save button is clicked', (domain: OptionalString, done: jest.DoneCallback) => { const wrapper = createWrapper({ shortCode, tags: [], diff --git a/test/short-urls/helpers/PreviewModal.test.js b/test/short-urls/helpers/PreviewModal.test.tsx similarity index 55% rename from test/short-urls/helpers/PreviewModal.test.js rename to test/short-urls/helpers/PreviewModal.test.tsx index ee7356be..94c1145d 100644 --- a/test/short-urls/helpers/PreviewModal.test.js +++ b/test/short-urls/helpers/PreviewModal.test.tsx @@ -1,14 +1,16 @@ import React from 'react'; -import { shallow } from 'enzyme'; +import { shallow, ShallowWrapper } from 'enzyme'; import { ExternalLink } from 'react-external-link'; +import { Mock } from 'ts-mockery'; import PreviewModal from '../../../src/short-urls/helpers/PreviewModal'; +import { ShortUrl } from '../../../src/short-urls/data'; describe('', () => { - let wrapper; - const url = 'https://doma.in/abc123'; + let wrapper: ShallowWrapper; + const shortUrl = 'https://doma.in/abc123'; beforeEach(() => { - wrapper = shallow(); + wrapper = shallow(({ shortUrl })} isOpen={true} toggle={() => {}} />); }); afterEach(() => wrapper.unmount()); @@ -16,13 +18,13 @@ describe('', () => { const externalLink = wrapper.find(ExternalLink); expect(externalLink).toHaveLength(1); - expect(externalLink.prop('href')).toEqual(url); + expect(externalLink.prop('href')).toEqual(shortUrl); }); it('displays an image with the preview of the URL', () => { const img = wrapper.find('img'); expect(img).toHaveLength(1); - expect(img.prop('src')).toEqual(`${url}/preview`); + expect(img.prop('src')).toEqual(`${shortUrl}/preview`); }); }); diff --git a/test/short-urls/helpers/QrCodeModal.test.js b/test/short-urls/helpers/QrCodeModal.test.tsx similarity index 55% rename from test/short-urls/helpers/QrCodeModal.test.js rename to test/short-urls/helpers/QrCodeModal.test.tsx index 44ccf2a3..b0d6b655 100644 --- a/test/short-urls/helpers/QrCodeModal.test.js +++ b/test/short-urls/helpers/QrCodeModal.test.tsx @@ -1,14 +1,16 @@ import React from 'react'; -import { shallow } from 'enzyme'; +import { shallow, ShallowWrapper } from 'enzyme'; import { ExternalLink } from 'react-external-link'; +import { Mock } from 'ts-mockery'; import QrCodeModal from '../../../src/short-urls/helpers/QrCodeModal'; +import { ShortUrl } from '../../../src/short-urls/data'; describe('', () => { - let wrapper; - const url = 'https://doma.in/abc123'; + let wrapper: ShallowWrapper; + const shortUrl = 'https://doma.in/abc123'; beforeEach(() => { - wrapper = shallow(); + wrapper = shallow(({ shortUrl })} isOpen={true} toggle={() => {}} />); }); afterEach(() => wrapper.unmount()); @@ -16,13 +18,13 @@ describe('', () => { const externalLink = wrapper.find(ExternalLink); expect(externalLink).toHaveLength(1); - expect(externalLink.prop('href')).toEqual(url); + expect(externalLink.prop('href')).toEqual(shortUrl); }); it('displays an image with the QR code of the URL', () => { const img = wrapper.find('img'); expect(img).toHaveLength(1); - expect(img.prop('src')).toEqual(`${url}/qr-code`); + expect(img.prop('src')).toEqual(`${shortUrl}/qr-code`); }); }); diff --git a/test/short-urls/helpers/ShortUrlVisitsCount.test.js b/test/short-urls/helpers/ShortUrlVisitsCount.test.tsx similarity index 74% rename from test/short-urls/helpers/ShortUrlVisitsCount.test.js rename to test/short-urls/helpers/ShortUrlVisitsCount.test.tsx index c7f34659..1803c4e8 100644 --- a/test/short-urls/helpers/ShortUrlVisitsCount.test.js +++ b/test/short-urls/helpers/ShortUrlVisitsCount.test.tsx @@ -1,22 +1,24 @@ import React from 'react'; -import { shallow } from 'enzyme'; +import { shallow, ShallowWrapper } from 'enzyme'; import { UncontrolledTooltip } from 'reactstrap'; +import { Mock } from 'ts-mockery'; import ShortUrlVisitsCount from '../../../src/short-urls/helpers/ShortUrlVisitsCount'; +import { ShortUrl } from '../../../src/short-urls/data'; describe('', () => { - let wrapper; + let wrapper: ShallowWrapper; - const createWrapper = (visitsCount, shortUrl) => { + const createWrapper = (visitsCount: number, shortUrl: ShortUrl) => { wrapper = shallow(); return wrapper; }; - afterEach(() => wrapper && wrapper.unmount()); + afterEach(() => wrapper?.unmount()); it.each([ undefined, {}])('just returns visits when no maxVisits is provided', (meta) => { const visitsCount = 45; - const wrapper = createWrapper(visitsCount, { meta }); + const wrapper = createWrapper(visitsCount, Mock.of({ meta })); const maxVisitsHelper = wrapper.find('.short-urls-visits-count__max-visits-control'); const maxVisitsTooltip = wrapper.find(UncontrolledTooltip); @@ -31,7 +33,7 @@ describe('', () => { const visitsCount = 45; const maxVisits = 500; const meta = { maxVisits }; - const wrapper = createWrapper(visitsCount, { meta }); + const wrapper = createWrapper(visitsCount, Mock.of({ meta })); const maxVisitsHelper = wrapper.find('.short-urls-visits-count__max-visits-control'); const maxVisitsTooltip = wrapper.find(UncontrolledTooltip); diff --git a/test/short-urls/helpers/ShortUrlsRow.test.js b/test/short-urls/helpers/ShortUrlsRow.test.tsx similarity index 75% rename from test/short-urls/helpers/ShortUrlsRow.test.js rename to test/short-urls/helpers/ShortUrlsRow.test.tsx index 38367d45..e878ec57 100644 --- a/test/short-urls/helpers/ShortUrlsRow.test.js +++ b/test/short-urls/helpers/ShortUrlsRow.test.tsx @@ -1,40 +1,51 @@ import React from 'react'; -import { shallow } from 'enzyme'; +import { shallow, ShallowWrapper } from 'enzyme'; import moment from 'moment'; import Moment from 'react-moment'; import { assoc, toString } from 'ramda'; +import { Mock } from 'ts-mockery'; import { ExternalLink } from 'react-external-link'; -import { CopyToClipboard } from 'react-copy-to-clipboard'; +import CopyToClipboard from 'react-copy-to-clipboard'; import createShortUrlsRow from '../../../src/short-urls/helpers/ShortUrlsRow'; import Tag from '../../../src/tags/helpers/Tag'; +import ColorGenerator from '../../../src/utils/services/ColorGenerator'; +import { StateFlagTimeout } from '../../../src/utils/helpers/hooks'; +import { ShortUrl } from '../../../src/short-urls/data'; +import { ReachableServer } from '../../../src/servers/data'; describe('', () => { - let wrapper; - const mockFunction = () => ''; + let wrapper: ShallowWrapper; + const mockFunction = () => null; const ShortUrlsRowMenu = mockFunction; const stateFlagTimeout = jest.fn(() => true); - const useStateFlagTimeout = jest.fn(() => [ false, stateFlagTimeout ]); - const colorGenerator = { - getColorForKey: mockFunction, - setColorForKey: mockFunction, - }; - const server = { + const useStateFlagTimeout = jest.fn(() => [ false, stateFlagTimeout ]) as StateFlagTimeout; + const colorGenerator = Mock.of({ + getColorForKey: jest.fn(), + setColorForKey: jest.fn(), + }); + const server = Mock.of({ url: 'https://doma.in', - }; - const shortUrl = { + }); + const shortUrl: ShortUrl = { shortCode: 'abc123', shortUrl: 'http://doma.in/abc123', longUrl: 'http://foo.com/bar', dateCreated: moment('2018-05-23 18:30:41').format(), tags: [ 'nodejs', 'reactjs' ], visitsCount: 45, + domain: null, + meta: { + validSince: null, + validUntil: null, + maxVisits: null, + }, }; beforeEach(() => { const ShortUrlsRow = createShortUrlsRow(ShortUrlsRowMenu, colorGenerator, useStateFlagTimeout); wrapper = shallow( - , + , ); }); afterEach(() => wrapper.unmount()); diff --git a/test/short-urls/helpers/ShortUrlsRowMenu.test.js b/test/short-urls/helpers/ShortUrlsRowMenu.test.tsx similarity index 67% rename from test/short-urls/helpers/ShortUrlsRowMenu.test.js rename to test/short-urls/helpers/ShortUrlsRowMenu.test.tsx index 28e1c5c5..e807ed19 100644 --- a/test/short-urls/helpers/ShortUrlsRowMenu.test.js +++ b/test/short-urls/helpers/ShortUrlsRowMenu.test.tsx @@ -1,43 +1,39 @@ import React from 'react'; -import { shallow } from 'enzyme'; +import { shallow, ShallowWrapper } from 'enzyme'; import { ButtonDropdown, DropdownItem } from 'reactstrap'; +import { Mock } from 'ts-mockery'; import createShortUrlsRowMenu from '../../../src/short-urls/helpers/ShortUrlsRowMenu'; import PreviewModal from '../../../src/short-urls/helpers/PreviewModal'; import QrCodeModal from '../../../src/short-urls/helpers/QrCodeModal'; +import { ReachableServer } from '../../../src/servers/data'; +import { ShortUrl } from '../../../src/short-urls/data'; describe('', () => { - let wrapper; - const DeleteShortUrlModal = () => ''; - const EditTagsModal = () => ''; - const EditMetaModal = () => ''; - const EditShortUrlModal = () => ''; - const onCopyToClipboard = jest.fn(); - const selectedServer = { id: 'abc123' }; - const shortUrl = { + let wrapper: ShallowWrapper; + const DeleteShortUrlModal = () => null; + const EditTagsModal = () => null; + const EditMetaModal = () => null; + const EditShortUrlModal = () => null; + const selectedServer = Mock.of({ id: 'abc123' }); + const shortUrl = Mock.of({ shortCode: 'abc123', shortUrl: 'https://doma.in/abc123', - }; + }); const createWrapper = () => { const ShortUrlsRowMenu = createShortUrlsRowMenu( DeleteShortUrlModal, EditTagsModal, EditMetaModal, EditShortUrlModal, - () => '', + () => null, ); - wrapper = shallow( - , - ); + wrapper = shallow(); return wrapper; }; - afterEach(() => wrapper && wrapper.unmount()); + afterEach(() => wrapper?.unmount()); it('renders modal windows', () => { const wrapper = createWrapper(); @@ -62,8 +58,8 @@ describe('', () => { expect(items.find('[divider]')).toHaveLength(1); }); - describe('toggles state when toggling modal windows', () => { - const assert = (modalComponent) => { + describe('toggles state when toggling modals or the dropdown', () => { + const assert = (modalComponent: Function) => { const wrapper = createWrapper(); expect(wrapper.find(modalComponent).prop('isOpen')).toEqual(false); @@ -76,13 +72,6 @@ describe('', () => { it('PreviewModal', () => assert(PreviewModal)); it('QrCodeModal', () => assert(QrCodeModal)); it('EditShortUrlModal', () => assert(EditShortUrlModal)); - }); - - it('toggles dropdown state when toggling dropdown', () => { - const wrapper = createWrapper(); - - expect(wrapper.find(ButtonDropdown).prop('isOpen')).toEqual(false); - wrapper.find(ButtonDropdown).prop('toggle')(); - expect(wrapper.find(ButtonDropdown).prop('isOpen')).toEqual(true); + it('EditShortUrlModal', () => assert(ButtonDropdown)); }); }); diff --git a/test/short-urls/helpers/VisitStatsLink.test.js b/test/short-urls/helpers/VisitStatsLink.test.tsx similarity index 51% rename from test/short-urls/helpers/VisitStatsLink.test.js rename to test/short-urls/helpers/VisitStatsLink.test.tsx index f029304e..a2d076dc 100644 --- a/test/short-urls/helpers/VisitStatsLink.test.js +++ b/test/short-urls/helpers/VisitStatsLink.test.tsx @@ -1,21 +1,25 @@ import React from 'react'; -import { shallow } from 'enzyme'; +import { shallow, ShallowWrapper } from 'enzyme'; import { Link } from 'react-router-dom'; +import { Mock } from 'ts-mockery'; import VisitStatsLink from '../../../src/short-urls/helpers/VisitStatsLink'; +import { NotFoundServer, ReachableServer } from '../../../src/servers/data'; +import { ShortUrl } from '../../../src/short-urls/data'; describe('', () => { - let wrapper; + let wrapper: ShallowWrapper; - afterEach(() => wrapper && wrapper.unmount()); + afterEach(() => wrapper?.unmount()); it.each([ [ undefined, undefined ], [ null, null ], - [{}, null ], - [{}, undefined ], - [ null, {}], - [ undefined, {}], - ])('only renders a plan span when either server or short URL are not set', (selectedServer, shortUrl) => { + [ Mock.of({ id: '1' }), null ], + [ Mock.of({ id: '1' }), undefined ], + [ Mock.of(), Mock.all() ], + [ null, Mock.all() ], + [ undefined, Mock.all() ], + ])('only renders a plain span when either server or short URL are not set', (selectedServer, shortUrl) => { wrapper = shallow(Something); const link = wrapper.find(Link); @@ -24,10 +28,14 @@ describe('', () => { }); it.each([ - [{ id: '1' }, { shortCode: 'abc123' }, '/server/1/short-code/abc123/visits' ], [ - { id: '3' }, - { shortCode: 'def456', domain: 'example.com' }, + Mock.of({ id: '1' }), + Mock.of({ shortCode: 'abc123' }), + '/server/1/short-code/abc123/visits', + ], + [ + Mock.of({ id: '3' }), + Mock.of({ shortCode: 'def456', domain: 'example.com' }), '/server/3/short-code/def456/visits?domain=example.com', ], ])('renders link with expected query when', (selectedServer, shortUrl, expectedLink) => { diff --git a/test/utils/helpers/leaflet.test.js b/test/utils/helpers/leaflet.test.ts similarity index 100% rename from test/utils/helpers/leaflet.test.js rename to test/utils/helpers/leaflet.test.ts From 8a9c694fbcb950da16623ab423f2e73ebdc05a8e Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 30 Aug 2020 19:45:17 +0200 Subject: [PATCH 40/59] Migrated all remaining short-url elements to TS --- src/short-urls/CreateShortUrl.js | 171 ----------------- src/short-urls/CreateShortUrl.tsx | 175 +++++++++++++++++ .../{Paginator.js => Paginator.tsx} | 19 +- src/short-urls/SearchBar.js | 81 -------- src/short-urls/SearchBar.tsx | 78 ++++++++ src/short-urls/ShortUrls.js | 41 ---- src/short-urls/ShortUrls.tsx | 33 ++++ src/short-urls/ShortUrlsList.js | 178 ------------------ src/short-urls/ShortUrlsList.tsx | 177 +++++++++++++++++ ...Icon.js => UseExistingIfFoundInfoIcon.tsx} | 4 +- src/short-urls/data/index.ts | 5 +- .../helpers/CreateShortUrlResult.tsx | 6 +- src/short-urls/reducers/shortUrlCreation.ts | 10 - src/short-urls/reducers/shortUrlsList.ts | 18 +- .../reducers/shortUrlsListParams.ts | 14 +- src/short-urls/services/provideServices.ts | 4 +- src/utils/utils.ts | 2 +- ...ortUrl.test.js => CreateShortUrl.test.tsx} | 29 +-- .../{Paginator.test.js => Paginator.test.tsx} | 6 +- .../{SearchBar.test.js => SearchBar.test.tsx} | 22 +-- .../{ShortUrls.test.js => ShortUrls.test.tsx} | 19 +- ...rlsList.test.js => ShortUrlsList.test.tsx} | 31 +-- ...js => UseExistingIfFoundInfoIcon.test.tsx} | 4 +- .../short-urls/reducers/shortUrlsList.test.ts | 23 +-- 24 files changed, 555 insertions(+), 595 deletions(-) delete mode 100644 src/short-urls/CreateShortUrl.js create mode 100644 src/short-urls/CreateShortUrl.tsx rename src/short-urls/{Paginator.js => Paginator.tsx} (80%) delete mode 100644 src/short-urls/SearchBar.js create mode 100644 src/short-urls/SearchBar.tsx delete mode 100644 src/short-urls/ShortUrls.js create mode 100644 src/short-urls/ShortUrls.tsx delete mode 100644 src/short-urls/ShortUrlsList.js create mode 100644 src/short-urls/ShortUrlsList.tsx rename src/short-urls/{UseExistingIfFoundInfoIcon.js => UseExistingIfFoundInfoIcon.tsx} (93%) rename test/short-urls/{CreateShortUrl.test.js => CreateShortUrl.test.tsx} (72%) rename test/short-urls/{Paginator.test.js => Paginator.test.tsx} (85%) rename test/short-urls/{SearchBar.test.js => SearchBar.test.tsx} (75%) rename test/short-urls/{ShortUrls.test.js => ShortUrls.test.tsx} (61%) rename test/short-urls/{ShortUrlsList.test.js => ShortUrlsList.test.tsx} (80%) rename test/short-urls/{UseExistingIfFoundInfoIcon.test.js => UseExistingIfFoundInfoIcon.test.tsx} (89%) diff --git a/src/short-urls/CreateShortUrl.js b/src/short-urls/CreateShortUrl.js deleted file mode 100644 index e1591fd1..00000000 --- a/src/short-urls/CreateShortUrl.js +++ /dev/null @@ -1,171 +0,0 @@ -import { faAngleDoubleDown as downIcon, faAngleDoubleUp as upIcon } from '@fortawesome/free-solid-svg-icons'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { isEmpty, isNil, pipe, replace, trim } from 'ramda'; -import React, { useState } from 'react'; -import { Collapse, FormGroup, Input } from 'reactstrap'; -import * as PropTypes from 'prop-types'; -import DateInput from '../utils/DateInput'; -import Checkbox from '../utils/Checkbox'; -import { serverType } from '../servers/prop-types'; -import { versionMatch } from '../utils/helpers/version'; -import { handleEventPreventingDefault, hasValue } from '../utils/utils'; -import { useToggle } from '../utils/helpers/hooks'; -import { createShortUrlResultType } from './reducers/shortUrlCreation'; -import UseExistingIfFoundInfoIcon from './UseExistingIfFoundInfoIcon'; - -const normalizeTag = pipe(trim, replace(/ /g, '-')); -const formatDate = (date) => isNil(date) ? date : date.format(); - -const propTypes = { - createShortUrl: PropTypes.func, - shortUrlCreationResult: createShortUrlResultType, - resetCreateShortUrl: PropTypes.func, - selectedServer: serverType, -}; - -const initialState = { - longUrl: '', - tags: [], - customSlug: '', - shortCodeLength: '', - domain: '', - validSince: undefined, - validUntil: undefined, - maxVisits: '', - findIfExists: false, -}; - -const CreateShortUrl = (TagsSelector, CreateShortUrlResult, ForServerVersion) => { - const CreateShortUrlComp = ({ createShortUrl, shortUrlCreationResult, resetCreateShortUrl, selectedServer }) => { - const [ shortUrlCreation, setShortUrlCreation ] = useState(initialState); - const [ moreOptionsVisible, toggleMoreOptionsVisible ] = useToggle(); - - const changeTags = (tags) => setShortUrlCreation({ ...shortUrlCreation, tags: tags.map(normalizeTag) }); - const reset = () => setShortUrlCreation(initialState); - const save = handleEventPreventingDefault(() => { - const shortUrlData = { - ...shortUrlCreation, - validSince: formatDate(shortUrlCreation.validSince), - validUntil: formatDate(shortUrlCreation.validUntil), - }; - - createShortUrl(shortUrlData).then(reset).catch(() => {}); - }); - const renderOptionalInput = (id, placeholder, type = 'text', props = {}) => ( - - setShortUrlCreation({ ...shortUrlCreation, [id]: e.target.value })} - {...props} - /> - - ); - const renderDateInput = (id, placeholder, props = {}) => ( -
- setShortUrlCreation({ ...shortUrlCreation, [id]: date })} - {...props} - /> -
- ); - - const currentServerVersion = selectedServer && selectedServer.version; - const disableDomain = !versionMatch(currentServerVersion, { minVersion: '1.19.0-beta.1' }); - const disableShortCodeLength = !versionMatch(currentServerVersion, { minVersion: '2.1.0' }); - - return ( -
-
- setShortUrlCreation({ ...shortUrlCreation, longUrl: e.target.value })} - /> -
- - -
- -
- -
-
- {renderOptionalInput('customSlug', 'Custom slug')} -
-
- {renderOptionalInput('shortCodeLength', 'Short code length', 'number', { - min: 4, - disabled: disableShortCodeLength || hasValue(shortUrlCreation.customSlug), - ...disableShortCodeLength && { - title: 'Shlink 2.1.0 or higher is required to be able to provide the short code length', - }, - })} -
-
- {renderOptionalInput('domain', 'Domain', 'text', { - disabled: disableDomain, - ...disableDomain && { title: 'Shlink 1.19.0 or higher is required to be able to provide the domain' }, - })} -
-
- -
-
- {renderOptionalInput('maxVisits', 'Maximum number of visits allowed', 'number', { min: 1 })} -
-
- {renderDateInput('validSince', 'Enabled since...', { maxDate: shortUrlCreation.validUntil })} -
-
- {renderDateInput('validUntil', 'Enabled until...', { minDate: shortUrlCreation.validSince })} -
-
- - -
- setShortUrlCreation({ ...shortUrlCreation, findIfExists })} - > - Use existing URL if found - - -
-
-
- -
- - -
- - - - ); - }; - - CreateShortUrlComp.propTypes = propTypes; - - return CreateShortUrlComp; -}; - -export default CreateShortUrl; diff --git a/src/short-urls/CreateShortUrl.tsx b/src/short-urls/CreateShortUrl.tsx new file mode 100644 index 00000000..93f8ee0f --- /dev/null +++ b/src/short-urls/CreateShortUrl.tsx @@ -0,0 +1,175 @@ +import { faAngleDoubleDown as downIcon, faAngleDoubleUp as upIcon } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { isEmpty, pipe, replace, trim } from 'ramda'; +import React, { FC, useState } from 'react'; +import { Collapse, FormGroup, Input } from 'reactstrap'; +import { InputType } from 'reactstrap/lib/Input'; +import * as m from 'moment'; +import DateInput, { DateInputProps } from '../utils/DateInput'; +import Checkbox from '../utils/Checkbox'; +import { versionMatch, Versions } from '../utils/helpers/version'; +import { handleEventPreventingDefault, hasValue } from '../utils/utils'; +import { useToggle } from '../utils/helpers/hooks'; +import { isReachableServer, SelectedServer } from '../servers/data'; +import { formatIsoDate } from '../utils/helpers/date'; +import { ShortUrlData } from './data'; +import { ShortUrlCreation } from './reducers/shortUrlCreation'; +import UseExistingIfFoundInfoIcon from './UseExistingIfFoundInfoIcon'; +import { CreateShortUrlResultProps } from './helpers/CreateShortUrlResult'; + +const normalizeTag = pipe(trim, replace(/ /g, '-')); + +interface CreateShortUrlProps { + shortUrlCreationResult: ShortUrlCreation; + selectedServer: SelectedServer; + createShortUrl: Function; + resetCreateShortUrl: () => void; +} + +const initialState: ShortUrlData = { + longUrl: '', + tags: [], + customSlug: '', + shortCodeLength: undefined, + domain: '', + validSince: undefined, + validUntil: undefined, + maxVisits: undefined, + findIfExists: false, +}; + +type NonDateFields = 'longUrl' | 'customSlug' | 'shortCodeLength' | 'domain' | 'maxVisits'; +type DateFields = 'validSince' | 'validUntil'; + +const CreateShortUrl = ( + TagsSelector: FC, + CreateShortUrlResult: FC, + ForServerVersion: FC, +) => ({ createShortUrl, shortUrlCreationResult, resetCreateShortUrl, selectedServer }: CreateShortUrlProps) => { + const [ shortUrlCreation, setShortUrlCreation ] = useState(initialState); + const [ moreOptionsVisible, toggleMoreOptionsVisible ] = useToggle(); + + const changeTags = (tags: string[]) => setShortUrlCreation({ ...shortUrlCreation, tags: tags.map(normalizeTag) }); + const reset = () => setShortUrlCreation(initialState); + const save = handleEventPreventingDefault(() => { + const shortUrlData = { + ...shortUrlCreation, + validSince: formatIsoDate(shortUrlCreation.validSince), + validUntil: formatIsoDate(shortUrlCreation.validUntil), + }; + + createShortUrl(shortUrlData).then(reset).catch(() => {}); + }); + const renderOptionalInput = (id: NonDateFields, placeholder: string, type: InputType = 'text', props = {}) => ( + + setShortUrlCreation({ ...shortUrlCreation, [id]: e.target.value })} + {...props} + /> + + ); + const renderDateInput = (id: DateFields, placeholder: string, props: Partial = {}) => ( +
+ setShortUrlCreation({ ...shortUrlCreation, [id]: date })} + {...props} + /> +
+ ); + + const currentServerVersion = isReachableServer(selectedServer) ? selectedServer.version : ''; + const disableDomain = !versionMatch(currentServerVersion, { minVersion: '1.19.0-beta.1' }); + const disableShortCodeLength = !versionMatch(currentServerVersion, { minVersion: '2.1.0' }); + + return ( +
+
+ setShortUrlCreation({ ...shortUrlCreation, longUrl: e.target.value })} + /> +
+ + +
+ +
+ +
+
+ {renderOptionalInput('customSlug', 'Custom slug')} +
+
+ {renderOptionalInput('shortCodeLength', 'Short code length', 'number', { + min: 4, + disabled: disableShortCodeLength || hasValue(shortUrlCreation.customSlug), + ...disableShortCodeLength && { + title: 'Shlink 2.1.0 or higher is required to be able to provide the short code length', + }, + })} +
+
+ {renderOptionalInput('domain', 'Domain', 'text', { + disabled: disableDomain, + ...disableDomain && { title: 'Shlink 1.19.0 or higher is required to be able to provide the domain' }, + })} +
+
+ +
+
+ {renderOptionalInput('maxVisits', 'Maximum number of visits allowed', 'number', { min: 1 })} +
+
+ {renderDateInput('validSince', 'Enabled since...', { maxDate: shortUrlCreation.validUntil as m.Moment | undefined })} +
+
+ {renderDateInput('validUntil', 'Enabled until...', { minDate: shortUrlCreation.validSince as m.Moment | undefined })} +
+
+ + +
+ setShortUrlCreation({ ...shortUrlCreation, findIfExists })} + > + Use existing URL if found + + +
+
+
+ +
+ + +
+ + + + ); +}; + +export default CreateShortUrl; diff --git a/src/short-urls/Paginator.js b/src/short-urls/Paginator.tsx similarity index 80% rename from src/short-urls/Paginator.js rename to src/short-urls/Paginator.tsx index 4b051811..eeccb727 100644 --- a/src/short-urls/Paginator.js +++ b/src/short-urls/Paginator.tsx @@ -1,20 +1,17 @@ import React from 'react'; import { Link } from 'react-router-dom'; import { Pagination, PaginationItem, PaginationLink } from 'reactstrap'; -import PropTypes from 'prop-types'; import { pageIsEllipsis, keyForPage, progressivePagination } from '../utils/helpers/pagination'; +import { ShlinkPaginator } from '../utils/services/types'; import './Paginator.scss'; -const propTypes = { - serverId: PropTypes.string.isRequired, - paginator: PropTypes.shape({ - currentPage: PropTypes.number, - pagesCount: PropTypes.number, - }), -}; +interface PaginatorProps { + paginator?: ShlinkPaginator; + serverId: string; +} -const Paginator = ({ paginator = {}, serverId }) => { - const { currentPage, pagesCount = 0 } = paginator; +const Paginator = ({ paginator, serverId }: PaginatorProps) => { + const { currentPage = 0, pagesCount = 0 } = paginator ?? {}; if (pagesCount <= 1) { return null; @@ -57,6 +54,4 @@ const Paginator = ({ paginator = {}, serverId }) => { ); }; -Paginator.propTypes = propTypes; - export default Paginator; diff --git a/src/short-urls/SearchBar.js b/src/short-urls/SearchBar.js deleted file mode 100644 index 53232f4e..00000000 --- a/src/short-urls/SearchBar.js +++ /dev/null @@ -1,81 +0,0 @@ -import { faTags as tagsIcon } from '@fortawesome/free-solid-svg-icons'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import React from 'react'; -import { isEmpty, pipe } from 'ramda'; -import PropTypes from 'prop-types'; -import moment from 'moment'; -import SearchField from '../utils/SearchField'; -import Tag from '../tags/helpers/Tag'; -import DateRangeRow from '../utils/DateRangeRow'; -import { formatDate } from '../utils/helpers/date'; -import { shortUrlsListParamsType } from './reducers/shortUrlsListParams'; -import './SearchBar.scss'; - -const propTypes = { - listShortUrls: PropTypes.func, - shortUrlsListParams: shortUrlsListParamsType, -}; - -const dateOrUndefined = (date) => date ? moment(date) : undefined; - -const SearchBar = (colorGenerator, ForServerVersion) => { - const SearchBar = ({ listShortUrls, shortUrlsListParams }) => { - const selectedTags = shortUrlsListParams.tags || []; - const setDate = (dateName) => pipe( - formatDate(), - (date) => listShortUrls({ ...shortUrlsListParams, [dateName]: date }), - ); - - return ( -
- listShortUrls({ ...shortUrlsListParams, searchTerm }) - } - /> - - -
-
-
- -
-
-
-
- - {!isEmpty(selectedTags) && ( -

- -   - {selectedTags.map((tag) => ( - listShortUrls( - { - ...shortUrlsListParams, - tags: selectedTags.filter((selectedTag) => selectedTag !== tag), - }, - )} - /> - ))} -

- )} -
- ); - }; - - SearchBar.propTypes = propTypes; - - return SearchBar; -}; - -export default SearchBar; diff --git a/src/short-urls/SearchBar.tsx b/src/short-urls/SearchBar.tsx new file mode 100644 index 00000000..85150608 --- /dev/null +++ b/src/short-urls/SearchBar.tsx @@ -0,0 +1,78 @@ +import { faTags as tagsIcon } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import React, { FC } from 'react'; +import { isEmpty, pipe } from 'ramda'; +import moment from 'moment'; +import SearchField from '../utils/SearchField'; +import Tag from '../tags/helpers/Tag'; +import DateRangeRow from '../utils/DateRangeRow'; +import { formatDate } from '../utils/helpers/date'; +import ColorGenerator from '../utils/services/ColorGenerator'; +import { Versions } from '../utils/helpers/version'; +import { ShortUrlsListParams } from './reducers/shortUrlsListParams'; +import './SearchBar.scss'; + +interface SearchBarProps { + listShortUrls: (params: ShortUrlsListParams) => void; + shortUrlsListParams: ShortUrlsListParams; +} + +const dateOrUndefined = (date?: string) => date ? moment(date) : undefined; + +const SearchBar = (colorGenerator: ColorGenerator, ForServerVersion: FC) => ( + { listShortUrls, shortUrlsListParams }: SearchBarProps, +) => { + const selectedTags = shortUrlsListParams.tags ?? []; + const setDate = (dateName: 'startDate' | 'endDate') => pipe( + formatDate(), + (date) => listShortUrls({ ...shortUrlsListParams, [dateName]: date }), + ); + + return ( +
+ listShortUrls({ ...shortUrlsListParams, searchTerm }) + } + /> + + +
+
+
+ +
+
+
+
+ + {!isEmpty(selectedTags) && ( +

+ +   + {selectedTags.map((tag) => ( + listShortUrls( + { + ...shortUrlsListParams, + tags: selectedTags.filter((selectedTag) => selectedTag !== tag), + }, + )} + /> + ))} +

+ )} +
+ ); +}; + +export default SearchBar; diff --git a/src/short-urls/ShortUrls.js b/src/short-urls/ShortUrls.js deleted file mode 100644 index a4cc23c4..00000000 --- a/src/short-urls/ShortUrls.js +++ /dev/null @@ -1,41 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import PropTypes from 'prop-types'; -import Paginator from './Paginator'; - -const ShortUrls = (SearchBar, ShortUrlsList) => { - const propTypes = { - match: PropTypes.shape({ - params: PropTypes.object, - }), - shortUrlsList: PropTypes.object, - }; - - const ShortUrlsComponent = (props) => { - const { match: { params }, shortUrlsList } = props; - const { page, serverId } = params; - const { data = [], pagination } = shortUrlsList; - const [ urlsListKey, setUrlsListKey ] = useState(`${serverId}_${page}`); - - // Using a key on a component makes react to create a new instance every time the key changes - // Without it, pagination on the URL will not make the component to be refreshed - useEffect(() => { - setUrlsListKey(`${serverId}_${page}`); - }, [ serverId, page ]); - - return ( - -
-
- - -
-
- ); - }; - - ShortUrlsComponent.propTypes = propTypes; - - return ShortUrlsComponent; -}; - -export default ShortUrls; diff --git a/src/short-urls/ShortUrls.tsx b/src/short-urls/ShortUrls.tsx new file mode 100644 index 00000000..0a283bb1 --- /dev/null +++ b/src/short-urls/ShortUrls.tsx @@ -0,0 +1,33 @@ +import React, { FC, useEffect, useState } from 'react'; +import { ShlinkShortUrlsResponse } from '../utils/services/types'; +import Paginator from './Paginator'; +import { ShortUrlsListProps, WithList } from './ShortUrlsList'; + +interface ShortUrlsProps extends ShortUrlsListProps { + shortUrlsList?: ShlinkShortUrlsResponse; +} + +const ShortUrls = (SearchBar: FC, ShortUrlsList: FC) => (props: ShortUrlsProps) => { + const { match, shortUrlsList } = props; + const { page = '1', serverId = '' } = match?.params ?? {}; + const { data = [], pagination } = shortUrlsList ?? {}; + const [ urlsListKey, setUrlsListKey ] = useState(`${serverId}_${page}`); + + // Using a key on a component makes react to create a new instance every time the key changes + // Without it, pagination on the URL will not make the component to be refreshed + useEffect(() => { + setUrlsListKey(`${serverId}_${page}`); + }, [ serverId, page ]); + + return ( + +
+
+ + +
+
+ ); +}; + +export default ShortUrls; diff --git a/src/short-urls/ShortUrlsList.js b/src/short-urls/ShortUrlsList.js deleted file mode 100644 index a7b275e9..00000000 --- a/src/short-urls/ShortUrlsList.js +++ /dev/null @@ -1,178 +0,0 @@ -import { faCaretDown as caretDownIcon, faCaretUp as caretUpIcon } from '@fortawesome/free-solid-svg-icons'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { head, isEmpty, keys, values } from 'ramda'; -import React, { useState, useEffect } from 'react'; -import qs from 'qs'; -import PropTypes from 'prop-types'; -import { serverType } from '../servers/prop-types'; -import SortingDropdown from '../utils/SortingDropdown'; -import { determineOrderDir } from '../utils/utils'; -import { MercureInfoType } from '../mercure/reducers/mercureInfo'; -import { useMercureTopicBinding } from '../mercure/helpers'; -import { shortUrlType } from './reducers/shortUrlsList'; -import { shortUrlsListParamsType } from './reducers/shortUrlsListParams'; -import './ShortUrlsList.scss'; - -export const SORTABLE_FIELDS = { - dateCreated: 'Created at', - shortCode: 'Short URL', - longUrl: 'Long URL', - visits: 'Visits', -}; - -const propTypes = { - listShortUrls: PropTypes.func, - resetShortUrlParams: PropTypes.func, - shortUrlsListParams: shortUrlsListParamsType, - match: PropTypes.object, - location: PropTypes.object, - loading: PropTypes.bool, - error: PropTypes.bool, - shortUrlsList: PropTypes.arrayOf(shortUrlType), - selectedServer: serverType, - createNewVisit: PropTypes.func, - loadMercureInfo: PropTypes.func, - mercureInfo: MercureInfoType, -}; - -// FIXME Replace with typescript: (ShortUrlsRow component) -const ShortUrlsList = (ShortUrlsRow) => { - const ShortUrlsListComp = ({ - listShortUrls, - resetShortUrlParams, - shortUrlsListParams, - match, - location, - loading, - error, - shortUrlsList, - selectedServer, - createNewVisit, - loadMercureInfo, - mercureInfo, - }) => { - const { orderBy } = shortUrlsListParams; - const [ order, setOrder ] = useState({ - orderField: orderBy && head(keys(orderBy)), - orderDir: orderBy && head(values(orderBy)), - }); - const refreshList = (extraParams) => listShortUrls({ ...shortUrlsListParams, ...extraParams }); - const handleOrderBy = (orderField, orderDir) => { - setOrder({ orderField, orderDir }); - refreshList({ orderBy: { [orderField]: orderDir } }); - }; - const orderByColumn = (columnName) => () => - handleOrderBy(columnName, determineOrderDir(columnName, order.orderField, order.orderDir)); - const renderOrderIcon = (field) => { - if (order.orderField !== field) { - return null; - } - - if (!order.orderDir) { - return null; - } - - return ( - - ); - }; - const renderShortUrls = () => { - if (error) { - return ( - - Something went wrong while loading short URLs :( - - ); - } - - if (loading) { - return Loading...; - } - - if (!loading && isEmpty(shortUrlsList)) { - return No results found; - } - - return shortUrlsList.map((shortUrl) => ( - - )); - }; - - useEffect(() => { - const { params } = match; - const query = qs.parse(location.search, { ignoreQueryPrefix: true }); - const tags = query.tag ? [ query.tag ] : shortUrlsListParams.tags; - - refreshList({ page: params.page, tags }); - - return resetShortUrlParams; - }, []); - useMercureTopicBinding(mercureInfo, 'https://shlink.io/new-visit', createNewVisit, loadMercureInfo); - - return ( - -
- -
- - - - - - - - - - - - - {renderShortUrls()} - -
- {renderOrderIcon('dateCreated')} - Created at - - {renderOrderIcon('shortCode')} - Short URL - - {renderOrderIcon('longUrl')} - Long URL - Tags - {renderOrderIcon('visits')} Visits -  
-
- ); - }; - - ShortUrlsListComp.propTypes = propTypes; - - return ShortUrlsListComp; -}; - -export default ShortUrlsList; diff --git a/src/short-urls/ShortUrlsList.tsx b/src/short-urls/ShortUrlsList.tsx new file mode 100644 index 00000000..1273894d --- /dev/null +++ b/src/short-urls/ShortUrlsList.tsx @@ -0,0 +1,177 @@ +import { faCaretDown as caretDownIcon, faCaretUp as caretUpIcon } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { head, isEmpty, keys, values } from 'ramda'; +import React, { useState, useEffect, FC } from 'react'; +import qs from 'qs'; +import { RouteChildrenProps } from 'react-router'; +import SortingDropdown from '../utils/SortingDropdown'; +import { determineOrderDir, OrderDir } from '../utils/utils'; +import { MercureInfo } from '../mercure/reducers/mercureInfo'; +import { useMercureTopicBinding } from '../mercure/helpers'; +import { SelectedServer } from '../servers/data'; +import { ShortUrlsList as ShortUrlsListState } from './reducers/shortUrlsList'; +import { ShortUrlsRowProps } from './helpers/ShortUrlsRow'; +import { ShortUrl } from './data'; +import { ShortUrlsListParams } from './reducers/shortUrlsListParams'; +import './ShortUrlsList.scss'; + +export const SORTABLE_FIELDS = { + dateCreated: 'Created at', + shortCode: 'Short URL', + longUrl: 'Long URL', + visits: 'Visits', +}; +type OrderableFields = keyof typeof SORTABLE_FIELDS; + +interface RouteParams { + page: string; + serverId: string; +} + +export interface WithList { + shortUrlsList: ShortUrl[]; +} + +export interface ShortUrlsListProps extends ShortUrlsListState, RouteChildrenProps { + selectedServer: SelectedServer; + listShortUrls: (params: ShortUrlsListParams) => void; + shortUrlsListParams: ShortUrlsListParams; + resetShortUrlParams: () => void; + createNewVisit: (message: any) => void; + loadMercureInfo: Function; + mercureInfo: MercureInfo; +} + +const ShortUrlsList = (ShortUrlsRow: FC) => ({ + listShortUrls, + resetShortUrlParams, + shortUrlsListParams, + match, + location, + loading, + error, + shortUrlsList, + selectedServer, + createNewVisit, + loadMercureInfo, + mercureInfo, +}: ShortUrlsListProps & WithList) => { + const { orderBy } = shortUrlsListParams; + const [ order, setOrder ] = useState<{ orderField?: OrderableFields; orderDir?: OrderDir }>({ + orderField: orderBy && (head(keys(orderBy)) as OrderableFields), + orderDir: orderBy && head(values(orderBy)), + }); + const refreshList = (extraParams: ShortUrlsListParams) => listShortUrls({ ...shortUrlsListParams, ...extraParams }); + const handleOrderBy = (orderField: OrderableFields, orderDir: OrderDir) => { + setOrder({ orderField, orderDir }); + refreshList({ orderBy: { [orderField]: orderDir } }); + }; + const orderByColumn = (field: OrderableFields) => () => + handleOrderBy(field, determineOrderDir(field, order.orderField, order.orderDir)); + const renderOrderIcon = (field: OrderableFields) => { + if (order.orderField !== field) { + return null; + } + + if (!order.orderDir) { + return null; + } + + return ( + + ); + }; + const renderShortUrls = () => { + if (error) { + return ( + + Something went wrong while loading short URLs :( + + ); + } + + if (loading) { + return Loading...; + } + + if (!loading && isEmpty(shortUrlsList)) { + return No results found; + } + + return shortUrlsList.map((shortUrl) => ( + + )); + }; + + useEffect(() => { + const query = qs.parse(location.search, { ignoreQueryPrefix: true }); + const tags = query.tag ? [ query.tag as string ] : shortUrlsListParams.tags; + + refreshList({ page: match?.params.page, tags }); + + return resetShortUrlParams; + }, []); + useMercureTopicBinding(mercureInfo, 'https://shlink.io/new-visit', createNewVisit, loadMercureInfo); + + return ( + +
+ +
+ + + + + + + + + + + + + {renderShortUrls()} + +
+ {renderOrderIcon('dateCreated')} + Created at + + {renderOrderIcon('shortCode')} + Short URL + + {renderOrderIcon('longUrl')} + Long URL + Tags + {renderOrderIcon('visits')} Visits +  
+
+ ); +}; + +export default ShortUrlsList; diff --git a/src/short-urls/UseExistingIfFoundInfoIcon.js b/src/short-urls/UseExistingIfFoundInfoIcon.tsx similarity index 93% rename from src/short-urls/UseExistingIfFoundInfoIcon.js rename to src/short-urls/UseExistingIfFoundInfoIcon.tsx index 803d4b23..e1c13fa1 100644 --- a/src/short-urls/UseExistingIfFoundInfoIcon.js +++ b/src/short-urls/UseExistingIfFoundInfoIcon.tsx @@ -5,7 +5,7 @@ import { Modal, ModalBody, ModalHeader } from 'reactstrap'; import './UseExistingIfFoundInfoIcon.scss'; import { useToggle } from '../utils/helpers/hooks'; -const renderInfoModal = (isOpen, toggle) => ( +const InfoModal = ({ isOpen, toggle }: { isOpen: boolean; toggle: () => void }) => ( Info @@ -45,7 +45,7 @@ const UseExistingIfFoundInfoIcon = () => { - {renderInfoModal(isModalOpen, toggleModal)} + ); }; diff --git a/src/short-urls/data/index.ts b/src/short-urls/data/index.ts index f0045e83..ba7d7c6e 100644 --- a/src/short-urls/data/index.ts +++ b/src/short-urls/data/index.ts @@ -1,3 +1,4 @@ +import * as m from 'moment'; import { Nullable, OptionalString } from '../../utils/utils'; export interface ShortUrlData { @@ -6,8 +7,8 @@ export interface ShortUrlData { customSlug?: string; shortCodeLength?: number; domain?: string; - validSince?: string; - validUntil?: string; + validSince?: m.Moment | string; + validUntil?: m.Moment | string; maxVisits?: number; findIfExists?: boolean; } diff --git a/src/short-urls/helpers/CreateShortUrlResult.tsx b/src/short-urls/helpers/CreateShortUrlResult.tsx index 83701cf8..8a561d28 100644 --- a/src/short-urls/helpers/CreateShortUrlResult.tsx +++ b/src/short-urls/helpers/CreateShortUrlResult.tsx @@ -5,11 +5,11 @@ import React, { useEffect } from 'react'; import CopyToClipboard from 'react-copy-to-clipboard'; import { Card, CardBody, Tooltip } from 'reactstrap'; import { ShortUrlCreation } from '../reducers/shortUrlCreation'; -import './CreateShortUrlResult.scss'; import { StateFlagTimeout } from '../../utils/helpers/hooks'; +import './CreateShortUrlResult.scss'; -interface CreateShortUrlResultProps extends ShortUrlCreation { - resetCreateShortUrl: Function; +export interface CreateShortUrlResultProps extends ShortUrlCreation { + resetCreateShortUrl: () => void; } const CreateShortUrlResult = (useStateFlagTimeout: StateFlagTimeout) => ( diff --git a/src/short-urls/reducers/shortUrlCreation.ts b/src/short-urls/reducers/shortUrlCreation.ts index f062b694..3f066655 100644 --- a/src/short-urls/reducers/shortUrlCreation.ts +++ b/src/short-urls/reducers/shortUrlCreation.ts @@ -1,4 +1,3 @@ -import PropTypes from 'prop-types'; import { Action, Dispatch } from 'redux'; import { GetState } from '../../container/types'; import { ShortUrl, ShortUrlData } from '../data'; @@ -12,15 +11,6 @@ export const CREATE_SHORT_URL = 'shlink/createShortUrl/CREATE_SHORT_URL'; export const RESET_CREATE_SHORT_URL = 'shlink/createShortUrl/RESET_CREATE_SHORT_URL'; /* eslint-enable padding-line-between-statements */ -/** @deprecated Use ShortUrlCreation interface instead */ -export const createShortUrlResultType = PropTypes.shape({ - result: PropTypes.shape({ - shortUrl: PropTypes.string, - }), - saving: PropTypes.bool, - error: PropTypes.bool, -}); - export interface ShortUrlCreation { result: ShortUrl | null; saving: boolean; diff --git a/src/short-urls/reducers/shortUrlsList.ts b/src/short-urls/reducers/shortUrlsList.ts index de4a2370..71a153e5 100644 --- a/src/short-urls/reducers/shortUrlsList.ts +++ b/src/short-urls/reducers/shortUrlsList.ts @@ -7,6 +7,7 @@ import { ShortUrl, ShortUrlIdentifier } from '../data'; import { buildReducer } from '../../utils/helpers/redux'; import { GetState } from '../../container/types'; import { ShlinkApiClientBuilder } from '../../utils/services/ShlinkApiClientBuilder'; +import { ShlinkShortUrlsResponse } from '../../utils/services/types'; import { EditShortUrlTagsAction, SHORT_URL_TAGS_EDITED } from './shortUrlTags'; import { SHORT_URL_DELETED } from './shortUrlDeletion'; import { SHORT_URL_META_EDITED, ShortUrlMetaEditedAction, shortUrlMetaType } from './shortUrlMeta'; @@ -30,18 +31,14 @@ export const shortUrlType = PropTypes.shape({ domain: PropTypes.string, }); -interface ShortUrlsData { - data: ShortUrl[]; -} - export interface ShortUrlsList { - shortUrls: ShortUrlsData; + shortUrls?: ShlinkShortUrlsResponse; loading: boolean; error: boolean; } export interface ListShortUrlsAction extends Action { - shortUrls: ShortUrlsData; + shortUrls: ShlinkShortUrlsResponse; params: ShortUrlsListParams; } @@ -50,9 +47,6 @@ export type ListShortUrlsCombinedAction = ( ); const initialState: ShortUrlsList = { - shortUrls: { - data: [], - }, loading: true, error: false, }; @@ -60,7 +54,7 @@ const initialState: ShortUrlsList = { const setPropFromActionOnMatchingShortUrl = (prop: keyof T) => ( state: ShortUrlsList, { shortCode, domain, [prop]: propValue }: T, -): ShortUrlsList => assocPath( +): ShortUrlsList => !state.shortUrls ? state : assocPath( [ 'shortUrls', 'data' ], state.shortUrls.data.map( (shortUrl: ShortUrl) => @@ -71,9 +65,9 @@ const setPropFromActionOnMatchingShortUrl = (prop: export default buildReducer({ [LIST_SHORT_URLS_START]: (state) => ({ ...state, loading: true, error: false }), - [LIST_SHORT_URLS_ERROR]: () => ({ loading: false, error: true, shortUrls: { data: [] } }), + [LIST_SHORT_URLS_ERROR]: () => ({ loading: false, error: true }), [LIST_SHORT_URLS]: (_, { shortUrls }) => ({ loading: false, error: false, shortUrls }), - [SHORT_URL_DELETED]: (state, { shortCode, domain }) => assocPath( + [SHORT_URL_DELETED]: (state, { shortCode, domain }) => !state.shortUrls ? state : assocPath( [ 'shortUrls', 'data' ], reject((shortUrl) => shortUrlMatches(shortUrl, shortCode, domain), state.shortUrls.data), state, diff --git a/src/short-urls/reducers/shortUrlsListParams.ts b/src/short-urls/reducers/shortUrlsListParams.ts index e5ed91d5..db9ba232 100644 --- a/src/short-urls/reducers/shortUrlsListParams.ts +++ b/src/short-urls/reducers/shortUrlsListParams.ts @@ -1,26 +1,16 @@ -import PropTypes from 'prop-types'; import { buildActionCreator, buildReducer } from '../../utils/helpers/redux'; +import { OrderDir } from '../../utils/utils'; import { LIST_SHORT_URLS, ListShortUrlsAction } from './shortUrlsList'; export const RESET_SHORT_URL_PARAMS = 'shlink/shortUrlsListParams/RESET_SHORT_URL_PARAMS'; -/** @deprecated Use ShortUrlsListParams interface instead */ -export const shortUrlsListParamsType = PropTypes.shape({ - page: PropTypes.string, - tags: PropTypes.arrayOf(PropTypes.string), - searchTerm: PropTypes.string, - startDate: PropTypes.string, - endDate: PropTypes.string, - orderBy: PropTypes.object, -}); - export interface ShortUrlsListParams { page?: string; tags?: string[]; searchTerm?: string; startDate?: string; endDate?: string; - orderBy?: string | Record; + orderBy?: Record; } const initialState: ShortUrlsListParams = { page: '1' }; diff --git a/src/short-urls/services/provideServices.ts b/src/short-urls/services/provideServices.ts index a5f43f4c..8e86e7c9 100644 --- a/src/short-urls/services/provideServices.ts +++ b/src/short-urls/services/provideServices.ts @@ -19,13 +19,13 @@ import { editShortUrlTags, resetShortUrlsTags } from '../reducers/shortUrlTags'; import { editShortUrlMeta, resetShortUrlMeta } from '../reducers/shortUrlMeta'; import { resetShortUrlParams } from '../reducers/shortUrlsListParams'; import { editShortUrl } from '../reducers/shortUrlEdition'; -import { ConnectDecorator } from '../../container/types'; +import { ConnectDecorator, ShlinkState } from '../../container/types'; const provideServices = (bottle: Bottle, connect: ConnectDecorator) => { // Components bottle.serviceFactory('ShortUrls', ShortUrls, 'SearchBar', 'ShortUrlsList'); bottle.decorator('ShortUrls', reduxConnect( - (state: any) => assoc('shortUrlsList', state.shortUrlsList.shortUrls, state.shortUrlsList), + (state: ShlinkState) => assoc('shortUrlsList', state.shortUrlsList.shortUrls, state.shortUrlsList), )); // Services diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 45592f26..e3a061e4 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -3,7 +3,7 @@ import { SyntheticEvent } from 'react'; export type OrderDir = 'ASC' | 'DESC' | undefined; -export const determineOrderDir = (currentField: string, newField: string, currentOrderDir?: OrderDir): OrderDir => { +export const determineOrderDir = (currentField: string, newField?: string, currentOrderDir?: OrderDir): OrderDir => { if (currentField !== newField) { return 'ASC'; } diff --git a/test/short-urls/CreateShortUrl.test.js b/test/short-urls/CreateShortUrl.test.tsx similarity index 72% rename from test/short-urls/CreateShortUrl.test.js rename to test/short-urls/CreateShortUrl.test.tsx index e9ca10cb..18081131 100644 --- a/test/short-urls/CreateShortUrl.test.js +++ b/test/short-urls/CreateShortUrl.test.tsx @@ -1,29 +1,32 @@ import React from 'react'; -import { shallow } from 'enzyme'; +import { shallow, ShallowWrapper } from 'enzyme'; import moment from 'moment'; import { identity } from 'ramda'; +import { Mock } from 'ts-mockery'; import createShortUrlsCreator from '../../src/short-urls/CreateShortUrl'; import DateInput from '../../src/utils/DateInput'; +import { ShortUrlCreation } from '../../src/short-urls/reducers/shortUrlCreation'; describe('', () => { - let wrapper; - const TagsSelector = () => ''; - const shortUrlCreationResult = { - loading: false, - }; - const createShortUrl = jest.fn(() => Promise.resolve()); + let wrapper: ShallowWrapper; + const TagsSelector = () => null; + const shortUrlCreationResult = Mock.all(); + const createShortUrl = jest.fn(async () => Promise.resolve()); beforeEach(() => { - const CreateShortUrl = createShortUrlsCreator(TagsSelector, () => '', () => ''); + const CreateShortUrl = createShortUrlsCreator(TagsSelector, () => null, () => null); wrapper = shallow( - , + {}} + />, ); }); - afterEach(() => { - wrapper.unmount(); - createShortUrl.mockClear(); - }); + afterEach(() => wrapper.unmount()); + afterEach(jest.clearAllMocks); it('saves short URL with data set in form controls', () => { const validSince = moment('2017-01-01'); diff --git a/test/short-urls/Paginator.test.js b/test/short-urls/Paginator.test.tsx similarity index 85% rename from test/short-urls/Paginator.test.js rename to test/short-urls/Paginator.test.tsx index f292dea9..7e63d972 100644 --- a/test/short-urls/Paginator.test.js +++ b/test/short-urls/Paginator.test.tsx @@ -1,12 +1,12 @@ import React from 'react'; -import { shallow } from 'enzyme'; +import { shallow, ShallowWrapper } from 'enzyme'; import { PaginationItem } from 'reactstrap'; import Paginator from '../../src/short-urls/Paginator'; describe('', () => { - let wrapper; + let wrapper: ShallowWrapper; - afterEach(() => wrapper && wrapper.unmount()); + afterEach(() => wrapper?.unmount()); it('renders nothing if the number of pages is below 2', () => { wrapper = shallow(); diff --git a/test/short-urls/SearchBar.test.js b/test/short-urls/SearchBar.test.tsx similarity index 75% rename from test/short-urls/SearchBar.test.js rename to test/short-urls/SearchBar.test.tsx index efbdddc1..97f155f6 100644 --- a/test/short-urls/SearchBar.test.js +++ b/test/short-urls/SearchBar.test.tsx @@ -1,34 +1,34 @@ import React from 'react'; -import { shallow } from 'enzyme'; +import { shallow, ShallowWrapper } from 'enzyme'; +import { Mock } from 'ts-mockery'; import searchBarCreator from '../../src/short-urls/SearchBar'; import SearchField from '../../src/utils/SearchField'; import Tag from '../../src/tags/helpers/Tag'; import DateRangeRow from '../../src/utils/DateRangeRow'; +import ColorGenerator from '../../src/utils/services/ColorGenerator'; describe('', () => { - let wrapper; + let wrapper: ShallowWrapper; const listShortUrlsMock = jest.fn(); - const SearchBar = searchBarCreator({}, () => ''); + const SearchBar = searchBarCreator(Mock.all(), () => null); - afterEach(() => { - listShortUrlsMock.mockReset(); - wrapper && wrapper.unmount(); - }); + afterEach(jest.clearAllMocks); + afterEach(() => wrapper?.unmount()); it('renders a SearchField', () => { - wrapper = shallow(); + wrapper = shallow(); expect(wrapper.find(SearchField)).toHaveLength(1); }); it('renders a DateRangeRow', () => { - wrapper = shallow(); + wrapper = shallow(); expect(wrapper.find(DateRangeRow)).toHaveLength(1); }); it('renders no tags when the list of tags is empty', () => { - wrapper = shallow(); + wrapper = shallow(); expect(wrapper.find(Tag)).toHaveLength(0); }); @@ -36,7 +36,7 @@ describe('', () => { it('renders the proper amount of tags', () => { const tags = [ 'foo', 'bar', 'baz' ]; - wrapper = shallow(); + wrapper = shallow(); expect(wrapper.find(Tag)).toHaveLength(tags.length); }); diff --git a/test/short-urls/ShortUrls.test.js b/test/short-urls/ShortUrls.test.tsx similarity index 61% rename from test/short-urls/ShortUrls.test.js rename to test/short-urls/ShortUrls.test.tsx index d2327a31..45d845b8 100644 --- a/test/short-urls/ShortUrls.test.js +++ b/test/short-urls/ShortUrls.test.tsx @@ -1,22 +1,21 @@ import React from 'react'; -import { shallow } from 'enzyme'; +import { shallow, ShallowWrapper } from 'enzyme'; +import { Mock } from 'ts-mockery'; import shortUrlsCreator from '../../src/short-urls/ShortUrls'; import Paginator from '../../src/short-urls/Paginator'; +import { ShortUrlsListProps } from '../../src/short-urls/ShortUrlsList'; describe('', () => { - let wrapper; - const SearchBar = () => ''; - const ShortUrlsList = () => ''; + let wrapper: ShallowWrapper; + const SearchBar = () => null; + const ShortUrlsList = () => null; beforeEach(() => { - const params = { - serverId: '1', - page: '1', - }; - const ShortUrls = shortUrlsCreator(SearchBar, ShortUrlsList); - wrapper = shallow(); + wrapper = shallow( + ()} />, + ); }); afterEach(() => wrapper.unmount()); diff --git a/test/short-urls/ShortUrlsList.test.js b/test/short-urls/ShortUrlsList.test.tsx similarity index 80% rename from test/short-urls/ShortUrlsList.test.js rename to test/short-urls/ShortUrlsList.test.tsx index 65246833..60161780 100644 --- a/test/short-urls/ShortUrlsList.test.js +++ b/test/short-urls/ShortUrlsList.test.tsx @@ -1,12 +1,14 @@ import React from 'react'; -import { shallow } from 'enzyme'; +import { shallow, ShallowWrapper } from 'enzyme'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faCaretDown as caretDownIcon, faCaretUp as caretUpIcon } from '@fortawesome/free-solid-svg-icons'; -import shortUrlsListCreator, { SORTABLE_FIELDS } from '../../src/short-urls/ShortUrlsList'; +import { Mock } from 'ts-mockery'; +import shortUrlsListCreator, { ShortUrlsListProps, SORTABLE_FIELDS } from '../../src/short-urls/ShortUrlsList'; +import { ShortUrl } from '../../src/short-urls/data'; describe('', () => { - let wrapper; - const ShortUrlsRow = () => ''; + let wrapper: ShallowWrapper; + const ShortUrlsRow = () => null; const listShortUrlsMock = jest.fn(); const resetShortUrlParamsMock = jest.fn(); @@ -15,6 +17,7 @@ describe('', () => { beforeEach(() => { wrapper = shallow( ()} listShortUrls={listShortUrlsMock} resetShortUrlParams={resetShortUrlParamsMock} shortUrlsListParams={{ @@ -22,29 +25,27 @@ describe('', () => { tags: [ 'test tag' ], searchTerm: 'example.com', }} - match={{ params: {} }} - location={{}} + match={{ params: {} } as any} + location={{} as any} loading={false} error={false} shortUrlsList={ [ - { + Mock.of({ shortCode: 'testShortCode', shortUrl: 'https://www.example.com/testShortUrl', longUrl: 'https://www.example.com/testLongUrl', tags: [ 'test tag' ], - }, + }), ] } - mercureInfo={{ loading: true }} + mercureInfo={{ loading: true } as any} />, ); }); - afterEach(() => { - jest.resetAllMocks(); - wrapper && wrapper.unmount(); - }); + afterEach(jest.resetAllMocks); + afterEach(() => wrapper?.unmount()); it('wraps a ShortUrlsList with 1 ShortUrlsRow', () => { expect(wrapper.find(ShortUrlsRow)).toHaveLength(1); @@ -71,11 +72,11 @@ describe('', () => { }); it('should render 6 table header cells with conditional order by icon', () => { - const getThElementForSortableField = (sortableField) => wrapper.find('table') + const getThElementForSortableField = (sortableField: string) => wrapper.find('table') .find('thead') .find('tr') .find('th') - .filterWhere((e) => e.text().includes(SORTABLE_FIELDS[sortableField])); + .filterWhere((e) => e.text().includes(SORTABLE_FIELDS[sortableField as keyof typeof SORTABLE_FIELDS])); Object.keys(SORTABLE_FIELDS).forEach((sortableField) => { expect(getThElementForSortableField(sortableField).find(FontAwesomeIcon)).toHaveLength(0); diff --git a/test/short-urls/UseExistingIfFoundInfoIcon.test.js b/test/short-urls/UseExistingIfFoundInfoIcon.test.tsx similarity index 89% rename from test/short-urls/UseExistingIfFoundInfoIcon.test.js rename to test/short-urls/UseExistingIfFoundInfoIcon.test.tsx index 12f25a0d..4cac6e05 100644 --- a/test/short-urls/UseExistingIfFoundInfoIcon.test.js +++ b/test/short-urls/UseExistingIfFoundInfoIcon.test.tsx @@ -1,11 +1,11 @@ import React from 'react'; -import { mount } from 'enzyme'; +import { mount, ReactWrapper } from 'enzyme'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Modal } from 'reactstrap'; import UseExistingIfFoundInfoIcon from '../../src/short-urls/UseExistingIfFoundInfoIcon'; describe('', () => { - let wrapped; + let wrapped: ReactWrapper; beforeEach(() => { wrapped = mount(); diff --git a/test/short-urls/reducers/shortUrlsList.test.ts b/test/short-urls/reducers/shortUrlsList.test.ts index 00aa1740..ea77b662 100644 --- a/test/short-urls/reducers/shortUrlsList.test.ts +++ b/test/short-urls/reducers/shortUrlsList.test.ts @@ -11,14 +11,12 @@ import { SHORT_URL_META_EDITED } from '../../../src/short-urls/reducers/shortUrl import { CREATE_VISIT } from '../../../src/visits/reducers/visitCreation'; import { ShortUrl } from '../../../src/short-urls/data'; import ShlinkApiClient from '../../../src/utils/services/ShlinkApiClient'; +import { ShlinkShortUrlsResponse } from '../../../src/utils/services/types'; describe('shortUrlsListReducer', () => { describe('reducer', () => { it('returns loading on LIST_SHORT_URLS_START', () => expect(reducer(undefined, { type: LIST_SHORT_URLS_START } as any)).toEqual({ - shortUrls: { - data: [], - }, loading: true, error: false, })); @@ -32,9 +30,6 @@ describe('shortUrlsListReducer', () => { it('returns error on LIST_SHORT_URLS_ERROR', () => expect(reducer(undefined, { type: LIST_SHORT_URLS_ERROR } as any)).toEqual({ - shortUrls: { - data: [], - }, loading: false, error: true, })); @@ -43,13 +38,13 @@ describe('shortUrlsListReducer', () => { const shortCode = 'abc123'; const tags = [ 'foo', 'bar', 'baz' ]; const state = { - shortUrls: { + shortUrls: Mock.of({ data: [ Mock.of({ shortCode, tags: [] }), Mock.of({ shortCode, tags: [], domain: 'example.com' }), Mock.of({ shortCode: 'foo', tags: [] }), ], - }, + }), loading: false, error: false, }; @@ -75,13 +70,13 @@ describe('shortUrlsListReducer', () => { validSince: '2020-05-05', }; const state = { - shortUrls: { + shortUrls: Mock.of({ data: [ Mock.of({ shortCode, meta: { maxVisits: 10 }, domain }), Mock.of({ shortCode, meta: { maxVisits: 50 } }), Mock.of({ shortCode: 'foo', meta: {} }), ], - }, + }), loading: false, error: false, }; @@ -102,13 +97,13 @@ describe('shortUrlsListReducer', () => { it('removes matching URL on SHORT_URL_DELETED', () => { const shortCode = 'abc123'; const state = { - shortUrls: { + shortUrls: Mock.of({ data: [ Mock.of({ shortCode }), Mock.of({ shortCode, domain: 'example.com' }), Mock.of({ shortCode: 'foo' }), ], - }, + }), loading: false, error: false, }; @@ -129,13 +124,13 @@ describe('shortUrlsListReducer', () => { visitsCount: 11, }; const state = { - shortUrls: { + shortUrls: Mock.of({ data: [ Mock.of({ shortCode, domain: 'example.com', visitsCount: 5 }), Mock.of({ shortCode, visitsCount: 10 }), Mock.of({ shortCode: 'foo', visitsCount: 8 }), ], - }, + }), loading: false, error: false, }; From 84fc82b74e07d1e9e514f661cd405b723f574237 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 30 Aug 2020 19:50:40 +0200 Subject: [PATCH 41/59] Fixed custom slug field not being disabled when selecting a short code length --- CHANGELOG.md | 2 +- src/short-urls/CreateShortUrl.tsx | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 48dc4f1d..fe9d3674 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,7 +24,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), #### Fixed -* *Nothing* +* [#295](https://github.com/shlinkio/shlink-web-client/issues/295) Fixed custom slug field not being disabled when selecting a short code length. ## 2.5.1 - 2020-06-06 diff --git a/src/short-urls/CreateShortUrl.tsx b/src/short-urls/CreateShortUrl.tsx index 93f8ee0f..82780fd3 100644 --- a/src/short-urls/CreateShortUrl.tsx +++ b/src/short-urls/CreateShortUrl.tsx @@ -108,7 +108,9 @@ const CreateShortUrl = (
- {renderOptionalInput('customSlug', 'Custom slug')} + {renderOptionalInput('customSlug', 'Custom slug', 'text', { + disabled: hasValue(shortUrlCreation.shortCodeLength), + })}
{renderOptionalInput('shortCodeLength', 'Short code length', 'number', { From 18883caa6dcf36ea828617a70c5d190b55634885 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 30 Aug 2020 20:31:31 +0200 Subject: [PATCH 42/59] Migrated tags helpers to TS --- package-lock.json | 43 +++++++++- package.json | 5 +- src/short-urls/CreateShortUrl.tsx | 5 +- src/short-urls/helpers/EditTagsModal.tsx | 3 +- src/tags/data/index.ts | 10 +++ ...firmModal.js => DeleteTagConfirmModal.tsx} | 23 +++-- src/tags/helpers/EditTagModal.js | 82 ------------------ src/tags/helpers/EditTagModal.tsx | 74 ++++++++++++++++ src/tags/helpers/Tag.js | 35 -------- src/tags/helpers/Tag.tsx | 24 ++++++ src/tags/helpers/TagBullet.js | 20 ----- src/tags/helpers/TagBullet.tsx | 17 ++++ src/tags/helpers/TagsSelector.js | 84 ------------------- src/tags/helpers/TagsSelector.tsx | 80 ++++++++++++++++++ src/tags/reducers/tagDelete.ts | 7 -- src/tags/reducers/tagsList.ts | 13 +-- ...test.js => DeleteTagConfirmModal.test.tsx} | 16 ++-- 17 files changed, 279 insertions(+), 262 deletions(-) create mode 100644 src/tags/data/index.ts rename src/tags/helpers/{DeleteTagConfirmModal.js => DeleteTagConfirmModal.tsx} (69%) delete mode 100644 src/tags/helpers/EditTagModal.js create mode 100644 src/tags/helpers/EditTagModal.tsx delete mode 100644 src/tags/helpers/Tag.js create mode 100644 src/tags/helpers/Tag.tsx delete mode 100644 src/tags/helpers/TagBullet.js create mode 100644 src/tags/helpers/TagBullet.tsx delete mode 100644 src/tags/helpers/TagsSelector.js create mode 100644 src/tags/helpers/TagsSelector.tsx rename test/tags/helpers/{DeleteTagConfirmModal.test.js => DeleteTagConfirmModal.test.tsx} (86%) diff --git a/package-lock.json b/package-lock.json index ee22ba94..7187451f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3384,6 +3384,25 @@ "csstype": "^3.0.2" } }, + "@types/react-autosuggest": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@types/react-autosuggest/-/react-autosuggest-10.0.0.tgz", + "integrity": "sha512-lcu3L3158I8DlgicRpyIuYzIc+E70RgXcVI8uJ+nw5JnAlhNht7dE1hJ0Q0x2A5VVBMOi5dcCQACqi5wWfm8ow==", + "dev": true, + "requires": { + "@types/react": "*" + } + }, + "@types/react-color": { + "version": "2.17.4", + "resolved": "https://registry.npmjs.org/@types/react-color/-/react-color-2.17.4.tgz", + "integrity": "sha512-pAO3+7uHoESg5QMqjnGjw9F7sALjEZsaU41yGiUZbmHiJMoSXH1UklFJ1bZkwhYskaJgiY+AS6wirl17yBh5GA==", + "dev": true, + "requires": { + "@types/react": "*", + "@types/reactcss": "*" + } + }, "@types/react-copy-to-clipboard": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/@types/react-copy-to-clipboard/-/react-copy-to-clipboard-4.3.0.tgz", @@ -3457,6 +3476,24 @@ "@types/react-router": "*" } }, + "@types/react-tagsinput": { + "version": "3.19.7", + "resolved": "https://registry.npmjs.org/@types/react-tagsinput/-/react-tagsinput-3.19.7.tgz", + "integrity": "sha512-yj/3iFBLoan/0vzXMxC9zGhO1uJ89qjQldekf0o3fX4mYdaAPW/VbP921fsyYt6PdHmJ9UMo+kERSMzUAml1xQ==", + "dev": true, + "requires": { + "@types/react": "*" + } + }, + "@types/reactcss": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@types/reactcss/-/reactcss-1.2.3.tgz", + "integrity": "sha512-d2gQQ0IL6hXLnoRfVYZukQNWHuVsE75DzFTLPUuyyEhJS8G2VvlE+qfQQ91SJjaMqlURRCNIsX7Jcsw6cEuJlA==", + "dev": true, + "requires": { + "@types/react": "*" + } + }, "@types/reactstrap": { "version": "8.5.1", "resolved": "https://registry.npmjs.org/@types/reactstrap/-/reactstrap-8.5.1.tgz", @@ -18146,9 +18183,9 @@ } }, "react-color": { - "version": "2.17.3", - "resolved": "https://registry.npmjs.org/react-color/-/react-color-2.17.3.tgz", - "integrity": "sha512-1dtO8LqAVotPIChlmo6kLtFS1FP89ll8/OiA8EcFRDR+ntcK+0ukJgByuIQHRtzvigf26dV5HklnxDIvhON9VQ==", + "version": "2.18.1", + "resolved": "https://registry.npmjs.org/react-color/-/react-color-2.18.1.tgz", + "integrity": "sha512-X5XpyJS6ncplZs74ak0JJoqPi+33Nzpv5RYWWxn17bslih+X7OlgmfpmGC1fNvdkK7/SGWYf1JJdn7D2n5gSuQ==", "requires": { "@icons/material": "^0.2.4", "lodash": "^4.17.11", diff --git a/package.json b/package.json index 4139b384..32b42d36 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,7 @@ "react": "^16.13.1", "react-autosuggest": "^9.4.3", "react-chartjs-2": "^2.8.0", - "react-color": "^2.17.3", + "react-color": "^2.17.4", "react-copy-to-clipboard": "^5.0.1", "react-datepicker": "~1.5.0", "react-dom": "^16.13.1", @@ -83,11 +83,14 @@ "@types/qs": "^6.9.4", "@types/ramda": "^0.27.14", "@types/react": "^16.9.46", + "@types/react-autosuggest": "^10.0.0", + "@types/react-color": "^2.17.4", "@types/react-copy-to-clipboard": "^4.3.0", "@types/react-datepicker": "~1.8.0", "@types/react-dom": "^16.9.8", "@types/react-redux": "^7.1.9", "@types/react-router-dom": "^5.1.5", + "@types/react-tagsinput": "^3.19.7", "@types/reactstrap": "^8.5.1", "@types/uuid": "^8.3.0", "adm-zip": "^0.4.13", diff --git a/src/short-urls/CreateShortUrl.tsx b/src/short-urls/CreateShortUrl.tsx index 82780fd3..9ade84ac 100644 --- a/src/short-urls/CreateShortUrl.tsx +++ b/src/short-urls/CreateShortUrl.tsx @@ -12,6 +12,7 @@ import { handleEventPreventingDefault, hasValue } from '../utils/utils'; import { useToggle } from '../utils/helpers/hooks'; import { isReachableServer, SelectedServer } from '../servers/data'; import { formatIsoDate } from '../utils/helpers/date'; +import { TagsSelectorProps } from '../tags/helpers/TagsSelector'; import { ShortUrlData } from './data'; import { ShortUrlCreation } from './reducers/shortUrlCreation'; import UseExistingIfFoundInfoIcon from './UseExistingIfFoundInfoIcon'; @@ -42,7 +43,7 @@ type NonDateFields = 'longUrl' | 'customSlug' | 'shortCodeLength' | 'domain' | ' type DateFields = 'validSince' | 'validUntil'; const CreateShortUrl = ( - TagsSelector: FC, + TagsSelector: FC, CreateShortUrlResult: FC, ForServerVersion: FC, ) => ({ createShortUrl, shortUrlCreationResult, resetCreateShortUrl, selectedServer }: CreateShortUrlProps) => { @@ -103,7 +104,7 @@ const CreateShortUrl = (
- +
diff --git a/src/short-urls/helpers/EditTagsModal.tsx b/src/short-urls/helpers/EditTagsModal.tsx index be7cf4ee..6c38e662 100644 --- a/src/short-urls/helpers/EditTagsModal.tsx +++ b/src/short-urls/helpers/EditTagsModal.tsx @@ -4,6 +4,7 @@ import { ExternalLink } from 'react-external-link'; import { ShortUrlTags } from '../reducers/shortUrlTags'; import { ShortUrlModalProps } from '../data'; import { OptionalString } from '../../utils/utils'; +import { TagsSelectorProps } from '../../tags/helpers/TagsSelector'; interface EditTagsModalProps extends ShortUrlModalProps { shortUrlTags: ShortUrlTags; @@ -11,7 +12,7 @@ interface EditTagsModalProps extends ShortUrlModalProps { resetShortUrlsTags: () => void; } -const EditTagsModal = (TagsSelector: FC) => ( // TODO Use TagsSelector type when available +const EditTagsModal = (TagsSelector: FC) => ( { isOpen, toggle, shortUrl, shortUrlTags, editShortUrlTags, resetShortUrlsTags }: EditTagsModalProps, ) => { const [ selectedTags, setSelectedTags ] = useState(shortUrl.tags || []); diff --git a/src/tags/data/index.ts b/src/tags/data/index.ts new file mode 100644 index 00000000..2a1e1aa8 --- /dev/null +++ b/src/tags/data/index.ts @@ -0,0 +1,10 @@ +export interface TagStats { + shortUrlsCount: number; + visitsCount: number; +} + +export interface TagModalProps { + tag: string; + isOpen: boolean; + toggle: () => void; +} diff --git a/src/tags/helpers/DeleteTagConfirmModal.js b/src/tags/helpers/DeleteTagConfirmModal.tsx similarity index 69% rename from src/tags/helpers/DeleteTagConfirmModal.js rename to src/tags/helpers/DeleteTagConfirmModal.tsx index 0cb8664e..da1c8720 100644 --- a/src/tags/helpers/DeleteTagConfirmModal.js +++ b/src/tags/helpers/DeleteTagConfirmModal.tsx @@ -1,18 +1,17 @@ import React from 'react'; import { Modal, ModalBody, ModalFooter, ModalHeader } from 'reactstrap'; -import PropTypes from 'prop-types'; -import { tagDeleteType } from '../reducers/tagDelete'; +import { TagDeletion } from '../reducers/tagDelete'; +import { TagModalProps } from '../data'; -const propTypes = { - tag: PropTypes.string.isRequired, - toggle: PropTypes.func.isRequired, - isOpen: PropTypes.bool.isRequired, - deleteTag: PropTypes.func, - tagDelete: tagDeleteType, - tagDeleted: PropTypes.func, -}; +interface DeleteTagConfirmModalProps extends TagModalProps { + deleteTag: (tag: string) => Promise; + tagDeleted: (tag: string) => void; + tagDelete: TagDeletion; +} -const DeleteTagConfirmModal = ({ tag, toggle, isOpen, deleteTag, tagDelete, tagDeleted }) => { +const DeleteTagConfirmModal = ( + { tag, toggle, isOpen, deleteTag, tagDelete, tagDeleted }: DeleteTagConfirmModalProps, +) => { const doDelete = async () => { await deleteTag(tag); tagDeleted(tag); @@ -42,6 +41,4 @@ const DeleteTagConfirmModal = ({ tag, toggle, isOpen, deleteTag, tagDelete, tagD ); }; -DeleteTagConfirmModal.propTypes = propTypes; - export default DeleteTagConfirmModal; diff --git a/src/tags/helpers/EditTagModal.js b/src/tags/helpers/EditTagModal.js deleted file mode 100644 index f6eaf456..00000000 --- a/src/tags/helpers/EditTagModal.js +++ /dev/null @@ -1,82 +0,0 @@ -import React, { useState } from 'react'; -import { Modal, ModalBody, ModalFooter, ModalHeader, Popover } from 'reactstrap'; -import { ChromePicker } from 'react-color'; -import { faPalette as colorIcon } from '@fortawesome/free-solid-svg-icons'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import PropTypes from 'prop-types'; -import './EditTagModal.scss'; -import { useToggle } from '../../utils/helpers/hooks'; -import { handleEventPreventingDefault } from '../../utils/utils'; - -const propTypes = { - tag: PropTypes.string, - editTag: PropTypes.func, - toggle: PropTypes.func, - tagEdited: PropTypes.func, - isOpen: PropTypes.bool, - tagEdit: PropTypes.shape({ - error: PropTypes.bool, - editing: PropTypes.bool, - }), -}; - -const EditTagModal = ({ getColorForKey }) => { - const EditTagModalComp = ({ tag, editTag, toggle, tagEdited, isOpen, tagEdit }) => { - const [ newTagName, setNewTagName ] = useState(tag); - const [ color, setColor ] = useState(getColorForKey(tag)); - const [ showColorPicker, toggleColorPicker ] = useToggle(); - const saveTag = handleEventPreventingDefault(() => editTag(tag, newTagName, color) - .then(() => tagEdited(tag, newTagName, color)) - .then(toggle) - .catch(() => {})); - - return ( - -
- Edit tag - -
-
-
- -
-
- - setColor(hex)} /> - - setNewTagName(e.target.value)} - /> -
- - {tagEdit.error && ( -
- Something went wrong while editing the tag :( -
- )} -
- - - - -
-
- ); - }; - - EditTagModalComp.propTypes = propTypes; - - return EditTagModalComp; -}; - -export default EditTagModal; diff --git a/src/tags/helpers/EditTagModal.tsx b/src/tags/helpers/EditTagModal.tsx new file mode 100644 index 00000000..7b8f2cd3 --- /dev/null +++ b/src/tags/helpers/EditTagModal.tsx @@ -0,0 +1,74 @@ +import React, { useState } from 'react'; +import { Modal, ModalBody, ModalFooter, ModalHeader, Popover } from 'reactstrap'; +import { ChromePicker } from 'react-color'; +import { faPalette as colorIcon } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { useToggle } from '../../utils/helpers/hooks'; +import { handleEventPreventingDefault } from '../../utils/utils'; +import ColorGenerator from '../../utils/services/ColorGenerator'; +import { TagModalProps } from '../data'; +import { TagEdition } from '../reducers/tagEdit'; +import './EditTagModal.scss'; + +interface EditTagModalProps extends TagModalProps { + tagEdit: TagEdition; + editTag: (oldName: string, newName: string, color: string) => Promise; + tagEdited: (oldName: string, newName: string, color: string) => void; +} + +const EditTagModal = ({ getColorForKey }: ColorGenerator) => ( + { tag, editTag, toggle, tagEdited, isOpen, tagEdit }: EditTagModalProps, +) => { + const [ newTagName, setNewTagName ] = useState(tag); + const [ color, setColor ] = useState(getColorForKey(tag)); + const [ showColorPicker, toggleColorPicker ] = useToggle(); + const saveTag = handleEventPreventingDefault(async () => editTag(tag, newTagName, color) + .then(() => tagEdited(tag, newTagName, color)) + .then(toggle) + .catch(() => {})); + + return ( + +
+ Edit tag + +
+
+
+ +
+
+ + setColor(hex)} /> + + setNewTagName(e.target.value)} + /> +
+ + {tagEdit.error && ( +
+ Something went wrong while editing the tag :( +
+ )} +
+ + + + +
+
+ ); +}; + +export default EditTagModal; diff --git a/src/tags/helpers/Tag.js b/src/tags/helpers/Tag.js deleted file mode 100644 index e2a29e9a..00000000 --- a/src/tags/helpers/Tag.js +++ /dev/null @@ -1,35 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { colorGeneratorType } from '../../utils/services/ColorGenerator'; -import './Tag.scss'; - -const propTypes = { - text: PropTypes.string, - children: PropTypes.node, - clearable: PropTypes.bool, - colorGenerator: colorGeneratorType, - onClick: PropTypes.func, - onClose: PropTypes.func, -}; - -const Tag = ({ - text, - children, - clearable, - colorGenerator, - onClick, - onClose, -}) => ( - - {children || text} - {clearable && ×} - -); - -Tag.propTypes = propTypes; - -export default Tag; diff --git a/src/tags/helpers/Tag.tsx b/src/tags/helpers/Tag.tsx new file mode 100644 index 00000000..e0edcc0f --- /dev/null +++ b/src/tags/helpers/Tag.tsx @@ -0,0 +1,24 @@ +import React, { FC } from 'react'; +import ColorGenerator from '../../utils/services/ColorGenerator'; +import './Tag.scss'; + +interface TagProps { + colorGenerator: ColorGenerator; + text: string; + clearable?: boolean; + onClick?: () => void; + onClose?: () => void; +} + +const Tag: FC = ({ text, children, clearable, colorGenerator, onClick, onClose }) => ( + + {children ?? text} + {clearable && ×} + +); + +export default Tag; diff --git a/src/tags/helpers/TagBullet.js b/src/tags/helpers/TagBullet.js deleted file mode 100644 index 896eaf8f..00000000 --- a/src/tags/helpers/TagBullet.js +++ /dev/null @@ -1,20 +0,0 @@ -import React from 'react'; -import * as PropTypes from 'prop-types'; -import { colorGeneratorType } from '../../utils/services/ColorGenerator'; -import './TagBullet.scss'; - -const propTypes = { - tag: PropTypes.string.isRequired, - colorGenerator: colorGeneratorType, -}; - -const TagBullet = ({ tag, colorGenerator }) => ( -
-); - -TagBullet.propTypes = propTypes; - -export default TagBullet; diff --git a/src/tags/helpers/TagBullet.tsx b/src/tags/helpers/TagBullet.tsx new file mode 100644 index 00000000..aad11d7d --- /dev/null +++ b/src/tags/helpers/TagBullet.tsx @@ -0,0 +1,17 @@ +import React from 'react'; +import ColorGenerator from '../../utils/services/ColorGenerator'; +import './TagBullet.scss'; + +interface TagBulletProps { + tag: string; + colorGenerator: ColorGenerator; +} + +const TagBullet = ({ tag, colorGenerator }: TagBulletProps) => ( +
+); + +export default TagBullet; diff --git a/src/tags/helpers/TagsSelector.js b/src/tags/helpers/TagsSelector.js deleted file mode 100644 index 23d75127..00000000 --- a/src/tags/helpers/TagsSelector.js +++ /dev/null @@ -1,84 +0,0 @@ -import React, { useEffect } from 'react'; -import TagsInput from 'react-tagsinput'; -import PropTypes from 'prop-types'; -import Autosuggest from 'react-autosuggest'; -import { identity } from 'ramda'; -import TagBullet from './TagBullet'; -import './TagsSelector.scss'; - -const propTypes = { - tags: PropTypes.arrayOf(PropTypes.string).isRequired, - onChange: PropTypes.func.isRequired, - listTags: PropTypes.func, - placeholder: PropTypes.string, - tagsList: PropTypes.shape({ - tags: PropTypes.arrayOf(PropTypes.string), - }), -}; - -const TagsSelector = (colorGenerator) => { - const TagsSelectorComp = ({ tags, onChange, listTags, tagsList, placeholder = 'Add tags to the URL' }) => { - useEffect(() => { - listTags(); - }, []); - - // eslint-disable-next-line - const renderTag = ({ tag, key, disabled, onRemove, classNameRemove, getTagDisplayValue, ...other }) => ( - - {getTagDisplayValue(tag)} - {!disabled && onRemove(key)} />} - - ); - const renderAutocompleteInput = (data) => { - const { addTag, ...otherProps } = data; - const handleOnChange = (e, { method }) => { - method === 'enter' ? e.preventDefault() : otherProps.onChange(e); - }; - - const inputValue = (otherProps.value && otherProps.value.trim().toLowerCase()) || ''; - const inputLength = inputValue.length; - const suggestions = tagsList.tags.filter((state) => state.toLowerCase().slice(0, inputLength) === inputValue); - - return ( - value && value.trim().length > 0} - getSuggestionValue={(suggestion) => suggestion} - renderSuggestion={(suggestion) => ( - - - {suggestion} - - )} - onSuggestionSelected={(e, { suggestion }) => { - addTag(suggestion); - }} - onSuggestionsClearRequested={identity} - onSuggestionsFetchRequested={identity} - /> - ); - }; - - return ( - - ); - }; - - TagsSelectorComp.propTypes = propTypes; - - return TagsSelectorComp; -}; - -export default TagsSelector; diff --git a/src/tags/helpers/TagsSelector.tsx b/src/tags/helpers/TagsSelector.tsx new file mode 100644 index 00000000..63da0deb --- /dev/null +++ b/src/tags/helpers/TagsSelector.tsx @@ -0,0 +1,80 @@ +import React, { ChangeEvent, useEffect } from 'react'; +import TagsInput, { RenderInputProps, RenderTagProps } from 'react-tagsinput'; +import Autosuggest, { ChangeEvent as AutoChangeEvent, SuggestionSelectedEventData } from 'react-autosuggest'; +import ColorGenerator from '../../utils/services/ColorGenerator'; +import { TagsList } from '../reducers/tagsList'; +import TagBullet from './TagBullet'; +import './TagsSelector.scss'; + +export interface TagsSelectorProps { + tags: string[]; + onChange: (tags: string[]) => void; + placeholder?: string; +} + +interface TagsSelectorConnectProps extends TagsSelectorProps { + listTags: Function; + tagsList: TagsList; +} + +const TagsSelector = (colorGenerator: ColorGenerator) => ( + { tags, onChange, listTags, tagsList, placeholder = 'Add tags to the URL' }: TagsSelectorConnectProps, +) => { + useEffect(() => { + listTags(); + }, []); + + const renderTag = ( + { tag, key, disabled, onRemove, classNameRemove, getTagDisplayValue, ...other }: RenderTagProps, + ) => ( + + {getTagDisplayValue(tag)} + {!disabled && onRemove(key)} />} + + ); + const renderAutocompleteInput = (data: RenderInputProps) => { + const { addTag, ...otherProps } = data; + const handleOnChange = (e: ChangeEvent, { method }: AutoChangeEvent) => { + method === 'enter' ? e.preventDefault() : otherProps.onChange(e); + }; + + const inputValue = otherProps.value?.trim().toLowerCase() ?? ''; + const suggestions = tagsList.tags.filter((tag) => tag.startsWith(inputValue)); + + return ( + value.trim().length > 0} + getSuggestionValue={(suggestion) => suggestion} + renderSuggestion={(suggestion) => ( + + + {suggestion} + + )} + onSuggestionsFetchRequested={() => {}} + onSuggestionSelected={(_, { suggestion }: SuggestionSelectedEventData) => { + addTag(suggestion); + }} + /> + ); + }; + + return ( + + ); +}; + +export default TagsSelector; diff --git a/src/tags/reducers/tagDelete.ts b/src/tags/reducers/tagDelete.ts index e662a179..55c08b55 100644 --- a/src/tags/reducers/tagDelete.ts +++ b/src/tags/reducers/tagDelete.ts @@ -1,4 +1,3 @@ -import PropTypes from 'prop-types'; import { Action, Dispatch } from 'redux'; import { buildReducer } from '../../utils/helpers/redux'; import { GetState } from '../../container/types'; @@ -11,12 +10,6 @@ export const DELETE_TAG = 'shlink/deleteTag/DELETE_TAG'; export const TAG_DELETED = 'shlink/deleteTag/TAG_DELETED'; /* eslint-enable padding-line-between-statements */ -/** @deprecated Use TagDeletion interface */ -export const tagDeleteType = PropTypes.shape({ - deleting: PropTypes.bool, - error: PropTypes.bool, -}); - export interface TagDeletion { deleting: boolean; error: boolean; diff --git a/src/tags/reducers/tagsList.ts b/src/tags/reducers/tagsList.ts index 2e4cf1c0..56975c8f 100644 --- a/src/tags/reducers/tagsList.ts +++ b/src/tags/reducers/tagsList.ts @@ -5,9 +5,10 @@ import { CREATE_VISIT, CreateVisitAction } from '../../visits/reducers/visitCrea import { buildReducer } from '../../utils/helpers/redux'; import { ShlinkTags } from '../../utils/services/types'; import { GetState } from '../../container/types'; +import { ShlinkApiClientBuilder } from '../../utils/services/ShlinkApiClientBuilder'; +import { TagStats } from '../data'; import { DeleteTagAction, TAG_DELETED } from './tagDelete'; import { EditTagAction, TAG_EDITED } from './tagEdit'; -import { ShlinkApiClientBuilder } from '../../utils/services/ShlinkApiClientBuilder'; /* eslint-disable padding-line-between-statements */ export const LIST_TAGS_START = 'shlink/tagsList/LIST_TAGS_START'; @@ -28,19 +29,19 @@ export const TagsListType = PropTypes.shape({ error: PropTypes.bool, }); -type TagsStats = Record; +type TagsStatsMap = Record; export interface TagsList { tags: string[]; filteredTags: string[]; - stats: TagsStats; + stats: TagsStatsMap; loading: boolean; error: boolean; } interface ListTagsAction extends Action { tags: string[]; - stats: TagsStats; + stats: TagsStatsMap; } interface FilterTagsAction extends Action { @@ -59,7 +60,7 @@ const initialState = { const renameTag = (oldName: string, newName: string) => (tag: string) => tag === oldName ? newName : tag; const rejectTag = (tags: string[], tagToReject: string) => reject((tag) => tag === tagToReject, tags); -const increaseVisitsForTags = (tags: string[], stats: TagsStats) => tags.reduce((stats, tag) => { +const increaseVisitsForTags = (tags: string[], stats: TagsStatsMap) => tags.reduce((stats, tag) => { if (!stats[tag]) { return stats; } @@ -111,7 +112,7 @@ export const listTags = (buildShlinkApiClient: ShlinkApiClientBuilder, force = t try { const { listTags } = buildShlinkApiClient(getState); const { tags, stats = [] }: ShlinkTags = await listTags(); - const processedStats = stats.reduce((acc, { tag, shortUrlsCount, visitsCount }) => { + const processedStats = stats.reduce((acc, { tag, shortUrlsCount, visitsCount }) => { acc[tag] = { shortUrlsCount, visitsCount }; return acc; diff --git a/test/tags/helpers/DeleteTagConfirmModal.test.js b/test/tags/helpers/DeleteTagConfirmModal.test.tsx similarity index 86% rename from test/tags/helpers/DeleteTagConfirmModal.test.js rename to test/tags/helpers/DeleteTagConfirmModal.test.tsx index a13881a1..694f16d8 100644 --- a/test/tags/helpers/DeleteTagConfirmModal.test.js +++ b/test/tags/helpers/DeleteTagConfirmModal.test.tsx @@ -1,14 +1,15 @@ import React from 'react'; -import { shallow } from 'enzyme'; +import { shallow, ShallowWrapper } from 'enzyme'; import { Modal, ModalBody, ModalFooter } from 'reactstrap'; import DeleteTagConfirmModal from '../../../src/tags/helpers/DeleteTagConfirmModal'; +import { TagDeletion } from '../../../src/tags/reducers/tagDelete'; describe('', () => { - let wrapper; + let wrapper: ShallowWrapper; const tag = 'nodejs'; const deleteTag = jest.fn(); const tagDeleted = jest.fn(); - const createWrapper = (tagDelete) => { + const createWrapper = (tagDelete: TagDeletion) => { wrapper = shallow( ', () => { return wrapper; }; - afterEach(() => { - wrapper && wrapper.unmount(); - jest.resetAllMocks(); - }); + afterEach(() => wrapper?.unmount()); + afterEach(jest.resetAllMocks); it('asks confirmation for provided tag to be deleted', () => { wrapper = createWrapper({ error: false, deleting: false }); @@ -60,7 +59,8 @@ describe('', () => { const footer = wrapper.find(ModalFooter); const delBtn = footer.find('.btn-danger'); - await delBtn.simulate('click'); + await delBtn.simulate('click'); // eslint-disable-line @typescript-eslint/await-thenable + expect(deleteTag).toHaveBeenCalledTimes(1); expect(deleteTag).toHaveBeenCalledWith(tag); expect(tagDeleted).toHaveBeenCalledTimes(1); From f8ea1ae3d55d21bc0b089e3bfa2b15f9e9d5a0a7 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 30 Aug 2020 20:48:09 +0200 Subject: [PATCH 43/59] Migrated remaining tags-related elements to TS --- src/servers/prop-types/index.js | 21 ----- src/tags/TagCard.js | 84 ----------------- src/tags/TagCard.tsx | 82 +++++++++++++++++ src/tags/TagsList.js | 91 ------------------- src/tags/TagsList.tsx | 85 +++++++++++++++++ src/tags/reducers/tagsList.ts | 13 --- .../{TagCard.test.js => TagCard.test.tsx} | 19 +++- .../{TagsList.test.js => TagsList.test.tsx} | 28 +++--- 8 files changed, 198 insertions(+), 225 deletions(-) delete mode 100644 src/servers/prop-types/index.js delete mode 100644 src/tags/TagCard.js create mode 100644 src/tags/TagCard.tsx delete mode 100644 src/tags/TagsList.js create mode 100644 src/tags/TagsList.tsx rename test/tags/{TagCard.test.js => TagCard.test.tsx} (77%) rename test/tags/{TagsList.test.js => TagsList.test.tsx} (75%) diff --git a/src/servers/prop-types/index.js b/src/servers/prop-types/index.js deleted file mode 100644 index f86d1744..00000000 --- a/src/servers/prop-types/index.js +++ /dev/null @@ -1,21 +0,0 @@ -import PropTypes from 'prop-types'; - -const regularServerType = PropTypes.shape({ - id: PropTypes.string, - name: PropTypes.string, - url: PropTypes.string, - apiKey: PropTypes.string, - version: PropTypes.string, - printableVersion: PropTypes.string, - serverNotReachable: PropTypes.bool, -}); - -const notFoundServerType = PropTypes.shape({ - serverNotFound: PropTypes.bool.isRequired, -}); - -/** @deprecated Use SelectedServer type instead */ -export const serverType = PropTypes.oneOfType([ - regularServerType, - notFoundServerType, -]); diff --git a/src/tags/TagCard.js b/src/tags/TagCard.js deleted file mode 100644 index 9ec68982..00000000 --- a/src/tags/TagCard.js +++ /dev/null @@ -1,84 +0,0 @@ -import { Card, CardHeader, CardBody, Button, Collapse } from 'reactstrap'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { faTrash as deleteIcon, faPencilAlt as editIcon, faLink, faEye } from '@fortawesome/free-solid-svg-icons'; -import PropTypes from 'prop-types'; -import React from 'react'; -import { Link } from 'react-router-dom'; -import { serverType } from '../servers/prop-types'; -import { prettify } from '../utils/helpers/numbers'; -import { useToggle } from '../utils/helpers/hooks'; -import TagBullet from './helpers/TagBullet'; -import './TagCard.scss'; - -const propTypes = { - tag: PropTypes.string, - tagStats: PropTypes.shape({ - shortUrlsCount: PropTypes.number, - visitsCount: PropTypes.number, - }), - selectedServer: serverType, - displayed: PropTypes.bool, - toggle: PropTypes.func, -}; - -const TagCard = (DeleteTagConfirmModal, EditTagModal, ForServerVersion, colorGenerator) => { - const TagCardComp = ({ tag, tagStats, selectedServer, displayed, toggle }) => { - const [ isDeleteModalOpen, toggleDelete ] = useToggle(); - const [ isEditModalOpen, toggleEdit ] = useToggle(); - - const { id } = selectedServer; - const shortUrlsLink = `/server/${id}/list-short-urls/1?tag=${tag}`; - - return ( - - - - -
- - - {tag} - - - {tag} - -
-
- - {tagStats && ( - - - - Short URLs - {prettify(tagStats.shortUrlsCount)} - - - Visits - {prettify(tagStats.visitsCount)} - - - - )} - - - -
- ); - }; - - TagCardComp.propTypes = propTypes; - - return TagCardComp; -}; - -export default TagCard; diff --git a/src/tags/TagCard.tsx b/src/tags/TagCard.tsx new file mode 100644 index 00000000..6fe774f9 --- /dev/null +++ b/src/tags/TagCard.tsx @@ -0,0 +1,82 @@ +import { Card, CardHeader, CardBody, Button, Collapse } from 'reactstrap'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faTrash as deleteIcon, faPencilAlt as editIcon, faLink, faEye } from '@fortawesome/free-solid-svg-icons'; +import React, { FC } from 'react'; +import { Link } from 'react-router-dom'; +import { prettify } from '../utils/helpers/numbers'; +import { useToggle } from '../utils/helpers/hooks'; +import { Versions } from '../utils/helpers/version'; +import ColorGenerator from '../utils/services/ColorGenerator'; +import { isServerWithId, SelectedServer } from '../servers/data'; +import TagBullet from './helpers/TagBullet'; +import { TagModalProps, TagStats } from './data'; +import './TagCard.scss'; + +export interface TagCardProps { + tag: string; + tagStats?: TagStats; + selectedServer: SelectedServer; + displayed: boolean; + toggle: () => void; +} + +const TagCard = ( + DeleteTagConfirmModal: FC, + EditTagModal: FC, + ForServerVersion: FC, + colorGenerator: ColorGenerator, +) => ({ tag, tagStats, selectedServer, displayed, toggle }: TagCardProps) => { + const [ isDeleteModalOpen, toggleDelete ] = useToggle(); + const [ isEditModalOpen, toggleEdit ] = useToggle(); + + const serverId = isServerWithId(selectedServer) ? selectedServer.id : ''; + const shortUrlsLink = `/server/${serverId}/list-short-urls/1?tag=${tag}`; + + return ( + + + + +
+ + + {tag} + + + {tag} + +
+
+ + {tagStats && ( + + + + Short URLs + {prettify(tagStats.shortUrlsCount)} + + + Visits + {prettify(tagStats.visitsCount)} + + + + )} + + + +
+ ); +}; + +export default TagCard; diff --git a/src/tags/TagsList.js b/src/tags/TagsList.js deleted file mode 100644 index a923bf1b..00000000 --- a/src/tags/TagsList.js +++ /dev/null @@ -1,91 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import { splitEvery } from 'ramda'; -import PropTypes from 'prop-types'; -import Message from '../utils/Message'; -import SearchField from '../utils/SearchField'; -import { serverType } from '../servers/prop-types'; -import { MercureInfoType } from '../mercure/reducers/mercureInfo'; -import { useMercureTopicBinding } from '../mercure/helpers'; -import { TagsListType } from './reducers/tagsList'; - -const { ceil } = Math; -const TAGS_GROUPS_AMOUNT = 4; - -const propTypes = { - filterTags: PropTypes.func, - forceListTags: PropTypes.func, - tagsList: TagsListType, - selectedServer: serverType, - createNewVisit: PropTypes.func, - loadMercureInfo: PropTypes.func, - mercureInfo: MercureInfoType, -}; - -const TagsList = (TagCard) => { - const TagListComp = ( - { filterTags, forceListTags, tagsList, selectedServer, createNewVisit, loadMercureInfo, mercureInfo }, - ) => { - const [ displayedTag, setDisplayedTag ] = useState(); - - useEffect(() => { - forceListTags(); - }, []); - useMercureTopicBinding(mercureInfo, 'https://shlink.io/new-visit', createNewVisit, loadMercureInfo); - - const renderContent = () => { - if (tagsList.loading) { - return ; - } - - if (tagsList.error) { - return ( -
-
Error loading tags :(
-
- ); - } - - const tagsCount = tagsList.filteredTags.length; - - if (tagsCount < 1) { - return No tags found; - } - - const tagsGroups = splitEvery(ceil(tagsCount / TAGS_GROUPS_AMOUNT), tagsList.filteredTags); - - return ( - - {tagsGroups.map((group, index) => ( -
- {group.map((tag) => ( - setDisplayedTag(displayedTag !== tag ? tag : undefined)} - /> - ))} -
- ))} -
- ); - }; - - return ( - - {!tagsList.loading && } -
- {renderContent()} -
-
- ); - }; - - TagListComp.propTypes = propTypes; - - return TagListComp; -}; - -export default TagsList; diff --git a/src/tags/TagsList.tsx b/src/tags/TagsList.tsx new file mode 100644 index 00000000..0da20b1b --- /dev/null +++ b/src/tags/TagsList.tsx @@ -0,0 +1,85 @@ +import React, { FC, useEffect, useState } from 'react'; +import { splitEvery } from 'ramda'; +import Message from '../utils/Message'; +import SearchField from '../utils/SearchField'; +import { MercureInfo } from '../mercure/reducers/mercureInfo'; +import { useMercureTopicBinding } from '../mercure/helpers'; +import { SelectedServer } from '../servers/data'; +import { TagsList as TagsListState } from './reducers/tagsList'; +import { TagCardProps } from './TagCard'; + +const { ceil } = Math; +const TAGS_GROUPS_AMOUNT = 4; + +export interface TagsListProps { + filterTags: (searchTerm: string) => void; + forceListTags: Function; + tagsList: TagsListState; + selectedServer: SelectedServer; + createNewVisit: () => void; + loadMercureInfo: Function; + mercureInfo: MercureInfo; +} + +const TagsList = (TagCard: FC) => ( + { filterTags, forceListTags, tagsList, selectedServer, createNewVisit, loadMercureInfo, mercureInfo }: TagsListProps, +) => { + const [ displayedTag, setDisplayedTag ] = useState(); + + useEffect(() => { + forceListTags(); + }, []); + useMercureTopicBinding(mercureInfo, 'https://shlink.io/new-visit', createNewVisit, loadMercureInfo); + + const renderContent = () => { + if (tagsList.loading) { + return ; + } + + if (tagsList.error) { + return ( +
+
Error loading tags :(
+
+ ); + } + + const tagsCount = tagsList.filteredTags.length; + + if (tagsCount < 1) { + return No tags found; + } + + const tagsGroups = splitEvery(ceil(tagsCount / TAGS_GROUPS_AMOUNT), tagsList.filteredTags); + + return ( + + {tagsGroups.map((group, index) => ( +
+ {group.map((tag) => ( + setDisplayedTag(displayedTag !== tag ? tag : undefined)} + /> + ))} +
+ ))} +
+ ); + }; + + return ( + + {!tagsList.loading && } +
+ {renderContent()} +
+
+ ); +}; + +export default TagsList; diff --git a/src/tags/reducers/tagsList.ts b/src/tags/reducers/tagsList.ts index 56975c8f..ea6a6711 100644 --- a/src/tags/reducers/tagsList.ts +++ b/src/tags/reducers/tagsList.ts @@ -1,5 +1,4 @@ import { isEmpty, reject } from 'ramda'; -import PropTypes from 'prop-types'; import { Action, Dispatch } from 'redux'; import { CREATE_VISIT, CreateVisitAction } from '../../visits/reducers/visitCreation'; import { buildReducer } from '../../utils/helpers/redux'; @@ -17,18 +16,6 @@ export const LIST_TAGS = 'shlink/tagsList/LIST_TAGS'; export const FILTER_TAGS = 'shlink/tagsList/FILTER_TAGS'; /* eslint-enable padding-line-between-statements */ -/** @deprecated Use TagsList interface instead */ -export const TagsListType = PropTypes.shape({ - tags: PropTypes.arrayOf(PropTypes.string), - filteredTags: PropTypes.arrayOf(PropTypes.string), - stats: PropTypes.objectOf(PropTypes.shape({ - shortUrlsCount: PropTypes.number, - visitsCount: PropTypes.number, - })), // Record - loading: PropTypes.bool, - error: PropTypes.bool, -}); - type TagsStatsMap = Record; export interface TagsList { diff --git a/test/tags/TagCard.test.js b/test/tags/TagCard.test.tsx similarity index 77% rename from test/tags/TagCard.test.js rename to test/tags/TagCard.test.tsx index 6d806752..e1e3bc6f 100644 --- a/test/tags/TagCard.test.js +++ b/test/tags/TagCard.test.tsx @@ -1,11 +1,14 @@ import React from 'react'; -import { shallow } from 'enzyme'; +import { shallow, ShallowWrapper } from 'enzyme'; import { Link } from 'react-router-dom'; +import { Mock } from 'ts-mockery'; import createTagCard from '../../src/tags/TagCard'; import TagBullet from '../../src/tags/helpers/TagBullet'; +import ColorGenerator from '../../src/utils/services/ColorGenerator'; +import { ReachableServer } from '../../src/servers/data'; describe('', () => { - let wrapper; + let wrapper: ShallowWrapper; const tagStats = { shortUrlsCount: 48, visitsCount: 23257, @@ -14,9 +17,17 @@ describe('', () => { const EditTagModal = jest.fn(); beforeEach(() => { - const TagCard = createTagCard(DeleteTagConfirmModal, EditTagModal, () => '', {}); + const TagCard = createTagCard(DeleteTagConfirmModal, EditTagModal, () => null, Mock.all()); - wrapper = shallow(); + wrapper = shallow( + ({ id: '1' })} + tagStats={tagStats} + displayed={true} + toggle={() => {}} + />, + ); }); afterEach(() => wrapper.unmount()); diff --git a/test/tags/TagsList.test.js b/test/tags/TagsList.test.tsx similarity index 75% rename from test/tags/TagsList.test.js rename to test/tags/TagsList.test.tsx index cd20409e..4cfe8790 100644 --- a/test/tags/TagsList.test.js +++ b/test/tags/TagsList.test.tsx @@ -1,30 +1,34 @@ import React from 'react'; -import { shallow } from 'enzyme'; +import { shallow, ShallowWrapper } from 'enzyme'; import { identity } from 'ramda'; -import createTagsList from '../../src/tags/TagsList'; +import { Mock } from 'ts-mockery'; +import createTagsList, { TagsListProps } from '../../src/tags/TagsList'; import Message from '../../src/utils/Message'; import SearchField from '../../src/utils/SearchField'; import { rangeOf } from '../../src/utils/utils'; +import { TagsList } from '../../src/tags/reducers/tagsList'; describe('', () => { - let wrapper; + let wrapper: ShallowWrapper; const filterTags = jest.fn(); - const TagCard = () => ''; - const createWrapper = (tagsList) => { - const params = { serverId: '1' }; - const TagsList = createTagsList(TagCard); + const TagCard = () => null; + const createWrapper = (tagsList: Partial) => { + const TagsListComp = createTagsList(TagCard); wrapper = shallow( - , + ()} + forceListTags={identity} + filterTags={filterTags} + tagsList={Mock.of(tagsList)} + />, ); return wrapper; }; - afterEach(() => { - wrapper && wrapper.unmount(); - filterTags.mockReset(); - }); + afterEach(() => wrapper?.unmount()); + afterEach(jest.clearAllMocks); it('shows a loading message when tags are being loaded', () => { const wrapper = createWrapper({ loading: true }); From 16d96efa4a5b8d885daef48107bec25e94bc6d9f Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Mon, 31 Aug 2020 18:38:27 +0200 Subject: [PATCH 44/59] Finished migrating all remaining utils to TS --- src/short-urls/SearchBar.tsx | 6 +-- src/short-urls/ShortUrlsList.tsx | 4 +- .../{DateRangeRow.js => DateRangeRow.tsx} | 27 +++++++------ ...alFormGroup.js => HorizontalFormGroup.tsx} | 25 ++++++------ src/utils/{Message.js => Message.tsx} | 36 +++++++++--------- ...tionDropdown.js => PaginationDropdown.tsx} | 17 ++++----- src/utils/{SearchField.js => SearchField.tsx} | 27 +++++++------ ...SortingDropdown.js => SortingDropdown.tsx} | 38 ++++++++----------- src/utils/utils.ts | 6 ++- ...RangeRow.test.js => DateRangeRow.test.tsx} | 4 +- ...pdown.test.js => SortingDropdown.test.tsx} | 10 ++--- 11 files changed, 95 insertions(+), 105 deletions(-) rename src/utils/{DateRangeRow.js => DateRangeRow.tsx} (55%) rename src/utils/{HorizontalFormGroup.js => HorizontalFormGroup.tsx} (50%) rename src/utils/{Message.js => Message.tsx} (55%) rename src/utils/{PaginationDropdown.js => PaginationDropdown.tsx} (73%) rename src/utils/{SearchField.js => SearchField.tsx} (72%) rename src/utils/{SortingDropdown.js => SortingDropdown.tsx} (70%) rename test/utils/{DateRangeRow.test.js => DateRangeRow.test.tsx} (93%) rename test/utils/{SortingDropdown.test.js => SortingDropdown.test.tsx} (90%) diff --git a/src/short-urls/SearchBar.tsx b/src/short-urls/SearchBar.tsx index 85150608..5e9b44b9 100644 --- a/src/short-urls/SearchBar.tsx +++ b/src/short-urls/SearchBar.tsx @@ -17,7 +17,7 @@ interface SearchBarProps { shortUrlsListParams: ShortUrlsListParams; } -const dateOrUndefined = (date?: string) => date ? moment(date) : undefined; +const dateOrNull = (date?: string) => date ? moment(date) : null; const SearchBar = (colorGenerator: ColorGenerator, ForServerVersion: FC) => ( { listShortUrls, shortUrlsListParams }: SearchBarProps, @@ -41,8 +41,8 @@ const SearchBar = (colorGenerator: ColorGenerator, ForServerVersion: FC
diff --git a/src/short-urls/ShortUrlsList.tsx b/src/short-urls/ShortUrlsList.tsx index 1273894d..c045c892 100644 --- a/src/short-urls/ShortUrlsList.tsx +++ b/src/short-urls/ShortUrlsList.tsx @@ -62,9 +62,9 @@ const ShortUrlsList = (ShortUrlsRow: FC) => ({ orderDir: orderBy && head(values(orderBy)), }); const refreshList = (extraParams: ShortUrlsListParams) => listShortUrls({ ...shortUrlsListParams, ...extraParams }); - const handleOrderBy = (orderField: OrderableFields, orderDir: OrderDir) => { + const handleOrderBy = (orderField?: OrderableFields, orderDir?: OrderDir) => { setOrder({ orderField, orderDir }); - refreshList({ orderBy: { [orderField]: orderDir } }); + refreshList({ orderBy: orderField ? { [orderField]: orderDir } : undefined }); }; const orderByColumn = (field: OrderableFields) => () => handleOrderBy(field, determineOrderDir(field, order.orderField, order.orderDir)); diff --git a/src/utils/DateRangeRow.js b/src/utils/DateRangeRow.tsx similarity index 55% rename from src/utils/DateRangeRow.js rename to src/utils/DateRangeRow.tsx index ca547e4a..c96d1af9 100644 --- a/src/utils/DateRangeRow.js +++ b/src/utils/DateRangeRow.tsx @@ -1,25 +1,26 @@ import React from 'react'; -import PropTypes from 'prop-types'; +import moment from 'moment'; import DateInput from './DateInput'; import './DateRangeRow.scss'; -const dateType = PropTypes.oneOfType([ PropTypes.string, PropTypes.object ]); -const propTypes = { - startDate: dateType, - endDate: dateType, - onStartDateChange: PropTypes.func.isRequired, - onEndDateChange: PropTypes.func.isRequired, - disabled: PropTypes.bool, -}; +interface DateRangeRowProps { + startDate?: moment.Moment | null; + endDate?: moment.Moment | null; + onStartDateChange: (date: moment.Moment | null) => void; + onEndDateChange: (date: moment.Moment | null) => void; + disabled?: boolean; +} -const DateRangeRow = ({ startDate, endDate, onStartDateChange, onEndDateChange, disabled = false }) => ( +const DateRangeRow = ( + { startDate = null, endDate = null, disabled = false, onStartDateChange, onEndDateChange }: DateRangeRowProps, +) => (
@@ -30,7 +31,7 @@ const DateRangeRow = ({ startDate, endDate, onStartDateChange, onEndDateChange, selected={endDate} placeholderText="Until" isClearable - minDate={startDate} + minDate={startDate ?? undefined} disabled={disabled} onChange={onEndDateChange} /> @@ -38,6 +39,4 @@ const DateRangeRow = ({ startDate, endDate, onStartDateChange, onEndDateChange,
); -DateRangeRow.propTypes = propTypes; - export default DateRangeRow; diff --git a/src/utils/HorizontalFormGroup.js b/src/utils/HorizontalFormGroup.tsx similarity index 50% rename from src/utils/HorizontalFormGroup.js rename to src/utils/HorizontalFormGroup.tsx index 2586c9c3..7d53c9ed 100644 --- a/src/utils/HorizontalFormGroup.js +++ b/src/utils/HorizontalFormGroup.tsx @@ -1,17 +1,18 @@ -import React from 'react'; +import React, { FC } from 'react'; import { v4 as uuid } from 'uuid'; -import PropTypes from 'prop-types'; +import { InputType } from 'reactstrap/lib/Input'; -const propTypes = { - children: PropTypes.node.isRequired, - value: PropTypes.string.isRequired, - onChange: PropTypes.func.isRequired, - id: PropTypes.string, - type: PropTypes.string, - required: PropTypes.bool, -}; +interface HorizontalFormGroupProps { + value: string; + onChange: (newValue: string) => void; + id?: string; + type?: InputType; + required?: boolean; +} -export const HorizontalFormGroup = ({ children, value, onChange, id = uuid(), type = 'text', required = true }) => ( +export const HorizontalFormGroup: FC = ( + { children, value, onChange, id = uuid(), type = 'text', required = true }, +) => (
); - -HorizontalFormGroup.propTypes = propTypes; diff --git a/src/utils/Message.js b/src/utils/Message.tsx similarity index 55% rename from src/utils/Message.js rename to src/utils/Message.tsx index 2aa723f8..daf9e48e 100644 --- a/src/utils/Message.js +++ b/src/utils/Message.tsx @@ -1,33 +1,35 @@ -import React from 'react'; +import React, { FC } from 'react'; import { Card } from 'reactstrap'; import classNames from 'classnames'; -import PropTypes from 'prop-types'; import { faCircleNotch as preloader } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -const getClassForType = (type) => { - const map = { +type MessageType = 'default' | 'error'; + +const getClassForType = (type: MessageType) => { + const map: Record = { error: 'border-danger', + default: '', }; - return map[type] || ''; + return map[type]; }; -const getTextClassForType = (type) => { - const map = { +const getTextClassForType = (type: MessageType) => { + const map: Record = { error: 'text-danger', + default: 'text-muted', }; - return map[type] || 'text-muted'; + return map[type]; }; -const propTypes = { - noMargin: PropTypes.bool, - loading: PropTypes.bool, - children: PropTypes.node, - type: PropTypes.oneOf([ 'default', 'error' ]), -}; +interface MessageProps { + noMargin?: boolean; + loading?: boolean; + type?: MessageType; +} -const Message = ({ children, loading = false, noMargin = false, type = 'default' }) => { +const Message: FC = ({ children, loading = false, noMargin = false, type = 'default' }) => { const cardClasses = classNames('bg-light', getClassForType(type), { 'mt-4': !noMargin }); return ( @@ -35,7 +37,7 @@ const Message = ({ children, loading = false, noMargin = false, type = 'default'

{loading && } - {loading && {children || 'Loading...'}} + {loading && {children ?? 'Loading...'}} {!loading && children}

@@ -43,6 +45,4 @@ const Message = ({ children, loading = false, noMargin = false, type = 'default' ); }; -Message.propTypes = propTypes; - export default Message; diff --git a/src/utils/PaginationDropdown.js b/src/utils/PaginationDropdown.tsx similarity index 73% rename from src/utils/PaginationDropdown.js rename to src/utils/PaginationDropdown.tsx index 0789e9bf..077354f3 100644 --- a/src/utils/PaginationDropdown.js +++ b/src/utils/PaginationDropdown.tsx @@ -1,15 +1,14 @@ import React from 'react'; import { DropdownItem, DropdownMenu, DropdownToggle, UncontrolledDropdown } from 'reactstrap'; -import * as PropTypes from 'prop-types'; -const propTypes = { - toggleClassName: PropTypes.string, - ranges: PropTypes.arrayOf(PropTypes.number).isRequired, - value: PropTypes.number.isRequired, - setValue: PropTypes.func.isRequired, -}; +interface PaginationDropdownProps { + ranges: number[]; + value: number; + setValue: (newValue: number) => void; + toggleClassName?: string; +} -const PaginationDropdown = ({ toggleClassName, ranges, value, setValue }) => ( +const PaginationDropdown = ({ toggleClassName, ranges, value, setValue }: PaginationDropdownProps) => ( Paginate @@ -28,6 +27,4 @@ const PaginationDropdown = ({ toggleClassName, ranges, value, setValue }) => ( ); -PaginationDropdown.propTypes = propTypes; - export default PaginationDropdown; diff --git a/src/utils/SearchField.js b/src/utils/SearchField.tsx similarity index 72% rename from src/utils/SearchField.js rename to src/utils/SearchField.tsx index 2b460fa8..7b666a56 100644 --- a/src/utils/SearchField.js +++ b/src/utils/SearchField.tsx @@ -1,29 +1,30 @@ import React, { useState } from 'react'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faSearch as searchIcon } from '@fortawesome/free-solid-svg-icons'; -import PropTypes from 'prop-types'; import classNames from 'classnames'; import './SearchField.scss'; const DEFAULT_SEARCH_INTERVAL = 500; -let timer; +let timer: NodeJS.Timeout | null; -const propTypes = { - onChange: PropTypes.func.isRequired, - className: PropTypes.string, - placeholder: PropTypes.string, - large: PropTypes.bool, - noBorder: PropTypes.bool, -}; +interface SearchField { + onChange: (value: string) => void; + className?: string; + placeholder?: string; + large?: boolean; + noBorder?: boolean; +} -const SearchField = ({ onChange, className, placeholder = 'Search...', large = true, noBorder = false }) => { +const SearchField = ( + { onChange, className, placeholder = 'Search...', large = true, noBorder = false }: SearchField, +) => { const [ searchTerm, setSearchTerm ] = useState(''); const resetTimer = () => { - clearTimeout(timer); + timer && clearTimeout(timer); timer = null; }; - const searchTermChanged = (newSearchTerm, timeout = DEFAULT_SEARCH_INTERVAL) => { + const searchTermChanged = (newSearchTerm: string, timeout = DEFAULT_SEARCH_INTERVAL) => { setSearchTerm(newSearchTerm); resetTimer(); @@ -59,6 +60,4 @@ const SearchField = ({ onChange, className, placeholder = 'Search...', large = t ); }; -SearchField.propTypes = propTypes; - export default SearchField; diff --git a/src/utils/SortingDropdown.js b/src/utils/SortingDropdown.tsx similarity index 70% rename from src/utils/SortingDropdown.js rename to src/utils/SortingDropdown.tsx index 0f123ca2..c85c8bb4 100644 --- a/src/utils/SortingDropdown.js +++ b/src/utils/SortingDropdown.tsx @@ -1,28 +1,25 @@ import React from 'react'; import { UncontrolledDropdown, DropdownToggle, DropdownMenu, DropdownItem } from 'reactstrap'; import { toPairs } from 'ramda'; -import PropTypes from 'prop-types'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faSortAmountUp as sortAscIcon, faSortAmountDown as sortDescIcon } from '@fortawesome/free-solid-svg-icons'; import classNames from 'classnames'; -import { determineOrderDir } from './utils'; +import { determineOrderDir, OrderDir } from './utils'; import './SortingDropdown.scss'; -const propTypes = { - items: PropTypes.object.isRequired, - orderField: PropTypes.string, - orderDir: PropTypes.oneOf([ 'ASC', 'DESC' ]), - onChange: PropTypes.func.isRequired, - isButton: PropTypes.bool, - right: PropTypes.bool, -}; -const defaultProps = { - isButton: true, - right: false, -}; +export interface SortingDropdownProps { + items: Record; + orderField?: T; + orderDir?: OrderDir; + onChange: (orderField?: T, orderDir?: OrderDir) => void; + isButton?: boolean; + right?: boolean; +} -const SortingDropdown = ({ items, orderField, orderDir, onChange, isButton, right }) => { - const handleItemClick = (fieldKey) => () => { +export default function SortingDropdown( + { items, orderField, orderDir, onChange, isButton = true, right = false }: SortingDropdownProps, +) { + const handleItemClick = (fieldKey: T) => () => { const newOrderDir = determineOrderDir(fieldKey, orderField, orderDir); onChange(newOrderDir ? fieldKey : undefined, newOrderDir); @@ -42,7 +39,7 @@ const SortingDropdown = ({ items, orderField, orderDir, onChange, isButton, righ className={classNames('sorting-dropdown__menu', { 'sorting-dropdown__menu--link': !isButton })} > {toPairs(items).map(([ fieldKey, fieldValue ]) => ( - + {fieldValue} {orderField === fieldKey && ( ); -}; - -SortingDropdown.propTypes = propTypes; -SortingDropdown.defaultProps = defaultProps; - -export default SortingDropdown; +} diff --git a/src/utils/utils.ts b/src/utils/utils.ts index e3a061e4..25c22222 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -3,7 +3,11 @@ import { SyntheticEvent } from 'react'; export type OrderDir = 'ASC' | 'DESC' | undefined; -export const determineOrderDir = (currentField: string, newField?: string, currentOrderDir?: OrderDir): OrderDir => { +export const determineOrderDir = ( + currentField: T, + newField?: T, + currentOrderDir?: OrderDir, +): OrderDir => { if (currentField !== newField) { return 'ASC'; } diff --git a/test/utils/DateRangeRow.test.js b/test/utils/DateRangeRow.test.tsx similarity index 93% rename from test/utils/DateRangeRow.test.js rename to test/utils/DateRangeRow.test.tsx index 5b6a4cc5..6c06135f 100644 --- a/test/utils/DateRangeRow.test.js +++ b/test/utils/DateRangeRow.test.tsx @@ -1,10 +1,10 @@ import React from 'react'; -import { shallow } from 'enzyme'; +import { shallow, ShallowWrapper } from 'enzyme'; import DateRangeRow from '../../src/utils/DateRangeRow'; import DateInput from '../../src/utils/DateInput'; describe('', () => { - let wrapper; + let wrapper: ShallowWrapper; const onEndDateChange = jest.fn(); const onStartDateChange = jest.fn(); diff --git a/test/utils/SortingDropdown.test.js b/test/utils/SortingDropdown.test.tsx similarity index 90% rename from test/utils/SortingDropdown.test.js rename to test/utils/SortingDropdown.test.tsx index 1547d31c..3f697a74 100644 --- a/test/utils/SortingDropdown.test.js +++ b/test/utils/SortingDropdown.test.tsx @@ -1,25 +1,25 @@ import React from 'react'; -import { shallow } from 'enzyme'; +import { shallow, ShallowWrapper } from 'enzyme'; import { DropdownItem } from 'reactstrap'; import { identity, values } from 'ramda'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faSortAmountDown as caretDownIcon } from '@fortawesome/free-solid-svg-icons'; -import SortingDropdown from '../../src/utils/SortingDropdown'; +import SortingDropdown, { SortingDropdownProps } from '../../src/utils/SortingDropdown'; describe('', () => { - let wrapper; + let wrapper: ShallowWrapper; const items = { foo: 'Foo', bar: 'Bar', baz: 'Hello World', }; - const createWrapper = (props) => { + const createWrapper = (props: Partial = {}) => { wrapper = shallow(); return wrapper; }; - afterEach(() => wrapper && wrapper.unmount()); + afterEach(() => wrapper?.unmount()); it('properly renders provided list of items', () => { const wrapper = createWrapper(); From d0d664ef79406dd64539e00ccdd281dad6a11631 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Wed, 2 Sep 2020 19:32:07 +0200 Subject: [PATCH 45/59] Migrated VisitsParser to TS --- src/App.tsx | 3 +- src/container/index.ts | 4 +- src/utils/helpers/visits.ts | 8 +-- src/visits/services/VisitsParser.js | 75 --------------------------- src/visits/services/VisitsParser.ts | 79 +++++++++++++++++++++++++++++ src/visits/types/index.ts | 33 +++++++++++- 6 files changed, 117 insertions(+), 85 deletions(-) delete mode 100644 src/visits/services/VisitsParser.js create mode 100644 src/visits/services/VisitsParser.ts diff --git a/src/App.tsx b/src/App.tsx index 81311fd2..70709bc5 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,11 +1,12 @@ import React, { useEffect, FC } from 'react'; import { Route, Switch } from 'react-router-dom'; import NotFound from './common/NotFound'; +import { ServersMap } from './servers/data'; import './App.scss'; interface AppProps { fetchServers: Function; - servers: Record; + servers: ServersMap; } const App = (MainHeader: FC, Home: FC, MenuLayout: FC, CreateServer: FC, EditServer: FC, Settings: FC) => ( diff --git a/src/container/index.ts b/src/container/index.ts index 22271324..77d2a640 100644 --- a/src/container/index.ts +++ b/src/container/index.ts @@ -13,13 +13,13 @@ import provideMercureServices from '../mercure/services/provideServices'; import provideSettingsServices from '../settings/services/provideServices'; import { ConnectDecorator } from './types'; -type ActionMap = Record; +type LazyActionMap = Record; const bottle = new Bottle(); const { container } = bottle; const lazyService = (container: IContainer, serviceName: string) => (...args: any[]) => container[serviceName](...args); -const mapActionService = (map: ActionMap, actionName: string): ActionMap => ({ +const mapActionService = (map: LazyActionMap, actionName: string): LazyActionMap => ({ ...map, // Wrap actual action service in a function so that it is lazily created the first time it is called [actionName]: lazyService(container, actionName), diff --git a/src/utils/helpers/visits.ts b/src/utils/helpers/visits.ts index 4bd0ab49..ea45421a 100644 --- a/src/utils/helpers/visits.ts +++ b/src/utils/helpers/visits.ts @@ -1,6 +1,7 @@ import bowser from 'bowser'; import { zipObj } from 'ramda'; import { Empty, hasValue } from '../utils'; +import { Stats, UserAgent } from '../../visits/types'; const DEFAULT = 'Others'; const BROWSERS_WHITELIST = [ @@ -17,11 +18,6 @@ const BROWSERS_WHITELIST = [ 'WeChat', ]; -interface UserAgent { - browser: string; - os: string; -} - export const parseUserAgent = (userAgent: string | Empty): UserAgent => { if (!hasValue(userAgent)) { return { browser: DEFAULT, os: DEFAULT }; @@ -42,5 +38,5 @@ export const extractDomain = (url: string | Empty): string => { return domain.split(':')[0]; }; -export const fillTheGaps = (stats: Record, labels: string[]): number[] => +export const fillTheGaps = (stats: Stats, labels: string[]): number[] => Object.values({ ...zipObj(labels, labels.map(() => 0)), ...stats }); diff --git a/src/visits/services/VisitsParser.js b/src/visits/services/VisitsParser.js deleted file mode 100644 index 83f984bb..00000000 --- a/src/visits/services/VisitsParser.js +++ /dev/null @@ -1,75 +0,0 @@ -import { isNil, map } from 'ramda'; -import { extractDomain, parseUserAgent } from '../../utils/helpers/visits'; -import { hasValue } from '../../utils/utils'; - -const visitHasProperty = (visit, propertyName) => !isNil(visit) && hasValue(visit[propertyName]); - -const updateOsStatsForVisit = (osStats, { os }) => { - osStats[os] = (osStats[os] || 0) + 1; -}; - -const updateBrowsersStatsForVisit = (browsersStats, { browser }) => { - browsersStats[browser] = (browsersStats[browser] || 0) + 1; -}; - -const updateReferrersStatsForVisit = (referrersStats, { referer: domain }) => { - referrersStats[domain] = (referrersStats[domain] || 0) + 1; -}; - -const updateLocationsStatsForVisit = (propertyName) => (stats, visit) => { - const hasLocationProperty = visitHasProperty(visit, propertyName); - const value = hasLocationProperty ? visit[propertyName] : 'Unknown'; - - stats[value] = (stats[value] || 0) + 1; -}; - -const updateCountriesStatsForVisit = updateLocationsStatsForVisit('country'); -const updateCitiesStatsForVisit = updateLocationsStatsForVisit('city'); - -const updateCitiesForMapForVisit = (citiesForMapStats, visit) => { - if (!visitHasProperty(visit, 'city') || visit.city === 'Unknown') { - return; - } - - const { city, latitude, longitude } = visit; - const currentCity = citiesForMapStats[city] || { - cityName: city, - count: 0, - latLong: [ parseFloat(latitude), parseFloat(longitude) ], - }; - - currentCity.count++; - - citiesForMapStats[city] = currentCity; -}; - -export const processStatsFromVisits = (normalizedVisits) => - normalizedVisits.reduce( - (stats, visit) => { - // We mutate the original object because it has a big performance impact when large data sets are processed - updateOsStatsForVisit(stats.os, visit); - updateBrowsersStatsForVisit(stats.browsers, visit); - updateReferrersStatsForVisit(stats.referrers, visit); - updateCountriesStatsForVisit(stats.countries, visit); - updateCitiesStatsForVisit(stats.cities, visit); - updateCitiesForMapForVisit(stats.citiesForMap, visit); - - return stats; - }, - { os: {}, browsers: {}, referrers: {}, countries: {}, cities: {}, citiesForMap: {} }, - ); - -export const normalizeVisits = map(({ userAgent, date, referer, visitLocation }) => { - const { browser, os } = parseUserAgent(userAgent); - - return { - date, - browser, - os, - referer: extractDomain(referer), - country: (visitLocation && visitLocation.countryName) || 'Unknown', - city: (visitLocation && visitLocation.cityName) || 'Unknown', - latitude: visitLocation && visitLocation.latitude, - longitude: visitLocation && visitLocation.longitude, - }; -}); diff --git a/src/visits/services/VisitsParser.ts b/src/visits/services/VisitsParser.ts new file mode 100644 index 00000000..c905d0b1 --- /dev/null +++ b/src/visits/services/VisitsParser.ts @@ -0,0 +1,79 @@ +import { isNil, map, reduce } from 'ramda'; +import { extractDomain, parseUserAgent } from '../../utils/helpers/visits'; +import { hasValue } from '../../utils/utils'; +import { CityStats, NormalizedVisit, Stats, Visit, VisitsStats } from '../types'; + +const visitHasProperty = (visit: NormalizedVisit, propertyName: keyof NormalizedVisit) => + !isNil(visit) && hasValue(visit[propertyName]); + +const optionalNumericToNumber = (numeric: string | number | null | undefined): number => { + if (typeof numeric === 'number') { + return numeric; + } + + return numeric ? parseFloat(numeric) : 0; +}; + +const updateOsStatsForVisit = (osStats: Stats, { os }: NormalizedVisit) => { + osStats[os] = (osStats[os] || 0) + 1; +}; + +const updateBrowsersStatsForVisit = (browsersStats: Stats, { browser }: NormalizedVisit) => { + browsersStats[browser] = (browsersStats[browser] || 0) + 1; +}; + +const updateReferrersStatsForVisit = (referrersStats: Stats, { referer: domain }: NormalizedVisit) => { + referrersStats[domain] = (referrersStats[domain] || 0) + 1; +}; + +const updateLocationsStatsForVisit = (propertyName: 'country' | 'city') => (stats: Stats, visit: NormalizedVisit) => { + const hasLocationProperty = visitHasProperty(visit, propertyName); + const value = hasLocationProperty ? visit[propertyName] : 'Unknown'; + + stats[value] = (stats[value] || 0) + 1; +}; + +const updateCountriesStatsForVisit = updateLocationsStatsForVisit('country'); +const updateCitiesStatsForVisit = updateLocationsStatsForVisit('city'); + +const updateCitiesForMapForVisit = (citiesForMapStats: Record, visit: NormalizedVisit) => { + if (!visitHasProperty(visit, 'city') || visit.city === 'Unknown') { + return; + } + + const { city, latitude, longitude } = visit; + const currentCity = citiesForMapStats[city] || { + cityName: city, + count: 0, + latLong: [ optionalNumericToNumber(latitude), optionalNumericToNumber(longitude) ], + }; + + currentCity.count++; + + citiesForMapStats[city] = currentCity; +}; + +export const processStatsFromVisits = reduce( + (stats: VisitsStats, visit: NormalizedVisit) => { + // We mutate the original object because it has a big performance impact when large data sets are processed + updateOsStatsForVisit(stats.os, visit); + updateBrowsersStatsForVisit(stats.browsers, visit); + updateReferrersStatsForVisit(stats.referrers, visit); + updateCountriesStatsForVisit(stats.countries, visit); + updateCitiesStatsForVisit(stats.cities, visit); + updateCitiesForMapForVisit(stats.citiesForMap, visit); + + return stats; + }, + { os: {}, browsers: {}, referrers: {}, countries: {}, cities: {}, citiesForMap: {} }, +); + +export const normalizeVisits = map(({ userAgent, date, referer, visitLocation }: Visit): NormalizedVisit => ({ + date, + ...parseUserAgent(userAgent), + referer: extractDomain(referer), + country: visitLocation?.countryName ?? 'Unknown', + city: visitLocation?.cityName ?? 'Unknown', + latitude: visitLocation?.latitude, + longitude: visitLocation?.longitude, +})); diff --git a/src/visits/types/index.ts b/src/visits/types/index.ts index b66ee1c4..0b553d4e 100644 --- a/src/visits/types/index.ts +++ b/src/visits/types/index.ts @@ -1,6 +1,6 @@ import PropTypes from 'prop-types'; -import { ShortUrl } from '../../short-urls/data'; import { Action } from 'redux'; +import { ShortUrl } from '../../short-urls/data'; /** @deprecated Use Visit interface instead */ export const VisitType = PropTypes.shape({ @@ -59,7 +59,38 @@ export interface Visit { visitLocation: VisitLocation | null; } +export interface UserAgent { + browser: string; + os: string; +} + +export interface NormalizedVisit extends UserAgent { + date: string; + referer: string; + country: string; + city: string; + latitude?: number | null; + longitude?: number | null; +} + export interface CreateVisit { shortUrl: ShortUrl; visit: Visit; } + +export type Stats = Record; + +export interface CityStats { + cityName: string; + count: number; + latLong: [number, number]; +} + +export interface VisitsStats { + os: Stats; + browsers: Stats; + referrers: Stats; + countries: Stats; + cities: Stats; + citiesForMap: Record; +} From f9c57ca659543406e24ce9f36a08d6fea796b0ef Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Wed, 2 Sep 2020 20:13:31 +0200 Subject: [PATCH 46/59] Migrated first visits helper components to TS --- package-lock.json | 10 ++++ package.json | 1 + src/visits/VisitsStats.js | 3 +- src/visits/helpers/MapModal.scss | 12 ++-- .../helpers/{MapModal.js => MapModal.tsx} | 36 ++++------- src/visits/helpers/OpenMapModalBtn.js | 60 ------------------- src/visits/helpers/OpenMapModalBtn.tsx | 55 +++++++++++++++++ src/visits/services/provideServices.ts | 4 +- .../{MapModal.test.js => MapModal.test.tsx} | 13 ++-- ...alBtn.test.js => OpenMapModalBtn.test.tsx} | 42 ++++--------- 10 files changed, 106 insertions(+), 130 deletions(-) rename src/visits/helpers/{MapModal.js => MapModal.tsx} (65%) delete mode 100644 src/visits/helpers/OpenMapModalBtn.js create mode 100644 src/visits/helpers/OpenMapModalBtn.tsx rename test/visits/helpers/{MapModal.test.js => MapModal.test.tsx} (81%) rename test/visits/helpers/{OpenMapModalBtn.test.js => OpenMapModalBtn.test.tsx} (65%) diff --git a/package-lock.json b/package-lock.json index 7187451f..c17aa0ed 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3432,6 +3432,16 @@ "@types/react": "*" } }, + "@types/react-leaflet": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/@types/react-leaflet/-/react-leaflet-2.5.2.tgz", + "integrity": "sha512-XBNFsBm4wQiz6BpzUMJAMXru+h2ESyW/mAdPoSzEirtF/g0NOwbUvPYnZtpbiW70AEXT40ZONtsu3lxwr/yliA==", + "dev": true, + "requires": { + "@types/leaflet": "*", + "@types/react": "*" + } + }, "@types/react-redux": { "version": "7.1.9", "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.9.tgz", diff --git a/package.json b/package.json index 32b42d36..f53e98e2 100644 --- a/package.json +++ b/package.json @@ -88,6 +88,7 @@ "@types/react-copy-to-clipboard": "^4.3.0", "@types/react-datepicker": "~1.8.0", "@types/react-dom": "^16.9.8", + "@types/react-leaflet": "^2.5.2", "@types/react-redux": "^7.1.9", "@types/react-router-dom": "^5.1.5", "@types/react-tagsinput": "^3.19.7", diff --git a/src/visits/VisitsStats.js b/src/visits/VisitsStats.js index dc4d539d..544c36dc 100644 --- a/src/visits/VisitsStats.js +++ b/src/visits/VisitsStats.js @@ -14,6 +14,7 @@ import GraphCard from './helpers/GraphCard'; import LineChartCard from './helpers/LineChartCard'; import VisitsTable from './VisitsTable'; import { VisitsInfoType } from './types'; +import OpenMapModalBtn from './helpers/OpenMapModalBtn'; const propTypes = { children: PropTypes.node, @@ -35,7 +36,7 @@ const highlightedVisitsToStats = (highlightedVisits, prop) => highlightedVisits. const format = formatDate(); let selectedBar; -const VisitsStats = ({ processStatsFromVisits, normalizeVisits }, OpenMapModalBtn) => { +const VisitsStats = ({ processStatsFromVisits, normalizeVisits }) => { const VisitsStatsComp = ({ children, visitsInfo, getVisits, cancelGetVisits, matchMedia = window.matchMedia }) => { const [ startDate, setStartDate ] = useState(undefined); const [ endDate, setEndDate ] = useState(undefined); diff --git a/src/visits/helpers/MapModal.scss b/src/visits/helpers/MapModal.scss index d37029c6..bcf0d938 100644 --- a/src/visits/helpers/MapModal.scss +++ b/src/visits/helpers/MapModal.scss @@ -1,7 +1,7 @@ @import '../../utils/base'; @import '../../utils/mixins/fit-with-margin'; -.map-modal__modal { +.map-modal__modal.map-modal__modal { @media (min-width: $mdMin) { $margin: 20px; @@ -15,11 +15,11 @@ } } -.map-modal__modal-content { +.map-modal__modal-content.map-modal__modal-content { height: 100%; } -.map-modal__modal-title { +.map-modal__modal-title.map-modal__modal-title { position: absolute; width: 100%; z-index: 1001; @@ -29,17 +29,17 @@ background: linear-gradient(rgba(0, 0, 0, .5), rgba(0, 0, 0, 0)); } -.map-modal__modal-body { +.map-modal__modal-body.map-modal__modal-body { padding: 0; display: flex; overflow: hidden; } -.map-modal__modal .leaflet-container { +.map-modal__modal.map-modal__modal .leaflet-container.leaflet-container { flex: 1 1 auto; border-radius: .3rem; } -.map-modal__modal .leaflet-top .leaflet-control { +.map-modal__modal.map-modal__modal .leaflet-top.leaflet-top .leaflet-control.leaflet-control { margin-top: 60px; } diff --git a/src/visits/helpers/MapModal.js b/src/visits/helpers/MapModal.tsx similarity index 65% rename from src/visits/helpers/MapModal.js rename to src/visits/helpers/MapModal.tsx index 45cc7304..85ce36e6 100644 --- a/src/visits/helpers/MapModal.js +++ b/src/visits/helpers/MapModal.tsx @@ -1,32 +1,25 @@ -import React from 'react'; +import React, { FC } from 'react'; import { Modal, ModalBody } from 'reactstrap'; -import { Map, TileLayer, Marker, Popup } from 'react-leaflet'; +import { Map, TileLayer, Marker, Popup, MapProps } from 'react-leaflet'; import { prop } from 'ramda'; -import * as PropTypes from 'prop-types'; +import { CityStats } from '../types'; import './MapModal.scss'; -const propTypes = { - toggle: PropTypes.func, - isOpen: PropTypes.bool, - title: PropTypes.string, - locations: PropTypes.arrayOf(PropTypes.shape({ - cityName: PropTypes.string.isRequired, - latLong: PropTypes.arrayOf(PropTypes.number).isRequired, - count: PropTypes.number.isRequired, - })), -}; -const defaultProps = { - locations: [], -}; +interface MapModalProps { + toggle: () => void; + isOpen: boolean; + title: string; + locations?: CityStats[]; +} -const OpenStreetMapTile = () => ( +const OpenStreetMapTile: FC = () => ( ); -const calculateMapProps = (locations) => { +const calculateMapProps = (locations: CityStats[]): Partial => { if (locations.length === 0) { return {}; } @@ -39,10 +32,10 @@ const calculateMapProps = (locations) => { // When that happens, we use zoom and center as a workaround const [{ latLong: center }] = locations; - return { zoom: '10', center }; + return { zoom: 10, center }; }; -const MapModal = ({ toggle, isOpen, title, locations }) => ( +const MapModal = ({ toggle, isOpen, title, locations = [] }: MapModalProps) => (

@@ -61,7 +54,4 @@ const MapModal = ({ toggle, isOpen, title, locations }) => ( ); -MapModal.propTypes = propTypes; -MapModal.defaultProps = defaultProps; - export default MapModal; diff --git a/src/visits/helpers/OpenMapModalBtn.js b/src/visits/helpers/OpenMapModalBtn.js deleted file mode 100644 index fab798c3..00000000 --- a/src/visits/helpers/OpenMapModalBtn.js +++ /dev/null @@ -1,60 +0,0 @@ -import React, { useState } from 'react'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { faMapMarkedAlt as mapIcon } from '@fortawesome/free-solid-svg-icons'; -import { Dropdown, DropdownItem, DropdownMenu, UncontrolledTooltip } from 'reactstrap'; -import * as PropTypes from 'prop-types'; -import { useToggle } from '../../utils/helpers/hooks'; -import './OpenMapModalBtn.scss'; - -const propTypes = { - modalTitle: PropTypes.string.isRequired, - locations: PropTypes.arrayOf(PropTypes.object), - activeCities: PropTypes.arrayOf(PropTypes.string), -}; - -const OpenMapModalBtn = (MapModal) => { - const OpenMapModalBtn = ({ modalTitle, locations = [], activeCities }) => { - const [ mapIsOpened, , openMap, closeMap ] = useToggle(); - const [ dropdownIsOpened, toggleDropdown, openDropdown ] = useToggle(); - const [ locationsToShow, setLocationsToShow ] = useState([]); - - const buttonRef = React.createRef(); - const filterLocations = (locations) => locations.filter(({ cityName }) => activeCities.includes(cityName)); - const onClick = () => { - if (!activeCities) { - setLocationsToShow(locations); - openMap(); - - return; - } - - openDropdown(); - }; - const openMapWithLocations = (filtered) => () => { - setLocationsToShow(filtered ? filterLocations(locations) : locations); - openMap(); - }; - - return ( - - - buttonRef.current}>Show in map - - - Show all locations - Show locations in current page - - - - - ); - }; - - OpenMapModalBtn.propTypes = propTypes; - - return OpenMapModalBtn; -}; - -export default OpenMapModalBtn; diff --git a/src/visits/helpers/OpenMapModalBtn.tsx b/src/visits/helpers/OpenMapModalBtn.tsx new file mode 100644 index 00000000..6292ecba --- /dev/null +++ b/src/visits/helpers/OpenMapModalBtn.tsx @@ -0,0 +1,55 @@ +import React, { useRef, useState } from 'react'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faMapMarkedAlt as mapIcon } from '@fortawesome/free-solid-svg-icons'; +import { Dropdown, DropdownItem, DropdownMenu, UncontrolledTooltip } from 'reactstrap'; +import { useToggle } from '../../utils/helpers/hooks'; +import { CityStats } from '../types'; +import MapModal from './MapModal'; +import './OpenMapModalBtn.scss'; + +interface OpenMapModalBtnProps { + modalTitle: string; + activeCities: string[]; + locations?: CityStats[]; +} + +const OpenMapModalBtn = ({ modalTitle, activeCities, locations = [] }: OpenMapModalBtnProps) => { + const [ mapIsOpened, , openMap, closeMap ] = useToggle(); + const [ dropdownIsOpened, toggleDropdown, openDropdown ] = useToggle(); + const [ locationsToShow, setLocationsToShow ] = useState([]); + const buttonRef = useRef(); + + const filterLocations = (cities: CityStats[]) => cities.filter(({ cityName }) => activeCities.includes(cityName)); + const onClick = () => { + if (!activeCities) { + setLocationsToShow(locations); + openMap(); + + return; + } + + openDropdown(); + }; + const openMapWithLocations = (filtered: boolean) => () => { + setLocationsToShow(filtered ? filterLocations(locations) : locations); + openMap(); + }; + + return ( + + + buttonRef.current) as any}>Show in map + + + Show all locations + Show locations in current page + + + + + ); +}; + +export default OpenMapModalBtn; diff --git a/src/visits/services/provideServices.ts b/src/visits/services/provideServices.ts index 0d0c0b09..5880193c 100644 --- a/src/visits/services/provideServices.ts +++ b/src/visits/services/provideServices.ts @@ -2,7 +2,6 @@ import Bottle from 'bottlejs'; import ShortUrlVisits from '../ShortUrlVisits'; import { cancelGetShortUrlVisits, getShortUrlVisits } from '../reducers/shortUrlVisits'; import { getShortUrlDetail } from '../reducers/shortUrlDetail'; -import OpenMapModalBtn from '../helpers/OpenMapModalBtn'; import MapModal from '../helpers/MapModal'; import VisitsStats from '../VisitsStats'; import { createNewVisit } from '../reducers/visitCreation'; @@ -13,9 +12,8 @@ import * as visitsParser from './VisitsParser'; const provideServices = (bottle: Bottle, connect: ConnectDecorator) => { // Components - bottle.serviceFactory('OpenMapModalBtn', OpenMapModalBtn, 'MapModal'); bottle.serviceFactory('MapModal', () => MapModal); - bottle.serviceFactory('VisitsStats', VisitsStats, 'VisitsParser', 'OpenMapModalBtn'); + bottle.serviceFactory('VisitsStats', VisitsStats, 'VisitsParser'); bottle.serviceFactory('ShortUrlVisits', ShortUrlVisits, 'VisitsStats'); bottle.decorator('ShortUrlVisits', connect( [ 'shortUrlVisits', 'shortUrlDetail', 'mercureInfo' ], diff --git a/test/visits/helpers/MapModal.test.js b/test/visits/helpers/MapModal.test.tsx similarity index 81% rename from test/visits/helpers/MapModal.test.js rename to test/visits/helpers/MapModal.test.tsx index 41a6a370..c58ab6fa 100644 --- a/test/visits/helpers/MapModal.test.js +++ b/test/visits/helpers/MapModal.test.tsx @@ -1,11 +1,12 @@ import React from 'react'; -import { shallow } from 'enzyme'; +import { shallow, ShallowWrapper } from 'enzyme'; import { Modal } from 'reactstrap'; import { Marker, Popup } from 'react-leaflet'; import MapModal from '../../../src/visits/helpers/MapModal'; +import { CityStats } from '../../../src/visits/types'; describe('', () => { - let wrapper; + let wrapper: ShallowWrapper; const toggle = () => ''; const isOpen = true; const title = 'Foobar'; @@ -13,7 +14,7 @@ describe('', () => { const zaragozaLong = -0.876566; const newYorkLat = 40.730610; const newYorkLong = -73.935242; - const locations = [ + const locations: CityStats[] = [ { cityName: 'Zaragoza', count: 54, @@ -34,12 +35,12 @@ describe('', () => { it('renders modal with provided props', () => { const modal = wrapper.find(Modal); - const headerheader = wrapper.find('.map-modal__modal-title'); + const header = wrapper.find('.map-modal__modal-title'); expect(modal.prop('toggle')).toEqual(toggle); expect(modal.prop('isOpen')).toEqual(isOpen); - expect(headerheader.find('.close').prop('onClick')).toEqual(toggle); - expect(headerheader.text()).toContain(title); + expect(header.find('.close').prop('onClick')).toEqual(toggle); + expect(header.text()).toContain(title); }); it('renders open street map tile', () => { diff --git a/test/visits/helpers/OpenMapModalBtn.test.js b/test/visits/helpers/OpenMapModalBtn.test.tsx similarity index 65% rename from test/visits/helpers/OpenMapModalBtn.test.js rename to test/visits/helpers/OpenMapModalBtn.test.tsx index 895d9310..6bf9f04b 100644 --- a/test/visits/helpers/OpenMapModalBtn.test.js +++ b/test/visits/helpers/OpenMapModalBtn.test.tsx @@ -1,30 +1,25 @@ import React from 'react'; -import { mount } from 'enzyme'; +import { shallow, ShallowWrapper } from 'enzyme'; import { Dropdown, DropdownItem, UncontrolledTooltip } from 'reactstrap'; -import createOpenMapModalBtn from '../../../src/visits/helpers/OpenMapModalBtn'; +import { Mock } from 'ts-mockery'; +import OpenMapModalBtn from '../../../src/visits/helpers/OpenMapModalBtn'; +import MapModal from '../../../src/visits/helpers/MapModal'; +import { CityStats } from '../../../src/visits/types'; describe('', () => { - let wrapper; + let wrapper: ShallowWrapper; const title = 'Foo'; const locations = [ - { - cityName: 'foo', - count: 30, - }, - { - cityName: 'bar', - count: 45, - }, + Mock.of({ cityName: 'foo', count: 30 }), + Mock.of({ cityName: 'bar', count: 45 }), ]; - const MapModal = () => ''; - const OpenMapModalBtn = createOpenMapModalBtn(MapModal); - const createWrapper = (activeCities) => { - wrapper = mount(); + const createWrapper = (activeCities: string[] = []) => { + wrapper = shallow(); return wrapper; }; - afterEach(() => wrapper && wrapper.unmount()); + afterEach(() => wrapper?.unmount()); it('renders expected content', () => { const wrapper = createWrapper(); @@ -39,21 +34,6 @@ describe('', () => { expect(modal).toHaveLength(1); }); - it('sets provided props to the map', (done) => { - const wrapper = createWrapper(); - const button = wrapper.find('.open-map-modal-btn__btn'); - - button.simulate('click'); - setImmediate(() => { - const modal = wrapper.find(MapModal); - - expect(modal.prop('title')).toEqual(title); - expect(modal.prop('locations')).toEqual(locations); - expect(modal.prop('isOpen')).toEqual(true); - done(); - }); - }); - it('opens dropdown instead of modal when a list of active cities has been provided', (done) => { const wrapper = createWrapper([ 'bar' ]); const button = wrapper.find('.open-map-modal-btn__btn'); From 4083592212a60be1a548d8c3b87dc7ea5d0346b6 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Wed, 2 Sep 2020 20:27:50 +0200 Subject: [PATCH 47/59] Fixed coding styles and ensured linting command applies to ts and tsx files --- package-lock.json | 6 +++--- package.json | 4 ++-- src/short-urls/reducers/shortUrlDeletion.ts | 2 +- src/visits/helpers/MapModal.tsx | 4 ++-- src/visits/reducers/shortUrlVisits.ts | 2 +- src/visits/reducers/tagVisits.ts | 5 ++++- .../reducers/shortUrlDeletion.test.ts | 21 +++++++++---------- 7 files changed, 23 insertions(+), 21 deletions(-) diff --git a/package-lock.json b/package-lock.json index c17aa0ed..ae7c56e6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22525,9 +22525,9 @@ } }, "typescript": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.0.2.tgz", - "integrity": "sha512-e4ERvRV2wb+rRZ/IQeb3jm2VxBsirQLpQhdxplZ2MEzGvDkkMmPglecnNDfSUBivMjP93vRbngYYDQqQ/78bcQ==", + "version": "3.9.7", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.7.tgz", + "integrity": "sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw==", "dev": true }, "uglify-es": { diff --git a/package.json b/package.json index f53e98e2..75ecaa8d 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "license": "MIT", "scripts": { "lint": "npm run lint:js && npm run lint:css", - "lint:js": "eslint src test scripts config", + "lint:js": "eslint --ext .js,.ts,.tsx src test scripts config", "lint:js:fix": "npm run lint:js -- --fix", "lint:css": "stylelint src/*.scss src/**/*.scss", "lint:css:fix": "npm run lint:css -- --fix", @@ -148,7 +148,7 @@ "terser-webpack-plugin": "^2.1.2", "ts-jest": "^26.0.0", "ts-mockery": "^1.2.0", - "typescript": "^4.0.2", + "typescript": "^3.9.7", "url-loader": "^2.2.0", "webpack": "^4.41.0", "webpack-dev-server": "^3.8.2", diff --git a/src/short-urls/reducers/shortUrlDeletion.ts b/src/short-urls/reducers/shortUrlDeletion.ts index 21347852..86fa00ee 100644 --- a/src/short-urls/reducers/shortUrlDeletion.ts +++ b/src/short-urls/reducers/shortUrlDeletion.ts @@ -1,6 +1,6 @@ import { Action, Dispatch } from 'redux'; import { buildActionCreator, buildReducer } from '../../utils/helpers/redux'; -import { ProblemDetailsError} from '../../utils/services/types'; +import { ProblemDetailsError } from '../../utils/services/types'; import { GetState } from '../../container/types'; import { ShlinkApiClientBuilder } from '../../utils/services/ShlinkApiClientBuilder'; diff --git a/src/visits/helpers/MapModal.tsx b/src/visits/helpers/MapModal.tsx index 85ce36e6..331d2357 100644 --- a/src/visits/helpers/MapModal.tsx +++ b/src/visits/helpers/MapModal.tsx @@ -1,6 +1,6 @@ import React, { FC } from 'react'; import { Modal, ModalBody } from 'reactstrap'; -import { Map, TileLayer, Marker, Popup, MapProps } from 'react-leaflet'; +import { Map, TileLayer, Marker, Popup } from 'react-leaflet'; import { prop } from 'ramda'; import { CityStats } from '../types'; import './MapModal.scss'; @@ -19,7 +19,7 @@ const OpenStreetMapTile: FC = () => ( /> ); -const calculateMapProps = (locations: CityStats[]): Partial => { +const calculateMapProps = (locations: CityStats[]): any => { if (locations.length === 0) { return {}; } diff --git a/src/visits/reducers/shortUrlVisits.ts b/src/visits/reducers/shortUrlVisits.ts index 339f37a9..6a1e837b 100644 --- a/src/visits/reducers/shortUrlVisits.ts +++ b/src/visits/reducers/shortUrlVisits.ts @@ -77,7 +77,7 @@ export const getShortUrlVisits = (buildShlinkApiClient: ShlinkApiClientBuilder) query: { domain?: OptionalString } = {}, ) => async (dispatch: Dispatch, getState: GetState) => { const { getShortUrlVisits } = buildShlinkApiClient(getState); - const visitsLoader = (page: number, itemsPerPage: number) => getShortUrlVisits( + const visitsLoader = async (page: number, itemsPerPage: number) => getShortUrlVisits( shortCode, { ...query, page, itemsPerPage }, ); diff --git a/src/visits/reducers/tagVisits.ts b/src/visits/reducers/tagVisits.ts index ab1d0379..b0f5746d 100644 --- a/src/visits/reducers/tagVisits.ts +++ b/src/visits/reducers/tagVisits.ts @@ -68,7 +68,10 @@ export const getTagVisits = (buildShlinkApiClient: ShlinkApiClientBuilder) => (t getState: GetState, ) => { const { getTagVisits } = buildShlinkApiClient(getState); - const visitsLoader = (page: number, itemsPerPage: number) => getTagVisits(tag, { ...query, page, itemsPerPage }); + const visitsLoader = async (page: number, itemsPerPage: number) => getTagVisits( + tag, + { ...query, page, itemsPerPage }, + ); const extraFinishActionData: Partial = { tag }; const actionMap = { start: GET_TAG_VISITS_START, diff --git a/test/short-urls/reducers/shortUrlDeletion.test.ts b/test/short-urls/reducers/shortUrlDeletion.test.ts index d4fccee3..5c68d2cd 100644 --- a/test/short-urls/reducers/shortUrlDeletion.test.ts +++ b/test/short-urls/reducers/shortUrlDeletion.test.ts @@ -8,27 +8,26 @@ import reducer, { deleteShortUrl, } from '../../../src/short-urls/reducers/shortUrlDeletion'; import { ProblemDetailsError } from '../../../src/utils/services/types'; +import ShlinkApiClient from '../../../src/utils/services/ShlinkApiClient'; describe('shortUrlDeletionReducer', () => { describe('reducer', () => { - - it('returns loading on DELETE_SHORT_URL_START', () => - expect(reducer(undefined, { type: DELETE_SHORT_URL_START })).toEqual({ + expect(reducer(undefined, { type: DELETE_SHORT_URL_START } as any)).toEqual({ shortCode: '', loading: true, error: false, })); it('returns default on RESET_DELETE_SHORT_URL', () => - expect(reducer(undefined, { type: RESET_DELETE_SHORT_URL })).toEqual({ + expect(reducer(undefined, { type: RESET_DELETE_SHORT_URL } as any)).toEqual({ shortCode: '', loading: false, error: false, })); it('returns shortCode on SHORT_URL_DELETED', () => - expect(reducer(undefined, { type: SHORT_URL_DELETED, shortCode: 'foo' })).toEqual({ + expect(reducer(undefined, { type: SHORT_URL_DELETED, shortCode: 'foo' } as any)).toEqual({ shortCode: 'foo', loading: false, error: false, @@ -37,7 +36,7 @@ describe('shortUrlDeletionReducer', () => { it('returns errorData on DELETE_SHORT_URL_ERROR', () => { const errorData = Mock.of({ type: 'bar' }); - expect(reducer(undefined, { type: DELETE_SHORT_URL_ERROR, errorData })).toEqual({ + expect(reducer(undefined, { type: DELETE_SHORT_URL_ERROR, errorData } as any)).toEqual({ shortCode: '', loading: false, error: true, @@ -63,9 +62,9 @@ describe('shortUrlDeletionReducer', () => { it.each( [[ undefined ], [ null ], [ 'example.com' ]], )('dispatches proper actions if API client request succeeds', async (domain) => { - const apiClientMock = { + const apiClientMock = Mock.of({ deleteShortUrl: jest.fn(() => ''), - }; + }); const shortCode = 'abc123'; await deleteShortUrl(() => apiClientMock)(shortCode, domain)(dispatch, getState); @@ -81,9 +80,9 @@ describe('shortUrlDeletionReducer', () => { it('dispatches proper actions if API client request fails', async () => { const data = { foo: 'bar' }; const error = { response: { data } }; - const apiClientMock = { - deleteShortUrl: jest.fn(() => Promise.reject(error)), - }; + const apiClientMock = Mock.of({ + deleteShortUrl: jest.fn(async () => Promise.reject(error)), + }); const shortCode = 'abc123'; try { From 8a146021dd574c464fe07b29ddb3d4bfedf9b36e Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Thu, 3 Sep 2020 20:34:22 +0200 Subject: [PATCH 48/59] Migrated first charts to TS --- package-lock.json | 9 ++ package.json | 1 + .../{DefaultChart.js => DefaultChart.tsx} | 103 ++++++++++-------- src/visits/helpers/GraphCard.js | 30 ----- src/visits/helpers/GraphCard.tsx | 20 ++++ ...ultChart.test.js => DefaultChart.test.tsx} | 16 +-- .../{GraphCard.test.js => GraphCard.test.tsx} | 12 +- 7 files changed, 102 insertions(+), 89 deletions(-) rename src/visits/helpers/{DefaultChart.js => DefaultChart.tsx} (52%) delete mode 100644 src/visits/helpers/GraphCard.js create mode 100644 src/visits/helpers/GraphCard.tsx rename test/visits/helpers/{DefaultChart.test.js => DefaultChart.test.tsx} (92%) rename test/visits/helpers/{GraphCard.test.js => GraphCard.test.tsx} (80%) diff --git a/package-lock.json b/package-lock.json index ae7c56e6..17c76437 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3047,6 +3047,15 @@ "@babel/types": "^7.3.0" } }, + "@types/chart.js": { + "version": "2.9.24", + "resolved": "https://registry.npmjs.org/@types/chart.js/-/chart.js-2.9.24.tgz", + "integrity": "sha512-AQI7X+ow3SaONl44JrHoL/5B+lCsJyG31UHZ5RP98Uh15hI/zjEkDsAb4EIm4P9TGfNhZLXw/nMc5w0u10+/fQ==", + "dev": true, + "requires": { + "moment": "^2.10.2" + } + }, "@types/cheerio": { "version": "0.22.21", "resolved": "https://registry.npmjs.org/@types/cheerio/-/cheerio-0.22.21.tgz", diff --git a/package.json b/package.json index 75ecaa8d..513d0a43 100644 --- a/package.json +++ b/package.json @@ -75,6 +75,7 @@ "@stryker-mutator/javascript-mutator": "^3.2.4", "@stryker-mutator/jest-runner": "^3.2.4", "@svgr/webpack": "^4.3.3", + "@types/chart.js": "^2.8.0", "@types/classnames": "^2.2.10", "@types/enzyme": "^3.10.5", "@types/jest": "^26.0.10", diff --git a/src/visits/helpers/DefaultChart.js b/src/visits/helpers/DefaultChart.tsx similarity index 52% rename from src/visits/helpers/DefaultChart.js rename to src/visits/helpers/DefaultChart.tsx index 7d762210..47d2250e 100644 --- a/src/visits/helpers/DefaultChart.js +++ b/src/visits/helpers/DefaultChart.tsx @@ -1,22 +1,30 @@ -import React, { useRef } from 'react'; -import PropTypes from 'prop-types'; +import React, { ChangeEvent, useRef } from 'react'; import { Doughnut, HorizontalBar } from 'react-chartjs-2'; import { keys, values } from 'ramda'; import classNames from 'classnames'; +import Chart, { ChartData, ChartDataSets, ChartOptions } from 'chart.js'; import { fillTheGaps } from '../../utils/helpers/visits'; +import { Stats } from '../types'; import './DefaultChart.scss'; -const propTypes = { - title: PropTypes.oneOfType([ PropTypes.string, PropTypes.func ]), - isBarChart: PropTypes.bool, - stats: PropTypes.object, - max: PropTypes.number, - highlightedStats: PropTypes.object, - highlightedLabel: PropTypes.string, - onClick: PropTypes.func, -}; +export interface DefaultChartProps { + title: Function | string; + stats: Stats; + isBarChart?: boolean; + max?: number; + highlightedStats?: Stats; + highlightedLabel?: string; + onClick?: (label: string) => void; +} -const generateGraphData = (title, isBarChart, labels, data, highlightedData, highlightedLabel) => ({ +const generateGraphData = ( + title: Function | string, + isBarChart: boolean, + labels: string[], + data: number[], + highlightedData?: number[], + highlightedLabel?: string, +): ChartData => ({ labels, datasets: [ { @@ -39,40 +47,40 @@ const generateGraphData = (title, isBarChart, labels, data, highlightedData, hig borderColor: isBarChart ? 'rgba(70, 150, 229, 1)' : 'white', borderWidth: 2, }, - highlightedData && { + (highlightedData && { title, - label: highlightedLabel || 'Selected', + label: highlightedLabel ?? 'Selected', data: highlightedData, backgroundColor: 'rgba(247, 127, 40, 0.4)', borderColor: '#F77F28', borderWidth: 2, - }, + }) as unknown as ChartDataSets, ].filter(Boolean), }); -const dropLabelIfHidden = (label) => label.startsWith('hidden') ? '' : label; +const dropLabelIfHidden = (label: string) => label.startsWith('hidden') ? '' : label; -const determineHeight = (isBarChart, labels) => { +const determineHeight = (isBarChart: boolean, labels: string[]): number | undefined => { if (!isBarChart) { return 300; } - return isBarChart && labels.length > 20 ? labels.length * 8 : null; + return isBarChart && labels.length > 20 ? labels.length * 8 : undefined; }; /* eslint-disable react/prop-types */ -const renderPieChartLegend = ({ config }) => { - const { labels, datasets } = config.data; - const { defaultColor } = config.options; +const renderPieChartLegend = ({ config }: Chart) => { + const { labels = [], datasets = [] } = config.data ?? {}; + const { defaultColor } = config.options ?? {} as any; const [{ backgroundColor: colors }] = datasets; return (
    {labels.map((label, index) => ( -
  • +
  • {label}
  • @@ -82,7 +90,7 @@ const renderPieChartLegend = ({ config }) => { }; /* eslint-enable react/prop-types */ -const chartElementAtEvent = (onClick) => ([ chart ]) => { +const chartElementAtEvent = (onClick?: (label: string) => void) => ([ chart ]: [{ _index: number; _chart: Chart }]) => { if (!onClick || !chart) { return; } @@ -90,30 +98,35 @@ const chartElementAtEvent = (onClick) => ([ chart ]) => { const { _index, _chart: { data } } = chart; const { labels } = data; - onClick(labels[_index]); + onClick(labels?.[_index] as string); }; -const DefaultChart = ({ title, isBarChart, stats, max, highlightedStats, highlightedLabel, onClick }) => { - const hasHighlightedStats = highlightedStats && Object.keys(highlightedStats).length > 0; +const statsAreDefined = (stats: Stats | undefined): stats is Stats => !!stats && Object.keys(stats).length > 0; + +const DefaultChart = ( + { title, isBarChart = false, stats, max, highlightedStats, highlightedLabel, onClick }: DefaultChartProps, +) => { const Component = isBarChart ? HorizontalBar : Doughnut; const labels = keys(stats).map(dropLabelIfHidden); - const data = values(!hasHighlightedStats ? stats : keys(highlightedStats).reduce((acc, highlightedKey) => { - if (acc[highlightedKey]) { - acc[highlightedKey] -= highlightedStats[highlightedKey]; - } + const data = values( + !statsAreDefined(highlightedStats) ? stats : keys(highlightedStats).reduce((acc, highlightedKey) => { + if (acc[highlightedKey]) { + acc[highlightedKey] -= highlightedStats[highlightedKey]; + } - return acc; - }, { ...stats })); - const highlightedData = hasHighlightedStats && fillTheGaps(highlightedStats, labels); - const chartRef = useRef(); + return acc; + }, { ...stats }), + ); + const highlightedData = statsAreDefined(highlightedStats) ? fillTheGaps(highlightedStats, labels) : undefined; + const chartRef = useRef(); - const options = { + const options: ChartOptions = { legend: { display: false }, - legendCallback: !isBarChart && renderPieChartLegend, - scales: isBarChart && { + legendCallback: !isBarChart && renderPieChartLegend as any, + scales: !isBarChart ? undefined : { xAxes: [ { - ticks: { beginAtZero: true, precision: 0, max }, + ticks: { beginAtZero: true, precision: 0, max } as any, stacked: true, }, ], @@ -125,9 +138,11 @@ const DefaultChart = ({ title, isBarChart, stats, max, highlightedStats, highlig // Do not show tooltip on items with empty label when in a bar chart filter: ({ yLabel }) => !isBarChart || yLabel !== '', }, - onHover: isBarChart && (({ target }, chartElement) => { + onHover: !isBarChart ? undefined : ((e: ChangeEvent, chartElement: HorizontalBar[] | Doughnut[]) => { + const { target } = e; + target.style.cursor = chartElement[0] ? 'pointer' : 'default'; - }), + }) as any, // TODO Types seem to be incorrectly defined }; const graphData = generateGraphData(title, isBarChart, labels, data, highlightedData, highlightedLabel); const height = determineHeight(isBarChart, labels); @@ -137,7 +152,7 @@ const DefaultChart = ({ title, isBarChart, stats, max, highlightedStats, highlig
    {!isBarChart && (
    - {chartRef.current && chartRef.current.chartInstance.generateLegend()} + {chartRef.current?.chartInstance.generateLegend()}
    )}
    ); }; -DefaultChart.propTypes = propTypes; - export default DefaultChart; diff --git a/src/visits/helpers/GraphCard.js b/src/visits/helpers/GraphCard.js deleted file mode 100644 index 5fc23d3f..00000000 --- a/src/visits/helpers/GraphCard.js +++ /dev/null @@ -1,30 +0,0 @@ -import { Card, CardHeader, CardBody, CardFooter } from 'reactstrap'; -import PropTypes from 'prop-types'; -import React from 'react'; -import DefaultChart from './DefaultChart'; -import './GraphCard.scss'; - -const propTypes = { - title: PropTypes.oneOfType([ PropTypes.string, PropTypes.func ]), - footer: PropTypes.oneOfType([ PropTypes.string, PropTypes.node ]), - isBarChart: PropTypes.bool, - stats: PropTypes.object, - max: PropTypes.number, - highlightedStats: PropTypes.object, - highlightedLabel: PropTypes.string, - onClick: PropTypes.func, -}; - -const GraphCard = ({ title, footer, ...rest }) => ( - - {typeof title === 'function' ? title() : title} - - - - {footer && {footer}} - -); - -GraphCard.propTypes = propTypes; - -export default GraphCard; diff --git a/src/visits/helpers/GraphCard.tsx b/src/visits/helpers/GraphCard.tsx new file mode 100644 index 00000000..a624d547 --- /dev/null +++ b/src/visits/helpers/GraphCard.tsx @@ -0,0 +1,20 @@ +import { Card, CardHeader, CardBody, CardFooter } from 'reactstrap'; +import React, { ReactNode } from 'react'; +import DefaultChart, { DefaultChartProps } from './DefaultChart'; +import './GraphCard.scss'; + +interface GraphCardProps extends DefaultChartProps { + footer?: ReactNode; +} + +const GraphCard = ({ title, footer, ...rest }: GraphCardProps) => ( + + {typeof title === 'function' ? title() : title} + + + + {footer && {footer}} + +); + +export default GraphCard; diff --git a/test/visits/helpers/DefaultChart.test.js b/test/visits/helpers/DefaultChart.test.tsx similarity index 92% rename from test/visits/helpers/DefaultChart.test.js rename to test/visits/helpers/DefaultChart.test.tsx index 387fd0ed..43312cec 100644 --- a/test/visits/helpers/DefaultChart.test.js +++ b/test/visits/helpers/DefaultChart.test.tsx @@ -1,17 +1,17 @@ import React from 'react'; -import { shallow } from 'enzyme'; +import { shallow, ShallowWrapper } from 'enzyme'; import { Doughnut, HorizontalBar } from 'react-chartjs-2'; import { keys, values } from 'ramda'; import DefaultChart from '../../../src/visits/helpers/DefaultChart'; describe('', () => { - let wrapper; + let wrapper: ShallowWrapper; const stats = { foo: 123, bar: 456, }; - afterEach(() => wrapper && wrapper.unmount()); + afterEach(() => wrapper?.unmount()); it('renders Doughnut when is not a bar chart', () => { wrapper = shallow(); @@ -22,9 +22,9 @@ describe('', () => { expect(doughnut).toHaveLength(1); expect(horizontal).toHaveLength(0); - const { labels, datasets } = doughnut.prop('data'); + const { labels, datasets } = doughnut.prop('data') as any; const [{ title, data, backgroundColor, borderColor }] = datasets; - const { legend, legendCallback, scales } = doughnut.prop('options'); + const { legend, legendCallback, scales } = doughnut.prop('options') ?? {}; expect(title).toEqual('The chart'); expect(labels).toEqual(keys(stats)); @@ -59,8 +59,8 @@ describe('', () => { expect(doughnut).toHaveLength(0); expect(horizontal).toHaveLength(1); - const { datasets: [{ backgroundColor, borderColor }] } = horizontal.prop('data'); - const { legend, legendCallback, scales } = horizontal.prop('options'); + const { datasets: [{ backgroundColor, borderColor }] } = horizontal.prop('data') as any; + const { legend, legendCallback, scales } = horizontal.prop('options') ?? {}; expect(backgroundColor).toEqual('rgba(70, 150, 229, 0.4)'); expect(borderColor).toEqual('rgba(70, 150, 229, 1)'); @@ -88,7 +88,7 @@ describe('', () => { wrapper = shallow(); const horizontal = wrapper.find(HorizontalBar); - const { datasets: [{ data, label }, highlightedData ] } = horizontal.prop('data'); + const { datasets: [{ data, label }, highlightedData ] } = horizontal.prop('data') as any; expect(label).toEqual(highlightedStats ? 'Non-selected' : 'Visits'); expect(data).toEqual(expectedData); diff --git a/test/visits/helpers/GraphCard.test.js b/test/visits/helpers/GraphCard.test.tsx similarity index 80% rename from test/visits/helpers/GraphCard.test.js rename to test/visits/helpers/GraphCard.test.tsx index 0c0baeb9..3769bb7e 100644 --- a/test/visits/helpers/GraphCard.test.js +++ b/test/visits/helpers/GraphCard.test.tsx @@ -1,18 +1,18 @@ -import React from 'react'; -import { shallow } from 'enzyme'; +import React, { ReactNode } from 'react'; +import { shallow, ShallowWrapper } from 'enzyme'; import { Card, CardBody, CardHeader, CardFooter } from 'reactstrap'; import GraphCard from '../../../src/visits/helpers/GraphCard'; import DefaultChart from '../../../src/visits/helpers/DefaultChart'; describe('', () => { - let wrapper; - const createWrapper = (title = '', footer) => { - wrapper = shallow(); + let wrapper: ShallowWrapper; + const createWrapper = (title: Function | string = '', footer?: ReactNode) => { + wrapper = shallow(); return wrapper; }; - afterEach(() => wrapper && wrapper.unmount()); + afterEach(() => wrapper?.unmount()); it('renders expected components', () => { const wrapper = createWrapper(); From 260ed3041aaaf9b79663c88f5ea861130f19a91f Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Fri, 4 Sep 2020 18:43:26 +0200 Subject: [PATCH 49/59] Finished migrating visits helpers to TS --- src/visits/helpers/DefaultChart.tsx | 6 +- .../{LineChartCard.js => LineChartCard.tsx} | 67 ++++++++------- ...rtableBarGraph.js => SortableBarGraph.tsx} | 86 +++++++++---------- src/visits/types/index.ts | 2 + ...artCard.test.js => LineChartCard.test.tsx} | 26 +++--- ...raph.test.js => SortableBarGraph.test.tsx} | 35 ++++---- ...itsParser.test.js => VisitsParser.test.ts} | 26 +++--- 7 files changed, 130 insertions(+), 118 deletions(-) rename src/visits/helpers/{LineChartCard.js => LineChartCard.tsx} (71%) rename src/visits/helpers/{SortableBarGraph.js => SortableBarGraph.tsx} (66%) rename test/visits/helpers/{LineChartCard.test.js => LineChartCard.test.tsx} (78%) rename test/visits/helpers/{SortableBarGraph.test.js => SortableBarGraph.test.tsx} (70%) rename test/visits/services/{VisitsParser.test.js => VisitsParser.test.ts} (93%) diff --git a/src/visits/helpers/DefaultChart.tsx b/src/visits/helpers/DefaultChart.tsx index 47d2250e..1fa9a3bc 100644 --- a/src/visits/helpers/DefaultChart.tsx +++ b/src/visits/helpers/DefaultChart.tsx @@ -47,15 +47,15 @@ const generateGraphData = ( borderColor: isBarChart ? 'rgba(70, 150, 229, 1)' : 'white', borderWidth: 2, }, - (highlightedData && { + highlightedData && { title, label: highlightedLabel ?? 'Selected', data: highlightedData, backgroundColor: 'rgba(247, 127, 40, 0.4)', borderColor: '#F77F28', borderWidth: 2, - }) as unknown as ChartDataSets, - ].filter(Boolean), + }, + ].filter(Boolean) as ChartDataSets[], }); const dropLabelIfHidden = (label: string) => label.startsWith('hidden') ? '' : label; diff --git a/src/visits/helpers/LineChartCard.js b/src/visits/helpers/LineChartCard.tsx similarity index 71% rename from src/visits/helpers/LineChartCard.js rename to src/visits/helpers/LineChartCard.tsx index 3daeb969..fc57ce37 100644 --- a/src/visits/helpers/LineChartCard.js +++ b/src/visits/helpers/LineChartCard.tsx @@ -1,5 +1,4 @@ import React, { useState, useMemo } from 'react'; -import PropTypes from 'prop-types'; import { Card, CardHeader, @@ -12,35 +11,38 @@ import { import { Line } from 'react-chartjs-2'; import { always, cond, reverse } from 'ramda'; import moment from 'moment'; -import { VisitType } from '../types'; +import { ChartData, ChartDataSets } from 'chart.js'; +import { Stats, Visit } from '../types'; import { fillTheGaps } from '../../utils/helpers/visits'; -import './LineChartCard.scss'; import { useToggle } from '../../utils/helpers/hooks'; import { rangeOf } from '../../utils/utils'; import ToggleSwitch from '../../utils/ToggleSwitch'; +import './LineChartCard.scss'; -const propTypes = { - title: PropTypes.string, - highlightedLabel: PropTypes.string, - visits: PropTypes.arrayOf(VisitType), - highlightedVisits: PropTypes.arrayOf(VisitType), -}; +interface LineChartCardProps { + title: string; + highlightedLabel?: string; + visits: Visit[]; + highlightedVisits: Visit[]; +} -const STEPS_MAP = { +type Step = 'monthly' | 'weekly' | 'daily' | 'hourly'; + +const STEPS_MAP: Record = { monthly: 'Month', weekly: 'Week', daily: 'Day', hourly: 'Hour', }; -const STEP_TO_DATE_UNIT_MAP = { +const STEP_TO_DATE_UNIT_MAP: Record = { hourly: 'hour', daily: 'day', weekly: 'week', monthly: 'month', }; -const STEP_TO_DATE_FORMAT = { +const STEP_TO_DATE_FORMAT: Record string> = { hourly: (date) => moment(date).format('YYYY-MM-DD HH:00'), daily: (date) => moment(date).format('YYYY-MM-DD'), weekly(date) { @@ -52,19 +54,19 @@ const STEP_TO_DATE_FORMAT = { monthly: (date) => moment(date).format('YYYY-MM'), }; -const determineInitialStep = (oldestVisitDate) => { +const determineInitialStep = (oldestVisitDate: string): Step => { const now = moment(); const oldestDate = moment(oldestVisitDate); - const matcher = cond([ - [ () => now.diff(oldestDate, 'day') <= 2, always('hourly') ], // Less than 2 days - [ () => now.diff(oldestDate, 'month') <= 1, always('daily') ], // Between 2 days and 1 month - [ () => now.diff(oldestDate, 'month') <= 6, always('weekly') ], // Between 1 and 6 months + const matcher = cond([ + [ () => now.diff(oldestDate, 'day') <= 2, always('hourly') ], // Less than 2 days + [ () => now.diff(oldestDate, 'month') <= 1, always('daily') ], // Between 2 days and 1 month + [ () => now.diff(oldestDate, 'month') <= 6, always('weekly') ], // Between 1 and 6 months ]); - return matcher() || 'monthly'; + return matcher() ?? 'monthly'; }; -const groupVisitsByStep = (step, visits) => visits.reduce((acc, visit) => { +const groupVisitsByStep = (step: Step, visits: Visit[]): Stats => visits.reduce((acc, visit) => { const key = STEP_TO_DATE_FORMAT[step](visit.date); acc[key] = acc[key] ? acc[key] + 1 : 1; @@ -72,7 +74,7 @@ const groupVisitsByStep = (step, visits) => visits.reduce((acc, visit) => { return acc; }, {}); -const generateLabels = (step, visits) => { +const generateLabels = (step: Step, visits: Visit[]): string[] => { const unit = STEP_TO_DATE_UNIT_MAP[step]; const formatter = STEP_TO_DATE_FORMAT[step]; const newerDate = moment(visits[0].date); @@ -85,9 +87,14 @@ const generateLabels = (step, visits) => { ]; }; -const generateLabelsAndGroupedVisits = (visits, groupedVisitsWithGaps, step, skipNoElements) => { +const generateLabelsAndGroupedVisits = ( + visits: Visit[], + groupedVisitsWithGaps: Stats, + step: Step, + skipNoElements: boolean, +): [string[], number[]] => { if (skipNoElements) { - return [ Object.keys(groupedVisitsWithGaps), groupedVisitsWithGaps ]; + return [ Object.keys(groupedVisitsWithGaps), Object.values(groupedVisitsWithGaps) ]; } const labels = generateLabels(step, visits); @@ -95,17 +102,17 @@ const generateLabelsAndGroupedVisits = (visits, groupedVisitsWithGaps, step, ski return [ labels, fillTheGaps(groupedVisitsWithGaps, labels) ]; }; -const generateDataset = (stats, label, color) => ({ +const generateDataset = (data: number[], label: string, color: string): ChartDataSets => ({ label, - data: Object.values(stats), + data, fill: false, lineTension: 0.2, borderColor: color, backgroundColor: color, }); -const LineChartCard = ({ title, visits, highlightedVisits, highlightedLabel = 'Selected' }) => { - const [ step, setStep ] = useState( +const LineChartCard = ({ title, visits, highlightedVisits, highlightedLabel = 'Selected' }: LineChartCardProps) => { + const [ step, setStep ] = useState( visits.length > 0 ? determineInitialStep(visits[visits.length - 1].date) : 'monthly', ); const [ skipNoVisits, toggleSkipNoVisits ] = useToggle(true); @@ -120,12 +127,12 @@ const LineChartCard = ({ title, visits, highlightedVisits, highlightedLabel = 'S [ highlightedVisits, step, labels ], ); - const data = { + const data: ChartData = { labels, datasets: [ generateDataset(groupedVisits, 'Visits', '#4696e5'), highlightedVisits.length > 0 && generateDataset(groupedHighlighted, highlightedLabel, '#F77F28'), - ].filter(Boolean), + ].filter(Boolean) as ChartDataSets[], }; const options = { maintainAspectRatio: false, @@ -159,7 +166,7 @@ const LineChartCard = ({ title, visits, highlightedVisits, highlightedLabel = 'S {Object.entries(STEPS_MAP).map(([ value, menuText ]) => ( - setStep(value)}> + setStep(value as Step)}> {menuText} ))} @@ -179,6 +186,4 @@ const LineChartCard = ({ title, visits, highlightedVisits, highlightedLabel = 'S ); }; -LineChartCard.propTypes = propTypes; - export default LineChartCard; diff --git a/src/visits/helpers/SortableBarGraph.js b/src/visits/helpers/SortableBarGraph.tsx similarity index 66% rename from src/visits/helpers/SortableBarGraph.js rename to src/visits/helpers/SortableBarGraph.tsx index 7ff4c123..286a48ed 100644 --- a/src/visits/helpers/SortableBarGraph.js +++ b/src/visits/helpers/SortableBarGraph.tsx @@ -1,27 +1,23 @@ import React, { useState } from 'react'; -import PropTypes from 'prop-types'; -import { fromPairs, head, keys, pipe, prop, reverse, sortBy, splitEvery, toLower, toPairs, type, zipObj } from 'ramda'; +import { fromPairs, pipe, reverse, sortBy, splitEvery, toLower, toPairs, type, zipObj } from 'ramda'; import SortingDropdown from '../../utils/SortingDropdown'; import PaginationDropdown from '../../utils/PaginationDropdown'; -import { rangeOf } from '../../utils/utils'; +import { OrderDir, rangeOf } from '../../utils/utils'; import { roundTen } from '../../utils/helpers/numbers'; import SimplePaginator from '../../common/SimplePaginator'; +import { Stats, StatsRow } from '../types'; import GraphCard from './GraphCard'; +import { DefaultChartProps } from './DefaultChart'; -const propTypes = { - stats: PropTypes.object.isRequired, - highlightedStats: PropTypes.object, - highlightedLabel: PropTypes.string, - title: PropTypes.string.isRequired, - sortingItems: PropTypes.object.isRequired, - extraHeaderContent: PropTypes.func, - withPagination: PropTypes.bool, - onClick: PropTypes.func, -}; +const toLowerIfString = (value: any) => type(value) === 'String' ? toLower(value) : value; +const pickKeyFromPair = ([ key ]: StatsRow) => key; +const pickValueFromPair = ([ , value ]: StatsRow) => value; -const toLowerIfString = (value) => type(value) === 'String' ? toLower(value) : value; -const pickKeyFromPair = ([ key ]) => key; -const pickValueFromPair = ([ , value ]) => value; +interface SortableBarGraphProps extends DefaultChartProps { + sortingItems: Record; + withPagination?: boolean; + extraHeaderContent?: Function; +} const SortableBarGraph = ({ stats, @@ -31,19 +27,19 @@ const SortableBarGraph = ({ extraHeaderContent, withPagination = true, ...rest -}) => { - const [ order, setOrder ] = useState({ +}: SortableBarGraphProps) => { + const [ order, setOrder ] = useState<{ orderField?: string; orderDir?: OrderDir }>({ orderField: undefined, orderDir: undefined, }); const [ currentPage, setCurrentPage ] = useState(1); const [ itemsPerPage, setItemsPerPage ] = useState(50); - const getSortedPairsForStats = (stats, sortingItems) => { + const getSortedPairsForStats = (stats: Stats, sortingItems: Record) => { const pairs = toPairs(stats); const sortedPairs = !order.orderField ? pairs : sortBy( - pipe( - prop(order.orderField === head(keys(sortingItems)) ? 0 : 1), + pipe( + order.orderField === Object.keys(sortingItems)[0] ? pickKeyFromPair : pickValueFromPair, toLowerIfString, ), pairs, @@ -51,7 +47,21 @@ const SortableBarGraph = ({ return !order.orderDir || order.orderDir === 'ASC' ? sortedPairs : reverse(sortedPairs); }; - const determineStats = (stats, highlightedStats, sortingItems) => { + const determineCurrentPagePairs = (pages: StatsRow[][]): StatsRow[] => { + const page = pages[currentPage - 1]; + + if (currentPage < pages.length) { + return page; + } + + const firstPageLength = pages[0].length; + + // Using the "hidden" key, the chart will just replace the label by an empty string + return [ ...page, ...rangeOf(firstPageLength - page.length, (i): StatsRow => [ `hidden_${i}`, 0 ]) ]; + }; + const renderPagination = (pagesCount: number) => + ; + const determineStats = (stats: Stats, highlightedStats: Stats | undefined, sortingItems: Record) => { const sortedPairs = getSortedPairsForStats(stats, sortingItems); const sortedKeys = sortedPairs.map(pickKeyFromPair); // The highlighted stats have to be ordered based on the regular stats, not on its own values @@ -76,27 +86,13 @@ const SortableBarGraph = ({ max: roundTen(Math.max(...sortedPairs.map(pickValueFromPair))), }; }; - const determineCurrentPagePairs = (pages) => { - const page = pages[currentPage - 1]; - - if (currentPage < pages.length) { - return page; - } - - const firstPageLength = pages[0].length; - - // Using the "hidden" key, the chart will just replace the label by an empty string - return [ ...page, ...rangeOf(firstPageLength - page.length, (i) => [ `hidden_${i}`, 0 ]) ]; - }; - const renderPagination = (pagesCount) => - ; const { currentPageStats, currentPageHighlightedStats, pagination, max } = determineStats( stats, - highlightedStats && keys(highlightedStats).length > 0 ? highlightedStats : undefined, + highlightedStats && Object.keys(highlightedStats).length > 0 ? highlightedStats : undefined, sortingItems, ); - const activeCities = keys(currentPageStats); + const activeCities = Object.keys(currentPageStats); const computeTitle = () => ( {title} @@ -107,16 +103,22 @@ const SortableBarGraph = ({ items={sortingItems} orderField={order.orderField} orderDir={order.orderDir} - onChange={(orderField, orderDir) => setOrder({ orderField, orderDir }) || setCurrentPage(1)} + onChange={(orderField, orderDir) => { + setOrder({ orderField, orderDir }); + setCurrentPage(1); + }} />
    - {withPagination && keys(stats).length > 50 && ( + {withPagination && Object.keys(stats).length > 50 && (
    setItemsPerPage(itemsPerPage) || setCurrentPage(1)} + setValue={(itemsPerPage) => { + setItemsPerPage(itemsPerPage); + setCurrentPage(1); + }} />
    )} @@ -141,6 +143,4 @@ const SortableBarGraph = ({ ); }; -SortableBarGraph.propTypes = propTypes; - export default SortableBarGraph; diff --git a/src/visits/types/index.ts b/src/visits/types/index.ts index 0b553d4e..1400ee77 100644 --- a/src/visits/types/index.ts +++ b/src/visits/types/index.ts @@ -80,6 +80,8 @@ export interface CreateVisit { export type Stats = Record; +export type StatsRow = [string, number]; + export interface CityStats { cityName: string; count: number; diff --git a/test/visits/helpers/LineChartCard.test.js b/test/visits/helpers/LineChartCard.test.tsx similarity index 78% rename from test/visits/helpers/LineChartCard.test.js rename to test/visits/helpers/LineChartCard.test.tsx index 348ba365..1fc0113a 100644 --- a/test/visits/helpers/LineChartCard.test.js +++ b/test/visits/helpers/LineChartCard.test.tsx @@ -1,20 +1,22 @@ import React from 'react'; -import { shallow } from 'enzyme'; +import { shallow, ShallowWrapper } from 'enzyme'; import { CardHeader, DropdownItem } from 'reactstrap'; import { Line } from 'react-chartjs-2'; import moment from 'moment'; +import { Mock } from 'ts-mockery'; import LineChartCard from '../../../src/visits/helpers/LineChartCard'; import ToggleSwitch from '../../../src/utils/ToggleSwitch'; +import { Visit } from '../../../src/visits/types'; describe('', () => { - let wrapper; - const createWrapper = (visits = [], highlightedVisits = []) => { + let wrapper: ShallowWrapper; + const createWrapper = (visits: Visit[] = [], highlightedVisits: Visit[] = []) => { wrapper = shallow(); return wrapper; }; - afterEach(() => wrapper && wrapper.unmount()); + afterEach(() => wrapper?.unmount()); it('renders provided title', () => { const wrapper = createWrapper(); @@ -32,7 +34,7 @@ describe('', () => { [[{ date: moment().subtract(7, 'month').format() }], 'monthly' ], [[{ date: moment().subtract(1, 'year').format() }], 'monthly' ], ])('renders group menu and selects proper grouping item based on visits dates', (visits, expectedActiveItem) => { - const wrapper = createWrapper(visits); + const wrapper = createWrapper(visits.map((visit) => Mock.of(visit))); const items = wrapper.find(DropdownItem); expect(items).toHaveLength(4); @@ -73,24 +75,24 @@ describe('', () => { }); it.each([ - [[{}], [], 1 ], - [[{}], [{}], 2 ], + [[ Mock.of({}) ], [], 1 ], + [[ Mock.of({}) ], [ Mock.of({}) ], 2 ], ])('renders chart with expected data', (visits, highlightedVisits, expectedLines) => { const wrapper = createWrapper(visits, highlightedVisits); const chart = wrapper.find(Line); - const { datasets } = chart.prop('data'); + const { datasets } = chart.prop('data') as any; expect(datasets).toHaveLength(expectedLines); }); it('includes stats for visits with no dates if selected', () => { const wrapper = createWrapper([ - { date: '2016-04-01' }, - { date: '2016-01-01' }, + Mock.of({ date: '2016-04-01' }), + Mock.of({ date: '2016-01-01' }), ]); - expect(wrapper.find(Line).prop('data').labels).toHaveLength(2); + expect((wrapper.find(Line).prop('data') as any).labels).toHaveLength(2); wrapper.find(ToggleSwitch).simulate('change'); - expect(wrapper.find(Line).prop('data').labels).toHaveLength(4); + expect((wrapper.find(Line).prop('data') as any).labels).toHaveLength(4); }); }); diff --git a/test/visits/helpers/SortableBarGraph.test.js b/test/visits/helpers/SortableBarGraph.test.tsx similarity index 70% rename from test/visits/helpers/SortableBarGraph.test.js rename to test/visits/helpers/SortableBarGraph.test.tsx index 4ea06650..e22fa0a4 100644 --- a/test/visits/helpers/SortableBarGraph.test.js +++ b/test/visits/helpers/SortableBarGraph.test.tsx @@ -1,14 +1,15 @@ import React from 'react'; -import { shallow } from 'enzyme'; -import { keys, range, values } from 'ramda'; +import { shallow, ShallowWrapper } from 'enzyme'; +import { range } from 'ramda'; import SortableBarGraph from '../../../src/visits/helpers/SortableBarGraph'; import GraphCard from '../../../src/visits/helpers/GraphCard'; import SortingDropdown from '../../../src/utils/SortingDropdown'; import PaginationDropdown from '../../../src/utils/PaginationDropdown'; -import { rangeOf } from '../../../src/utils/utils'; +import { OrderDir, rangeOf } from '../../../src/utils/utils'; +import { Stats } from '../../../src/visits/types'; describe('', () => { - let wrapper; + let wrapper: ShallowWrapper; const sortingItems = { name: 'Name', amount: 'Amount', @@ -30,7 +31,7 @@ describe('', () => { return wrapper; }; - afterEach(() => wrapper && wrapper.unmount()); + afterEach(() => wrapper?.unmount()); it('renders stats unchanged when no ordering is set', () => { const wrapper = createWrapper(); @@ -40,19 +41,19 @@ describe('', () => { }); describe('renders properly ordered stats when ordering is set', () => { - let assert; + let assert: (sortName: string, sortDir: OrderDir, keys: string[], values: number[], done: Function) => void; beforeEach(() => { const wrapper = createWrapper(); - const dropdown = wrapper.renderProp('title')().find(SortingDropdown); + const dropdown = wrapper.renderProp('title' as never)().find(SortingDropdown); - assert = (sortName, sortDir, expectedKeys, expectedValues, done) => { + assert = (sortName: string, sortDir: OrderDir, keys: string[], values: number[], done: Function) => { dropdown.prop('onChange')(sortName, sortDir); setImmediate(() => { const stats = wrapper.find(GraphCard).prop('stats'); - expect(keys(stats)).toEqual(expectedKeys); - expect(values(stats)).toEqual(expectedValues); + expect(Object.keys(stats)).toEqual(keys); + expect(Object.values(stats)).toEqual(values); done(); }); }; @@ -65,28 +66,28 @@ describe('', () => { }); describe('renders properly paginated stats when pagination is set', () => { - let assert; + let assert: (itemsPerPage: number, expectedStats: string[], done: Function) => void; beforeEach(() => { - const wrapper = createWrapper(true, range(1, 159).reduce((accum, value) => { + const wrapper = createWrapper(true, range(1, 159).reduce((accum, value) => { accum[`key_${value}`] = value; return accum; }, {})); - const dropdown = wrapper.renderProp('title')().find(PaginationDropdown); + const dropdown = wrapper.renderProp('title' as never)().find(PaginationDropdown); - assert = (itemsPerPage, expectedStats, done) => { + assert = (itemsPerPage: number, expectedStats: string[], done: Function) => { dropdown.prop('setValue')(itemsPerPage); setImmediate(() => { const stats = wrapper.find(GraphCard).prop('stats'); - expect(keys(stats)).toEqual(expectedStats); + expect(Object.keys(stats)).toEqual(expectedStats); done(); }); }; }); - const buildExpected = (size) => [ 'Foo', 'Bar', ...rangeOf(size - 2, (i) => `key_${i}`) ]; + const buildExpected = (size: number): string[] => [ 'Foo', 'Bar', ...rangeOf(size - 2, (i) => `key_${i}`) ]; it('50 items per page', (done) => assert(50, buildExpected(50), done)); it('100 items per page', (done) => assert(100, buildExpected(100), done)); @@ -95,7 +96,7 @@ describe('', () => { }); it('renders extra header content', () => { - wrapper = shallow( + const wrapper = shallow( { - const visits = [ - { + const visits: Visit[] = [ + Mock.of({ userAgent: 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:47.0) Gecko/20100101 Firefox/47.0', referer: 'https://google.com', visitLocation: { @@ -11,8 +13,8 @@ describe('VisitsParser', () => { latitude: 123.45, longitude: -543.21, }, - }, - { + }), + Mock.of({ userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X x.y; rv:42.0) Gecko/20100101 Firefox/42.0', referer: 'https://google.com', visitLocation: { @@ -21,14 +23,14 @@ describe('VisitsParser', () => { latitude: 1029, longitude: 6758, }, - }, - { + }), + Mock.of({ userAgent: 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36', visitLocation: { countryName: 'Spain', }, - }, - { + }), + Mock.of({ userAgent: 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36', referer: 'https://m.facebook.com', visitLocation: { @@ -37,14 +39,14 @@ describe('VisitsParser', () => { latitude: 123.45, longitude: -543.21, }, - }, - { + }), + Mock.of({ userAgent: 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36 OPR/38.0.2220.41', - }, + }), ]; describe('processStatsFromVisits', () => { - let stats; + let stats: VisitsStats; beforeAll(() => { stats = processStatsFromVisits(normalizeVisits(visits)); From f2e7a2161d35d7beabb39040c43dcd5f23e5e7b9 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Fri, 4 Sep 2020 19:05:41 +0200 Subject: [PATCH 50/59] Removed duplicated code on mercure-bound components --- src/common/MainHeader.tsx | 4 +- src/common/ScrollToTop.tsx | 4 +- src/mercure/helpers/index.ts | 6 ++ src/servers/helpers/withSelectedServer.tsx | 8 +-- src/short-urls/ShortUrlsList.tsx | 12 ++-- src/tags/TagsList.tsx | 8 +-- src/visits/ShortUrlVisits.js | 68 ---------------------- src/visits/ShortUrlVisits.tsx | 49 ++++++++++++++++ test/common/ScrollToTop.test.tsx | 4 +- 9 files changed, 71 insertions(+), 92 deletions(-) delete mode 100644 src/visits/ShortUrlVisits.js create mode 100644 src/visits/ShortUrlVisits.tsx diff --git a/src/common/MainHeader.tsx b/src/common/MainHeader.tsx index e4a8c163..627aaa58 100644 --- a/src/common/MainHeader.tsx +++ b/src/common/MainHeader.tsx @@ -4,12 +4,12 @@ import React, { FC, useEffect } from 'react'; import { Link } from 'react-router-dom'; import { Collapse, Nav, Navbar, NavbarBrand, NavbarToggler, NavItem, NavLink } from 'reactstrap'; import classNames from 'classnames'; -import { RouteChildrenProps } from 'react-router'; +import { RouteComponentProps } from 'react-router'; import { useToggle } from '../utils/helpers/hooks'; import shlinkLogo from './shlink-logo-white.png'; import './MainHeader.scss'; -const MainHeader = (ServersDropdown: FC) => ({ location }: RouteChildrenProps) => { +const MainHeader = (ServersDropdown: FC) => ({ location }: RouteComponentProps) => { const [ isOpen, toggleOpen, , close ] = useToggle(); const { pathname } = location; diff --git a/src/common/ScrollToTop.tsx b/src/common/ScrollToTop.tsx index 48bf2a04..f24a8b80 100644 --- a/src/common/ScrollToTop.tsx +++ b/src/common/ScrollToTop.tsx @@ -1,7 +1,7 @@ import React, { PropsWithChildren, useEffect } from 'react'; -import { RouteChildrenProps } from 'react-router'; +import { RouteComponentProps } from 'react-router'; -const ScrollToTop = () => ({ location, children }: PropsWithChildren) => { +const ScrollToTop = () => ({ location, children }: PropsWithChildren) => { useEffect(() => { scrollTo(0, 0); }, [ location ]); diff --git a/src/mercure/helpers/index.ts b/src/mercure/helpers/index.ts index 8f6eec78..4fa89f24 100644 --- a/src/mercure/helpers/index.ts +++ b/src/mercure/helpers/index.ts @@ -32,3 +32,9 @@ export const useMercureTopicBinding = ( ) => { useEffect(bindToMercureTopic(mercureInfo, topic, onMessage, onTokenExpired), [ mercureInfo ]); }; + +export interface MercureBoundProps { + createNewVisit: (message: any) => void; + loadMercureInfo: Function; + mercureInfo: MercureInfo; +} diff --git a/src/servers/helpers/withSelectedServer.tsx b/src/servers/helpers/withSelectedServer.tsx index 41f8a45c..0f602f6e 100644 --- a/src/servers/helpers/withSelectedServer.tsx +++ b/src/servers/helpers/withSelectedServer.tsx @@ -1,9 +1,9 @@ import React, { FC, useEffect } from 'react'; -import { RouteChildrenProps } from 'react-router'; +import { RouteComponentProps } from 'react-router'; import Message from '../../utils/Message'; import { isNotFoundServer, SelectedServer } from '../data'; -interface WithSelectedServerProps extends RouteChildrenProps<{ serverId: string }> { +interface WithSelectedServerProps extends RouteComponentProps<{ serverId: string }> { selectServer: (serverId: string) => void; selectedServer: SelectedServer; } @@ -13,8 +13,8 @@ export function withSelectedServer(WrappedComponent: FC { - match?.params?.serverId && selectServer(match?.params.serverId); - }, [ match?.params.serverId ]); + selectServer(match.params.serverId); + }, [ match.params.serverId ]); if (!selectedServer) { return ; diff --git a/src/short-urls/ShortUrlsList.tsx b/src/short-urls/ShortUrlsList.tsx index c045c892..ccab7bca 100644 --- a/src/short-urls/ShortUrlsList.tsx +++ b/src/short-urls/ShortUrlsList.tsx @@ -3,11 +3,10 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { head, isEmpty, keys, values } from 'ramda'; import React, { useState, useEffect, FC } from 'react'; import qs from 'qs'; -import { RouteChildrenProps } from 'react-router'; +import { RouteComponentProps } from 'react-router'; import SortingDropdown from '../utils/SortingDropdown'; import { determineOrderDir, OrderDir } from '../utils/utils'; -import { MercureInfo } from '../mercure/reducers/mercureInfo'; -import { useMercureTopicBinding } from '../mercure/helpers'; +import { MercureBoundProps, useMercureTopicBinding } from '../mercure/helpers'; import { SelectedServer } from '../servers/data'; import { ShortUrlsList as ShortUrlsListState } from './reducers/shortUrlsList'; import { ShortUrlsRowProps } from './helpers/ShortUrlsRow'; @@ -32,14 +31,11 @@ export interface WithList { shortUrlsList: ShortUrl[]; } -export interface ShortUrlsListProps extends ShortUrlsListState, RouteChildrenProps { +export interface ShortUrlsListProps extends ShortUrlsListState, RouteComponentProps, MercureBoundProps { selectedServer: SelectedServer; listShortUrls: (params: ShortUrlsListParams) => void; shortUrlsListParams: ShortUrlsListParams; resetShortUrlParams: () => void; - createNewVisit: (message: any) => void; - loadMercureInfo: Function; - mercureInfo: MercureInfo; } const ShortUrlsList = (ShortUrlsRow: FC) => ({ @@ -116,7 +112,7 @@ const ShortUrlsList = (ShortUrlsRow: FC) => ({ const query = qs.parse(location.search, { ignoreQueryPrefix: true }); const tags = query.tag ? [ query.tag as string ] : shortUrlsListParams.tags; - refreshList({ page: match?.params.page, tags }); + refreshList({ page: match.params.page, tags }); return resetShortUrlParams; }, []); diff --git a/src/tags/TagsList.tsx b/src/tags/TagsList.tsx index 0da20b1b..e73ee4da 100644 --- a/src/tags/TagsList.tsx +++ b/src/tags/TagsList.tsx @@ -2,8 +2,7 @@ import React, { FC, useEffect, useState } from 'react'; import { splitEvery } from 'ramda'; import Message from '../utils/Message'; import SearchField from '../utils/SearchField'; -import { MercureInfo } from '../mercure/reducers/mercureInfo'; -import { useMercureTopicBinding } from '../mercure/helpers'; +import { MercureBoundProps, useMercureTopicBinding } from '../mercure/helpers'; import { SelectedServer } from '../servers/data'; import { TagsList as TagsListState } from './reducers/tagsList'; import { TagCardProps } from './TagCard'; @@ -11,14 +10,11 @@ import { TagCardProps } from './TagCard'; const { ceil } = Math; const TAGS_GROUPS_AMOUNT = 4; -export interface TagsListProps { +export interface TagsListProps extends MercureBoundProps { filterTags: (searchTerm: string) => void; forceListTags: Function; tagsList: TagsListState; selectedServer: SelectedServer; - createNewVisit: () => void; - loadMercureInfo: Function; - mercureInfo: MercureInfo; } const TagsList = (TagCard: FC) => ( diff --git a/src/visits/ShortUrlVisits.js b/src/visits/ShortUrlVisits.js deleted file mode 100644 index 20e7aeea..00000000 --- a/src/visits/ShortUrlVisits.js +++ /dev/null @@ -1,68 +0,0 @@ -import React, { useEffect } from 'react'; -import PropTypes from 'prop-types'; -import qs from 'qs'; -import { MercureInfoType } from '../mercure/reducers/mercureInfo'; -import { useMercureTopicBinding } from '../mercure/helpers'; -import { shortUrlVisitsType } from './reducers/shortUrlVisits'; -import ShortUrlVisitsHeader from './ShortUrlVisitsHeader'; -import { shortUrlDetailType } from './reducers/shortUrlDetail'; - -const propTypes = { - history: PropTypes.shape({ - goBack: PropTypes.func, - }), - match: PropTypes.shape({ - params: PropTypes.object, - }), - location: PropTypes.shape({ - search: PropTypes.string, - }), - getShortUrlVisits: PropTypes.func, - shortUrlVisits: shortUrlVisitsType, - getShortUrlDetail: PropTypes.func, - shortUrlDetail: shortUrlDetailType, - cancelGetShortUrlVisits: PropTypes.func, - createNewVisit: PropTypes.func, - loadMercureInfo: PropTypes.func, - mercureInfo: MercureInfoType, -}; - -const ShortUrlVisits = (VisitsStats) => { - const ShortUrlVisitsComp = ({ - history, - match, - location, - shortUrlVisits, - shortUrlDetail, - getShortUrlVisits, - getShortUrlDetail, - cancelGetShortUrlVisits, - createNewVisit, - loadMercureInfo, - mercureInfo, - }) => { - const { params } = match; - const { shortCode } = params; - const { search } = location; - const { domain } = qs.parse(search, { ignoreQueryPrefix: true }); - - const loadVisits = (dates) => getShortUrlVisits(shortCode, { ...dates, domain }); - - useEffect(() => { - getShortUrlDetail(shortCode, domain); - }, []); - useMercureTopicBinding(mercureInfo, `https://shlink.io/new-visit/${shortCode}`, createNewVisit, loadMercureInfo); - - return ( - - - - ); - }; - - ShortUrlVisitsComp.propTypes = propTypes; - - return ShortUrlVisitsComp; -}; - -export default ShortUrlVisits; diff --git a/src/visits/ShortUrlVisits.tsx b/src/visits/ShortUrlVisits.tsx new file mode 100644 index 00000000..805f5524 --- /dev/null +++ b/src/visits/ShortUrlVisits.tsx @@ -0,0 +1,49 @@ +import React, { FC, useEffect } from 'react'; +import qs from 'qs'; +import { RouteComponentProps } from 'react-router'; +import { MercureBoundProps, useMercureTopicBinding } from '../mercure/helpers'; +import { ShlinkVisitsParams } from '../utils/services/types'; +import { ShortUrlVisits as ShortUrlVisitsState } from './reducers/shortUrlVisits'; +import ShortUrlVisitsHeader from './ShortUrlVisitsHeader'; +import { ShortUrlDetail } from './reducers/shortUrlDetail'; + +interface ShortUrlVisitsProps extends RouteComponentProps<{ shortCode: string }>, MercureBoundProps { + getShortUrlVisits: (shortCode: string, query?: ShlinkVisitsParams) => void; + shortUrlVisits: ShortUrlVisitsState; + getShortUrlDetail: Function; + shortUrlDetail: ShortUrlDetail; + cancelGetShortUrlVisits: Function; +} + +const ShortUrlVisits = (VisitsStats: FC) => ({ // TODO Use VisitsStatsProps once available + history: { goBack }, + match, + location: { search }, + shortUrlVisits, + shortUrlDetail, + getShortUrlVisits, + getShortUrlDetail, + cancelGetShortUrlVisits, + createNewVisit, + loadMercureInfo, + mercureInfo, +}: ShortUrlVisitsProps) => { + const { params } = match; + const { shortCode } = params; + const { domain } = qs.parse(search, { ignoreQueryPrefix: true }) as { domain?: string }; + + const loadVisits = (dates: Partial) => getShortUrlVisits(shortCode, { ...dates, domain }); + + useEffect(() => { + getShortUrlDetail(shortCode, domain); + }, []); + useMercureTopicBinding(mercureInfo, `https://shlink.io/new-visit/${shortCode}`, createNewVisit, loadMercureInfo); + + return ( + + + + ); +}; + +export default ShortUrlVisits; diff --git a/test/common/ScrollToTop.test.tsx b/test/common/ScrollToTop.test.tsx index ff925292..59918f1a 100644 --- a/test/common/ScrollToTop.test.tsx +++ b/test/common/ScrollToTop.test.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { shallow, ShallowWrapper } from 'enzyme'; import { Mock } from 'ts-mockery'; -import { RouteChildrenProps } from 'react-router'; +import { RouteComponentProps } from 'react-router'; import createScrollToTop from '../../src/common/ScrollToTop'; describe('', () => { @@ -10,7 +10,7 @@ describe('', () => { beforeEach(() => { const ScrollToTop = createScrollToTop(); - wrapper = shallow(()}>Foobar); + wrapper = shallow(()}>Foobar); }); afterEach(() => wrapper.unmount()); From 73b854037de0a53a7d03ba057079be273deab45b Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Fri, 4 Sep 2020 19:33:16 +0200 Subject: [PATCH 51/59] Migrated to TS all visits components except the biggest two --- src/mercure/reducers/mercureInfo.ts | 9 ---- src/short-urls/reducers/shortUrlMeta.ts | 8 --- src/short-urls/reducers/shortUrlsList.ts | 14 +---- src/utils/services/ColorGenerator.ts | 7 --- src/visits/ShortUrlVisits.tsx | 2 +- ...sitsHeader.js => ShortUrlVisitsHeader.tsx} | 23 ++++---- src/visits/TagVisits.js | 52 ------------------- src/visits/TagVisits.tsx | 37 +++++++++++++ ...TagVisitsHeader.js => TagVisitsHeader.tsx} | 19 +++---- .../{VisitsHeader.js => VisitsHeader.tsx} | 24 ++++----- src/visits/reducers/shortUrlDetail.ts | 9 ---- src/visits/reducers/shortUrlVisits.ts | 14 +---- src/visits/reducers/tagVisits.ts | 13 +---- ...Visits.test.js => ShortUrlVisits.test.tsx} | 27 ++++++---- 14 files changed, 85 insertions(+), 173 deletions(-) rename src/visits/{ShortUrlVisitsHeader.js => ShortUrlVisitsHeader.tsx} (71%) delete mode 100644 src/visits/TagVisits.js create mode 100644 src/visits/TagVisits.tsx rename src/visits/{TagVisitsHeader.js => TagVisitsHeader.tsx} (56%) rename src/visits/{VisitsHeader.js => VisitsHeader.tsx} (68%) rename test/visits/{ShortUrlVisits.test.js => ShortUrlVisits.test.tsx} (53%) diff --git a/src/mercure/reducers/mercureInfo.ts b/src/mercure/reducers/mercureInfo.ts index 768d8acf..5b08c364 100644 --- a/src/mercure/reducers/mercureInfo.ts +++ b/src/mercure/reducers/mercureInfo.ts @@ -1,4 +1,3 @@ -import PropTypes from 'prop-types'; import { Action, Dispatch } from 'redux'; import { ShlinkMercureInfo } from '../../utils/services/types'; import { GetState } from '../../container/types'; @@ -11,14 +10,6 @@ export const GET_MERCURE_INFO_ERROR = 'shlink/mercure/GET_MERCURE_INFO_ERROR'; export const GET_MERCURE_INFO = 'shlink/mercure/GET_MERCURE_INFO'; /* eslint-enable padding-line-between-statements */ -/** @deprecated Use MercureInfo interface */ -export const MercureInfoType = PropTypes.shape({ - token: PropTypes.string, - mercureHubUrl: PropTypes.string, - loading: PropTypes.bool, - error: PropTypes.bool, -}); - export interface MercureInfo { token?: string; mercureHubUrl?: string; diff --git a/src/short-urls/reducers/shortUrlMeta.ts b/src/short-urls/reducers/shortUrlMeta.ts index 71736ea0..8b4e8a51 100644 --- a/src/short-urls/reducers/shortUrlMeta.ts +++ b/src/short-urls/reducers/shortUrlMeta.ts @@ -1,4 +1,3 @@ -import PropTypes from 'prop-types'; import { Dispatch, Action } from 'redux'; import { ShortUrlIdentifier, ShortUrlMeta } from '../data'; import { GetState } from '../../container/types'; @@ -13,13 +12,6 @@ export const SHORT_URL_META_EDITED = 'shlink/shortUrlMeta/SHORT_URL_META_EDITED' export const RESET_EDIT_SHORT_URL_META = 'shlink/shortUrlMeta/RESET_EDIT_SHORT_URL_META'; /* eslint-enable padding-line-between-statements */ -/** @deprecated Use ShortUrlMeta interface instead */ -export const shortUrlMetaType = PropTypes.shape({ - validSince: PropTypes.string, - validUntil: PropTypes.string, - maxVisits: PropTypes.number, -}); - export interface ShortUrlMetaEdition { shortCode: string | null; meta: ShortUrlMeta; diff --git a/src/short-urls/reducers/shortUrlsList.ts b/src/short-urls/reducers/shortUrlsList.ts index 71a153e5..4370fb20 100644 --- a/src/short-urls/reducers/shortUrlsList.ts +++ b/src/short-urls/reducers/shortUrlsList.ts @@ -1,5 +1,4 @@ import { assoc, assocPath, reject } from 'ramda'; -import PropTypes from 'prop-types'; import { Action, Dispatch } from 'redux'; import { shortUrlMatches } from '../helpers'; import { CREATE_VISIT, CreateVisitAction } from '../../visits/reducers/visitCreation'; @@ -10,7 +9,7 @@ import { ShlinkApiClientBuilder } from '../../utils/services/ShlinkApiClientBuil import { ShlinkShortUrlsResponse } from '../../utils/services/types'; import { EditShortUrlTagsAction, SHORT_URL_TAGS_EDITED } from './shortUrlTags'; import { SHORT_URL_DELETED } from './shortUrlDeletion'; -import { SHORT_URL_META_EDITED, ShortUrlMetaEditedAction, shortUrlMetaType } from './shortUrlMeta'; +import { SHORT_URL_META_EDITED, ShortUrlMetaEditedAction } from './shortUrlMeta'; import { SHORT_URL_EDITED, ShortUrlEditedAction } from './shortUrlEdition'; import { ShortUrlsListParams } from './shortUrlsListParams'; @@ -20,17 +19,6 @@ export const LIST_SHORT_URLS_ERROR = 'shlink/shortUrlsList/LIST_SHORT_URLS_ERROR export const LIST_SHORT_URLS = 'shlink/shortUrlsList/LIST_SHORT_URLS'; /* eslint-enable padding-line-between-statements */ -/** @deprecated Use ShortUrl interface instead */ -export const shortUrlType = PropTypes.shape({ - shortCode: PropTypes.string, - shortUrl: PropTypes.string, - longUrl: PropTypes.string, - visitsCount: PropTypes.number, - meta: shortUrlMetaType, - tags: PropTypes.arrayOf(PropTypes.string), - domain: PropTypes.string, -}); - export interface ShortUrlsList { shortUrls?: ShlinkShortUrlsResponse; loading: boolean; diff --git a/src/utils/services/ColorGenerator.ts b/src/utils/services/ColorGenerator.ts index 5f79b0b8..7a9c8abd 100644 --- a/src/utils/services/ColorGenerator.ts +++ b/src/utils/services/ColorGenerator.ts @@ -1,4 +1,3 @@ -import PropTypes from 'prop-types'; import { rangeOf } from '../utils'; import LocalStorage from './LocalStorage'; @@ -36,9 +35,3 @@ export default class ColorGenerator { return color; }; } - -/** @deprecated Use ColorGenerator class instead */ -export const colorGeneratorType = PropTypes.shape({ - getColorForKey: PropTypes.func, - setColorForKey: PropTypes.func, -}); diff --git a/src/visits/ShortUrlVisits.tsx b/src/visits/ShortUrlVisits.tsx index 805f5524..e4f8a5c2 100644 --- a/src/visits/ShortUrlVisits.tsx +++ b/src/visits/ShortUrlVisits.tsx @@ -7,7 +7,7 @@ import { ShortUrlVisits as ShortUrlVisitsState } from './reducers/shortUrlVisits import ShortUrlVisitsHeader from './ShortUrlVisitsHeader'; import { ShortUrlDetail } from './reducers/shortUrlDetail'; -interface ShortUrlVisitsProps extends RouteComponentProps<{ shortCode: string }>, MercureBoundProps { +export interface ShortUrlVisitsProps extends RouteComponentProps<{ shortCode: string }>, MercureBoundProps { getShortUrlVisits: (shortCode: string, query?: ShlinkVisitsParams) => void; shortUrlVisits: ShortUrlVisitsState; getShortUrlDetail: Function; diff --git a/src/visits/ShortUrlVisitsHeader.js b/src/visits/ShortUrlVisitsHeader.tsx similarity index 71% rename from src/visits/ShortUrlVisitsHeader.js rename to src/visits/ShortUrlVisitsHeader.tsx index 1d901cbc..9704bcba 100644 --- a/src/visits/ShortUrlVisitsHeader.js +++ b/src/visits/ShortUrlVisitsHeader.tsx @@ -1,24 +1,23 @@ import { UncontrolledTooltip } from 'reactstrap'; import Moment from 'react-moment'; import React from 'react'; -import PropTypes from 'prop-types'; import { ExternalLink } from 'react-external-link'; -import { shortUrlDetailType } from './reducers/shortUrlDetail'; -import { shortUrlVisitsType } from './reducers/shortUrlVisits'; +import { ShortUrlDetail } from './reducers/shortUrlDetail'; +import { ShortUrlVisits } from './reducers/shortUrlVisits'; import VisitsHeader from './VisitsHeader'; import './ShortUrlVisitsHeader.scss'; -const propTypes = { - shortUrlDetail: shortUrlDetailType.isRequired, - shortUrlVisits: shortUrlVisitsType.isRequired, - goBack: PropTypes.func.isRequired, -}; +interface ShortUrlVisitsHeaderProps { + shortUrlDetail: ShortUrlDetail; + shortUrlVisits: ShortUrlVisits; + goBack: () => void; +} -const ShortUrlVisitsHeader = ({ shortUrlDetail, shortUrlVisits, goBack }) => { +const ShortUrlVisitsHeader = ({ shortUrlDetail, shortUrlVisits, goBack }: ShortUrlVisitsHeaderProps) => { const { shortUrl, loading } = shortUrlDetail; const { visits } = shortUrlVisits; - const shortLink = shortUrl && shortUrl.shortUrl ? shortUrl.shortUrl : ''; - const longLink = shortUrl && shortUrl.longUrl ? shortUrl.longUrl : ''; + const shortLink = shortUrl?.shortUrl ?? ''; + const longLink = shortUrl?.longUrl ?? ''; const renderDate = () => !shortUrl ? Loading... : ( @@ -49,6 +48,4 @@ const ShortUrlVisitsHeader = ({ shortUrlDetail, shortUrlVisits, goBack }) => { ); }; -ShortUrlVisitsHeader.propTypes = propTypes; - export default ShortUrlVisitsHeader; diff --git a/src/visits/TagVisits.js b/src/visits/TagVisits.js deleted file mode 100644 index 299e6e0c..00000000 --- a/src/visits/TagVisits.js +++ /dev/null @@ -1,52 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { MercureInfoType } from '../mercure/reducers/mercureInfo'; -import { useMercureTopicBinding } from '../mercure/helpers'; -import { TagVisitsType } from './reducers/tagVisits'; -import TagVisitsHeader from './TagVisitsHeader'; - -const propTypes = { - history: PropTypes.shape({ - goBack: PropTypes.func, - }), - match: PropTypes.shape({ - params: PropTypes.object, - }), - getTagVisits: PropTypes.func, - tagVisits: TagVisitsType, - cancelGetTagVisits: PropTypes.func, - createNewVisit: PropTypes.func, - loadMercureInfo: PropTypes.func, - mercureInfo: MercureInfoType, -}; - -const TagVisits = (VisitsStats, colorGenerator) => { - const TagVisitsComp = ({ - history, - match, - getTagVisits, - tagVisits, - cancelGetTagVisits, - createNewVisit, - loadMercureInfo, - mercureInfo, - }) => { - const { params } = match; - const { tag } = params; - const loadVisits = (dates) => getTagVisits(tag, dates); - - useMercureTopicBinding(mercureInfo, 'https://shlink.io/new-visit', createNewVisit, loadMercureInfo); - - return ( - - - - ); - }; - - TagVisitsComp.propTypes = propTypes; - - return TagVisitsComp; -}; - -export default TagVisits; diff --git a/src/visits/TagVisits.tsx b/src/visits/TagVisits.tsx new file mode 100644 index 00000000..2efd5c98 --- /dev/null +++ b/src/visits/TagVisits.tsx @@ -0,0 +1,37 @@ +import React, { FC } from 'react'; +import { RouteComponentProps } from 'react-router'; +import { MercureBoundProps, useMercureTopicBinding } from '../mercure/helpers'; +import ColorGenerator from '../utils/services/ColorGenerator'; +import { TagVisits as TagVisitsState } from './reducers/tagVisits'; +import TagVisitsHeader from './TagVisitsHeader'; + +export interface TagVisitsProps extends RouteComponentProps<{ tag: string }>, MercureBoundProps { + getTagVisits: (tag: string, query: any) => void; + tagVisits: TagVisitsState; + cancelGetTagVisits: Function; +} + +const TagVisits = (VisitsStats: FC, colorGenerator: ColorGenerator) => ({ // TODO Use VisitsStatsProps once available + history: { goBack }, + match, + getTagVisits, + tagVisits, + cancelGetTagVisits, + createNewVisit, + loadMercureInfo, + mercureInfo, +}: TagVisitsProps) => { + const { params } = match; + const { tag } = params; + const loadVisits = (dates: any) => getTagVisits(tag, dates); + + useMercureTopicBinding(mercureInfo, 'https://shlink.io/new-visit', createNewVisit, loadMercureInfo); + + return ( + + + + ); +}; + +export default TagVisits; diff --git a/src/visits/TagVisitsHeader.js b/src/visits/TagVisitsHeader.tsx similarity index 56% rename from src/visits/TagVisitsHeader.js rename to src/visits/TagVisitsHeader.tsx index 48c8bf3c..b27acc2b 100644 --- a/src/visits/TagVisitsHeader.js +++ b/src/visits/TagVisitsHeader.tsx @@ -1,18 +1,17 @@ import React from 'react'; -import PropTypes from 'prop-types'; import Tag from '../tags/helpers/Tag'; -import { colorGeneratorType } from '../utils/services/ColorGenerator'; +import ColorGenerator from '../utils/services/ColorGenerator'; import VisitsHeader from './VisitsHeader'; -import { TagVisitsType } from './reducers/tagVisits'; +import { TagVisits } from './reducers/tagVisits'; import './ShortUrlVisitsHeader.scss'; -const propTypes = { - tagVisits: TagVisitsType.isRequired, - goBack: PropTypes.func.isRequired, - colorGenerator: colorGeneratorType, -}; +interface TagVisitsHeader { + tagVisits: TagVisits; + goBack: () => void; + colorGenerator: ColorGenerator; +} -const TagVisitsHeader = ({ tagVisits, goBack, colorGenerator }) => { +const TagVisitsHeader = ({ tagVisits, goBack, colorGenerator }: TagVisitsHeader) => { const { visits, tag } = tagVisits; const visitsStatsTitle = ( @@ -25,6 +24,4 @@ const TagVisitsHeader = ({ tagVisits, goBack, colorGenerator }) => { return ; }; -TagVisitsHeader.propTypes = propTypes; - export default TagVisitsHeader; diff --git a/src/visits/VisitsHeader.js b/src/visits/VisitsHeader.tsx similarity index 68% rename from src/visits/VisitsHeader.js rename to src/visits/VisitsHeader.tsx index fa59beee..51ef2e22 100644 --- a/src/visits/VisitsHeader.js +++ b/src/visits/VisitsHeader.tsx @@ -1,21 +1,19 @@ import { Button, Card } from 'reactstrap'; -import React from 'react'; -import PropTypes from 'prop-types'; +import React, { FC, ReactNode } from 'react'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faArrowLeft } from '@fortawesome/free-solid-svg-icons'; import ShortUrlVisitsCount from '../short-urls/helpers/ShortUrlVisitsCount'; -import { shortUrlType } from '../short-urls/reducers/shortUrlsList'; -import { VisitType } from './types'; +import { ShortUrl } from '../short-urls/data'; +import { Visit } from './types'; -const propTypes = { - visits: PropTypes.arrayOf(VisitType).isRequired, - goBack: PropTypes.func.isRequired, - title: PropTypes.node.isRequired, - children: PropTypes.node, - shortUrl: shortUrlType, -}; +interface VisitsHeaderProps { + visits: Visit[]; + goBack: () => void; + title: ReactNode; + shortUrl?: ShortUrl; +} -const VisitsHeader = ({ visits, goBack, shortUrl, children, title }) => ( +const VisitsHeader: FC = ({ visits, goBack, shortUrl, children, title }) => (

    @@ -39,6 +37,4 @@ const VisitsHeader = ({ visits, goBack, shortUrl, children, title }) => (

    ); -VisitsHeader.propTypes = propTypes; - export default VisitsHeader; diff --git a/src/visits/reducers/shortUrlDetail.ts b/src/visits/reducers/shortUrlDetail.ts index 177380df..07e27711 100644 --- a/src/visits/reducers/shortUrlDetail.ts +++ b/src/visits/reducers/shortUrlDetail.ts @@ -1,6 +1,4 @@ -import PropTypes from 'prop-types'; import { Action, Dispatch } from 'redux'; -import { shortUrlType } from '../../short-urls/reducers/shortUrlsList'; import { ShortUrl } from '../../short-urls/data'; import { buildReducer } from '../../utils/helpers/redux'; import { ShlinkApiClientBuilder } from '../../utils/services/ShlinkApiClientBuilder'; @@ -13,13 +11,6 @@ export const GET_SHORT_URL_DETAIL_ERROR = 'shlink/shortUrlDetail/GET_SHORT_URL_D export const GET_SHORT_URL_DETAIL = 'shlink/shortUrlDetail/GET_SHORT_URL_DETAIL'; /* eslint-enable padding-line-between-statements */ -/** @deprecated Use ShortUrlDetail interface instead */ -export const shortUrlDetailType = PropTypes.shape({ - shortUrl: shortUrlType, - loading: PropTypes.bool, - error: PropTypes.bool, -}); - export interface ShortUrlDetail { shortUrl?: ShortUrl; loading: boolean; diff --git a/src/visits/reducers/shortUrlVisits.ts b/src/visits/reducers/shortUrlVisits.ts index 6a1e837b..5f56121e 100644 --- a/src/visits/reducers/shortUrlVisits.ts +++ b/src/visits/reducers/shortUrlVisits.ts @@ -1,7 +1,6 @@ -import PropTypes from 'prop-types'; import { Action, Dispatch } from 'redux'; import { shortUrlMatches } from '../../short-urls/helpers'; -import { Visit, VisitsInfo, VisitsLoadProgressChangedAction, VisitType } from '../types'; +import { Visit, VisitsInfo, VisitsLoadProgressChangedAction } from '../types'; import { ShortUrlIdentifier } from '../../short-urls/data'; import { buildActionCreator, buildReducer } from '../../utils/helpers/redux'; import { ShlinkApiClientBuilder } from '../../utils/services/ShlinkApiClientBuilder'; @@ -19,17 +18,6 @@ export const GET_SHORT_URL_VISITS_CANCEL = 'shlink/shortUrlVisits/GET_SHORT_URL_ export const GET_SHORT_URL_VISITS_PROGRESS_CHANGED = 'shlink/shortUrlVisits/GET_SHORT_URL_VISITS_PROGRESS_CHANGED'; /* eslint-enable padding-line-between-statements */ -/** @deprecated Use ShortUrlVisits interface instead */ -export const shortUrlVisitsType = PropTypes.shape({ - visits: PropTypes.arrayOf(VisitType), - shortCode: PropTypes.string, - domain: PropTypes.string, - loading: PropTypes.bool, - loadingLarge: PropTypes.bool, - error: PropTypes.bool, - progress: PropTypes.number, -}); - export interface ShortUrlVisits extends VisitsInfo, ShortUrlIdentifier {} interface ShortUrlVisitsAction extends Action, ShortUrlIdentifier { diff --git a/src/visits/reducers/tagVisits.ts b/src/visits/reducers/tagVisits.ts index b0f5746d..76491994 100644 --- a/src/visits/reducers/tagVisits.ts +++ b/src/visits/reducers/tagVisits.ts @@ -1,6 +1,5 @@ -import PropTypes from 'prop-types'; import { Action, Dispatch } from 'redux'; -import { Visit, VisitsInfo, VisitsLoadProgressChangedAction, VisitType } from '../types'; +import { Visit, VisitsInfo, VisitsLoadProgressChangedAction } from '../types'; import { buildActionCreator, buildReducer } from '../../utils/helpers/redux'; import { ShlinkApiClientBuilder } from '../../utils/services/ShlinkApiClientBuilder'; import { GetState } from '../../container/types'; @@ -16,16 +15,6 @@ export const GET_TAG_VISITS_CANCEL = 'shlink/tagVisits/GET_TAG_VISITS_CANCEL'; export const GET_TAG_VISITS_PROGRESS_CHANGED = 'shlink/tagVisits/GET_TAG_VISITS_PROGRESS_CHANGED'; /* eslint-enable padding-line-between-statements */ -/** @deprecated Use TagVisits interface instead */ -export const TagVisitsType = PropTypes.shape({ - visits: PropTypes.arrayOf(VisitType), - tag: PropTypes.string, - loading: PropTypes.bool, - loadingLarge: PropTypes.bool, - error: PropTypes.bool, - progress: PropTypes.number, -}); - export interface TagVisits extends VisitsInfo { tag: string; } diff --git a/test/visits/ShortUrlVisits.test.js b/test/visits/ShortUrlVisits.test.tsx similarity index 53% rename from test/visits/ShortUrlVisits.test.js rename to test/visits/ShortUrlVisits.test.tsx index 89310a55..de93ea28 100644 --- a/test/visits/ShortUrlVisits.test.js +++ b/test/visits/ShortUrlVisits.test.tsx @@ -1,19 +1,24 @@ import React from 'react'; -import { shallow } from 'enzyme'; +import { shallow, ShallowWrapper } from 'enzyme'; import { identity } from 'ramda'; -import createShortUrlVisits from '../../src/visits/ShortUrlVisits'; +import { Mock } from 'ts-mockery'; +import { History, Location } from 'history'; +import { match } from 'react-router'; // eslint-disable-line @typescript-eslint/no-unused-vars +import createShortUrlVisits, { ShortUrlVisitsProps } from '../../src/visits/ShortUrlVisits'; import ShortUrlVisitsHeader from '../../src/visits/ShortUrlVisitsHeader'; +import { ShortUrlVisits as ShortUrlVisitsState } from '../../src/visits/reducers/shortUrlVisits'; +import { ShortUrlDetail } from '../../src/visits/reducers/shortUrlDetail'; describe('', () => { - let wrapper; + let wrapper: ShallowWrapper; const getShortUrlVisitsMock = jest.fn(); - const match = { + const match = Mock.of>({ params: { shortCode: 'abc123' }, - }; - const location = { search: '' }; - const history = { + }); + const location = Mock.of({ search: '' }); + const history = Mock.of({ goBack: jest.fn(), - }; + }); const VisitsStats = jest.fn(); beforeEach(() => { @@ -21,15 +26,15 @@ describe('', () => { wrapper = shallow( ()} getShortUrlDetail={identity} getShortUrlVisits={getShortUrlVisitsMock} match={match} location={location} history={history} - shortUrlVisits={{ loading: true, visits: [] }} - shortUrlDetail={{}} + shortUrlVisits={Mock.of({ loading: true, visits: [] })} + shortUrlDetail={Mock.all()} cancelGetShortUrlVisits={identity} - matchMedia={() => ({ matches: false })} />, ); }); From d4094e66b34877f592c636f2045ae0487c3f3afd Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 5 Sep 2020 08:49:18 +0200 Subject: [PATCH 52/59] Finished TS migration --- src/visits/ShortUrlVisits.tsx | 9 +- src/visits/TagVisits.tsx | 10 +- src/visits/VisitsStats.js | 247 ----------------- src/visits/VisitsStats.tsx | 250 ++++++++++++++++++ .../{VisitsTable.js => VisitsTable.tsx} | 59 +++-- src/visits/helpers/LineChartCard.tsx | 17 +- src/visits/services/VisitsParser.ts | 5 + src/visits/services/provideServices.ts | 6 +- src/visits/types/index.ts | 27 -- test/visits/ShortUrlVisits.test.tsx | 8 +- ....test.js => ShortUrlVisitsHeader.test.tsx} | 19 +- test/visits/TagVisits.test.js | 42 --- test/visits/TagVisits.test.tsx | 47 ++++ ...eader.test.js => TagVisitsHeader.test.tsx} | 13 +- ...tsHeader.test.js => VisitsHeader.test.tsx} | 8 +- ...sitsStats.test.js => VisitsStats.test.tsx} | 32 ++- ...sitsTable.test.js => VisitsTable.test.tsx} | 32 ++- 17 files changed, 417 insertions(+), 414 deletions(-) delete mode 100644 src/visits/VisitsStats.js create mode 100644 src/visits/VisitsStats.tsx rename src/visits/{VisitsTable.js => VisitsTable.tsx} (80%) rename test/visits/{ShortUrlVisitsHeader.test.js => ShortUrlVisitsHeader.test.tsx} (70%) delete mode 100644 test/visits/TagVisits.test.js create mode 100644 test/visits/TagVisits.test.tsx rename test/visits/{TagVisitsHeader.test.js => TagVisitsHeader.test.tsx} (68%) rename test/visits/{VisitsHeader.test.js => VisitsHeader.test.tsx} (78%) rename test/visits/{VisitsStats.test.js => VisitsStats.test.tsx} (83%) rename test/visits/{VisitsTable.test.js => VisitsTable.test.tsx} (79%) diff --git a/src/visits/ShortUrlVisits.tsx b/src/visits/ShortUrlVisits.tsx index e4f8a5c2..2d5f9461 100644 --- a/src/visits/ShortUrlVisits.tsx +++ b/src/visits/ShortUrlVisits.tsx @@ -1,4 +1,4 @@ -import React, { FC, useEffect } from 'react'; +import React, { useEffect } from 'react'; import qs from 'qs'; import { RouteComponentProps } from 'react-router'; import { MercureBoundProps, useMercureTopicBinding } from '../mercure/helpers'; @@ -6,16 +6,17 @@ import { ShlinkVisitsParams } from '../utils/services/types'; import { ShortUrlVisits as ShortUrlVisitsState } from './reducers/shortUrlVisits'; import ShortUrlVisitsHeader from './ShortUrlVisitsHeader'; import { ShortUrlDetail } from './reducers/shortUrlDetail'; +import VisitsStats from './VisitsStats'; export interface ShortUrlVisitsProps extends RouteComponentProps<{ shortCode: string }>, MercureBoundProps { getShortUrlVisits: (shortCode: string, query?: ShlinkVisitsParams) => void; shortUrlVisits: ShortUrlVisitsState; getShortUrlDetail: Function; shortUrlDetail: ShortUrlDetail; - cancelGetShortUrlVisits: Function; + cancelGetShortUrlVisits: () => void; } -const ShortUrlVisits = (VisitsStats: FC) => ({ // TODO Use VisitsStatsProps once available +const ShortUrlVisits = ({ history: { goBack }, match, location: { search }, @@ -32,7 +33,7 @@ const ShortUrlVisits = (VisitsStats: FC) => ({ // TODO Use VisitsStatsProps const { shortCode } = params; const { domain } = qs.parse(search, { ignoreQueryPrefix: true }) as { domain?: string }; - const loadVisits = (dates: Partial) => getShortUrlVisits(shortCode, { ...dates, domain }); + const loadVisits = (params: Partial) => getShortUrlVisits(shortCode, { ...params, domain }); useEffect(() => { getShortUrlDetail(shortCode, domain); diff --git a/src/visits/TagVisits.tsx b/src/visits/TagVisits.tsx index 2efd5c98..b14c3085 100644 --- a/src/visits/TagVisits.tsx +++ b/src/visits/TagVisits.tsx @@ -1,17 +1,19 @@ -import React, { FC } from 'react'; +import React from 'react'; import { RouteComponentProps } from 'react-router'; import { MercureBoundProps, useMercureTopicBinding } from '../mercure/helpers'; import ColorGenerator from '../utils/services/ColorGenerator'; +import { ShlinkVisitsParams } from '../utils/services/types'; import { TagVisits as TagVisitsState } from './reducers/tagVisits'; import TagVisitsHeader from './TagVisitsHeader'; +import VisitsStats from './VisitsStats'; export interface TagVisitsProps extends RouteComponentProps<{ tag: string }>, MercureBoundProps { getTagVisits: (tag: string, query: any) => void; tagVisits: TagVisitsState; - cancelGetTagVisits: Function; + cancelGetTagVisits: () => void; } -const TagVisits = (VisitsStats: FC, colorGenerator: ColorGenerator) => ({ // TODO Use VisitsStatsProps once available +const TagVisits = (colorGenerator: ColorGenerator) => ({ history: { goBack }, match, getTagVisits, @@ -23,7 +25,7 @@ const TagVisits = (VisitsStats: FC, colorGenerator: ColorGenerator) => ({ / }: TagVisitsProps) => { const { params } = match; const { tag } = params; - const loadVisits = (dates: any) => getTagVisits(tag, dates); + const loadVisits = (params: ShlinkVisitsParams) => getTagVisits(tag, params); useMercureTopicBinding(mercureInfo, 'https://shlink.io/new-visit', createNewVisit, loadMercureInfo); diff --git a/src/visits/VisitsStats.js b/src/visits/VisitsStats.js deleted file mode 100644 index 544c36dc..00000000 --- a/src/visits/VisitsStats.js +++ /dev/null @@ -1,247 +0,0 @@ -import { isEmpty, propEq, values } from 'ramda'; -import React, { useState, useEffect, useMemo } from 'react'; -import { Button, Card, Collapse, Progress } from 'reactstrap'; -import PropTypes from 'prop-types'; -import classNames from 'classnames'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { faChevronDown as chevronDown } from '@fortawesome/free-solid-svg-icons'; -import DateRangeRow from '../utils/DateRangeRow'; -import Message from '../utils/Message'; -import { formatDate } from '../utils/helpers/date'; -import { useToggle } from '../utils/helpers/hooks'; -import SortableBarGraph from './helpers/SortableBarGraph'; -import GraphCard from './helpers/GraphCard'; -import LineChartCard from './helpers/LineChartCard'; -import VisitsTable from './VisitsTable'; -import { VisitsInfoType } from './types'; -import OpenMapModalBtn from './helpers/OpenMapModalBtn'; - -const propTypes = { - children: PropTypes.node, - getVisits: PropTypes.func, - visitsInfo: VisitsInfoType, - cancelGetVisits: PropTypes.func, - matchMedia: PropTypes.func, -}; - -const highlightedVisitsToStats = (highlightedVisits, prop) => highlightedVisits.reduce((acc, highlightedVisit) => { - if (!acc[highlightedVisit[prop]]) { - acc[highlightedVisit[prop]] = 0; - } - - acc[highlightedVisit[prop]] += 1; - - return acc; -}, {}); -const format = formatDate(); -let selectedBar; - -const VisitsStats = ({ processStatsFromVisits, normalizeVisits }) => { - const VisitsStatsComp = ({ children, visitsInfo, getVisits, cancelGetVisits, matchMedia = window.matchMedia }) => { - const [ startDate, setStartDate ] = useState(undefined); - const [ endDate, setEndDate ] = useState(undefined); - const [ showTable, toggleTable ] = useToggle(); - const [ tableIsSticky, , setSticky, unsetSticky ] = useToggle(); - const [ highlightedVisits, setHighlightedVisits ] = useState([]); - const [ highlightedLabel, setHighlightedLabel ] = useState(); - const [ isMobileDevice, setIsMobileDevice ] = useState(false); - const determineIsMobileDevice = () => setIsMobileDevice(matchMedia('(max-width: 991px)').matches); - const setSelectedVisits = (selectedVisits) => { - selectedBar = undefined; - setHighlightedVisits(selectedVisits); - }; - const highlightVisitsForProp = (prop) => (value) => { - const newSelectedBar = `${prop}_${value}`; - - if (selectedBar === newSelectedBar) { - setHighlightedVisits([]); - setHighlightedLabel(undefined); - selectedBar = undefined; - } else { - setHighlightedVisits(normalizedVisits.filter(propEq(prop, value))); - setHighlightedLabel(value); - selectedBar = newSelectedBar; - } - }; - - const { visits, loading, loadingLarge, error, progress } = visitsInfo; - const showTableControls = !loading && visits.length > 0; - const normalizedVisits = useMemo(() => normalizeVisits(visits), [ visits ]); - const { os, browsers, referrers, countries, cities, citiesForMap } = useMemo( - () => processStatsFromVisits(normalizedVisits), - [ normalizedVisits ], - ); - const mapLocations = values(citiesForMap); - - useEffect(() => { - determineIsMobileDevice(); - window.addEventListener('resize', determineIsMobileDevice); - - return () => { - cancelGetVisits(); - window.removeEventListener('resize', determineIsMobileDevice); - }; - }, []); - useEffect(() => { - getVisits({ startDate: format(startDate), endDate: format(endDate) }); - }, [ startDate, endDate ]); - - const renderVisitsContent = () => { - if (loadingLarge) { - return ( - - This is going to take a while... :S - - - ); - } - - if (loading) { - return ; - } - - if (error) { - return ( - - An error occurred while loading visits :( - - ); - } - - if (isEmpty(visits)) { - return There are no visits matching current filter :(; - } - - return ( -
    -
    - -
    -
    - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - mapLocations.length > 0 && - - } - sortingItems={{ - name: 'City name', - amount: 'Visits amount', - }} - onClick={highlightVisitsForProp('city')} - /> -
    -
    - ); - }; - - return ( - - {children} - -
    -
    -
    - -
    -
    - {showTableControls && ( - - - - - - - - - )} -
    -
    -
    - - {showTableControls && ( - - - - )} - -
    - {renderVisitsContent()} -
    -
    - ); - }; - - VisitsStatsComp.propTypes = propTypes; - - return VisitsStatsComp; -}; - -export default VisitsStats; diff --git a/src/visits/VisitsStats.tsx b/src/visits/VisitsStats.tsx new file mode 100644 index 00000000..67f5b0ee --- /dev/null +++ b/src/visits/VisitsStats.tsx @@ -0,0 +1,250 @@ +import { isEmpty, propEq, values } from 'ramda'; +import React, { useState, useEffect, useMemo, FC } from 'react'; +import { Button, Card, Collapse, Progress } from 'reactstrap'; +import classNames from 'classnames'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faChevronDown as chevronDown } from '@fortawesome/free-solid-svg-icons'; +import moment from 'moment'; +import DateRangeRow from '../utils/DateRangeRow'; +import Message from '../utils/Message'; +import { formatDate } from '../utils/helpers/date'; +import { useToggle } from '../utils/helpers/hooks'; +import { ShlinkVisitsParams } from '../utils/services/types'; +import SortableBarGraph from './helpers/SortableBarGraph'; +import GraphCard from './helpers/GraphCard'; +import LineChartCard from './helpers/LineChartCard'; +import VisitsTable from './VisitsTable'; +import { NormalizedVisit, Stats, VisitsInfo } from './types'; +import OpenMapModalBtn from './helpers/OpenMapModalBtn'; +import { normalizeVisits, processStatsFromVisits } from './services/VisitsParser'; + +export interface VisitsStatsProps { + matchMedia?: (query: string) => MediaQueryList; + getVisits: (params: Partial) => void; + visitsInfo: VisitsInfo; + cancelGetVisits: () => void; +} + +type HighlightableProps = 'referer' | 'country' | 'city'; + +const highlightedVisitsToStats = ( + highlightedVisits: NormalizedVisit[], + prop: HighlightableProps, +): Stats => highlightedVisits.reduce((acc, highlightedVisit) => { + if (!acc[highlightedVisit[prop]]) { + acc[highlightedVisit[prop]] = 0; + } + + acc[highlightedVisit[prop]] += 1; + + return acc; +}, {}); +const format = formatDate(); +let selectedBar: string | undefined; + +const VisitsStats: FC = ( + { children, visitsInfo, getVisits, cancelGetVisits, matchMedia = window.matchMedia }, +) => { + const [ startDate, setStartDate ] = useState(null); + const [ endDate, setEndDate ] = useState(null); + const [ showTable, toggleTable ] = useToggle(); + const [ tableIsSticky, , setSticky, unsetSticky ] = useToggle(); + const [ highlightedVisits, setHighlightedVisits ] = useState([]); + const [ highlightedLabel, setHighlightedLabel ] = useState(); + const [ isMobileDevice, setIsMobileDevice ] = useState(false); + + const { visits, loading, loadingLarge, error, progress } = visitsInfo; + const showTableControls = !loading && visits.length > 0; + const normalizedVisits = useMemo(() => normalizeVisits(visits), [ visits ]); + const { os, browsers, referrers, countries, cities, citiesForMap } = useMemo( + () => processStatsFromVisits(normalizedVisits), + [ normalizedVisits ], + ); + const mapLocations = values(citiesForMap); + + const determineIsMobileDevice = () => setIsMobileDevice(matchMedia('(max-width: 991px)').matches); + const setSelectedVisits = (selectedVisits: NormalizedVisit[]) => { + selectedBar = undefined; + setHighlightedVisits(selectedVisits); + }; + const highlightVisitsForProp = (prop: HighlightableProps) => (value: string) => { + const newSelectedBar = `${prop}_${value}`; + + if (selectedBar === newSelectedBar) { + setHighlightedVisits([]); + setHighlightedLabel(undefined); + selectedBar = undefined; + } else { + setHighlightedVisits(normalizedVisits.filter(propEq(prop, value))); + setHighlightedLabel(value); + selectedBar = newSelectedBar; + } + }; + + useEffect(() => { + determineIsMobileDevice(); + window.addEventListener('resize', determineIsMobileDevice); + + return () => { + cancelGetVisits(); + window.removeEventListener('resize', determineIsMobileDevice); + }; + }, []); + useEffect(() => { + getVisits({ startDate: format(startDate) ?? undefined, endDate: format(endDate) ?? undefined }); + }, [ startDate, endDate ]); + + const renderVisitsContent = () => { + if (loadingLarge) { + return ( + + This is going to take a while... :S + + + ); + } + + if (loading) { + return ; + } + + if (error) { + return ( + + An error occurred while loading visits :( + + ); + } + + if (isEmpty(visits)) { + return There are no visits matching current filter :(; + } + + return ( +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + + mapLocations.length > 0 && + + } + sortingItems={{ + name: 'City name', + amount: 'Visits amount', + }} + onClick={highlightVisitsForProp('city')} + /> +
    +
    + ); + }; + + return ( + + {children} + +
    +
    +
    + +
    +
    + {showTableControls && ( + + + + + + + + + )} +
    +
    +
    + + {showTableControls && ( + + + + )} + +
    + {renderVisitsContent()} +
    +
    + ); +}; + +export default VisitsStats; diff --git a/src/visits/VisitsTable.js b/src/visits/VisitsTable.tsx similarity index 80% rename from src/visits/VisitsTable.js rename to src/visits/VisitsTable.tsx index 0beb7635..13de3fcf 100644 --- a/src/visits/VisitsTable.js +++ b/src/visits/VisitsTable.tsx @@ -1,5 +1,4 @@ import React, { useEffect, useMemo, useState } from 'react'; -import PropTypes from 'prop-types'; import Moment from 'react-moment'; import classNames from 'classnames'; import { min, splitEvery } from 'ramda'; @@ -11,35 +10,42 @@ import { import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import SimplePaginator from '../common/SimplePaginator'; import SearchField from '../utils/SearchField'; -import { determineOrderDir } from '../utils/utils'; +import { determineOrderDir, OrderDir } from '../utils/utils'; import { prettify } from '../utils/helpers/numbers'; +import { NormalizedVisit } from './types'; import './VisitsTable.scss'; -const NormalizedVisitType = PropTypes.shape({ +interface VisitsTableProps { + visits: NormalizedVisit[]; + selectedVisits?: NormalizedVisit[]; + setSelectedVisits: (visits: NormalizedVisit[]) => void; + isSticky?: boolean; + matchMedia?: (query: string) => MediaQueryList; +} -}); +type OrderableFields = 'date' | 'country' | 'city' | 'browser' | 'os' | 'referer'; -const propTypes = { - visits: PropTypes.arrayOf(NormalizedVisitType).isRequired, - selectedVisits: PropTypes.arrayOf(NormalizedVisitType), - setSelectedVisits: PropTypes.func.isRequired, - isSticky: PropTypes.bool, - matchMedia: PropTypes.func, -}; +interface Order { + field?: OrderableFields; + dir?: OrderDir; +} const PAGE_SIZE = 20; -const visitMatchesSearch = ({ browser, os, referer, country, city }, searchTerm) => +const visitMatchesSearch = ({ browser, os, referer, country, city }: NormalizedVisit, searchTerm: string) => `${browser} ${os} ${referer} ${country} ${city}`.toLowerCase().includes(searchTerm.toLowerCase()); -const searchVisits = (searchTerm, visits) => visits.filter((visit) => visitMatchesSearch(visit, searchTerm)); -const sortVisits = ({ field, dir }, visits) => visits.sort((a, b) => { - const greaterThan = dir === 'ASC' ? 1 : -1; - const smallerThan = dir === 'ASC' ? -1 : 1; +const searchVisits = (searchTerm: string, visits: NormalizedVisit[]) => + visits.filter((visit) => visitMatchesSearch(visit, searchTerm)); +const sortVisits = ({ field, dir }: Order, visits: NormalizedVisit[]) => !field || !dir ? visits : visits.sort( + (a, b) => { + const greaterThan = dir === 'ASC' ? 1 : -1; + const smallerThan = dir === 'ASC' ? -1 : 1; - return a[field] > b[field] ? greaterThan : smallerThan; -}); -const calculateVisits = (allVisits, searchTerm, order) => { + return a[field] > b[field] ? greaterThan : smallerThan; + }, +); +const calculateVisits = (allVisits: NormalizedVisit[], searchTerm: string | undefined, order: Order) => { const filteredVisits = searchTerm ? searchVisits(searchTerm, allVisits) : [ ...allVisits ]; - const sortedVisits = order.dir ? sortVisits(order, filteredVisits) : filteredVisits; + const sortedVisits = sortVisits(order, filteredVisits); const total = sortedVisits.length; const visitsGroups = splitEvery(PAGE_SIZE, sortedVisits); @@ -52,23 +58,24 @@ const VisitsTable = ({ setSelectedVisits, isSticky = false, matchMedia = window.matchMedia, -}) => { +}: VisitsTableProps) => { const headerCellsClass = classNames('visits-table__header-cell', { 'visits-table__sticky': isSticky, }); const matchMobile = () => matchMedia('(max-width: 767px)').matches; const [ isMobileDevice, setIsMobileDevice ] = useState(matchMobile()); - const [ searchTerm, setSearchTerm ] = useState(undefined); - const [ order, setOrder ] = useState({ field: undefined, dir: undefined }); + const [ searchTerm, setSearchTerm ] = useState(undefined); + const [ order, setOrder ] = useState({ field: undefined, dir: undefined }); const resultSet = useMemo(() => calculateVisits(visits, searchTerm, order), [ searchTerm, order ]); const [ page, setPage ] = useState(1); const end = page * PAGE_SIZE; const start = end - PAGE_SIZE; - const orderByColumn = (field) => () => setOrder({ field, dir: determineOrderDir(field, order.field, order.dir) }); - const renderOrderIcon = (field) => order.dir && order.field === field && ( + const orderByColumn = (field: OrderableFields) => + () => setOrder({ field, dir: determineOrderDir(field, order.field, order.dir) }); + const renderOrderIcon = (field: OrderableFields) => order.dir && order.field === field && ( { return matcher() ?? 'monthly'; }; -const groupVisitsByStep = (step: Step, visits: Visit[]): Stats => visits.reduce((acc, visit) => { - const key = STEP_TO_DATE_FORMAT[step](visit.date); +const groupVisitsByStep = (step: Step, visits: (Visit | NormalizedVisit)[]): Stats => visits.reduce( + (acc, visit) => { + const key = STEP_TO_DATE_FORMAT[step](visit.date); - acc[key] = acc[key] ? acc[key] + 1 : 1; + acc[key] = acc[key] ? acc[key] + 1 : 1; - return acc; -}, {}); + return acc; + }, + {}, +); const generateLabels = (step: Step, visits: Visit[]): string[] => { const unit = STEP_TO_DATE_UNIT_MAP[step]; diff --git a/src/visits/services/VisitsParser.ts b/src/visits/services/VisitsParser.ts index c905d0b1..3956788d 100644 --- a/src/visits/services/VisitsParser.ts +++ b/src/visits/services/VisitsParser.ts @@ -77,3 +77,8 @@ export const normalizeVisits = map(({ userAgent, date, referer, visitLocation }: latitude: visitLocation?.latitude, longitude: visitLocation?.longitude, })); + +export interface VisitsParser { + processStatsFromVisits: (normalizedVisits: NormalizedVisit[]) => VisitsStats; + normalizeVisits: (visits: Visit[]) => NormalizedVisit[]; +} diff --git a/src/visits/services/provideServices.ts b/src/visits/services/provideServices.ts index 5880193c..80372ebc 100644 --- a/src/visits/services/provideServices.ts +++ b/src/visits/services/provideServices.ts @@ -3,7 +3,6 @@ import ShortUrlVisits from '../ShortUrlVisits'; import { cancelGetShortUrlVisits, getShortUrlVisits } from '../reducers/shortUrlVisits'; import { getShortUrlDetail } from '../reducers/shortUrlDetail'; import MapModal from '../helpers/MapModal'; -import VisitsStats from '../VisitsStats'; import { createNewVisit } from '../reducers/visitCreation'; import { cancelGetTagVisits, getTagVisits } from '../reducers/tagVisits'; import TagVisits from '../TagVisits'; @@ -13,13 +12,12 @@ import * as visitsParser from './VisitsParser'; const provideServices = (bottle: Bottle, connect: ConnectDecorator) => { // Components bottle.serviceFactory('MapModal', () => MapModal); - bottle.serviceFactory('VisitsStats', VisitsStats, 'VisitsParser'); - bottle.serviceFactory('ShortUrlVisits', ShortUrlVisits, 'VisitsStats'); + bottle.serviceFactory('ShortUrlVisits', () => ShortUrlVisits); bottle.decorator('ShortUrlVisits', connect( [ 'shortUrlVisits', 'shortUrlDetail', 'mercureInfo' ], [ 'getShortUrlVisits', 'getShortUrlDetail', 'cancelGetShortUrlVisits', 'createNewVisit', 'loadMercureInfo' ], )); - bottle.serviceFactory('TagVisits', TagVisits, 'VisitsStats', 'ColorGenerator'); + bottle.serviceFactory('TagVisits', TagVisits, 'ColorGenerator'); bottle.decorator('TagVisits', connect( [ 'tagVisits', 'mercureInfo' ], [ 'getTagVisits', 'cancelGetTagVisits', 'createNewVisit', 'loadMercureInfo' ], diff --git a/src/visits/types/index.ts b/src/visits/types/index.ts index 1400ee77..b54c19f7 100644 --- a/src/visits/types/index.ts +++ b/src/visits/types/index.ts @@ -1,33 +1,6 @@ -import PropTypes from 'prop-types'; import { Action } from 'redux'; import { ShortUrl } from '../../short-urls/data'; -/** @deprecated Use Visit interface instead */ -export const VisitType = PropTypes.shape({ - referer: PropTypes.string, - date: PropTypes.string, - userAgent: PropTypes.string, - visitLocation: PropTypes.shape({ - countryCode: PropTypes.string, - countryName: PropTypes.string, - regionName: PropTypes.string, - cityName: PropTypes.string, - latitude: PropTypes.number, - longitude: PropTypes.number, - timezone: PropTypes.string, - isEmpty: PropTypes.bool, - }), -}); - -/** @deprecated Use VisitsInfo interface instead */ -export const VisitsInfoType = PropTypes.shape({ - visits: PropTypes.arrayOf(VisitType), - loading: PropTypes.bool, - loadingLarge: PropTypes.bool, - error: PropTypes.bool, - progress: PropTypes.number, -}); - export interface VisitsInfo { visits: Visit[]; loading: boolean; diff --git a/test/visits/ShortUrlVisits.test.tsx b/test/visits/ShortUrlVisits.test.tsx index de93ea28..f3516b4e 100644 --- a/test/visits/ShortUrlVisits.test.tsx +++ b/test/visits/ShortUrlVisits.test.tsx @@ -4,10 +4,11 @@ import { identity } from 'ramda'; import { Mock } from 'ts-mockery'; import { History, Location } from 'history'; import { match } from 'react-router'; // eslint-disable-line @typescript-eslint/no-unused-vars -import createShortUrlVisits, { ShortUrlVisitsProps } from '../../src/visits/ShortUrlVisits'; +import ShortUrlVisits, { ShortUrlVisitsProps } from '../../src/visits/ShortUrlVisits'; import ShortUrlVisitsHeader from '../../src/visits/ShortUrlVisitsHeader'; import { ShortUrlVisits as ShortUrlVisitsState } from '../../src/visits/reducers/shortUrlVisits'; import { ShortUrlDetail } from '../../src/visits/reducers/shortUrlDetail'; +import VisitsStats from '../../src/visits/VisitsStats'; describe('', () => { let wrapper: ShallowWrapper; @@ -19,11 +20,8 @@ describe('', () => { const history = Mock.of({ goBack: jest.fn(), }); - const VisitsStats = jest.fn(); beforeEach(() => { - const ShortUrlVisits = createShortUrlVisits(VisitsStats); - wrapper = shallow( ()} @@ -34,7 +32,7 @@ describe('', () => { history={history} shortUrlVisits={Mock.of({ loading: true, visits: [] })} shortUrlDetail={Mock.all()} - cancelGetShortUrlVisits={identity} + cancelGetShortUrlVisits={() => {}} />, ); }); diff --git a/test/visits/ShortUrlVisitsHeader.test.js b/test/visits/ShortUrlVisitsHeader.test.tsx similarity index 70% rename from test/visits/ShortUrlVisitsHeader.test.js rename to test/visits/ShortUrlVisitsHeader.test.tsx index fef9c2ca..7eae6954 100644 --- a/test/visits/ShortUrlVisitsHeader.test.js +++ b/test/visits/ShortUrlVisitsHeader.test.tsx @@ -1,22 +1,25 @@ import React from 'react'; -import { shallow } from 'enzyme'; +import { shallow, ShallowWrapper } from 'enzyme'; import Moment from 'react-moment'; import { ExternalLink } from 'react-external-link'; +import { Mock } from 'ts-mockery'; import ShortUrlVisitsHeader from '../../src/visits/ShortUrlVisitsHeader'; +import { ShortUrlDetail } from '../../src/visits/reducers/shortUrlDetail'; +import { ShortUrlVisits } from '../../src/visits/reducers/shortUrlVisits'; describe('', () => { - let wrapper; - const shortUrlDetail = { + let wrapper: ShallowWrapper; + const shortUrlDetail = Mock.of({ shortUrl: { shortUrl: 'https://doma.in/abc123', longUrl: 'https://foo.bar/bar/foo', dateCreated: '2018-01-01T10:00:00+01:00', }, loading: false, - }; - const shortUrlVisits = { + }); + const shortUrlVisits = Mock.of({ visits: [{}, {}, {}], - }; + }); const goBack = jest.fn(); beforeEach(() => { @@ -29,12 +32,12 @@ describe('', () => { it('shows when the URL was created', () => { const moment = wrapper.find(Moment).first(); - expect(moment.prop('children')).toEqual(shortUrlDetail.shortUrl.dateCreated); + expect(moment.prop('children')).toEqual(shortUrlDetail.shortUrl?.dateCreated); }); it('shows the long URL', () => { const longUrlLink = wrapper.find(ExternalLink).last(); - expect(longUrlLink.prop('href')).toEqual(shortUrlDetail.shortUrl.longUrl); + expect(longUrlLink.prop('href')).toEqual(shortUrlDetail.shortUrl?.longUrl); }); }); diff --git a/test/visits/TagVisits.test.js b/test/visits/TagVisits.test.js deleted file mode 100644 index 023b5caf..00000000 --- a/test/visits/TagVisits.test.js +++ /dev/null @@ -1,42 +0,0 @@ -import React from 'react'; -import { shallow } from 'enzyme'; -import { identity } from 'ramda'; -import createTagVisits from '../../src/visits/TagVisits'; -import TagVisitsHeader from '../../src/visits/TagVisitsHeader'; - -describe('', () => { - let wrapper; - const getTagVisitsMock = jest.fn(); - const match = { - params: { tag: 'foo' }, - }; - const history = { - goBack: jest.fn(), - }; - const VisitsStats = jest.fn(); - - beforeEach(() => { - const TagVisits = createTagVisits(VisitsStats, {}); - - wrapper = shallow( - , - ); - }); - - afterEach(() => wrapper.unmount()); - afterEach(jest.resetAllMocks); - - it('renders visit stats and visits header', () => { - const visitStats = wrapper.find(VisitsStats); - const visitHeader = wrapper.find(TagVisitsHeader); - - expect(visitStats).toHaveLength(1); - expect(visitHeader).toHaveLength(1); - }); -}); diff --git a/test/visits/TagVisits.test.tsx b/test/visits/TagVisits.test.tsx new file mode 100644 index 00000000..6c8e8b1c --- /dev/null +++ b/test/visits/TagVisits.test.tsx @@ -0,0 +1,47 @@ +import React from 'react'; +import { shallow, ShallowWrapper } from 'enzyme'; +import { Mock } from 'ts-mockery'; +import { History } from 'history'; +import { match } from 'react-router'; // eslint-disable-line @typescript-eslint/no-unused-vars +import createTagVisits, { TagVisitsProps } from '../../src/visits/TagVisits'; +import TagVisitsHeader from '../../src/visits/TagVisitsHeader'; +import ColorGenerator from '../../src/utils/services/ColorGenerator'; +import { TagVisits as TagVisitsStats } from '../../src/visits/reducers/tagVisits'; +import VisitsStats from '../../src/visits/VisitsStats'; + +describe('', () => { + let wrapper: ShallowWrapper; + const getTagVisitsMock = jest.fn(); + const match = Mock.of>({ + params: { tag: 'foo' }, + }); + const history = Mock.of({ + goBack: jest.fn(), + }); + + beforeEach(() => { + const TagVisits = createTagVisits(Mock.of()); + + wrapper = shallow( + ()} + getTagVisits={getTagVisitsMock} + match={match} + history={history} + tagVisits={Mock.of({ loading: true, visits: [] })} + cancelGetTagVisits={() => {}} + />, + ); + }); + + afterEach(() => wrapper.unmount()); + afterEach(jest.resetAllMocks); + + it('renders visit stats and visits header', () => { + const visitStats = wrapper.find(VisitsStats); + const visitHeader = wrapper.find(TagVisitsHeader); + + expect(visitStats).toHaveLength(1); + expect(visitHeader).toHaveLength(1); + }); +}); diff --git a/test/visits/TagVisitsHeader.test.js b/test/visits/TagVisitsHeader.test.tsx similarity index 68% rename from test/visits/TagVisitsHeader.test.js rename to test/visits/TagVisitsHeader.test.tsx index 15a8defc..ce5918c9 100644 --- a/test/visits/TagVisitsHeader.test.js +++ b/test/visits/TagVisitsHeader.test.tsx @@ -1,19 +1,22 @@ import React from 'react'; -import { shallow } from 'enzyme'; +import { shallow, ShallowWrapper } from 'enzyme'; +import { Mock } from 'ts-mockery'; import Tag from '../../src/tags/helpers/Tag'; import TagVisitsHeader from '../../src/visits/TagVisitsHeader'; +import { TagVisits } from '../../src/visits/reducers/tagVisits'; +import ColorGenerator from '../../src/utils/services/ColorGenerator'; describe('', () => { - let wrapper; - const tagVisits = { + let wrapper: ShallowWrapper; + const tagVisits = Mock.of({ tag: 'foo', visits: [{}, {}, {}], - }; + }); const goBack = jest.fn(); beforeEach(() => { wrapper = shallow( - , + ()} />, ); }); afterEach(() => wrapper.unmount()); diff --git a/test/visits/VisitsHeader.test.js b/test/visits/VisitsHeader.test.tsx similarity index 78% rename from test/visits/VisitsHeader.test.js rename to test/visits/VisitsHeader.test.tsx index a23cf5ee..c76274f9 100644 --- a/test/visits/VisitsHeader.test.js +++ b/test/visits/VisitsHeader.test.tsx @@ -1,10 +1,12 @@ import React from 'react'; -import { shallow } from 'enzyme'; +import { shallow, ShallowWrapper } from 'enzyme'; +import { Mock } from 'ts-mockery'; import VisitsHeader from '../../src/visits/VisitsHeader'; +import { Visit } from '../../src/visits/types'; describe('', () => { - let wrapper; - const visits = [{}, {}, {}]; + let wrapper: ShallowWrapper; + const visits = [ Mock.all(), Mock.all(), Mock.all() ]; const title = 'My header title'; const goBack = jest.fn(); diff --git a/test/visits/VisitsStats.test.js b/test/visits/VisitsStats.test.tsx similarity index 83% rename from test/visits/VisitsStats.test.js rename to test/visits/VisitsStats.test.tsx index b4670d22..addb780d 100644 --- a/test/visits/VisitsStats.test.js +++ b/test/visits/VisitsStats.test.tsx @@ -1,36 +1,34 @@ import React from 'react'; -import { shallow } from 'enzyme'; -import { identity } from 'ramda'; +import { shallow, ShallowWrapper } from 'enzyme'; import { Card, Progress } from 'reactstrap'; -import createVisitStats from '../../src/visits/VisitsStats'; +import { Mock } from 'ts-mockery'; +import VisitStats from '../../src/visits/VisitsStats'; import Message from '../../src/utils/Message'; import GraphCard from '../../src/visits/helpers/GraphCard'; import SortableBarGraph from '../../src/visits/helpers/SortableBarGraph'; import DateRangeRow from '../../src/utils/DateRangeRow'; +import { Visit, VisitsInfo } from '../../src/visits/types'; describe('', () => { - let wrapper; - const processStatsFromVisits = () => ( - { os: {}, browsers: {}, referrers: {}, countries: {}, cities: {}, citiesForMap: {} } - ); + const visits = [ Mock.all(), Mock.all(), Mock.all() ]; + + let wrapper: ShallowWrapper; const getVisitsMock = jest.fn(); - const createComponent = (visitsInfo) => { - const VisitStats = createVisitStats({ processStatsFromVisits, normalizeVisits: identity }, () => ''); - + const createComponent = (visitsInfo: Partial) => { wrapper = shallow( ({ matches: false })} + visitsInfo={Mock.of(visitsInfo)} + cancelGetVisits={() => {}} + matchMedia={() => Mock.of({ matches: false })} />, ); return wrapper; }; - afterEach(() => wrapper && wrapper.unmount()); + afterEach(() => wrapper?.unmount()); it('renders a preloader when visits are loading', () => { const wrapper = createComponent({ loading: true, visits: [] }); @@ -70,7 +68,7 @@ describe('', () => { }); it('renders all graphics when visits are properly loaded', () => { - const wrapper = createComponent({ loading: false, error: false, visits: [{}, {}, {}] }); + const wrapper = createComponent({ loading: false, error: false, visits }); const graphs = wrapper.find(GraphCard); const sortableBarGraphs = wrapper.find(SortableBarGraph); @@ -78,7 +76,7 @@ describe('', () => { }); it('reloads visits when selected dates change', () => { - const wrapper = createComponent({ loading: false, error: false, visits: [{}, {}, {}] }); + const wrapper = createComponent({ loading: false, error: false, visits }); const dateRange = wrapper.find(DateRangeRow); dateRange.simulate('startDateChange', '2016-01-01T00:00:00+01:00'); @@ -90,7 +88,7 @@ describe('', () => { }); it('holds the map button content generator on cities graph extraHeaderContent', () => { - const wrapper = createComponent({ loading: false, error: false, visits: [{}, {}, {}] }); + const wrapper = createComponent({ loading: false, error: false, visits }); const citiesGraph = wrapper.find(SortableBarGraph).find('[title="Cities"]'); const extraHeaderContent = citiesGraph.prop('extraHeaderContent'); diff --git a/test/visits/VisitsTable.test.js b/test/visits/VisitsTable.test.tsx similarity index 79% rename from test/visits/VisitsTable.test.js rename to test/visits/VisitsTable.test.tsx index fdee519d..d117f53f 100644 --- a/test/visits/VisitsTable.test.js +++ b/test/visits/VisitsTable.test.tsx @@ -1,15 +1,17 @@ import React from 'react'; -import { shallow } from 'enzyme'; +import { shallow, ShallowWrapper } from 'enzyme'; +import { Mock } from 'ts-mockery'; import VisitsTable from '../../src/visits/VisitsTable'; import { rangeOf } from '../../src/utils/utils'; import SimplePaginator from '../../src/common/SimplePaginator'; import SearchField from '../../src/utils/SearchField'; +import { NormalizedVisit } from '../../src/visits/types'; describe('', () => { - const matchMedia = () => ({ matches: false }); + const matchMedia = () => Mock.of({ matches: false }); const setSelectedVisits = jest.fn(); - let wrapper; - const createWrapper = (visits, selectedVisits = []) => { + let wrapper: ShallowWrapper; + const createWrapper = (visits: NormalizedVisit[], selectedVisits: NormalizedVisit[] = []) => { wrapper = shallow( ', () => { return wrapper; }; - afterEach(() => { - jest.resetAllMocks(); - wrapper && wrapper.unmount(); - }); + afterEach(jest.resetAllMocks); + afterEach(() => wrapper?.unmount()); it('renders columns as expected', () => { const wrapper = createWrapper([]); @@ -55,7 +55,9 @@ describe('', () => { [ 60, 3 ], [ 115, 6 ], ])('renders the expected amount of pages', (visitsCount, expectedAmountOfPages) => { - const wrapper = createWrapper(rangeOf(visitsCount, () => ({ browser: '', date: '', referer: '' }))); + const wrapper = createWrapper( + rangeOf(visitsCount, () => Mock.of({ browser: '', date: '', referer: '' })), + ); const tr = wrapper.find('tbody').find('tr'); const paginator = wrapper.find(SimplePaginator); @@ -66,7 +68,9 @@ describe('', () => { it.each( rangeOf(20, (value) => [ value ]), )('does not render footer when there is only one page to render', (visitsCount) => { - const wrapper = createWrapper(rangeOf(visitsCount, () => ({ browser: '', date: '', referer: '' }))); + const wrapper = createWrapper( + rangeOf(visitsCount, () => Mock.of({ browser: '', date: '', referer: '' })), + ); const tr = wrapper.find('tbody').find('tr'); const paginator = wrapper.find(SimplePaginator); @@ -75,7 +79,7 @@ describe('', () => { }); it('selected rows are highlighted', () => { - const visits = rangeOf(10, () => ({ browser: '', date: '', referer: '' })); + const visits = rangeOf(10, () => Mock.of({ browser: '', date: '', referer: '' })); const wrapper = createWrapper( visits, [ visits[1], visits[2] ], @@ -98,7 +102,7 @@ describe('', () => { }); it('orders visits when column is clicked', () => { - const wrapper = createWrapper(rangeOf(9, (index) => ({ + const wrapper = createWrapper(rangeOf(9, (index) => Mock.of({ browser: '', date: `${9 - index}`, referer: `${index}`, @@ -118,8 +122,8 @@ describe('', () => { it('filters list when writing in search box', () => { const wrapper = createWrapper([ - ...rangeOf(7, () => ({ browser: 'aaa', date: 'aaa', referer: 'aaa' })), - ...rangeOf(2, () => ({ browser: 'bbb', date: 'bbb', referer: 'bbb' })), + ...rangeOf(7, () => Mock.of({ browser: 'aaa', date: 'aaa', referer: 'aaa' })), + ...rangeOf(2, () => Mock.of({ browser: 'bbb', date: 'bbb', referer: 'bbb' })), ]); const searchField = wrapper.find(SearchField); From 3cec2efbbde8f78fb817898fb6adf0fc705b4eae Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 5 Sep 2020 08:57:50 +0200 Subject: [PATCH 53/59] Removed no longer used dependencies --- package-lock.json | 43 --------------------------- package.json | 2 -- src/utils/services/ShlinkApiClient.ts | 6 ++-- src/utils/services/types.ts | 6 ++-- src/visits/helpers/DefaultChart.tsx | 2 -- 5 files changed, 6 insertions(+), 53 deletions(-) diff --git a/package-lock.json b/package-lock.json index 17c76437..62f0317c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14441,11 +14441,6 @@ "object.assign": "^4.1.0" } }, - "just-curry-it": { - "version": "3.1.0", - "resolved": "https://registry.yarnpkg.com/just-curry-it/-/just-curry-it-3.1.0.tgz", - "integrity": "sha1-q1na7TCKWLhHraFm7dCi1Adm+8U=" - }, "killable": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz", @@ -18717,11 +18712,6 @@ "strip-indent": "^1.0.1" } }, - "reduce-reducers": { - "version": "0.4.3", - "resolved": "https://registry.yarnpkg.com/reduce-reducers/-/reduce-reducers-0.4.3.tgz", - "integrity": "sha1-jgUmGIAc2PwnFLSRWtqok3621mw=" - }, "redux": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/redux/-/redux-4.0.4.tgz", @@ -18731,18 +18721,6 @@ "symbol-observable": "^1.2.0" } }, - "redux-actions": { - "version": "2.6.5", - "resolved": "https://registry.yarnpkg.com/redux-actions/-/redux-actions-2.6.5.tgz", - "integrity": "sha1-vcpUh2jumYMqY5EMJ23vheghon4=", - "requires": { - "invariant": "^2.2.4", - "just-curry-it": "^3.1.0", - "loose-envify": "^1.4.0", - "reduce-reducers": "^0.4.3", - "to-camel-case": "^1.0.0" - } - }, "redux-localstorage-simple": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/redux-localstorage-simple/-/redux-localstorage-simple-2.2.0.tgz", @@ -22007,25 +21985,12 @@ "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=", "dev": true }, - "to-camel-case": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/to-camel-case/-/to-camel-case-1.0.0.tgz", - "integrity": "sha1-GlYFSy+daWKYzmamCJcyK29CPkY=", - "requires": { - "to-space-case": "^1.0.0" - } - }, "to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", "dev": true }, - "to-no-case": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/to-no-case/-/to-no-case-1.0.2.tgz", - "integrity": "sha1-xyKQcWTvaxeBMsjmmTAhLRtKoWo=" - }, "to-object-path": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", @@ -22088,14 +22053,6 @@ "repeat-string": "^1.6.1" } }, - "to-space-case": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/to-space-case/-/to-space-case-1.0.0.tgz", - "integrity": "sha1-sFLar7Gysp3HcM6gFj5ewOvJ/Bc=", - "requires": { - "to-no-case": "^1.0.0" - } - }, "toggle-selection": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz", diff --git a/package.json b/package.json index 513d0a43..5dfe27e9 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,6 @@ "leaflet": "^1.5.1", "moment": "^2.24.0", "promise": "^8.0.3", - "prop-types": "^15.7.2", "qs": "^6.9.0", "ramda": "^0.26.1", "react": "^16.13.1", @@ -61,7 +60,6 @@ "react-tagsinput": "^3.19.0", "reactstrap": "^8.0.1", "redux": "^4.0.4", - "redux-actions": "^2.6.5", "redux-localstorage-simple": "^2.2.0", "redux-thunk": "^2.3.0", "uuid": "^3.3.3" diff --git a/src/utils/services/ShlinkApiClient.ts b/src/utils/services/ShlinkApiClient.ts index 84b5bc9e..a4db10ad 100644 --- a/src/utils/services/ShlinkApiClient.ts +++ b/src/utils/services/ShlinkApiClient.ts @@ -2,7 +2,7 @@ import qs from 'qs'; import { isEmpty, isNil, reject } from 'ramda'; import { AxiosInstance, AxiosResponse, Method } from 'axios'; import { ShortUrlsListParams } from '../../short-urls/reducers/shortUrlsListParams'; -import { ShortUrl } from '../../short-urls/data'; +import { ShortUrl, ShortUrlData } from '../../short-urls/data'; import { OptionalString } from '../utils'; import { ShlinkHealth, @@ -33,8 +33,8 @@ export default class ShlinkApiClient { this.performRequest<{ shortUrls: ShlinkShortUrlsResponse }>('/short-urls', 'GET', params) .then(({ data }) => data.shortUrls); - public readonly createShortUrl = async (options: any): Promise => { // TODO CreateShortUrl interface - const filteredOptions = reject((value) => isEmpty(value) || isNil(value), options); + public readonly createShortUrl = async (options: ShortUrlData): Promise => { + const filteredOptions = reject((value) => isEmpty(value) || isNil(value), options as any); return this.performRequest('/short-urls', 'POST', {}, filteredOptions) .then((resp) => resp.data); diff --git a/src/utils/services/types.ts b/src/utils/services/types.ts index 8c4c53d3..9c10ce7d 100644 --- a/src/utils/services/types.ts +++ b/src/utils/services/types.ts @@ -25,12 +25,12 @@ interface ShlinkTagsStats { export interface ShlinkTags { tags: string[]; - stats?: ShlinkTagsStats[]; // TODO Is only optional in old versions + stats?: ShlinkTagsStats[]; // Is only optional in old Shlink versions } export interface ShlinkTagsResponse { data: string[]; - stats?: ShlinkTagsStats[]; // TODO Is only optional in old versions + stats?: ShlinkTagsStats[]; // Is only optional in old Shlink versions } export interface ShlinkPaginator { @@ -40,7 +40,7 @@ export interface ShlinkPaginator { export interface ShlinkVisits { data: Visit[]; - pagination?: ShlinkPaginator; // TODO Is only optional in old versions + pagination?: ShlinkPaginator; // Is only optional in old Shlink versions } export interface ShlinkVisitsParams { diff --git a/src/visits/helpers/DefaultChart.tsx b/src/visits/helpers/DefaultChart.tsx index 1fa9a3bc..2d8f1094 100644 --- a/src/visits/helpers/DefaultChart.tsx +++ b/src/visits/helpers/DefaultChart.tsx @@ -68,7 +68,6 @@ const determineHeight = (isBarChart: boolean, labels: string[]): number | undefi return isBarChart && labels.length > 20 ? labels.length * 8 : undefined; }; -/* eslint-disable react/prop-types */ const renderPieChartLegend = ({ config }: Chart) => { const { labels = [], datasets = [] } = config.data ?? {}; const { defaultColor } = config.options ?? {} as any; @@ -88,7 +87,6 @@ const renderPieChartLegend = ({ config }: Chart) => {
); }; -/* eslint-enable react/prop-types */ const chartElementAtEvent = (onClick?: (label: string) => void) => ([ chart ]: [{ _index: number; _chart: Chart }]) => { if (!onClick || !chart) { From 7e907ba9b68e65a16d5ff2a26d6a177ec78f822d Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 5 Sep 2020 09:03:40 +0200 Subject: [PATCH 54/59] Updated changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fe9d3674..1442f3fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), #### Changed -* *Nothing* +* [#40](https://github.com/shlinkio/shlink-web-client/issues/40) Migrated project to TypeScript. #### Deprecated From f33d1fca39cf511e273b3e6d8d53b4a83457a894 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 6 Sep 2020 09:18:02 +0200 Subject: [PATCH 55/59] Updated pattern for code coverage collection --- jest.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jest.config.js b/jest.config.js index 1460edba..d1896e6f 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,7 +1,7 @@ module.exports = { coverageDirectory: '/coverage', collectCoverageFrom: [ - 'src/**/*.{js,jsx,ts,tsx}', + 'src/**/*.{js,ts,tsx}', '!src/registerServiceWorker.js', '!src/index.ts', '!src/reducers/index.ts', From dcdee8b3080f92965b74a70099d8a01ce086d5d2 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 6 Sep 2020 09:32:16 +0200 Subject: [PATCH 56/59] Simplified eslint config --- .eslintrc | 77 ++++++------------------------- config/env.js | 1 + config/paths.js | 1 + config/webpack.config.js | 4 +- config/webpackDevServer.config.js | 1 + scripts/build.js | 6 +-- scripts/start.js | 2 +- src/registerServiceWorker.js | 4 +- 8 files changed, 26 insertions(+), 70 deletions(-) diff --git a/.eslintrc b/.eslintrc index 84103e15..4d3db727 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,4 +1,7 @@ { + "extends": [ + "@shlinkio/js-coding-standard" + ], "plugins": ["jest"], "env": { "jest/globals": true @@ -11,67 +14,15 @@ "process": true, "setImmediate": true }, - "overrides": [ - { - "files": ["**/*.js"], - "extends": [ - "adidas-env/browser", - "adidas-env/module", - "adidas-env/node", - "adidas-es9", - "adidas-babel", - "adidas-react" - ], - "settings": { - "react": { - "version": "detect" - } - }, - "rules": { - "max-len": ["error", { - "code": 120, - "ignoreStrings": true, - "ignoreTemplateLiterals": true, - "ignoreComments": true - }], - "no-mixed-operators": "off", - "comma-dangle": ["error", "always-multiline"], - "no-invalid-this": "off", - "no-inline-comments": "off", - "no-console": "warn", - "template-curly-spacing": ["error", "never"], - "no-warning-comments": "off", - "no-undefined": "off", - "indent": ["error", 2, {"SwitchCase": 1}], - "no-empty-function": "off", - "lines-around-comment": "off", - "no-magic-numbers": "off", - "react/no-array-index-key": "off", - "react/no-did-update-set-state": "off", - "react/display-name": "off", - "react/jsx-curly-spacing": ["error", "never"], - "react/jsx-indent-props": ["error", 2], - "react/jsx-first-prop-new-line": ["error", "multiline-multiprop"], - "react/jsx-closing-bracket-location": ["error", "tag-aligned"], - "react/jsx-filename-extension": ["error", {"extensions": [".js", ".jsx"]}] - } - }, - { - "files": ["**/*.ts", "**/*.tsx"], - "extends": [ - "@shlinkio/js-coding-standard" - ], - "rules": { - "max-len": ["error", { - "code": 120, - "ignoreStrings": true, - "ignoreTemplateLiterals": true, - "ignoreComments": true - }], - "no-mixed-operators": "off", - "react/display-name": "off", - "@typescript-eslint/require-array-sort-compare": "off" - } - } - ] + "rules": { + "max-len": ["error", { + "code": 120, + "ignoreStrings": true, + "ignoreTemplateLiterals": true, + "ignoreComments": true + }], + "no-mixed-operators": "off", + "react/display-name": "off", + "@typescript-eslint/require-array-sort-compare": "off" + } } diff --git a/config/env.js b/config/env.js index b57375d1..c4ca2b01 100644 --- a/config/env.js +++ b/config/env.js @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/prefer-nullish-coalescing, @typescript-eslint/promise-function-async, @typescript-eslint/prefer-optional-chain */ const fs = require('fs'); const path = require('path'); diff --git a/config/paths.js b/config/paths.js index b34d9058..0728ca85 100644 --- a/config/paths.js +++ b/config/paths.js @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/prefer-nullish-coalescing, @typescript-eslint/promise-function-async, @typescript-eslint/prefer-optional-chain */ const path = require('path'); const fs = require('fs'); diff --git a/config/webpack.config.js b/config/webpack.config.js index 5134e5a2..a182eced 100644 --- a/config/webpack.config.js +++ b/config/webpack.config.js @@ -227,7 +227,7 @@ module.exports = (webpackEnv) => { // Turned on because emoji and regex is not minified properly using default // https://github.com/facebook/create-react-app/issues/2488 - ascii_only: true, + ascii_only: true, // eslint-disable-line @typescript-eslint/camelcase }, }, @@ -668,7 +668,7 @@ module.exports = (webpackEnv) => { fs: 'empty', net: 'empty', tls: 'empty', - child_process: 'empty', + child_process: 'empty', // eslint-disable-line @typescript-eslint/camelcase }, // Turn off performance processing because we utilize diff --git a/config/webpackDevServer.config.js b/config/webpackDevServer.config.js index 6dc74982..61ca020b 100644 --- a/config/webpackDevServer.config.js +++ b/config/webpackDevServer.config.js @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/prefer-nullish-coalescing */ const fs = require('fs'); const errorOverlayMiddleware = require('react-dev-utils/errorOverlayMiddleware'); diff --git a/scripts/build.js b/scripts/build.js index b8854055..4ac2a6f9 100644 --- a/scripts/build.js +++ b/scripts/build.js @@ -1,4 +1,4 @@ -/* eslint-disable no-console */ +/* eslint-disable no-console, @typescript-eslint/prefer-nullish-coalescing, @typescript-eslint/promise-function-async, @typescript-eslint/prefer-optional-chain */ // Do this as the first thing so that any code reading it knows the right env. process.env.BABEL_ENV = 'production'; @@ -43,8 +43,8 @@ if (!checkRequiredFiles([ paths.appHtml, paths.appIndexJs ])) { // Process CLI arguments const argvSliceStart = 2; const argv = process.argv.slice(argvSliceStart); -const writeStatsJson = argv.indexOf('--stats') !== -1; -const withoutDist = argv.indexOf('--no-dist') !== -1; +const writeStatsJson = argv.includes('--stats'); +const withoutDist = argv.includes('--no-dist'); const { version, hasVersion } = getVersionFromArgs(argv); // Generate configuration diff --git a/scripts/start.js b/scripts/start.js index 68a4a08c..f1d2ba11 100644 --- a/scripts/start.js +++ b/scripts/start.js @@ -1,4 +1,4 @@ -/* eslint-disable no-console */ +/* eslint-disable no-console, @typescript-eslint/prefer-nullish-coalescing, @typescript-eslint/promise-function-async, @typescript-eslint/prefer-optional-chain */ // Do this as the first thing so that any code reading it knows the right env. process.env.BABEL_ENV = 'development'; diff --git a/src/registerServiceWorker.js b/src/registerServiceWorker.js index 023439b2..f12df500 100644 --- a/src/registerServiceWorker.js +++ b/src/registerServiceWorker.js @@ -1,3 +1,5 @@ +/* eslint-disable @typescript-eslint/promise-function-async, @typescript-eslint/no-misused-promises */ + // In production, we register a service worker to serve assets from local cache. // This lets the app load faster on subsequent visits in production, and gives @@ -96,7 +98,7 @@ function checkValidServiceWorker(swUrl) { // Ensure service worker exists, and that we really are getting a JS file. if ( response.status === NOT_FOUND_STATUS || - response.headers.get('content-type').indexOf('javascript') === -1 + response.headers.get('content-type').includes('javascript') ) { // No service worker found. Probably a different app. Reload the page. return navigator.serviceWorker.ready.then((registration) => From 125b13e05929ad9a1c95446a389e7a6ca93d7622 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 6 Sep 2020 09:46:07 +0200 Subject: [PATCH 57/59] Added stryker mutator for TS --- .travis.yml | 2 +- package-lock.json | 212 ++++++++++------------------------------------ package.json | 2 +- stryker.conf.js | 2 +- 4 files changed, 47 insertions(+), 171 deletions(-) diff --git a/.travis.yml b/.travis.yml index 6d7070d8..ff7be9a8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -41,7 +41,7 @@ install: before_script: - echo "Building commit range ${TRAVIS_COMMIT_RANGE}" - - export MUTATION_FILES=$(git diff ${TRAVIS_COMMIT_RANGE:-origin/main} --name-only | grep -E 'src\/(.*).(js|ts|jsx|tsx)$' | paste -sd ",") + - export MUTATION_FILES=$(git diff ${TRAVIS_COMMIT_RANGE:-origin/main} --name-only | grep -E 'src\/(.*).(ts|tsx)$' | paste -sd ",") script: - npm run lint diff --git a/package-lock.json b/package-lock.json index 62f0317c..0f23ee36 100644 --- a/package-lock.json +++ b/package-lock.json @@ -360,12 +360,6 @@ "@babel/types": "^7.4.4" } }, - "@babel/helper-validator-identifier": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.1.tgz", - "integrity": "sha512-5vW/JXLALhczRCWP0PnFDMCJAchlBvM7f4uk/jXritBnIa6E1KmqmtrS3yn1LAnxFBypQ3eneLuXjsnfQsgILw==", - "dev": true - }, "@babel/helper-wrap-function": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.2.0.tgz", @@ -2689,168 +2683,6 @@ } } }, - "@stryker-mutator/javascript-mutator": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@stryker-mutator/javascript-mutator/-/javascript-mutator-3.2.4.tgz", - "integrity": "sha512-Jp64+nBz97Nnn1h9aoBCH7OUtppGf2i1sugeTebp2T4b8cLZsOo2zMEKffCLEesC7+1KNEH0JKzenSen6rM4rw==", - "dev": true, - "requires": { - "@babel/generator": "~7.9.4", - "@babel/parser": "~7.9.4", - "@babel/traverse": "~7.9.5", - "@stryker-mutator/api": "^3.2.4", - "tslib": "~2.0.0" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.1.tgz", - "integrity": "sha512-IGhtTmpjGbYzcEDOw7DcQtbQSXcG9ftmAXtWTu9V936vDye4xjjekktFAtgZsWpzTj/X01jocB46mTywm/4SZw==", - "dev": true, - "requires": { - "@babel/highlight": "^7.10.1" - } - }, - "@babel/generator": { - "version": "7.9.6", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.9.6.tgz", - "integrity": "sha512-+htwWKJbH2bL72HRluF8zumBxzuX0ZZUFl3JLNyoUjM/Ho8wnVpPXM6aUz8cfKDqQ/h7zHqKt4xzJteUosckqQ==", - "dev": true, - "requires": { - "@babel/types": "^7.9.6", - "jsesc": "^2.5.1", - "lodash": "^4.17.13", - "source-map": "^0.5.0" - } - }, - "@babel/helper-function-name": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.1.tgz", - "integrity": "sha512-fcpumwhs3YyZ/ttd5Rz0xn0TpIwVkN7X0V38B9TWNfVF42KEkhkAAuPCQ3oXmtTRtiPJrmZ0TrfS0GKF0eMaRQ==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.10.1", - "@babel/template": "^7.10.1", - "@babel/types": "^7.10.1" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.1.tgz", - "integrity": "sha512-F5qdXkYGOQUb0hpRaPoetF9AnsXknKjWMZ+wmsIRsp5ge5sFh4c3h1eH2pRTTuy9KKAA2+TTYomGXAtEL2fQEw==", - "dev": true, - "requires": { - "@babel/types": "^7.10.1" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.10.1.tgz", - "integrity": "sha512-UQ1LVBPrYdbchNhLwj6fetj46BcFwfS4NllJo/1aJsT+1dLTEnXJL0qHqtY7gPzF8S2fXBJamf1biAXV3X077g==", - "dev": true, - "requires": { - "@babel/types": "^7.10.1" - } - }, - "@babel/highlight": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.1.tgz", - "integrity": "sha512-8rMof+gVP8mxYZApLF/JgNDAkdKa+aJt3ZYxF8z6+j/hpeXL7iMsKCPHa2jNMHu/qqBwzQF4OHNoYi8dMA/rYg==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.10.1", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.9.6", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.9.6.tgz", - "integrity": "sha512-AoeIEJn8vt+d/6+PXDRPaksYhnlbMIiejioBZvvMQsOjW/JYK6k/0dKnvvP3EhK5GfMBWDPtrxRtegWdAcdq9Q==", - "dev": true - }, - "@babel/template": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.1.tgz", - "integrity": "sha512-OQDg6SqvFSsc9A0ej6SKINWrpJiNonRIniYondK2ViKhB06i3c0s+76XUft71iqBEe9S1OKsHwPAjfHnuvnCig==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.10.1", - "@babel/parser": "^7.10.1", - "@babel/types": "^7.10.1" - }, - "dependencies": { - "@babel/parser": { - "version": "7.10.2", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.10.2.tgz", - "integrity": "sha512-PApSXlNMJyB4JiGVhCOlzKIif+TKFTvu0aQAhnTvfP/z3vVSN6ZypH5bfUNwFXXjRQtUEBNFd2PtmCmG2Py3qQ==", - "dev": true - } - } - }, - "@babel/traverse": { - "version": "7.9.6", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.9.6.tgz", - "integrity": "sha512-b3rAHSjbxy6VEAvlxM8OV/0X4XrG72zoxme6q1MOoe2vd0bEc+TwayhuC1+Dfgqh1QEG+pj7atQqvUprHIccsg==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.8.3", - "@babel/generator": "^7.9.6", - "@babel/helper-function-name": "^7.9.5", - "@babel/helper-split-export-declaration": "^7.8.3", - "@babel/parser": "^7.9.6", - "@babel/types": "^7.9.6", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.13" - } - }, - "@babel/types": { - "version": "7.10.2", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.10.2.tgz", - "integrity": "sha512-AD3AwWBSz0AWF0AkCN9VPiWrvldXq+/e3cHa4J89vo4ymjz1XwrBFFVZmkJTsQIPNk+ZVomPSXUJqq8yyjZsng==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.10.1", - "lodash": "^4.17.13", - "to-fast-properties": "^2.0.0" - } - }, - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", - "dev": true - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - }, - "tslib": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.0.tgz", - "integrity": "sha512-lTqkx847PI7xEDYJntxZH89L2/aXInsyF2luSafe/+0fHOMjlBNXdH6th7f70qxLDhul7KZK0zC8V5ZIyHl0/g==", - "dev": true - } - } - }, "@stryker-mutator/jest-runner": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/@stryker-mutator/jest-runner/-/jest-runner-3.2.4.tgz", @@ -2869,6 +2701,50 @@ } } }, + "@stryker-mutator/typescript": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@stryker-mutator/typescript/-/typescript-3.3.1.tgz", + "integrity": "sha512-fWQSXTiFJ8eVyQdlwN5IUy4nQ5HpzqVmUcCO90EnUX0HSsp7DQlec3hjBgE3X7XNGzTawjVwQlww2Fe91CX/kw==", + "dev": true, + "requires": { + "@stryker-mutator/api": "^3.3.1", + "@stryker-mutator/util": "^3.3.1", + "lodash.flatmap": "~4.5.0", + "semver": "~6.3.0", + "tslib": "~2.0.0" + }, + "dependencies": { + "@stryker-mutator/api": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@stryker-mutator/api/-/api-3.3.1.tgz", + "integrity": "sha512-t3TZyzxZYwZHUF1aCCpqLOQJjW0DZMZTrwGP/zGuDBUjr1/Opq+S6emeLFVjSJiDeMyqkqh9X01ZzHIWJPD+Sg==", + "dev": true, + "requires": { + "mutation-testing-report-schema": "~1.3.0", + "surrial": "~2.0.2", + "tslib": "~2.0.0" + } + }, + "@stryker-mutator/util": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@stryker-mutator/util/-/util-3.3.1.tgz", + "integrity": "sha512-JE9PT6/Fqo4343fQWb3YzbkGmBU4bUeuynYhsTeDmY0fhTT653oGHeyEKWeV39/kgSyKfIpY5ABb1RvJclOgkg==", + "dev": true + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, + "tslib": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.1.tgz", + "integrity": "sha512-SgIkNheinmEBgx1IUNirK0TUD4X9yjjBRTqqjggWCU3pUEqIk3/Uwl3yRixYKT6WjQuGiwDv4NomL3wqRCj+CQ==", + "dev": true + } + } + }, "@stryker-mutator/util": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/@stryker-mutator/util/-/util-3.2.4.tgz", diff --git a/package.json b/package.json index 5dfe27e9..d0723914 100644 --- a/package.json +++ b/package.json @@ -70,7 +70,7 @@ "@babel/plugin-proposal-optional-chaining": "^7.11.0", "@shlinkio/eslint-config-js-coding-standard": "~1.1.0", "@stryker-mutator/core": "^3.2.4", - "@stryker-mutator/javascript-mutator": "^3.2.4", + "@stryker-mutator/typescript": "^3.2.4", "@stryker-mutator/jest-runner": "^3.2.4", "@svgr/webpack": "^4.3.3", "@types/chart.js": "^2.8.0", diff --git a/stryker.conf.js b/stryker.conf.js index 931809aa..fb70d80b 100644 --- a/stryker.conf.js +++ b/stryker.conf.js @@ -2,7 +2,7 @@ const jestConfig = require(`${__dirname}/jest.config.js`); module.exports = { mutate: jestConfig.collectCoverageFrom, - mutator: 'javascript', + mutator: 'typescript', testRunner: 'jest', reporters: [ 'progress', 'clear-text' ], coverageAnalysis: 'off', From 58c9ef9b51ea14ff2ae522ef55efdef08bd374a7 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 6 Sep 2020 10:17:46 +0200 Subject: [PATCH 58/59] Updated dependencies --- package-lock.json | 3784 ++++++++++------- package.json | 76 +- src/common/AsideMenu.scss | 10 +- src/common/react-tagsinput.scss | 8 +- src/index.scss | 2 +- .../UseExistingIfFoundInfoIcon.scss | 2 +- src/tags/TagCard.scss | 2 +- src/utils/DateInput.scss | 4 +- src/utils/base.scss | 4 +- 9 files changed, 2356 insertions(+), 1536 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0f23ee36..587145fb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -360,6 +360,12 @@ "@babel/types": "^7.4.4" } }, + "@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==", + "dev": true + }, "@babel/helper-wrap-function": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.2.0.tgz", @@ -1328,37 +1334,37 @@ "dev": true }, "@fortawesome/fontawesome-common-types": { - "version": "0.2.25", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.25.tgz", - "integrity": "sha512-3RuZPDuuPELd7RXtUqTCfed14fcny9UiPOkdr2i+cYxBoTOfQgxcDoq77fHiiHcgWuo1LoBUpvGxFF1H/y7s3Q==" + "version": "0.2.30", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.30.tgz", + "integrity": "sha512-TsRwpTuKwFNiPhk1UfKgw7zNPeV5RhNp2Uw3pws+9gDAkPGKrtjR1y2lI3SYn7+YzyfuNknflpBA1LRKjt7hMg==" }, "@fortawesome/fontawesome-free": { - "version": "5.11.2", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-5.11.2.tgz", - "integrity": "sha512-XiUPoS79r1G7PcpnNtq85TJ7inJWe0v+b5oZJZKb0pGHNIV6+UiNeQWiFGmuQ0aj7GEhnD/v9iqxIsjuRKtEnQ==" + "version": "5.14.0", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-5.14.0.tgz", + "integrity": "sha512-OfdMsF+ZQgdKHP9jUbmDcRrP0eX90XXrsXIdyjLbkmSBzmMXPABB8eobUJtivaupucYaByz6WNe1PI1JuYm3qA==" }, "@fortawesome/fontawesome-svg-core": { - "version": "1.2.25", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-1.2.25.tgz", - "integrity": "sha512-MotKnn53JKqbkLQiwcZSBJVYtTgIKFbh7B8+kd05TSnfKYPFmjKKI59o2fpz5t0Hzl35vVGU6+N4twoOpZUrqA==", + "version": "1.2.30", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-1.2.30.tgz", + "integrity": "sha512-E3sAXATKCSVnT17HYmZjjbcmwihrNOCkoU7dVMlasrcwiJAHxSKeZ+4WN5O+ElgO/FaYgJmASl8p9N7/B/RttA==", "requires": { - "@fortawesome/fontawesome-common-types": "^0.2.25" + "@fortawesome/fontawesome-common-types": "^0.2.30" } }, "@fortawesome/free-regular-svg-icons": { - "version": "5.11.2", - "resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-5.11.2.tgz", - "integrity": "sha512-k0vbThRv9AvnXYBWi1gn1rFW4X7co/aFkbm0ZNmAR5PoWb9vY9EDDDobg8Ay4ISaXtCPypvJ0W1FWkSpLQwZ6w==", + "version": "5.14.0", + "resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-5.14.0.tgz", + "integrity": "sha512-6LCFvjGSMPoUQbn3NVlgiG4CY5iIY8fOm+to/D6QS/GvdqhDt+xZklQeERdCvVRbnFa1ITc1rJHPRXqkX5wztQ==", "requires": { - "@fortawesome/fontawesome-common-types": "^0.2.25" + "@fortawesome/fontawesome-common-types": "^0.2.30" } }, "@fortawesome/free-solid-svg-icons": { - "version": "5.11.2", - "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-5.11.2.tgz", - "integrity": "sha512-zBue4i0PAZJUXOmLBBvM7L0O7wmsDC8dFv9IhpW5QL4kT9xhhVUsYg/LX1+5KaukWq4/cbDcKT+RT1aRe543sg==", + "version": "5.14.0", + "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-5.14.0.tgz", + "integrity": "sha512-M933RDM8cecaKMWDSk3FRYdnzWGW7kBBlGNGfvqLVwcwhUPNj9gcw+xZMrqBdRqxnSXdl3zWzTCNNGEtFUq67Q==", "requires": { - "@fortawesome/fontawesome-common-types": "^0.2.25" + "@fortawesome/fontawesome-common-types": "^0.2.30" } }, "@fortawesome/react-fontawesome": { @@ -1539,16 +1545,10 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - }, "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { "has-flag": "^4.0.0" @@ -1557,9 +1557,9 @@ } }, "@jest/core": { - "version": "26.4.1", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-26.4.1.tgz", - "integrity": "sha512-EFziH1tJC5N8xb8OjUcQgyWdezJh6+zBX5p+9S7HR1jzBVeG8jCE/Edp7yqxW/cToLG/QKj8qrpox+HV9Qw1rw==", + "version": "26.4.2", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-26.4.2.tgz", + "integrity": "sha512-sDva7YkeNprxJfepOctzS8cAk9TOekldh+5FhVuXS40+94SHbiicRO1VV2tSoRtgIo+POs/Cdyf8p76vPTd6dg==", "dev": true, "requires": { "@jest/console": "^26.3.0", @@ -1573,17 +1573,17 @@ "exit": "^0.1.2", "graceful-fs": "^4.2.4", "jest-changed-files": "^26.3.0", - "jest-config": "^26.4.1", + "jest-config": "^26.4.2", "jest-haste-map": "^26.3.0", "jest-message-util": "^26.3.0", "jest-regex-util": "^26.0.0", "jest-resolve": "^26.4.0", - "jest-resolve-dependencies": "^26.4.1", - "jest-runner": "^26.4.1", - "jest-runtime": "^26.4.1", - "jest-snapshot": "^26.4.1", + "jest-resolve-dependencies": "^26.4.2", + "jest-runner": "^26.4.2", + "jest-runtime": "^26.4.2", + "jest-snapshot": "^26.4.2", "jest-util": "^26.3.0", - "jest-validate": "^26.4.0", + "jest-validate": "^26.4.2", "jest-watcher": "^26.3.0", "micromatch": "^4.0.2", "p-each-series": "^2.1.0", @@ -1682,12 +1682,6 @@ "glob": "^7.1.3" } }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - }, "strip-ansi": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", @@ -1698,9 +1692,9 @@ } }, "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { "has-flag": "^4.0.0" @@ -1744,14 +1738,14 @@ } }, "@jest/globals": { - "version": "26.4.1", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-26.4.1.tgz", - "integrity": "sha512-gdsHefnwjck+AwDUwW+6rmctmKEcZEEZ4F3PB5kKnub7r0dUoN1KVSyNRXtB5qpZgRYESnxgDXhpw/XYKIsAeg==", + "version": "26.4.2", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-26.4.2.tgz", + "integrity": "sha512-Ot5ouAlehhHLRhc+sDz2/9bmNv9p5ZWZ9LE1pXGGTCXBasmi5jnYjlgYcYt03FBwLmZXCZ7GrL29c33/XRQiow==", "dev": true, "requires": { "@jest/environment": "^26.3.0", "@jest/types": "^26.3.0", - "expect": "^26.4.1" + "expect": "^26.4.2" } }, "@jest/reporters": { @@ -1797,19 +1791,19 @@ } }, "@babel/core": { - "version": "7.11.4", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.11.4.tgz", - "integrity": "sha512-5deljj5HlqRXN+5oJTY7Zs37iH3z3b++KjiKtIsJy1NrjOOVSEaJHEetLBhyu0aQOSNNZ/0IuEAan9GzRuDXHg==", + "version": "7.11.6", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.11.6.tgz", + "integrity": "sha512-Wpcv03AGnmkgm6uS6k8iwhIwTrcP0m17TL1n1sy7qD0qelDu4XNeW0dN0mHfa+Gei211yDaLoEe/VlbXQzM4Bg==", "dev": true, "requires": { "@babel/code-frame": "^7.10.4", - "@babel/generator": "^7.11.4", + "@babel/generator": "^7.11.6", "@babel/helper-module-transforms": "^7.11.0", "@babel/helpers": "^7.10.4", - "@babel/parser": "^7.11.4", + "@babel/parser": "^7.11.5", "@babel/template": "^7.10.4", - "@babel/traverse": "^7.11.0", - "@babel/types": "^7.11.0", + "@babel/traverse": "^7.11.5", + "@babel/types": "^7.11.5", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.1", @@ -1835,12 +1829,12 @@ } }, "@babel/generator": { - "version": "7.11.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.11.4.tgz", - "integrity": "sha512-Rn26vueFx0eOoz7iifCN2UHT6rGtnkSGWSoDRIy8jZN3B91PzeSULbswfLoOWuTuAcNwpG/mxy+uCTDnZ9Mp1g==", + "version": "7.11.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.11.6.tgz", + "integrity": "sha512-DWtQ1PV3r+cLbySoHrwn9RWEgKMBLLma4OBQloPRyDYvc5msJM9kvTLo1YnlJd1P/ZuKbdli3ijr5q3FvAF3uA==", "dev": true, "requires": { - "@babel/types": "^7.11.0", + "@babel/types": "^7.11.5", "jsesc": "^2.5.1", "source-map": "^0.5.0" }, @@ -1946,12 +1940,6 @@ "@babel/types": "^7.11.0" } }, - "@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==", - "dev": true - }, "@babel/helpers": { "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.10.4.tgz", @@ -2027,9 +2015,9 @@ } }, "@babel/parser": { - "version": "7.11.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.11.4.tgz", - "integrity": "sha512-MggwidiH+E9j5Sh8pbrX5sJvMcsqS5o+7iB42M9/k0CD63MjYbdP4nhSh7uB5wnv2/RVzTZFTxzF/kIa5mrCqA==", + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.11.5.tgz", + "integrity": "sha512-X9rD8qqm695vgmeaQ4fvz/o3+Wk4ZzQvSHkDBgpYKxpD4qTAUm88ZKtHkVqIOsYFFbIQ6wQYhC6q7pjqVK0E0Q==", "dev": true }, "@babel/template": { @@ -2044,26 +2032,26 @@ } }, "@babel/traverse": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.11.0.tgz", - "integrity": "sha512-ZB2V+LskoWKNpMq6E5UUCrjtDUh5IOTAyIl0dTjIEoXum/iKWkoIEKIRDnUucO6f+2FzNkE0oD4RLKoPIufDtg==", + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.11.5.tgz", + "integrity": "sha512-EjiPXt+r7LiCZXEfRpSJd+jUMnBd4/9OUv7Nx3+0u9+eimMwJmG0Q98lw4/289JCoxSE8OolDMNZaaF/JZ69WQ==", "dev": true, "requires": { "@babel/code-frame": "^7.10.4", - "@babel/generator": "^7.11.0", + "@babel/generator": "^7.11.5", "@babel/helper-function-name": "^7.10.4", "@babel/helper-split-export-declaration": "^7.11.0", - "@babel/parser": "^7.11.0", - "@babel/types": "^7.11.0", + "@babel/parser": "^7.11.5", + "@babel/types": "^7.11.5", "debug": "^4.1.0", "globals": "^11.1.0", "lodash": "^4.17.19" } }, "@babel/types": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.0.tgz", - "integrity": "sha512-O53yME4ZZI0jO1EVGtF1ePGl0LHirG4P1ibcD80XyzZcKhcMFeCXmh4Xb1ifGBIV233Qg12x4rBfQgA+tmOukA==", + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.5.tgz", + "integrity": "sha512-bvM7Qz6eKnJVFIn+1LPtjlBFPVN5jNDc1XmN15vWe7Q3DPBufWWsLiIvUu7xW87uTG6QoggpIDnUgLQvPheU+Q==", "dev": true, "requires": { "@babel/helper-validator-identifier": "^7.10.4", @@ -2192,16 +2180,10 @@ "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "dev": true }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - }, "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { "has-flag": "^4.0.0" @@ -2247,16 +2229,16 @@ } }, "@jest/test-sequencer": { - "version": "26.4.1", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-26.4.1.tgz", - "integrity": "sha512-YR4PNPu1RVHxyv/HSQMjc+pBEWa6wuM7xbEX/u5M5FFg6ZM6m00m7Jf0fjRxGN6hZlY5vECmNhJu/kvJLrxR8w==", + "version": "26.4.2", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-26.4.2.tgz", + "integrity": "sha512-83DRD8N3M0tOhz9h0bn6Kl6dSp+US6DazuVF8J9m21WAp5x7CqSMaNycMP0aemC/SH/pDQQddbsfHRTBXVUgog==", "dev": true, "requires": { "@jest/test-result": "^26.3.0", "graceful-fs": "^4.2.4", "jest-haste-map": "^26.3.0", - "jest-runner": "^26.4.1", - "jest-runtime": "^26.4.1" + "jest-runner": "^26.4.2", + "jest-runtime": "^26.4.2" }, "dependencies": { "graceful-fs": { @@ -2481,12 +2463,40 @@ "glob-to-regexp": "^0.3.0" } }, + "@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==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "2.0.3", + "run-parallel": "^1.1.9" + }, + "dependencies": { + "@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==", + "dev": true + } + } + }, "@nodelib/fs.stat": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz", "integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==", "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==", + "dev": true, + "requires": { + "@nodelib/fs.scandir": "2.1.3", + "fastq": "^1.6.0" + } + }, "@shlinkio/eslint-config-js-coding-standard": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@shlinkio/eslint-config-js-coding-standard/-/eslint-config-js-coding-standard-1.1.0.tgz", @@ -2751,6 +2761,282 @@ "integrity": "sha512-s/yB6Ok0NZfGlQD1A/3BPdwCyS2YA5yQzIFNsFE8q/GqmMlZ5SjliEFNgp7u3LUYpqqBStmmz6qzP/PoL1BxqQ==", "dev": true }, + "@stylelint/postcss-css-in-js": { + "version": "0.37.2", + "resolved": "https://registry.npmjs.org/@stylelint/postcss-css-in-js/-/postcss-css-in-js-0.37.2.tgz", + "integrity": "sha512-nEhsFoJurt8oUmieT8qy4nk81WRHmJynmVwn/Vts08PL9fhgIsMhk1GId5yAN643OzqEEb5S/6At2TZW7pqPDA==", + "dev": true, + "requires": { + "@babel/core": ">=7.9.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" + } + }, + "@babel/core": { + "version": "7.11.6", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.11.6.tgz", + "integrity": "sha512-Wpcv03AGnmkgm6uS6k8iwhIwTrcP0m17TL1n1sy7qD0qelDu4XNeW0dN0mHfa+Gei211yDaLoEe/VlbXQzM4Bg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.11.6", + "@babel/helper-module-transforms": "^7.11.0", + "@babel/helpers": "^7.10.4", + "@babel/parser": "^7.11.5", + "@babel/template": "^7.10.4", + "@babel/traverse": "^7.11.5", + "@babel/types": "^7.11.5", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.1", + "json5": "^2.1.2", + "lodash": "^4.17.19", + "resolve": "^1.3.2", + "semver": "^5.4.1", + "source-map": "^0.5.0" + } + }, + "@babel/generator": { + "version": "7.11.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.11.6.tgz", + "integrity": "sha512-DWtQ1PV3r+cLbySoHrwn9RWEgKMBLLma4OBQloPRyDYvc5msJM9kvTLo1YnlJd1P/ZuKbdli3ijr5q3FvAF3uA==", + "dev": true, + "requires": { + "@babel/types": "^7.11.5", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + } + }, + "@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==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz", + "integrity": "sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A==", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.11.0.tgz", + "integrity": "sha512-JbFlKHFntRV5qKw3YC0CvQnDZ4XMwgzzBbld7Ly4Mj4cbFy3KywcR8NtNctRToMWJOVvLINJv525Gd6wwVEx/Q==", + "dev": true, + "requires": { + "@babel/types": "^7.11.0" + } + }, + "@babel/helper-module-imports": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.10.4.tgz", + "integrity": "sha512-nEQJHqYavI217oD9+s5MUBzk6x1IlvoS9WTPfgG43CbMEeStE0v+r+TucWdx8KFGowPGvyOkDT9+7DHedIDnVw==", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-module-transforms": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.11.0.tgz", + "integrity": "sha512-02EVu8COMuTRO1TAzdMtpBPbe6aQ1w/8fePD2YgQmxZU4gpNWaL9gK3Jp7dxlkUlUCJOTaSeA+Hrm1BRQwqIhg==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.10.4", + "@babel/helper-replace-supers": "^7.10.4", + "@babel/helper-simple-access": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.11.0", + "@babel/template": "^7.10.4", + "@babel/types": "^7.11.0", + "lodash": "^4.17.19" + } + }, + "@babel/helper-optimise-call-expression": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.4.tgz", + "integrity": "sha512-n3UGKY4VXwXThEiKrgRAoVPBMqeoPgHVqiHZOanAJCG9nQUL2pLRQirUzl0ioKclHGpGqRgIOkgcIJaIWLpygg==", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-replace-supers": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.10.4.tgz", + "integrity": "sha512-sPxZfFXocEymYTdVK1UNmFPBN+Hv5mJkLPsYWwGBxZAxaWfFu+xqp7b6qWD0yjNuNL2VKc6L5M18tOXUP7NU0A==", + "dev": true, + "requires": { + "@babel/helper-member-expression-to-functions": "^7.10.4", + "@babel/helper-optimise-call-expression": "^7.10.4", + "@babel/traverse": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-simple-access": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.10.4.tgz", + "integrity": "sha512-0fMy72ej/VEvF8ULmX6yb5MtHG4uH4Dbd6I/aHDb/JVg0bbivwt9Wg+h3uMvX+QSFtwr5MeItvazbrc4jtRAXw==", + "dev": true, + "requires": { + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@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==", + "dev": true, + "requires": { + "@babel/types": "^7.11.0" + } + }, + "@babel/helpers": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.10.4.tgz", + "integrity": "sha512-L2gX/XeUONeEbI78dXSrJzGdz4GQ+ZTA/aazfUsFaWjSe95kiCuOZ5HsXvkiw3iwF+mFHSRUfJU8t6YavocdXA==", + "dev": true, + "requires": { + "@babel/template": "^7.10.4", + "@babel/traverse": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/highlight": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", + "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.11.5.tgz", + "integrity": "sha512-X9rD8qqm695vgmeaQ4fvz/o3+Wk4ZzQvSHkDBgpYKxpD4qTAUm88ZKtHkVqIOsYFFbIQ6wQYhC6q7pjqVK0E0Q==", + "dev": true + }, + "@babel/template": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.4.tgz", + "integrity": "sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/parser": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/traverse": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.11.5.tgz", + "integrity": "sha512-EjiPXt+r7LiCZXEfRpSJd+jUMnBd4/9OUv7Nx3+0u9+eimMwJmG0Q98lw4/289JCoxSE8OolDMNZaaF/JZ69WQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.11.5", + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.11.0", + "@babel/parser": "^7.11.5", + "@babel/types": "^7.11.5", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.19" + } + }, + "@babel/types": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.5.tgz", + "integrity": "sha512-bvM7Qz6eKnJVFIn+1LPtjlBFPVN5jNDc1XmN15vWe7Q3DPBufWWsLiIvUu7xW87uTG6QoggpIDnUgLQvPheU+Q==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + }, + "convert-source-map": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", + "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.1" + } + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "json5": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz", + "integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, + "lodash": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", + "dev": true + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "@stylelint/postcss-markdown": { + "version": "0.36.1", + "resolved": "https://registry.npmjs.org/@stylelint/postcss-markdown/-/postcss-markdown-0.36.1.tgz", + "integrity": "sha512-iDxMBWk9nB2BPi1VFQ+Dc5+XpvODBHw2n3tYpaBZuEAFQlbtF9If0Qh5LTTwSi/XwdbJ2jt+0dis3i8omyggpw==", + "dev": true, + "requires": { + "remark": "^12.0.0", + "unist-util-find-all-after": "^3.0.1" + } + }, "@svgr/babel-plugin-add-jsx-attribute": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-4.2.0.tgz", @@ -3205,6 +3491,12 @@ "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==", "dev": true }, + "@types/minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-aaI6OtKcrwCX8G7aWbNh7i8GOfY=", + "dev": true + }, "@types/moment": { "version": "2.13.0", "resolved": "https://registry.npmjs.org/@types/moment/-/moment-2.13.0.tgz", @@ -3226,10 +3518,16 @@ "integrity": "sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==", "dev": true }, + "@types/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", + "dev": true + }, "@types/prettier": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.0.2.tgz", - "integrity": "sha512-IkVfat549ggtkZUthUzEX49562eGikhSYeVGX97SkMFn+sTZrgRewXjQ4tPKFPCykZHkX1Zfd9OoELGqKU2jJA==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.1.0.tgz", + "integrity": "sha512-hiYA88aHiEIgDmeKlsyVsuQdcFn3Z2VuFd/Xm/HCnGnPD8UFU5BM128uzzRVVGEzKDKYUrRsRH9S2o+NUy/3IA==", "dev": true }, "@types/prop-types": { @@ -3417,27 +3715,6 @@ "integrity": "sha512-eQ9qFW/fhfGJF8WKHGEHZEyVWfZxrT+6CLIJGBcZPfxUh/+BnEj+UCGYMlr9qZuX/2AltsvwrGqp0LhEW8D0zQ==", "dev": true }, - "@types/vfile": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/vfile/-/vfile-3.0.2.tgz", - "integrity": "sha512-b3nLFGaGkJ9rzOcuXRfHkZMdjsawuDD0ENL9fzTophtBg8FJHSGbH7daXkEpcwy3v7Xol3pAvsmlYyFhR4pqJw==", - "dev": true, - "requires": { - "@types/node": "*", - "@types/unist": "*", - "@types/vfile-message": "*" - } - }, - "@types/vfile-message": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@types/vfile-message/-/vfile-message-1.0.1.tgz", - "integrity": "sha512-mlGER3Aqmq7bqR1tTTIVHq8KSAFFRyGbrxuM8C/H82g6k7r2fS+IMEkIu3D7JHzG10NvPdR8DNx0jr0pwpp4dA==", - "dev": true, - "requires": { - "@types/node": "*", - "@types/unist": "*" - } - }, "@types/yargs": { "version": "15.0.5", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.5.tgz", @@ -3972,9 +4249,9 @@ "dev": true }, "arch": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/arch/-/arch-2.1.1.tgz", - "integrity": "sha512-BLM56aPo9vLLFVa8+/+pJLnrZ7QGGTVHWsCwieAWT9o9K8UeGaQbzZbGoabWLOo2ksBCztoXdqBZBplqLDDCSg==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/arch/-/arch-2.1.2.tgz", + "integrity": "sha512-NTBIIbAfkJeIletyABbVtdPgeKfDafR+1mZV/AyyfC1UkVkp9iUjV+wwmqtUgphHYajbI86jejBJp5e+jkGTiQ==", "dev": true }, "are-we-there-yet": { @@ -4553,9 +4830,9 @@ "dev": true }, "aws4": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", - "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==", + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.10.1.tgz", + "integrity": "sha512-zg7Hz2k5lI8kb7U32998pRRFin7zJlkfezGJjUc2heaD4Pw2wObakCDVzkKztTm/Ln7eiVvYsjqak0Ed4LkMDA==", "dev": true }, "axe-core": { @@ -4565,34 +4842,17 @@ "dev": true }, "axios": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.0.tgz", - "integrity": "sha512-1uvKqKQta3KBxIz14F2v06AEHZ/dIoeKfbTRkK1E5oqjDnuEerLmYTgJB5AiQZHJcljpg1TuRzdjDR06qNk0DQ==", + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.20.0.tgz", + "integrity": "sha512-ANA4rr2BDcmmAQLOKft2fufrtuvlqR+cXNNinUmvfeSNCOF98PZL+7M/v1zIdGo7OLjEA9J2gXJL+j4zGsl0bA==", "requires": { - "follow-redirects": "1.5.10", - "is-buffer": "^2.0.2" + "follow-redirects": "^1.10.0" }, "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "requires": { - "ms": "2.0.0" - } - }, "follow-redirects": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", - "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==", - "requires": { - "debug": "=3.1.0" - } - }, - "is-buffer": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz", - "integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==" + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.0.tgz", + "integrity": "sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA==" } } }, @@ -5572,9 +5832,9 @@ "dev": true }, "bail": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/bail/-/bail-1.0.4.tgz", - "integrity": "sha512-S8vuDB4w6YpRhICUDET3guPlQpaJl7od94tpZ0Fvnyp+MKW/HyDTcRDck+29C9g+d/qQHnddRH3+94kZdrW0Ww==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/bail/-/bail-1.0.5.tgz", + "integrity": "sha512-xFbRxM1tahm08yHBP16MMjVUAvDaBMD38zsM9EMAUN61omwLmKlOpB/Zku5QkjZ8TZ4vn53pj+t518cH0S03RQ==", "dev": true }, "balanced-match": { @@ -5728,19 +5988,19 @@ "dev": true }, "bootstrap": { - "version": "4.3.1", - "resolved": "https://registry.yarnpkg.com/bootstrap/-/bootstrap-4.3.1.tgz", - "integrity": "sha1-KAyo9hBQTZnXtrS/xLaM7GAXBKw=" + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.5.2.tgz", + "integrity": "sha512-vlGn0bcySYl/iV+BGA544JkkZP5LB3jsmkeKLFQakCOwCM3AOk7VkldBz4jrzSe+Z0Ezn99NVXa1o45cQY4R6A==" }, "bottlejs": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/bottlejs/-/bottlejs-1.7.2.tgz", - "integrity": "sha512-voMPQ+g8/4GBiDE5lp43fgfoBn7Q5fZI7k3ye8ErrPoHzuuRS0Gff9lZYeyalh86uz5P304iZ14wNAM+3TZguQ==" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/bottlejs/-/bottlejs-2.0.0.tgz", + "integrity": "sha512-Qhz5dd1YPTOHw0gZ1a1WpJ/oEWsq09BMMbxczeAPgubISij+ZFfah7wfOlHFeFQpWLQkS9TGz5C54tV6v0BlFA==" }, "bowser": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.9.0.tgz", - "integrity": "sha512-2ld76tuLBNFekRgmJfT2+3j5MIrP6bFict8WAIT3beq+srz1gcKNAdNKMqHqauQt63NmAa88HfP1/Ypa9Er3HA==" + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.10.0.tgz", + "integrity": "sha512-OCsqTQboTEWWsUjcp5jLSw2ZHsBiv2C105iFs61bOT0Hnwi9p7/uuXdd7mu8RYcarREfdjNN+8LitmEHATsLYg==" }, "boxen": { "version": "1.3.0", @@ -6152,9 +6412,9 @@ "dev": true }, "ccount": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/ccount/-/ccount-1.0.4.tgz", - "integrity": "sha512-fpZ81yYfzentuieinmGnphk0pLkOTMm6MZdVqwd77ROvhko6iujLNGrHH5E7utq3ygWklwfmwuG+A7P+NpqT6w==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-1.0.5.tgz", + "integrity": "sha512-MOli1W+nfbPLlKEhInaxhRdp7KVLFxLN5ykwzHgLsLI3H3gs5jjFAK4Eoj3OzzcxCtumDaI8onoVDeQyWaNTkw==", "dev": true }, "chalk": { @@ -6175,27 +6435,27 @@ "dev": true }, "character-entities": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.3.tgz", - "integrity": "sha512-yB4oYSAa9yLcGyTbB4ItFwHw43QHdH129IJ5R+WvxOkWlyFnR5FAaBNnUq4mcxsTVZGh28bHoeTHMKXH1wZf3w==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.4.tgz", + "integrity": "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==", "dev": true }, "character-entities-html4": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-1.1.3.tgz", - "integrity": "sha512-SwnyZ7jQBCRHELk9zf2CN5AnGEc2nA+uKMZLHvcqhpPprjkYhiLn0DywMHgN5ttFZuITMATbh68M6VIVKwJbcg==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-1.1.4.tgz", + "integrity": "sha512-HRcDxZuZqMx3/a+qrzxdBKBPUpxWEq9xw2OPZ3a/174ihfrQKVsFhqtthBInFy1zZ9GgZyFXOatNujm8M+El3g==", "dev": true }, "character-entities-legacy": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.3.tgz", - "integrity": "sha512-YAxUpPoPwxYFsslbdKkhrGnXAtXoHNgYjlBM3WMXkWGTl5RsY3QmOyhwAgL8Nxm9l5LBThXGawxKPn68y6/fww==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz", + "integrity": "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==", "dev": true }, "character-reference-invalid": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.3.tgz", - "integrity": "sha512-VOq6PRzQBam/8Jm6XBGk2fNEnHXAdGd6go0rtd4weAGECBamHDwwCQSOT12TACIYUZegUXnV6xBXqUssijtxIg==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz", + "integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==", "dev": true }, "chardet": { @@ -6205,28 +6465,21 @@ "dev": true }, "chart.js": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-2.8.0.tgz", - "integrity": "sha512-Di3wUL4BFvqI5FB5K26aQ+hvWh8wnP9A3DWGvXHVkO13D3DSnaSsdZx29cXlEsYKVkn1E2az+ZYFS4t0zi8x0w==", + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-2.9.3.tgz", + "integrity": "sha512-+2jlOobSk52c1VU6fzkh3UwqHMdSlgH1xFv9FKMqHiNCpXsGPQa/+81AFa+i3jZ253Mq9aAycPwDjnn1XbRNNw==", "requires": { "chartjs-color": "^2.1.0", "moment": "^2.10.2" } }, "chartjs-color": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/chartjs-color/-/chartjs-color-2.3.0.tgz", - "integrity": "sha512-hEvVheqczsoHD+fZ+tfPUE+1+RbV6b+eksp2LwAhwRTVXEjCSEavvk+Hg3H6SZfGlPh/UfmWKGIvZbtobOEm3g==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chartjs-color/-/chartjs-color-2.4.1.tgz", + "integrity": "sha512-haqOg1+Yebys/Ts/9bLo/BqUcONQOdr/hoEr2LLTRl6C5LXctUdHxsCYfvQVg5JIxITrfCNUDr4ntqmQk9+/0w==", "requires": { "chartjs-color-string": "^0.6.0", - "color-convert": "^0.5.3" - }, - "dependencies": { - "color-convert": { - "version": "0.5.3", - "resolved": "https://registry.yarnpkg.com/color-convert/-/color-convert-0.5.3.tgz", - "integrity": "sha1-vbbGnOZg+t/+CwAHzER+G59ygr0=" - } + "color-convert": "^1.9.3" } }, "chartjs-color-string": { @@ -6521,13 +6774,20 @@ "integrity": "sha1-QoRxk3dQvKnEjsv7wW9uIy90oD0=" }, "clone-regexp": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/clone-regexp/-/clone-regexp-1.0.1.tgz", - "integrity": "sha512-Fcij9IwRW27XedRIJnSOEupS7RVcXtObJXbcUOX93UCLqqOdRpkvzKywOOSizmEK/Is3S/RHX9dLdfo6R1Q1mw==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clone-regexp/-/clone-regexp-2.2.0.tgz", + "integrity": "sha512-beMpP7BOtTipFuW8hrJvREQ2DrRu3BE7by0ZpibtfBA+qfHYvMGTc2Yb1JMYPKg/JUw0CHYvpg796aNTSW9z7Q==", "dev": true, "requires": { - "is-regexp": "^1.0.0", - "is-supported-regexp-flag": "^1.0.0" + "is-regexp": "^2.0.0" + }, + "dependencies": { + "is-regexp": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-2.1.0.tgz", + "integrity": "sha512-OZ4IlER3zmRIoB9AqNhEggVxqIH4ofDns5nRrPS6yQxXE1TPCUpFznBfRQmQa8uC+pXqjMnukiJBxCisIxiLGA==", + "dev": true + } } }, "co": { @@ -6554,9 +6814,9 @@ "dev": true }, "collapse-white-space": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-1.0.5.tgz", - "integrity": "sha512-703bOOmytCYAX9cXYqoikYIx6twmFCXsnzRQheBcTG3nzKYBR4P/+wkYeH+Mvj7qUz8zZDtdyzbxfnEi/kYzRQ==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-1.0.6.tgz", + "integrity": "sha512-jEovNnrhMuqyCcjfEJA56v0Xq8SkIoPKDyaHahwo3POf4qcSXqMYuwNcOTzp74vTsR9Tn08z4MxWqAhcekogkQ==", "dev": true }, "collect-v8-coverage": { @@ -6589,7 +6849,6 @@ "version": "1.9.3", "resolved": "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha1-u3GFBpDh8TZWfeYp0tVHHe2kweg=", - "dev": true, "requires": { "color-name": "1.1.3" }, @@ -6597,8 +6856,7 @@ "color-name": { "version": "1.1.3", "resolved": "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" } } }, @@ -6617,6 +6875,12 @@ "simple-swizzle": "^0.2.2" } }, + "colorette": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.1.tgz", + "integrity": "sha512-puCDz0CzydiSYOrnXpz/PKd69zRrribezjtE9yd4zvytoRc8+RY/KJPvtPFKZS3E3wP6neGyMe0vOTlHO5L3Pw==", + "dev": true + }, "combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -6645,9 +6909,9 @@ "dev": true }, "compare-versions": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-3.5.1.tgz", - "integrity": "sha512-9fGPIB7C6AyM18CJJBHt5EnCZDG3oiTJYy0NjfIAGjKpzv0tkxWko7TNQHF5ymqm7IH03tqmeuBxtvD+Izh6mg==" + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-3.6.0.tgz", + "integrity": "sha512-W6Af2Iw1z4CB7q4uU4hv646dW9GQuBM+YpC0UvUCWSD8w90SJjp+ujJuXaEMtAXBtSqGfMPuFOVn4/+FlaqfBA==" }, "component-emitter": { "version": "1.2.1", @@ -6798,11 +7062,11 @@ "dev": true }, "copy-to-clipboard": { - "version": "3.0.8", - "resolved": "https://registry.yarnpkg.com/copy-to-clipboard/-/copy-to-clipboard-3.0.8.tgz", - "integrity": "sha1-9OgvSogw3ORma3643tDJvMMTq6k=", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.1.tgz", + "integrity": "sha512-i13qo6kIHTTpCm8/Wup+0b1mVWETvu2kIMzKoK8FpkLkFxlt0znUAHcMzox+T8sPlqtZXq3CulEjQHsYiGFJUw==", "requires": { - "toggle-selection": "^1.0.3" + "toggle-selection": "^1.0.6" } }, "core-js": { @@ -7797,9 +8061,9 @@ "dev": true }, "duplexer": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", - "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=", + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", + "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", "dev": true }, "duplexer3": { @@ -8348,6 +8612,12 @@ "integrity": "sha1-2m0NVpLvtGHggsFIF/4kJ9j10FQ=", "dev": true }, + "escalade": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.0.2.tgz", + "integrity": "sha512-gPYAU37hYCUhW5euPeR+Y74F7BL+IBsV93j5cvGriSaD1aG6MGsqsV1yamRdrWrb2j3aiZvb0X+UBOWpx3JWtQ==", + "dev": true + }, "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -9315,9 +9585,9 @@ "dev": true }, "event-source-polyfill": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/event-source-polyfill/-/event-source-polyfill-1.0.12.tgz", - "integrity": "sha512-WjOTn0LIbaN08z/8gNt3GYAomAdm6cZ2lr/QdvhTTEipr5KR6lds2ziUH+p/Iob4Lk6NClKhwPOmn1NjQEcJCg==" + "version": "1.0.17", + "resolved": "https://registry.npmjs.org/event-source-polyfill/-/event-source-polyfill-1.0.17.tgz", + "integrity": "sha512-eLZQQpKZahOH5sFaqfrbLNXJKz+JawiDQVrl6lZmQHHSamIn5PlNV3HXAY9+ZRaQC5YTIBRDd8jeTxjuEveJnQ==" }, "eventemitter3": { "version": "4.0.0", @@ -9385,12 +9655,12 @@ } }, "execall": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execall/-/execall-1.0.0.tgz", - "integrity": "sha1-c9CQTjlbPKsGWLCNCewlMH8pu3M=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/execall/-/execall-2.0.0.tgz", + "integrity": "sha512-0FU2hZ5Hh6iQnarpRtQurM/aAvp3RIbfvgLHrcqJYzhXyV2KFruhuChf9NC6waAhiUR7FFtlugkI4p7f2Fqlow==", "dev": true, "requires": { - "clone-regexp": "^1.0.0" + "clone-regexp": "^2.1.0" } }, "exit": { @@ -9415,15 +9685,15 @@ } }, "expect": { - "version": "26.4.1", - "resolved": "https://registry.npmjs.org/expect/-/expect-26.4.1.tgz", - "integrity": "sha512-PnsyF/VmPRH/HAWELjrIAgQ5h+4JLTiomA1A2djx+jXrCQzQ/4egZYBOEx9hShoX+mQLS4enYk6Ouxk8b4kcEw==", + "version": "26.4.2", + "resolved": "https://registry.npmjs.org/expect/-/expect-26.4.2.tgz", + "integrity": "sha512-IlJ3X52Z0lDHm7gjEp+m76uX46ldH5VpqmU0006vqDju/285twh7zaWMRhs67VpQhBwjjMchk+p5aA0VkERCAA==", "dev": true, "requires": { "@jest/types": "^26.3.0", "ansi-styles": "^4.0.0", "jest-get-type": "^26.3.0", - "jest-matcher-utils": "^26.4.1", + "jest-matcher-utils": "^26.4.2", "jest-message-util": "^26.3.0", "jest-regex-util": "^26.0.0" }, @@ -9584,7 +9854,8 @@ "fast-deep-equal": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", - "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", + "dev": true }, "fast-glob": { "version": "2.2.7", @@ -9629,6 +9900,21 @@ } } }, + "fastest-levenshtein": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.12.tgz", + "integrity": "sha512-On2N+BpYJ15xIC974QNVuYGMOlEVt4s0EOI3wwMqOmK1fdDY+FN/zltPV8vosq4ad4c/gJ1KHScUn/6AWIgiow==", + "dev": true + }, + "fastq": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.8.0.tgz", + "integrity": "sha512-SMIZoZdLh/fgofivvIkmknUXyPnvxRE3DhtZ5Me3Mrsk5gyPL42F0xr51TdRXskBxHfMp+07bcYzfsYEsSQA9Q==", + "dev": true, + "requires": { + "reusify": "^1.0.4" + } + }, "faye-websocket": { "version": "0.11.3", "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.3.tgz", @@ -9718,9 +10004,9 @@ "dev": true }, "filesize": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/filesize/-/filesize-3.6.1.tgz", - "integrity": "sha512-7KjR1vv6qnicaPMi1iiTcI85CyYwRO/PSFCu6SvqL8jN2Wjt/NIYQTFtFs7fSDCYOstUkEWIQGFUg5YZQfjlcg==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/filesize/-/filesize-6.0.1.tgz", + "integrity": "sha512-u4AYWPgbI5GBhs6id1KdImZWn5yfyFrrQ8OWZdN7ZMfA8Bf4HcO0BGo9bmUIEV8yrp8I1xVfJ/dn90GtFNNJcg==", "dev": true }, "fill-range": { @@ -9869,19 +10155,126 @@ "dev": true }, "fork-ts-checker-webpack-plugin": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-1.5.0.tgz", - "integrity": "sha512-zEhg7Hz+KhZlBhILYpXy+Beu96gwvkROWJiTXOCyOOMMrdBIRPvsBpBqgTI4jfJGrJXcqGwJR8zsBGDmzY0jsA==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-3.1.1.tgz", + "integrity": "sha512-DuVkPNrM12jR41KM2e+N+styka0EgLkTnXmNcXdgOM37vtGeY+oCBK/Jx0hzSeEU6memFCtWb4htrHPMDfwwUQ==", "dev": true, "requires": { "babel-code-frame": "^6.22.0", "chalk": "^2.4.1", - "chokidar": "^2.0.4", + "chokidar": "^3.3.0", "micromatch": "^3.1.10", "minimatch": "^3.0.4", "semver": "^5.6.0", "tapable": "^1.0.0", "worker-rpc": "^0.1.0" + }, + "dependencies": { + "anymatch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", + "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "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==", + "dev": true + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "chokidar": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.2.tgz", + "integrity": "sha512-IZHaDeBeI+sZJRX7lGcXsdzgvZqKv6sECqsbErJA4mHWfpRrD8B97kSFN4cQz6nGBGiuFia1MKR4d6c1o8Cv7A==", + "dev": true, + "requires": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "fsevents": "~2.1.2", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.4.0" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "fsevents": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", + "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", + "dev": true, + "optional": true + }, + "glob-parent": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", + "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "readdirp": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.4.0.tgz", + "integrity": "sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ==", + "dev": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + } } }, "fork-ts-checker-webpack-plugin-alt": { @@ -10883,9 +11276,9 @@ "dev": true }, "globule": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/globule/-/globule-1.2.1.tgz", - "integrity": "sha512-g7QtgWF4uYSL5/dn71WxubOrS7JVGCnFPEnoeChJmBnyR9Mw8nGoEwOgJL/RC2Te0WhbsEUCejfH8SZNJ+adYQ==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/globule/-/globule-1.3.2.tgz", + "integrity": "sha512-7IDTQTIu2xzXkT+6mlluidnWo+BypnbSoEVVQCGfzqnl5Ik8d3e1d4wycb8Rj9tWW+Z39uPWsdlquqiqPCd/pA==", "dev": true, "requires": { "glob": "~7.1.1", @@ -10894,18 +11287,18 @@ } }, "gonzales-pe": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/gonzales-pe/-/gonzales-pe-4.2.4.tgz", - "integrity": "sha512-v0Ts/8IsSbh9n1OJRnSfa7Nlxi4AkXIsWB6vPept8FDbL4bXn3FNuxjYtO/nmBGu7GDkL9MFeGebeSu6l55EPQ==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/gonzales-pe/-/gonzales-pe-4.3.0.tgz", + "integrity": "sha512-otgSPpUmdWJ43VXyiNgEYE4luzHCL2pz4wQ0OnDluC6Eg4Ko3Vexy/SrSynglw/eR+OhkzmqFCZa/OFa/RgAOQ==", "dev": true, "requires": { - "minimist": "1.1.x" + "minimist": "^1.2.5" }, "dependencies": { "minimist": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.1.3.tgz", - "integrity": "sha1-O+39kaktOQFvz6ocaB6Pqhoe/ag=", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", "dev": true } } @@ -10978,15 +11371,41 @@ "dev": true }, "har-validator": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", - "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", "dev": true, "requires": { - "ajv": "^6.5.5", + "ajv": "^6.12.3", "har-schema": "^2.0.0" + }, + "dependencies": { + "ajv": { + "version": "6.12.4", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.4.tgz", + "integrity": "sha512-eienB2c9qVQs2KWexhkrdMLVDoIQCz5KSeLxwg9Lzk4DOfBtIK9PQwwufcsn1jjGuf9WZmqPMbGxOzfcuphJCQ==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + } } }, + "hard-rejection": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", + "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==", + "dev": true + }, "harmony-reflect": { "version": "1.6.1", "resolved": "https://registry.yarnpkg.com/harmony-reflect/-/harmony-reflect-1.6.1.tgz", @@ -11118,11 +11537,11 @@ } }, "hoist-non-react-statics": { - "version": "3.2.1", - "resolved": "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.2.1.tgz", - "integrity": "sha1-wJwFVchLOKft5pErYe/dr9bnXh4=", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", "requires": { - "react-is": "^16.3.2" + "react-is": "^16.7.0" } }, "hoopy": { @@ -11231,9 +11650,9 @@ } }, "html-tags": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-2.0.0.tgz", - "integrity": "sha1-ELMKOGCF9Dzt41PMj6fLDe7qZos=", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.1.0.tgz", + "integrity": "sha512-1qYz89hW3lFDEazhjW0yVAV87lw8lVkrJocr72XmBkMKsoSVJCQx3W8BXsC7hO2qAt8BoVjYjtAcZ9perqGnNg==", "dev": true }, "html-webpack-plugin": { @@ -11442,9 +11861,9 @@ } }, "import-lazy": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-3.1.0.tgz", - "integrity": "sha512-8/gvXvX2JMn0F+CDlSC4l6kOmVaLOO3XLkksI7CI3Ud95KDYJuYur2b9P/PUt/i/pDAMd/DulQsNbbbmRRsDIQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-4.0.0.tgz", + "integrity": "sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==", "dev": true }, "import-local": { @@ -11464,9 +11883,9 @@ "dev": true }, "in-publish": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/in-publish/-/in-publish-2.0.0.tgz", - "integrity": "sha1-4g/146KvwmkDILbcVSaCqcf631E=", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/in-publish/-/in-publish-2.0.1.tgz", + "integrity": "sha512-oDM0kUSNFC31ShNxHKUyfZKy8ZeXZBWMjMdZHKLOk13uvT27VTL/QzRGfRUcevJhpkZAvlhPYuXkF7eNWrtyxQ==", "dev": true }, "indent-string": { @@ -11780,16 +12199,11 @@ "version": "2.2.4", "resolved": "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz", "integrity": "sha1-YQ88ksk1nOHbYW5TgAjSP/NRWOY=", + "dev": true, "requires": { "loose-envify": "^1.0.0" } }, - "invert-kv": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", - "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", - "dev": true - }, "ip": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", @@ -11832,9 +12246,9 @@ } }, "is-alphabetical": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.3.tgz", - "integrity": "sha512-eEMa6MKpHFzw38eKm56iNNi6GJ7lf6aLLio7Kr23sJPAECscgRtZvOBYybejWDQ2bM949Y++61PY+udzj5QMLA==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz", + "integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==", "dev": true }, "is-alphanumeric": { @@ -11844,9 +12258,9 @@ "dev": true }, "is-alphanumerical": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.3.tgz", - "integrity": "sha512-A1IGAPO5AW9vSh7omxIlOGwIqEvpW/TA+DksVOPM5ODuxKlZS09+TEM1E3275lJqO2oJ38vDpeAL3DCIiHE6eA==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz", + "integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==", "dev": true, "requires": { "is-alphabetical": "^1.0.0", @@ -11948,9 +12362,9 @@ "dev": true }, "is-decimal": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.3.tgz", - "integrity": "sha512-bvLSwoDg2q6Gf+E2LEPiklHZxxiSi3XAh4Mav65mKqTfCO1HM3uBs24TjEH8iJX3bbDdLXKJXBTmGzuTUuAEjQ==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz", + "integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==", "dev": true }, "is-descriptor": { @@ -11982,8 +12396,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.1.1.tgz", "integrity": "sha512-ZOoqiXfEwtGknTiuDEy8pN2CfE3TxMHprvNer1mXiqwkOT77Rw3YVrUQ52EqAOU3QAWDQ+bQdx7HJzrv7LS2Hw==", - "dev": true, - "optional": true + "dev": true }, "is-extendable": { "version": "0.1.1", @@ -12028,9 +12441,9 @@ } }, "is-hexadecimal": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.3.tgz", - "integrity": "sha512-zxQ9//Q3D/34poZf8fiy3m3XVpbQc7ren15iKqrTtLPwkPD/t3Scy9Imp63FujULGxuK0ZlCwoo5xNpktFgbOA==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz", + "integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==", "dev": true }, "is-installed-globally": { @@ -12178,12 +12591,6 @@ "integrity": "sha1-ilkRfZMt4d4A8kX83TnOQ/HpOaY=", "dev": true }, - "is-supported-regexp-flag": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-supported-regexp-flag/-/is-supported-regexp-flag-1.0.1.tgz", - "integrity": "sha512-3vcJecUUrpgCqc/ca0aWeNu64UGgxcvO60K/Fkr1N6RSvfGCTU60UKN68JDmKokgba0rFFJs12EnzOQa14ubKQ==", - "dev": true - }, "is-svg": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-svg/-/is-svg-3.0.0.tgz", @@ -12215,9 +12622,9 @@ "dev": true }, "is-whitespace-character": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-whitespace-character/-/is-whitespace-character-1.0.3.tgz", - "integrity": "sha512-SNPgMLz9JzPccD3nPctcj8sZlX9DAMJSKH8bP7Z6bohCwuNgX8xbWr1eTAYXX9Vpi/aSn8Y1akL9WgM3t43YNQ==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-whitespace-character/-/is-whitespace-character-1.0.4.tgz", + "integrity": "sha512-SDweEzfIZM0SJV0EUga669UTKlmL0Pq8Lno0QDQsPnvECB3IM2aP0gdx5TrU0A01MAPfViaZiI2V1QMZLaKK5w==", "dev": true }, "is-windows": { @@ -12227,9 +12634,9 @@ "dev": true }, "is-word-character": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-word-character/-/is-word-character-1.0.3.tgz", - "integrity": "sha512-0wfcrFgOOOBdgRNT9H33xe6Zi6yhX/uoc4U8NBZGeQQB0ctU1dnlNTyL9JM2646bHDTpsDm1Brb3VPoCIMrd/A==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-word-character/-/is-word-character-1.0.4.tgz", + "integrity": "sha512-5SMO8RVennx3nZrqtKwCGyyetPE9VDba5ugvKLaD4KopPG5kR4mQ7tNt/r7feL5yt5h3lpuBbIUmCOG2eSzXHA==", "dev": true }, "is-wsl": { @@ -12330,9 +12737,9 @@ "dev": true }, "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { "has-flag": "^4.0.0" @@ -12385,14 +12792,14 @@ } }, "jest": { - "version": "26.4.1", - "resolved": "https://registry.npmjs.org/jest/-/jest-26.4.1.tgz", - "integrity": "sha512-q+az+ZXFOTxTlD6BRIMcZC+a33O9lsryV4Wo9gU4D/AI+Y6KKgVRCmyzpc4H2gWv0rn45lACukmMS2uSB7e1LA==", + "version": "26.4.2", + "resolved": "https://registry.npmjs.org/jest/-/jest-26.4.2.tgz", + "integrity": "sha512-LLCjPrUh98Ik8CzW8LLVnSCfLaiY+wbK53U7VxnFSX7Q+kWC4noVeDvGWIFw0Amfq1lq2VfGm7YHWSLBV62MJw==", "dev": true, "requires": { - "@jest/core": "^26.4.1", + "@jest/core": "^26.4.2", "import-local": "^3.0.2", - "jest-cli": "^26.4.1" + "jest-cli": "^26.4.2" }, "dependencies": { "ansi-styles": { @@ -12472,12 +12879,12 @@ } }, "jest-cli": { - "version": "26.4.1", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-26.4.1.tgz", - "integrity": "sha512-c6px+IOO0OsZ7X/uSr65wcjZnd7NYNUDWFT5OETyCnJRkkwoTER7gneRDrwgr3Ex5+gCGO7D/IMWxUHB/L624A==", + "version": "26.4.2", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-26.4.2.tgz", + "integrity": "sha512-zb+lGd/SfrPvoRSC/0LWdaWCnscXc1mGYW//NP4/tmBvRPT3VntZ2jtKUONsRi59zc5JqmsSajA9ewJKFYp8Cw==", "dev": true, "requires": { - "@jest/core": "^26.4.1", + "@jest/core": "^26.4.2", "@jest/test-result": "^26.3.0", "@jest/types": "^26.3.0", "chalk": "^4.0.0", @@ -12485,9 +12892,9 @@ "graceful-fs": "^4.2.4", "import-local": "^3.0.2", "is-ci": "^2.0.0", - "jest-config": "^26.4.1", + "jest-config": "^26.4.2", "jest-util": "^26.3.0", - "jest-validate": "^26.4.0", + "jest-validate": "^26.4.2", "prompts": "^2.0.1", "yargs": "^15.3.1" } @@ -12541,9 +12948,9 @@ "dev": true }, "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { "has-flag": "^4.0.0" @@ -12662,13 +13069,13 @@ } }, "jest-config": { - "version": "26.4.1", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-26.4.1.tgz", - "integrity": "sha512-0kUnVceEax0sYN+wdkNYF7fxjYKbsvmKmjVWwJvsSYA2p94bIL6wSy3oehewev7L9Dp/FDZFhmc9dyOoavdT6A==", + "version": "26.4.2", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-26.4.2.tgz", + "integrity": "sha512-QBf7YGLuToiM8PmTnJEdRxyYy3mHWLh24LJZKVdXZ2PNdizSe1B/E8bVm+HYcjbEzGuVXDv/di+EzdO/6Gq80A==", "dev": true, "requires": { "@babel/core": "^7.1.0", - "@jest/test-sequencer": "^26.4.1", + "@jest/test-sequencer": "^26.4.2", "@jest/types": "^26.3.0", "babel-jest": "^26.3.0", "chalk": "^4.0.0", @@ -12678,13 +13085,13 @@ "jest-environment-jsdom": "^26.3.0", "jest-environment-node": "^26.3.0", "jest-get-type": "^26.3.0", - "jest-jasmine2": "^26.4.1", + "jest-jasmine2": "^26.4.2", "jest-regex-util": "^26.0.0", "jest-resolve": "^26.4.0", "jest-util": "^26.3.0", - "jest-validate": "^26.4.0", + "jest-validate": "^26.4.2", "micromatch": "^4.0.2", - "pretty-format": "^26.4.0" + "pretty-format": "^26.4.2" }, "dependencies": { "ansi-styles": { @@ -12763,9 +13170,9 @@ } }, "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { "has-flag": "^4.0.0" @@ -12783,15 +13190,15 @@ } }, "jest-diff": { - "version": "26.4.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-26.4.0.tgz", - "integrity": "sha512-wwC38HlOW+iTq6j5tkj/ZamHn6/nrdcEOc/fKaVILNtN2NLWGdkfRaHWwfNYr5ehaLvuoG2LfCZIcWByVj0gjg==", + "version": "26.4.2", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-26.4.2.tgz", + "integrity": "sha512-6T1XQY8U28WH0Z5rGpQ+VqZSZz8EN8rZcBtfvXaOkbwxIEeRre6qnuZQlbY1AJ4MKDxQF8EkrCvK+hL/VkyYLQ==", "dev": true, "requires": { "chalk": "^4.0.0", "diff-sequences": "^26.3.0", "jest-get-type": "^26.3.0", - "pretty-format": "^26.4.0" + "pretty-format": "^26.4.2" }, "dependencies": { "ansi-styles": { @@ -12830,9 +13237,9 @@ "dev": true }, "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { "has-flag": "^4.0.0" @@ -12850,16 +13257,16 @@ } }, "jest-each": { - "version": "26.4.0", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-26.4.0.tgz", - "integrity": "sha512-+cyBh1ehs6thVT/bsZVG+WwmRn2ix4Q4noS9yLZgM10yGWPW12/TDvwuOV2VZXn1gi09/ZwJKJWql6YW1C9zNw==", + "version": "26.4.2", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-26.4.2.tgz", + "integrity": "sha512-p15rt8r8cUcRY0Mvo1fpkOGYm7iI8S6ySxgIdfh3oOIv+gHwrHTy5VWCGOecWUhDsit4Nz8avJWdT07WLpbwDA==", "dev": true, "requires": { "@jest/types": "^26.3.0", "chalk": "^4.0.0", "jest-get-type": "^26.3.0", "jest-util": "^26.3.0", - "pretty-format": "^26.4.0" + "pretty-format": "^26.4.2" }, "dependencies": { "ansi-styles": { @@ -12898,9 +13305,9 @@ "dev": true }, "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { "has-flag": "^4.0.0" @@ -13066,9 +13473,9 @@ } }, "jest-jasmine2": { - "version": "26.4.1", - "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-26.4.1.tgz", - "integrity": "sha512-GMPqJXyAWpohCg4wfA82lwac65lmgANH4/rOhNNaAN9yjInMAeMExQcWE1xb3fcCgLwibqeAuqVrV83oQl+szg==", + "version": "26.4.2", + "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-26.4.2.tgz", + "integrity": "sha512-z7H4EpCldHN1J8fNgsja58QftxBSL+JcwZmaXIvV9WKIM+x49F4GLHu/+BQh2kzRKHAgaN/E82od+8rTOBPyPA==", "dev": true, "requires": { "@babel/traverse": "^7.1.0", @@ -13079,15 +13486,15 @@ "@types/node": "*", "chalk": "^4.0.0", "co": "^4.6.0", - "expect": "^26.4.1", + "expect": "^26.4.2", "is-generator-fn": "^2.0.0", - "jest-each": "^26.4.0", - "jest-matcher-utils": "^26.4.1", + "jest-each": "^26.4.2", + "jest-matcher-utils": "^26.4.2", "jest-message-util": "^26.3.0", - "jest-runtime": "^26.4.1", - "jest-snapshot": "^26.4.1", + "jest-runtime": "^26.4.2", + "jest-snapshot": "^26.4.2", "jest-util": "^26.3.0", - "pretty-format": "^26.4.0", + "pretty-format": "^26.4.2", "throat": "^5.0.0" }, "dependencies": { @@ -13127,9 +13534,9 @@ "dev": true }, "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { "has-flag": "^4.0.0" @@ -13138,25 +13545,25 @@ } }, "jest-leak-detector": { - "version": "26.4.0", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-26.4.0.tgz", - "integrity": "sha512-7EXKKEKnAWUPyiVtGZzJflbPOtYUdlNoevNVOkAcPpdR8xWiYKPGNGA6sz25S+8YhZq3rmkQJYAh3/P0VnoRwA==", + "version": "26.4.2", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-26.4.2.tgz", + "integrity": "sha512-akzGcxwxtE+9ZJZRW+M2o+nTNnmQZxrHJxX/HjgDaU5+PLmY1qnQPnMjgADPGCRPhB+Yawe1iij0REe+k/aHoA==", "dev": true, "requires": { "jest-get-type": "^26.3.0", - "pretty-format": "^26.4.0" + "pretty-format": "^26.4.2" } }, "jest-matcher-utils": { - "version": "26.4.1", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-26.4.1.tgz", - "integrity": "sha512-nmHWaOz54R/w6zJju5tuW0bw6+m38Rb1jnDKehKM/bOngDDL0UwtN634cRxpFoUNVRUrX8Wa0Z34xq/f8iuP5A==", + "version": "26.4.2", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-26.4.2.tgz", + "integrity": "sha512-KcbNqWfWUG24R7tu9WcAOKKdiXiXCbMvQYT6iodZ9k1f7065k0keUOW6XpJMMvah+hTfqkhJhRXmA3r3zMAg0Q==", "dev": true, "requires": { "chalk": "^4.0.0", - "jest-diff": "^26.4.0", + "jest-diff": "^26.4.2", "jest-get-type": "^26.3.0", - "pretty-format": "^26.4.0" + "pretty-format": "^26.4.2" }, "dependencies": { "ansi-styles": { @@ -13195,9 +13602,9 @@ "dev": true }, "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { "has-flag": "^4.0.0" @@ -13296,16 +13703,10 @@ "picomatch": "^2.0.5" } }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - }, "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { "has-flag": "^4.0.0" @@ -13333,9 +13734,9 @@ } }, "jest-pnp-resolver": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.1.tgz", - "integrity": "sha512-pgFw2tm54fzgYvc/OHrnysABEObZCUNFnhjoRjaVOCN8NYc032/gVjPaHD4Aq6ApkSieWtfKAFQtmDKAmhupnQ==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz", + "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==", "dev": true }, "jest-regex-util": { @@ -13529,20 +13930,20 @@ } }, "jest-resolve-dependencies": { - "version": "26.4.1", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-26.4.1.tgz", - "integrity": "sha512-Gx4JfQ1k/hGb4lqVOOx8TPOkNtyJIQSHcJU68pB+sdyDJi9rbMxD1XXiYyaEq9WXufiZo90k9GTK6z6a5m0SQw==", + "version": "26.4.2", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-26.4.2.tgz", + "integrity": "sha512-ADHaOwqEcVc71uTfySzSowA/RdxUpCxhxa2FNLiin9vWLB1uLPad3we+JSSROq5+SrL9iYPdZZF8bdKM7XABTQ==", "dev": true, "requires": { "@jest/types": "^26.3.0", "jest-regex-util": "^26.0.0", - "jest-snapshot": "^26.4.1" + "jest-snapshot": "^26.4.2" } }, "jest-runner": { - "version": "26.4.1", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-26.4.1.tgz", - "integrity": "sha512-QcKwn1YNlzFumTtFsocETgIm13KNt2X8sae4wcqsF3JnxGUcYYUGBstCQhtAG4fKD/TKThHkgE/ZgQVKipj7oA==", + "version": "26.4.2", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-26.4.2.tgz", + "integrity": "sha512-FgjDHeVknDjw1gRAYaoUoShe1K3XUuFMkIaXbdhEys+1O4bEJS8Avmn4lBwoMfL8O5oFTdWYKcf3tEJyyYyk8g==", "dev": true, "requires": { "@jest/console": "^26.3.0", @@ -13554,13 +13955,13 @@ "emittery": "^0.7.1", "exit": "^0.1.2", "graceful-fs": "^4.2.4", - "jest-config": "^26.4.1", + "jest-config": "^26.4.2", "jest-docblock": "^26.0.0", "jest-haste-map": "^26.3.0", - "jest-leak-detector": "^26.4.0", + "jest-leak-detector": "^26.4.2", "jest-message-util": "^26.3.0", "jest-resolve": "^26.4.0", - "jest-runtime": "^26.4.1", + "jest-runtime": "^26.4.2", "jest-util": "^26.3.0", "jest-worker": "^26.3.0", "source-map-support": "^0.5.6", @@ -13620,9 +14021,9 @@ } }, "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { "has-flag": "^4.0.0" @@ -13631,15 +14032,15 @@ } }, "jest-runtime": { - "version": "26.4.1", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-26.4.1.tgz", - "integrity": "sha512-zXPQBS4iL/CEZtDfX+rDz+oZ/inQK/EYOeVt3uDWu8kwSdP/Cw4yOZtCTPApeNsGtZy6X5WQ1U+fyagN1B/Qkw==", + "version": "26.4.2", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-26.4.2.tgz", + "integrity": "sha512-4Pe7Uk5a80FnbHwSOk7ojNCJvz3Ks2CNQWT5Z7MJo4tX0jb3V/LThKvD9tKPNVNyeMH98J/nzGlcwc00R2dSHQ==", "dev": true, "requires": { "@jest/console": "^26.3.0", "@jest/environment": "^26.3.0", "@jest/fake-timers": "^26.3.0", - "@jest/globals": "^26.4.1", + "@jest/globals": "^26.4.2", "@jest/source-map": "^26.3.0", "@jest/test-result": "^26.3.0", "@jest/transform": "^26.3.0", @@ -13650,15 +14051,15 @@ "exit": "^0.1.2", "glob": "^7.1.3", "graceful-fs": "^4.2.4", - "jest-config": "^26.4.1", + "jest-config": "^26.4.2", "jest-haste-map": "^26.3.0", "jest-message-util": "^26.3.0", "jest-mock": "^26.3.0", "jest-regex-util": "^26.0.0", "jest-resolve": "^26.4.0", - "jest-snapshot": "^26.4.1", + "jest-snapshot": "^26.4.2", "jest-util": "^26.3.0", - "jest-validate": "^26.4.0", + "jest-validate": "^26.4.2", "slash": "^3.0.0", "strip-bom": "^4.0.0", "yargs": "^15.3.1" @@ -13705,12 +14106,6 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - }, "strip-bom": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", @@ -13718,9 +14113,9 @@ "dev": true }, "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { "has-flag": "^4.0.0" @@ -13747,25 +14142,25 @@ } }, "jest-snapshot": { - "version": "26.4.1", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-26.4.1.tgz", - "integrity": "sha512-5DsxbSSuYA8rZ/ynO+l5J65wSIyzDB2AXjuIvep90YmtslrROqDtba2hBgq1Cj6L6A0j/jv6h8JydEe2WYPM/g==", + "version": "26.4.2", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-26.4.2.tgz", + "integrity": "sha512-N6Uub8FccKlf5SBFnL2Ri/xofbaA68Cc3MGjP/NuwgnsvWh+9hLIR/DhrxbSiKXMY9vUW5dI6EW1eHaDHqe9sg==", "dev": true, "requires": { "@babel/types": "^7.0.0", "@jest/types": "^26.3.0", "@types/prettier": "^2.0.0", "chalk": "^4.0.0", - "expect": "^26.4.1", + "expect": "^26.4.2", "graceful-fs": "^4.2.4", - "jest-diff": "^26.4.0", + "jest-diff": "^26.4.2", "jest-get-type": "^26.3.0", "jest-haste-map": "^26.3.0", - "jest-matcher-utils": "^26.4.1", + "jest-matcher-utils": "^26.4.2", "jest-message-util": "^26.3.0", "jest-resolve": "^26.4.0", "natural-compare": "^1.4.0", - "pretty-format": "^26.4.0", + "pretty-format": "^26.4.2", "semver": "^7.3.2" }, "dependencies": { @@ -13817,9 +14212,9 @@ "dev": true }, "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { "has-flag": "^4.0.0" @@ -13952,9 +14347,9 @@ } }, "jest-validate": { - "version": "26.4.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-26.4.0.tgz", - "integrity": "sha512-t56Z/FRMrLP6mpmje7/YgHy0wOzcuc6i3LBXz6kjmsUWYN62OuMdC86Vg9/dX59SvyitSqqegOrx+h7BkNXeaQ==", + "version": "26.4.2", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-26.4.2.tgz", + "integrity": "sha512-blft+xDX7XXghfhY0mrsBCYhX365n8K5wNDC4XAcNKqqjEzsRUSXP44m6PL0QJEW2crxQFLLztVnJ4j7oPlQrQ==", "dev": true, "requires": { "@jest/types": "^26.3.0", @@ -13962,7 +14357,7 @@ "chalk": "^4.0.0", "jest-get-type": "^26.3.0", "leven": "^3.1.0", - "pretty-format": "^26.4.0" + "pretty-format": "^26.4.2" }, "dependencies": { "ansi-styles": { @@ -14007,9 +14402,9 @@ "dev": true }, "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { "has-flag": "^4.0.0" @@ -14068,9 +14463,9 @@ "dev": true }, "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { "has-flag": "^4.0.0" @@ -14100,9 +14495,9 @@ } }, "js-base64": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.5.1.tgz", - "integrity": "sha512-M7kLczedRMYX4L8Mdh4MzyAMM9O5osx+4FcOQuTvr3A9F2D9S5JXheN0ewNbrvK2UatkTRhL5ejGmGSjNMiZuw==", + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.6.4.tgz", + "integrity": "sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ==", "dev": true }, "js-levenshtein": { @@ -14171,52 +14566,6 @@ "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==", "dev": true - }, - "qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", - "dev": true - }, - "request": { - "version": "2.88.2", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", - "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", - "dev": true, - "requires": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.3", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.5.0", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" - }, - "dependencies": { - "tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "dev": true, - "requires": { - "psl": "^1.1.28", - "punycode": "^2.1.1" - } - } - } } } }, @@ -14232,6 +14581,12 @@ "integrity": "sha1-u4Z8+zRQ5pEHwTHRxRS6s9yLyqk=", "dev": true }, + "json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, "json-schema": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", @@ -14338,10 +14693,16 @@ "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", "dev": true }, + "klona": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.3.tgz", + "integrity": "sha512-CgPOT3ZadDpXxKcfV56lEQ9OQSZ42Mk26gnozI+uN/k39vzD8toUhRQoqsX0m9Q3eMPEfsLWmtyUpK/yqST4yg==", + "dev": true + }, "known-css-properties": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.11.0.tgz", - "integrity": "sha512-bEZlJzXo5V/ApNNa5z375mJC6Nrz4vG43UgcSCrg2OHC+yuB6j0iDSrY7RQ/+PRofFB03wNIIt9iXIVLr4wc7w==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.19.0.tgz", + "integrity": "sha512-eYboRV94Vco725nKMlpkn3nV2+96p9c3gKXRsYqAJSswSENvBhN7n5L+uDhY58xQa0UukWsDMTGELzmD8Q+wTA==", "dev": true }, "language-subtag-registry": { @@ -14384,19 +14745,10 @@ "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=", "dev": true }, - "lcid": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", - "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", - "dev": true, - "requires": { - "invert-kv": "^1.0.0" - } - }, "leaflet": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.5.1.tgz", - "integrity": "sha512-ekM9KAeG99tYisNBg0IzEywAlp0hYI5XRipsqRXyRTeuU8jcuntilpp+eFf5gaE0xubc9RuSNIVtByEKwqFV0w==" + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.7.1.tgz", + "integrity": "sha512-/xwPEBidtg69Q3HlqPdU3DnrXQOvQU/CCHA1tcDQVzOwm91YMYaILjNp7L4Eaw5Z4sOYdbBz6koWyibppd8Zqw==" }, "leven": { "version": "3.1.0", @@ -14654,12 +15006,58 @@ "dev": true }, "log-symbols": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", - "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz", + "integrity": "sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==", "dev": true, "requires": { - "chalk": "^2.0.1" + "chalk": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "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" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } } }, "log4js": { @@ -14699,9 +15097,9 @@ "dev": true }, "longest-streak": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-2.0.3.tgz", - "integrity": "sha512-9lz5IVdpwsKLMzQi0MQ+oD9EA0mIGcWYP7jXMTZVXP8D42PwuAk+M/HBFYQoxt1G5OR8m7aSIgb1UymfWGBWEw==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-2.0.4.tgz", + "integrity": "sha512-vM6rUVCVUJJt33bnmHiZEvr7wPT78ztX7rojL+LW51bHtLh6HTjx84LA5W4+oa6aKEJA7jJu5LR6vQRBpA5DVg==", "dev": true }, "loose-envify": { @@ -14813,16 +15211,19 @@ } }, "markdown-escapes": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/markdown-escapes/-/markdown-escapes-1.0.3.tgz", - "integrity": "sha512-XUi5HJhhV5R74k8/0H2oCbCiYf/u4cO/rX8tnGkRvrqhsr5BRNU6Mg0yt/8UIx1iIS8220BNJsDb7XnILhLepw==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/markdown-escapes/-/markdown-escapes-1.0.4.tgz", + "integrity": "sha512-8z4efJYk43E0upd0NbVXwgSTQs6cT3T06etieCMEg7dRbzCbxUCK/GHlX8mhHRDcp+OLlHkPKsvqQTCvsRl2cg==", "dev": true }, "markdown-table": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-1.1.3.tgz", - "integrity": "sha512-1RUZVgQlpJSPWYbFSpmudq5nHY1doEIv89gBtF0s4gW1GF2XorxcA/70M5vq7rLv0a6mhOUccRsqkwhwLCIQ2Q==", - "dev": true + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-2.0.0.tgz", + "integrity": "sha512-Ezda85ToJUBhM6WGaG6veasyym+Tbs3cMAw/ZhOPqXiYsr0jgocBV3j3nx+4lk47plLlIqjwuTm/ywVI+zjJ/A==", + "dev": true, + "requires": { + "repeat-string": "^1.0.0" + } }, "material-colors": { "version": "1.2.6", @@ -14830,9 +15231,9 @@ "integrity": "sha512-6qE4B9deFBIa9YSpOc9O0Sgc43zTeVYbgDT5veRKSlB2+ZuHNoVVxA1L/ckMUayV9Ay9y7Z/SZCLcGteW9i7bg==" }, "mathml-tag-names": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/mathml-tag-names/-/mathml-tag-names-2.1.1.tgz", - "integrity": "sha512-pWB896KPGSGkp1XtyzRBftpTzwSOL0Gfk0wLvxt4f2mgzjY19o0LxJ3U25vNWTzsh7da+KTbuXQoQ3lOJZ8WHw==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz", + "integrity": "sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==", "dev": true }, "md5.js": { @@ -14847,12 +15248,12 @@ } }, "mdast-util-compact": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/mdast-util-compact/-/mdast-util-compact-1.0.3.tgz", - "integrity": "sha512-nRiU5GpNy62rZppDKbLwhhtw5DXoFMqw9UNZFmlPsNaQCZ//WLjGKUwWMdJrUH+Se7UvtO2gXtAMe0g/N+eI5w==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-compact/-/mdast-util-compact-2.0.1.tgz", + "integrity": "sha512-7GlnT24gEwDrdAwEHrU4Vv5lLWrEer4KOkAiKT9nYstsTad7Oc1TwqT2zIMKRdZF7cTuaf+GA1E4Kv7jJh8mPA==", "dev": true, "requires": { - "unist-util-visit": "^1.1.0" + "unist-util-visit": "^2.0.0" } }, "mdn-data": { @@ -15050,28 +15451,33 @@ "integrity": "sha1-ggyGo5M0ZA6ZUWkovQP8qIBX0CI=", "dev": true }, + "min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true + }, "mini-create-react-context": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/mini-create-react-context/-/mini-create-react-context-0.3.2.tgz", - "integrity": "sha512-2v+OeetEyliMt5VHMXsBhABoJ0/M4RCe7fatd/fBy6SMiKazUSEt3gxxypfnk2SHMkdBYvorHRoQxuGoiwbzAw==", + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/mini-create-react-context/-/mini-create-react-context-0.4.0.tgz", + "integrity": "sha512-b0TytUgFSbgFJGzJqXPKCFCBWigAjpjo+Fl7Vf7ZbKRDptszpppKxXH6DRXEABZ/gcEQczeb0iZ7JvL8e8jjCA==", "requires": { - "@babel/runtime": "^7.4.0", - "gud": "^1.0.0", - "tiny-warning": "^1.0.2" + "@babel/runtime": "^7.5.5", + "tiny-warning": "^1.0.3" }, "dependencies": { "@babel/runtime": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.6.2.tgz", - "integrity": "sha512-EXxN64agfUqqIGeEjI5dL5z0Sw0ZwWo1mLTi4mQowCZ42O59b7DRpZAnTC6OqdF28wMBMFKNb/4uFGrVaigSpg==", + "version": "7.11.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", + "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", "requires": { - "regenerator-runtime": "^0.13.2" + "regenerator-runtime": "^0.13.4" } }, "regenerator-runtime": { - "version": "0.13.3", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz", - "integrity": "sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw==" + "version": "0.13.7", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", + "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" } } }, @@ -15115,13 +15521,22 @@ "dev": true }, "minimist-options": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-3.0.2.tgz", - "integrity": "sha512-FyBrT/d0d4+uiZRbqznPXqw3IpZZG3gl3wKWiX784FycUKVwBt0uLBFkQrtE4tZOrgo78nZp2jnKz3L65T5LdQ==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz", + "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==", "dev": true, "requires": { "arrify": "^1.0.1", - "is-plain-obj": "^1.1.0" + "is-plain-obj": "^1.1.0", + "kind-of": "^6.0.3" + }, + "dependencies": { + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true + } } }, "minipass": { @@ -15235,9 +15650,9 @@ } }, "moment": { - "version": "2.24.0", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz", - "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==" + "version": "2.27.0", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.27.0.tgz", + "integrity": "sha512-al0MUK7cpIcglMv3YF13qSgdAIqxHTO7brRtaz3DlSULbqfazqkc5kEjNrLDOM7fsjshoFIihnU8snrP7zUvhQ==" }, "moo": { "version": "0.5.1", @@ -15262,7 +15677,8 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true }, "multicast-dns": { "version": "6.2.3", @@ -15578,9 +15994,9 @@ } }, "node-sass": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.12.0.tgz", - "integrity": "sha512-A1Iv4oN+Iel6EPv77/HddXErL2a+gZ4uBeZUy+a8O35CFYTXhgA8MgLCWBtwpGZdCvTvQ9d+bQxX/QC36GDPpQ==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.14.1.tgz", + "integrity": "sha512-sjCuOlvGyCJS40R8BscF5vhVlQjNN069NtQ1gSxyK1u9iqvn6tf7O1R4GNowVZfiZUCRt5MmMs1xd+4V/7Yr0g==", "dev": true, "requires": { "async-foreach": "^0.1.3", @@ -15590,14 +16006,14 @@ "get-stdin": "^4.0.1", "glob": "^7.0.3", "in-publish": "^2.0.0", - "lodash": "^4.17.11", + "lodash": "^4.17.15", "meow": "^3.7.0", "mkdirp": "^0.5.1", "nan": "^2.13.2", "node-gyp": "^3.8.0", "npmlog": "^4.0.0", "request": "^2.88.0", - "sass-graph": "^2.2.4", + "sass-graph": "2.2.5", "stdout-stream": "^1.4.0", "true-case-path": "^1.0.2" }, @@ -15631,10 +16047,16 @@ "which": "^1.2.9" } }, + "lodash": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", + "dev": true + }, "nan": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", - "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==", + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.1.tgz", + "integrity": "sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw==", "dev": true }, "supports-color": { @@ -15999,12 +16421,24 @@ } }, "open": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/open/-/open-6.4.0.tgz", - "integrity": "sha512-IFenVPgF70fSm1keSd2iDBIDIBZkroLeuffXq+wKTzTJlBpesFWojV9lb8mzOfaAzM1sr7HQHuO0vtV0zYekGg==", + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/open/-/open-7.2.1.tgz", + "integrity": "sha512-xbYCJib4spUdmcs0g/2mK1nKo/jO2T7INClWd/beL7PFkXRWgr8B23ssDHX/USPn2M2IjDR5UdpYs6I67SnTSA==", "dev": true, "requires": { - "is-wsl": "^1.1.0" + "is-docker": "^2.0.0", + "is-wsl": "^2.1.1" + }, + "dependencies": { + "is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "requires": { + "is-docker": "^2.0.0" + } + } } }, "opn": { @@ -16061,15 +16495,6 @@ "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", "dev": true }, - "os-locale": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", - "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", - "dev": true, - "requires": { - "lcid": "^1.0.0" - } - }, "os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", @@ -16219,9 +16644,9 @@ } }, "parse-entities": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-1.2.2.tgz", - "integrity": "sha512-NzfpbxW/NPrzZ/yYSoQxyqUZMZXIdCfE0OIN4ESsnptHJECoUk3FZktxNuzQf4tjt5UEopnxpYJbvYuxIFDdsg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-2.0.0.tgz", + "integrity": "sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==", "dev": true, "requires": { "character-entities": "^1.0.0", @@ -16394,57 +16819,12 @@ } }, "pkg-up": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-2.0.0.tgz", - "integrity": "sha1-yBmscoBZpGHKscOImivjxJoATX8=", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-3.1.0.tgz", + "integrity": "sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==", "dev": true, "requires": { - "find-up": "^2.1.0" - }, - "dependencies": { - "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", - "dev": true, - "requires": { - "locate-path": "^2.0.0" - } - }, - "locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", - "dev": true, - "requires": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" - } - }, - "p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", - "dev": true, - "requires": { - "p-try": "^1.0.0" - } - }, - "p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", - "dev": true, - "requires": { - "p-limit": "^1.1.0" - } - }, - "p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", - "dev": true - } + "find-up": "^3.0.0" } }, "pnp-webpack-plugin": { @@ -16900,15 +17280,6 @@ } } }, - "postcss-jsx": { - "version": "0.36.3", - "resolved": "https://registry.npmjs.org/postcss-jsx/-/postcss-jsx-0.36.3.tgz", - "integrity": "sha512-yV8Ndo6KzU8eho5mCn7LoLUGPkXrRXRjhMpX4AaYJ9wLJPv099xbtpbRQ8FrPnzVxb/cuMebbPR7LweSt+hTfA==", - "dev": true, - "requires": { - "@babel/core": ">=7.2.2" - } - }, "postcss-lab-function": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/postcss-lab-function/-/postcss-lab-function-2.0.1.tgz", @@ -16974,16 +17345,6 @@ "postcss": "^7.0.2" } }, - "postcss-markdown": { - "version": "0.36.0", - "resolved": "https://registry.npmjs.org/postcss-markdown/-/postcss-markdown-0.36.0.tgz", - "integrity": "sha512-rl7fs1r/LNSB2bWRhyZ+lM/0bwKv9fhl38/06gF6mKMo/NPnp55+K1dSTosSVjFZc0e1ppBlu+WT91ba0PMBfQ==", - "dev": true, - "requires": { - "remark": "^10.0.1", - "unist-util-find-all-after": "^1.0.2" - } - }, "postcss-media-minmax": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/postcss-media-minmax/-/postcss-media-minmax-4.0.0.tgz", @@ -17512,18 +17873,6 @@ "postcss": "^7.0.2" } }, - "postcss-reporter": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-reporter/-/postcss-reporter-6.0.1.tgz", - "integrity": "sha512-LpmQjfRWyabc+fRygxZjpRxfhRf9u/fdlKf4VHG4TSPbV2XNsuISzYW1KL+1aQzx53CAppa1bKG4APIB/DOXXw==", - "dev": true, - "requires": { - "chalk": "^2.4.1", - "lodash": "^4.17.11", - "log-symbols": "^2.2.0", - "postcss": "^7.0.7" - } - }, "postcss-resolve-nested-selector": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/postcss-resolve-nested-selector/-/postcss-resolve-nested-selector-0.1.1.tgz", @@ -17540,22 +17889,44 @@ } }, "postcss-sass": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/postcss-sass/-/postcss-sass-0.3.5.tgz", - "integrity": "sha512-B5z2Kob4xBxFjcufFnhQ2HqJQ2y/Zs/ic5EZbCywCkxKd756Q40cIQ/veRDwSrw1BF6+4wUgmpm0sBASqVi65A==", + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/postcss-sass/-/postcss-sass-0.4.4.tgz", + "integrity": "sha512-BYxnVYx4mQooOhr+zer0qWbSPYnarAy8ZT7hAQtbxtgVf8gy+LSLT/hHGe35h14/pZDTw1DsxdbrwxBN++H+fg==", "dev": true, "requires": { - "gonzales-pe": "^4.2.3", - "postcss": "^7.0.1" + "gonzales-pe": "^4.3.0", + "postcss": "^7.0.21" + }, + "dependencies": { + "postcss": { + "version": "7.0.32", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.32.tgz", + "integrity": "sha512-03eXong5NLnNCD05xscnGKGDZ98CyzoqPSMjOe6SuoQY7Z2hIj0Ld1g/O/UQRuOle2aRtiIRDg9tDcTGAkLfKw==", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } } }, "postcss-scss": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/postcss-scss/-/postcss-scss-2.0.0.tgz", - "integrity": "sha512-um9zdGKaDZirMm+kZFKKVsnKPF7zF7qBAtIfTSnZXD1jZ0JNZIxdB6TxQOjCnlSzLRInVl2v3YdBh/M881C4ug==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/postcss-scss/-/postcss-scss-2.1.1.tgz", + "integrity": "sha512-jQmGnj0hSGLd9RscFw9LyuSVAa5Bl1/KBPqG1NQw9w8ND55nY4ZEsdlVuYJvLPpV+y0nwTV5v/4rHPzZRihQbA==", "dev": true, "requires": { - "postcss": "^7.0.0" + "postcss": "^7.0.6" } }, "postcss-selector-matches": { @@ -17672,9 +18043,9 @@ } }, "pretty-format": { - "version": "26.4.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.4.0.tgz", - "integrity": "sha512-mEEwwpCseqrUtuMbrJG4b824877pM5xald3AkilJ47Po2YLr97/siejYQHqj2oDQBeJNbu+Q0qUuekJ8F0NAPg==", + "version": "26.4.2", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.4.2.tgz", + "integrity": "sha512-zK6Gd8zDsEiVydOCGLkoBoZuqv8VTiHyAbKznXe/gaph/DAeZOmit9yMfgIz5adIgAMMs5XfoYSwAX3jcCO1tA==", "dev": true, "requires": { "@jest/types": "^26.3.0", @@ -17815,9 +18186,9 @@ "dev": true }, "psl": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.4.0.tgz", - "integrity": "sha512-HZzqCGPecFLyoRj5HLfuDSKYTJkAfB5thKBIkRHtGjWwY7p1dAyveIbXIq4tO0KYfDF2tHqPUgY9SDnGm00uFw==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", + "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==", "dev": true }, "public-encrypt": { @@ -17880,9 +18251,9 @@ "dev": true }, "qs": { - "version": "6.9.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.0.tgz", - "integrity": "sha512-27RP4UotQORTpmNQDX8BHPukOnBP3p1uUJY5UnDhaJB+rMt9iMsok724XL+UHU23bEFOHRMQ2ZhI99qOWUMGFA==" + "version": "6.9.4", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.4.tgz", + "integrity": "sha512-A1kFqHekCTM7cz0udomYUoYNWjBebHm/5wzU/XqrBRBNWectVH0QIiN+NEcZ0Dte5hvzHwbr8+XQmguPhJ6WdQ==" }, "query-string": { "version": "4.3.4", @@ -17913,9 +18284,9 @@ "dev": true }, "quick-lru": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-1.1.0.tgz", - "integrity": "sha1-Q2CxfGETatOAeDl/8RQW4Ybc+7g=", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", + "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", "dev": true }, "raf": { @@ -17934,9 +18305,9 @@ "dev": true }, "ramda": { - "version": "0.26.1", - "resolved": "https://registry.yarnpkg.com/ramda/-/ramda-0.26.1.tgz", - "integrity": "sha1-jUE1HrgRHFU1Nhf8O7/62OTTXQY=" + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.27.1.tgz", + "integrity": "sha512-PgIdVpn5y5Yns8vqb8FzBUEYn98V3xcPgawAkkgj0YJ0qDsnHCiNmZYfOGMgOvoB0eWFLpYbhxUR3mxfDIMvpw==" }, "randexp": { "version": "0.4.6", @@ -18016,47 +18387,57 @@ } }, "react-app-polyfill": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/react-app-polyfill/-/react-app-polyfill-1.0.4.tgz", - "integrity": "sha512-5Vte6ki7jpNsNCUKaboyofAhmURmCn2Y6Hu7ydJ6Iu4dct1CIGoh/1FT7gUZKAbowVX2lxVPlijvp1nKxfAl4w==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/react-app-polyfill/-/react-app-polyfill-1.0.6.tgz", + "integrity": "sha512-OfBnObtnGgLGfweORmdZbyEz+3dgVePQBb3zipiaDsMHV1NpWm0rDFYIVXFV/AK+x4VIIfWHhrdMIeoTLyRr2g==", "dev": true, "requires": { - "core-js": "3.2.1", - "object-assign": "4.1.1", - "promise": "8.0.3", - "raf": "3.4.1", - "regenerator-runtime": "0.13.3", - "whatwg-fetch": "3.0.0" + "core-js": "^3.5.0", + "object-assign": "^4.1.1", + "promise": "^8.0.3", + "raf": "^3.4.1", + "regenerator-runtime": "^0.13.3", + "whatwg-fetch": "^3.0.0" }, "dependencies": { "core-js": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.2.1.tgz", - "integrity": "sha512-Qa5XSVefSVPRxy2XfUC13WbvqkxhkwB3ve+pgCQveNgYzbM/UxZeu1dcOX/xr4UmfUd+muuvsaxilQzCyUurMw==", + "version": "3.6.5", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.5.tgz", + "integrity": "sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA==", "dev": true }, "regenerator-runtime": { - "version": "0.13.3", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz", - "integrity": "sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw==", + "version": "0.13.7", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", + "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==", "dev": true } } }, "react-autosuggest": { - "version": "9.4.3", - "resolved": "https://registry.yarnpkg.com/react-autosuggest/-/react-autosuggest-9.4.3.tgz", - "integrity": "sha1-60aFJCKkgUSrnzn7VHAxkiLybHw=", + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/react-autosuggest/-/react-autosuggest-10.0.2.tgz", + "integrity": "sha512-ouI0RJDSgM1FBfK0ZmLC3qUqithIwPVTpnC4JQW4DeId3mH2JnZmkNNDKImhcMrxLbSQRpV/DfTLn0uCs4b27w==", "requires": { - "prop-types": "^15.5.10", - "react-autowhatever": "^10.1.2", - "shallow-equal": "^1.0.0" + "es6-promise": "^4.2.8", + "prop-types": "^15.7.2", + "react-autowhatever": "^10.2.1", + "react-themeable": "^1.1.0", + "section-iterator": "^2.0.0", + "shallow-equal": "^1.2.1" + }, + "dependencies": { + "es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==" + } } }, "react-autowhatever": { - "version": "10.2.0", - "resolved": "https://registry.yarnpkg.com/react-autowhatever/-/react-autowhatever-10.2.0.tgz", - "integrity": "sha1-vdB78Z3feKzbjOeuFirBO2RodKs=", + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/react-autowhatever/-/react-autowhatever-10.2.1.tgz", + "integrity": "sha512-5gQyoETyBH6GmuW1N1J81CuoAV+Djeg66DEo03xiZOl3WOwJHBP5LisKUvCGOakjrXU4M3hcIvCIqMBYGUmqOA==", "requires": { "prop-types": "^15.5.8", "react-themeable": "^1.1.0", @@ -18064,12 +18445,19 @@ } }, "react-chartjs-2": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-2.8.0.tgz", - "integrity": "sha512-BPpC+qfnh37DkcXvxRwA1rdD9rX/0AQrwru4VZTLofCCuZBwRsc7PbfxjilvoB6YlHhorwZu40YDWEQkoz7xfQ==", + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-2.10.0.tgz", + "integrity": "sha512-1MjWEkUn8LLFf6GVyYUOrruJTW3yVU5hlEJOwGj3MiokuC+jH/BahjWVGAMonbe9UYbEIUbd2Rn36iVlC0Hb7w==", "requires": { - "lodash": "^4.17.4", - "prop-types": "^15.5.8" + "lodash": "^4.17.19", + "prop-types": "^15.7.2" + }, + "dependencies": { + "lodash": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==" + } } }, "react-color": { @@ -18086,9 +18474,9 @@ } }, "react-copy-to-clipboard": { - "version": "5.0.1", - "resolved": "https://registry.yarnpkg.com/react-copy-to-clipboard/-/react-copy-to-clipboard-5.0.1.tgz", - "integrity": "sha1-jq4Qe7QAvnMTLtO2p7T7FWCQII4=", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/react-copy-to-clipboard/-/react-copy-to-clipboard-5.0.2.tgz", + "integrity": "sha512-/2t5mLMMPuN5GmdXo6TebFa8IoFxZ+KTDDqYhcDm0PhkgEzSxVvIX26G20s1EB02A4h2UZgwtfymZ3lGJm0OLg==", "requires": { "copy-to-clipboard": "^3", "prop-types": "^15.5.8" @@ -18117,102 +18505,299 @@ } }, "react-dev-utils": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-9.1.0.tgz", - "integrity": "sha512-X2KYF/lIGyGwP/F/oXgGDF24nxDA2KC4b7AFto+eqzc/t838gpSGiaU8trTqHXOohuLxxc5qi1eDzsl9ucPDpg==", + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-10.2.1.tgz", + "integrity": "sha512-XxTbgJnYZmxuPtY3y/UV0D8/65NKkmaia4rXzViknVnZeVlklSh8u6TnaEYPfAi/Gh1TP4mEOXHI6jQOPbeakQ==", "dev": true, "requires": { - "@babel/code-frame": "7.5.5", + "@babel/code-frame": "7.8.3", "address": "1.1.2", - "browserslist": "4.7.0", + "browserslist": "4.10.0", "chalk": "2.4.2", - "cross-spawn": "6.0.5", + "cross-spawn": "7.0.1", "detect-port-alt": "1.1.6", - "escape-string-regexp": "1.0.5", - "filesize": "3.6.1", - "find-up": "3.0.0", - "fork-ts-checker-webpack-plugin": "1.5.0", + "escape-string-regexp": "2.0.0", + "filesize": "6.0.1", + "find-up": "4.1.0", + "fork-ts-checker-webpack-plugin": "3.1.1", "global-modules": "2.0.0", "globby": "8.0.2", "gzip-size": "5.1.1", "immer": "1.10.0", - "inquirer": "6.5.0", + "inquirer": "7.0.4", "is-root": "2.1.0", "loader-utils": "1.2.3", - "open": "^6.3.0", - "pkg-up": "2.0.0", - "react-error-overlay": "^6.0.3", + "open": "^7.0.2", + "pkg-up": "3.1.0", + "react-error-overlay": "^6.0.7", "recursive-readdir": "2.2.2", "shell-quote": "1.7.2", - "sockjs-client": "1.4.0", - "strip-ansi": "5.2.0", + "strip-ansi": "6.0.0", "text-table": "0.2.0" }, "dependencies": { "@babel/code-frame": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.5.5.tgz", - "integrity": "sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", + "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", "dev": true, "requires": { - "@babel/highlight": "^7.0.0" + "@babel/highlight": "^7.8.3" } }, - "ansi-escapes": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", - "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", - "dev": true - }, - "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 - }, - "inquirer": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.5.0.tgz", - "integrity": "sha512-scfHejeG/lVZSpvCXpsB4j/wQNPM5JC8kiElOI0OUTwmc1RTpXr4H32/HOlQHcZiYl2z2VElwuCVDRG8vFmbnA==", + "@babel/highlight": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", + "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", "dev": true, "requires": { - "ansi-escapes": "^3.2.0", + "@babel/helper-validator-identifier": "^7.10.4", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "browserslist": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.10.0.tgz", + "integrity": "sha512-TpfK0TDgv71dzuTsEAlQiHeWQ/tiPqgNZVdv046fvNtBZrjbv2O3TsWCDU0AWGJJKCF/KsjNdLzR9hXOsh/CfA==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30001035", + "electron-to-chromium": "^1.3.378", + "node-releases": "^1.1.52", + "pkg-up": "^3.1.0" + } + }, + "caniuse-lite": { + "version": "1.0.30001124", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001124.tgz", + "integrity": "sha512-zQW8V3CdND7GHRH6rxm6s59Ww4g/qGWTheoboW9nfeMg7sUoopIfKCcNZUjwYRCOrvereh3kwDpZj4VLQ7zGtA==", + "dev": true + }, + "cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "requires": { + "restore-cursor": "^3.1.0" + } + }, + "cross-spawn": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.1.tgz", + "integrity": "sha512-u7v4o84SwFpD32Z8IIcPZ6z1/ie24O6RU3RbtL5Y316l3KuHVPx9ItBgWQ6VlfAFnRnTtMUrsQ9MUUTuEZjogg==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "electron-to-chromium": { + "version": "1.3.562", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.562.tgz", + "integrity": "sha512-WhRe6liQ2q/w1MZc8mD8INkenHivuHdrr4r5EQHNomy3NJux+incP6M6lDMd0paShP3MD0WGe5R1TWmEClf+Bg==", + "dev": true + }, + "escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true + }, + "figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5" + }, + "dependencies": { + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + } + } + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "inquirer": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.0.4.tgz", + "integrity": "sha512-Bu5Td5+j11sCkqfqmUTiwv+tWisMtP0L7Q8WrqA2C/BbBhy1YTdFrvjjlrKq8oagA/tLQBski2Gcx/Sqyi2qSQ==", + "dev": true, + "requires": { + "ansi-escapes": "^4.2.1", "chalk": "^2.4.2", - "cli-cursor": "^2.1.0", + "cli-cursor": "^3.1.0", "cli-width": "^2.0.0", "external-editor": "^3.0.3", - "figures": "^2.0.0", - "lodash": "^4.17.12", - "mute-stream": "0.0.7", + "figures": "^3.0.0", + "lodash": "^4.17.15", + "mute-stream": "0.0.8", "run-async": "^2.2.0", - "rxjs": "^6.4.0", - "string-width": "^2.1.0", + "rxjs": "^6.5.3", + "string-width": "^4.1.0", "strip-ansi": "^5.1.0", "through": "^2.3.6" + }, + "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 + }, + "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" + } + } + } + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" } }, "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", "dev": true }, - "rxjs": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.3.tgz", - "integrity": "sha512-wuYsAYYFdWTAnAaPoKGNhfpWwKZbJW+HgAJ+mImp+Epl7BG8oNWBCTyRM8gba9k4lk8BgWdoYm21Mo/RYhhbgA==", + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true + }, + "mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "dev": true + }, + "node-releases": { + "version": "1.1.60", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.60.tgz", + "integrity": "sha512-gsO4vjEdQaTusZAEebUWp2a5d7dF5DYoIpDG7WySnk7BuZDW+GPpHXoXXuYawRBr/9t5q54tirPz79kFIWg4dA==", + "dev": true + }, + "onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", "dev": true, "requires": { - "tslib": "^1.9.0" + "mimic-fn": "^2.1.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "requires": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + } + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" } }, "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", "dev": true, "requires": { - "ansi-regex": "^4.1.0" + "ansi-regex": "^5.0.0" + } + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" } } } @@ -18229,9 +18814,9 @@ } }, "react-error-overlay": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.3.tgz", - "integrity": "sha512-bOUvMWFQVk5oz8Ded9Xb7WVdEi3QGLC8tH7HmYP0Fdp4Bn3qw0tRFmr5TW6mvahzvmrK4a6bqWGfCevBflP+Xw==", + "version": "6.0.7", + "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.7.tgz", + "integrity": "sha512-TAv1KJFh3RhqxNvhzxj6LeT5NWklP6rDr2a0jaTfsZ5wSZWHOGeqQyejUp3xxLfPt2UpyJEcVQB/zyPcmonNFA==", "dev": true }, "react-external-link": { @@ -18245,36 +18830,41 @@ "integrity": "sha1-wb0hxk8fE2TG9waV7ALWk5L0G/o=" }, "react-leaflet": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/react-leaflet/-/react-leaflet-2.4.0.tgz", - "integrity": "sha512-ex9MAz2cUAmdUucsjv180OYszdqxHIyEwzWAuMOOuxE7yUmRscxZKR5h0f+vG4shR+SekZYUBk0+gCv8apRADQ==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/react-leaflet/-/react-leaflet-2.7.0.tgz", + "integrity": "sha512-pMf5eRyWU8RH9HohM2i0NZymcWHraJA1m6iMFYu94/01PAaBJpOyxORZJmN6cV9dBzkVWaLjAAHTNmxbwIpcfw==", "requires": { - "@babel/runtime": "^7.4.5", - "fast-deep-equal": "^2.0.1", - "hoist-non-react-statics": "^3.3.0", + "@babel/runtime": "^7.9.2", + "fast-deep-equal": "^3.1.1", + "hoist-non-react-statics": "^3.3.2", "warning": "^4.0.3" }, "dependencies": { "@babel/runtime": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.6.2.tgz", - "integrity": "sha512-EXxN64agfUqqIGeEjI5dL5z0Sw0ZwWo1mLTi4mQowCZ42O59b7DRpZAnTC6OqdF28wMBMFKNb/4uFGrVaigSpg==", + "version": "7.11.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", + "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", "requires": { - "regenerator-runtime": "^0.13.2" + "regenerator-runtime": "^0.13.4" } }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, "hoist-non-react-statics": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.0.tgz", - "integrity": "sha512-0XsbTXxgiaCDYDIWFcwkmerZPSwywfUqYmwT4jzewKTQSWoE6FCMoUVOeBJWK3E/CrWbxRG3m5GzY4lnIwGRBA==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", "requires": { "react-is": "^16.7.0" } }, "regenerator-runtime": { - "version": "0.13.3", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz", - "integrity": "sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw==" + "version": "0.13.7", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", + "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" } } }, @@ -18284,9 +18874,9 @@ "integrity": "sha1-TxonOv38jzSIqMUWv9p4+HI1I2I=" }, "react-moment": { - "version": "0.9.5", - "resolved": "https://registry.npmjs.org/react-moment/-/react-moment-0.9.5.tgz", - "integrity": "sha512-lUTAjYgtxdBn27bcTkgtzJRQJ6h2/oo7w+asGRTqeoaVU3630H0hO9S2zR8OJ5uHuAKFOF++7GMPlivNk1Lm9g==" + "version": "0.9.7", + "resolved": "https://registry.npmjs.org/react-moment/-/react-moment-0.9.7.tgz", + "integrity": "sha512-ifzUrUGF6KRsUN2pRG5k56kO0mJBr8kRkWb0wNvtFIsBIxOuPxhUpL1YlXwpbQCbHq23hUu6A0VEk64HsFxk9g==" }, "react-onclickoutside": { "version": "6.9.0", @@ -18307,56 +18897,47 @@ } }, "react-redux": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.1.1.tgz", - "integrity": "sha512-QsW0vcmVVdNQzEkrgzh2W3Ksvr8cqpAv5FhEk7tNEft+5pp7rXxAudTz3VOPawRkLIepItpkEIyLcN/VVXzjTg==", + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.1.tgz", + "integrity": "sha512-T+VfD/bvgGTUA74iW9d2i5THrDQWbweXP0AVNI8tNd1Rk5ch1rnMiJkDD67ejw7YBKM4+REvcvqRuWJb7BLuEg==", "requires": { "@babel/runtime": "^7.5.5", "hoist-non-react-statics": "^3.3.0", - "invariant": "^2.2.4", "loose-envify": "^1.4.0", "prop-types": "^15.7.2", "react-is": "^16.9.0" }, "dependencies": { "@babel/runtime": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.6.2.tgz", - "integrity": "sha512-EXxN64agfUqqIGeEjI5dL5z0Sw0ZwWo1mLTi4mQowCZ42O59b7DRpZAnTC6OqdF28wMBMFKNb/4uFGrVaigSpg==", + "version": "7.11.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", + "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", "requires": { - "regenerator-runtime": "^0.13.2" - } - }, - "hoist-non-react-statics": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.0.tgz", - "integrity": "sha512-0XsbTXxgiaCDYDIWFcwkmerZPSwywfUqYmwT4jzewKTQSWoE6FCMoUVOeBJWK3E/CrWbxRG3m5GzY4lnIwGRBA==", - "requires": { - "react-is": "^16.7.0" + "regenerator-runtime": "^0.13.4" } }, "react-is": { - "version": "16.10.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.10.2.tgz", - "integrity": "sha512-INBT1QEgtcCCgvccr5/86CfD71fw9EPmDxgiJX4I2Ddr6ZsV6iFXsuby+qWJPtmNuMY0zByTsG4468P7nHuNWA==" + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, "regenerator-runtime": { - "version": "0.13.3", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz", - "integrity": "sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw==" + "version": "0.13.7", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", + "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" } } }, "react-router": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.1.2.tgz", - "integrity": "sha512-yjEuMFy1ONK246B+rsa0cUam5OeAQ8pyclRDgpxuSCrAlJ1qN9uZ5IgyKC7gQg0w8OM50NXHEegPh/ks9YuR2A==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.2.0.tgz", + "integrity": "sha512-smz1DUuFHRKdcJC0jobGo8cVbhO3x50tCL4icacOlcwDOEQPq4TMqwx3sY1TP+DvtTgz4nm3thuo7A+BK2U0Dw==", "requires": { "@babel/runtime": "^7.1.2", "history": "^4.9.0", "hoist-non-react-statics": "^3.1.0", "loose-envify": "^1.3.1", - "mini-create-react-context": "^0.3.0", + "mini-create-react-context": "^0.4.0", "path-to-regexp": "^1.7.0", "prop-types": "^15.6.2", "react-is": "^16.6.0", @@ -18365,23 +18946,23 @@ } }, "react-router-dom": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.1.2.tgz", - "integrity": "sha512-7BPHAaIwWpZS074UKaw1FjVdZBSVWEk8IuDXdB+OkLb8vd/WRQIpA4ag9WQk61aEfQs47wHyjWUoUGGZxpQXew==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.2.0.tgz", + "integrity": "sha512-gxAmfylo2QUjcwxI63RhQ5G85Qqt4voZpUXSEqCwykV0baaOTQDR1f0PmY8AELqIyVc0NEZUj0Gov5lNGcXgsA==", "requires": { "@babel/runtime": "^7.1.2", "history": "^4.9.0", "loose-envify": "^1.3.1", "prop-types": "^15.6.2", - "react-router": "5.1.2", + "react-router": "5.2.0", "tiny-invariant": "^1.0.2", "tiny-warning": "^1.0.0" } }, "react-swipeable": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/react-swipeable/-/react-swipeable-5.4.0.tgz", - "integrity": "sha512-TUbldupF3cxMF+pBWfz24TlQa23K4CecqQ8EA+onxEiAg+UEPSJ5IousOjMm0IVBYz62KX1klHL8p7SGSR3CiQ==", + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/react-swipeable/-/react-swipeable-5.5.1.tgz", + "integrity": "sha512-EQObuU3Qg3JdX3WxOn5reZvOSCpU4fwpUAs+NlXSN3y+qtsO2r8VGkVnOQzmByt3BSYj9EWYdUOUfi7vaMdZZw==", "requires": { "prop-types": "^15.6.2" } @@ -18754,43 +19335,44 @@ "dev": true }, "remark": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/remark/-/remark-10.0.1.tgz", - "integrity": "sha512-E6lMuoLIy2TyiokHprMjcWNJ5UxfGQjaMSMhV+f4idM625UjjK4j798+gPs5mfjzDE6vL0oFKVeZM6gZVSVrzQ==", + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/remark/-/remark-12.0.1.tgz", + "integrity": "sha512-gS7HDonkdIaHmmP/+shCPejCEEW+liMp/t/QwmF0Xt47Rpuhl32lLtDV1uKWvGoq+kxr5jSgg5oAIpGuyULjUw==", "dev": true, "requires": { - "remark-parse": "^6.0.0", - "remark-stringify": "^6.0.0", - "unified": "^7.0.0" + "remark-parse": "^8.0.0", + "remark-stringify": "^8.0.0", + "unified": "^9.0.0" } }, "remark-parse": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-6.0.3.tgz", - "integrity": "sha512-QbDXWN4HfKTUC0hHa4teU463KclLAnwpn/FBn87j9cKYJWWawbiLgMfP2Q4XwhxxuuuOxHlw+pSN0OKuJwyVvg==", + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-8.0.3.tgz", + "integrity": "sha512-E1K9+QLGgggHxCQtLt++uXltxEprmWzNfg+MxpfHsZlrddKzZ/hZyWHDbK3/Ap8HJQqYJRXP+jHczdL6q6i85Q==", "dev": true, "requires": { + "ccount": "^1.0.0", "collapse-white-space": "^1.0.2", "is-alphabetical": "^1.0.0", "is-decimal": "^1.0.0", "is-whitespace-character": "^1.0.0", "is-word-character": "^1.0.0", "markdown-escapes": "^1.0.0", - "parse-entities": "^1.1.0", + "parse-entities": "^2.0.0", "repeat-string": "^1.5.4", "state-toggle": "^1.0.0", "trim": "0.0.1", "trim-trailing-lines": "^1.0.0", "unherit": "^1.0.4", - "unist-util-remove-position": "^1.0.0", - "vfile-location": "^2.0.0", + "unist-util-remove-position": "^2.0.0", + "vfile-location": "^3.0.0", "xtend": "^4.0.1" } }, "remark-stringify": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-6.0.4.tgz", - "integrity": "sha512-eRWGdEPMVudijE/psbIDNcnJLRVx3xhfuEsTDGgH4GsFF91dVhw5nhmnBppafJ7+NWINW6C7ZwWbi30ImJzqWg==", + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-8.1.1.tgz", + "integrity": "sha512-q4EyPZT3PcA3Eq7vPpT6bIdokXzFGp9i85igjmhRyXWmPs0Y6/d2FYwUNotKAWyLch7g0ASZJn/KHHcHZQ163A==", "dev": true, "requires": { "ccount": "^1.0.0", @@ -18799,12 +19381,12 @@ "is-whitespace-character": "^1.0.0", "longest-streak": "^2.0.1", "markdown-escapes": "^1.0.0", - "markdown-table": "^1.1.0", - "mdast-util-compact": "^1.0.0", - "parse-entities": "^1.0.2", + "markdown-table": "^2.0.0", + "mdast-util-compact": "^2.0.0", + "parse-entities": "^2.0.0", "repeat-string": "^1.5.4", "state-toggle": "^1.0.0", - "stringify-entities": "^1.0.1", + "stringify-entities": "^3.0.0", "unherit": "^1.0.4", "xtend": "^4.0.1" } @@ -18880,9 +19462,9 @@ "dev": true }, "request": { - "version": "2.88.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", - "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", "dev": true, "requires": { "aws-sign2": "~0.7.0", @@ -18892,7 +19474,7 @@ "extend": "~3.0.2", "forever-agent": "~0.6.1", "form-data": "~2.3.2", - "har-validator": "~5.1.0", + "har-validator": "~5.1.3", "http-signature": "~1.2.0", "is-typedarray": "~1.0.0", "isstream": "~0.1.2", @@ -18902,17 +19484,11 @@ "performance-now": "^2.1.0", "qs": "~6.5.2", "safe-buffer": "^5.1.2", - "tough-cookie": "~2.4.3", + "tough-cookie": "~2.5.0", "tunnel-agent": "^0.6.0", "uuid": "^3.3.2" }, "dependencies": { - "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", - "dev": true - }, "qs": { "version": "6.5.2", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", @@ -18920,13 +19496,13 @@ "dev": true }, "tough-cookie": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", - "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", "dev": true, "requires": { - "psl": "^1.1.24", - "punycode": "^1.4.1" + "psl": "^1.1.28", + "punycode": "^2.1.1" } } } @@ -19052,6 +19628,12 @@ "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=", "dev": true }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true + }, "rfdc": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.1.4.tgz", @@ -19114,6 +19696,12 @@ "is-promise": "^2.1.0" } }, + "run-parallel": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.1.9.tgz", + "integrity": "sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q==", + "dev": true + }, "run-queue": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz", @@ -19123,6 +19711,15 @@ "aproba": "^1.1.1" } }, + "rxjs": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.2.tgz", + "integrity": "sha512-BHdBMVoWC2sL26w//BCu3YzKT4s2jip/WhwsGEDmeKYBhKDZeYezVUnHatYB7L85v5xs0BAQmg6BEYJEKxBabg==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz", @@ -19188,192 +19785,198 @@ } }, "sass-graph": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/sass-graph/-/sass-graph-2.2.4.tgz", - "integrity": "sha1-E/vWPNHK8JCLn9k0dq1DpR0eC0k=", + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/sass-graph/-/sass-graph-2.2.5.tgz", + "integrity": "sha512-VFWDAHOe6mRuT4mZRd4eKE+d8Uedrk6Xnh7Sh9b4NGufQLQjOrvf/MQoOdx+0s92L89FeyUUNfU597j/3uNpag==", "dev": true, "requires": { "glob": "^7.0.0", "lodash": "^4.0.0", "scss-tokenizer": "^0.2.3", - "yargs": "^7.0.0" + "yargs": "^13.3.2" }, "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 + }, "camelcase": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", - "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true }, "cliui": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", - "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", "dev": true, "requires": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wrap-ansi": "^2.0.0" + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" } }, - "get-caller-file": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", - "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "dev": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "require-main-filename": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", - "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", "dev": true }, "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", "dev": true, "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" } }, - "which-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", - "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=", - "dev": true + "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" + } }, "wrap-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", - "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", "dev": true, "requires": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1" + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" } }, - "y18n": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", - "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", - "dev": true - }, "yargs": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-7.1.0.tgz", - "integrity": "sha1-a6MY6xaWFyf10oT46gA+jWFU0Mg=", + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", + "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", "dev": true, "requires": { - "camelcase": "^3.0.0", - "cliui": "^3.2.0", - "decamelize": "^1.1.1", - "get-caller-file": "^1.0.1", - "os-locale": "^1.4.0", - "read-pkg-up": "^1.0.1", + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", "require-directory": "^2.1.1", - "require-main-filename": "^1.0.1", + "require-main-filename": "^2.0.0", "set-blocking": "^2.0.0", - "string-width": "^1.0.2", - "which-module": "^1.0.0", - "y18n": "^3.2.1", - "yargs-parser": "^5.0.0" + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.2" } }, "yargs-parser": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.0.tgz", - "integrity": "sha1-J17PDX/+Bcd+ZOfIbkzZS/DhIoo=", + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", "dev": true, "requires": { - "camelcase": "^3.0.0" + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" } } } }, "sass-loader": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-8.0.0.tgz", - "integrity": "sha512-+qeMu563PN7rPdit2+n5uuYVR0SSVwm0JsOUsaJXzgYcClWSlmX0iHDnmeOobPkf5kUglVot3QS6SyLyaQoJ4w==", + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-10.0.2.tgz", + "integrity": "sha512-wV6NDUVB8/iEYMalV/+139+vl2LaRFlZGEd5/xmdcdzQcgmis+npyco6NsDTVOlNA3y2NV9Gcz+vHyFMIT+ffg==", "dev": true, "requires": { - "clone-deep": "^4.0.1", - "loader-utils": "^1.2.3", - "neo-async": "^2.6.1", - "schema-utils": "^2.1.0", - "semver": "^6.3.0" + "klona": "^2.0.3", + "loader-utils": "^2.0.0", + "neo-async": "^2.6.2", + "schema-utils": "^2.7.1", + "semver": "^7.3.2" }, "dependencies": { "ajv": { - "version": "6.10.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz", - "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==", + "version": "6.12.4", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.4.tgz", + "integrity": "sha512-eienB2c9qVQs2KWexhkrdMLVDoIQCz5KSeLxwg9Lzk4DOfBtIK9PQwwufcsn1jjGuf9WZmqPMbGxOzfcuphJCQ==", "dev": true, "requires": { - "fast-deep-equal": "^2.0.1", + "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "ajv-keywords": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.4.1.tgz", - "integrity": "sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", "dev": true }, - "clone-deep": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", - "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", + "dev": true + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "json5": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz", + "integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==", "dev": true, "requires": { - "is-plain-object": "^2.0.4", - "kind-of": "^6.0.2", - "shallow-clone": "^3.0.0" + "minimist": "^1.2.5" } }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "loader-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", + "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + } + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + }, + "neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true }, "schema-utils": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.4.1.tgz", - "integrity": "sha512-RqYLpkPZX5Oc3fw/kHHHyP56fg5Y+XBpIpV8nCg0znIALfq3OH+Ea9Hfeac9BAMwG5IICltiZ0vxFvJQONfA5w==", + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", + "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", "dev": true, "requires": { - "ajv": "^6.10.2", - "ajv-keywords": "^3.4.1" + "@types/json-schema": "^7.0.5", + "ajv": "^6.12.4", + "ajv-keywords": "^3.5.2" } }, "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", "dev": true - }, - "shallow-clone": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", - "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", - "dev": true, - "requires": { - "kind-of": "^6.0.2" - } } } }, @@ -19516,9 +20119,9 @@ "dev": true }, "serve": { - "version": "11.2.0", - "resolved": "https://registry.npmjs.org/serve/-/serve-11.2.0.tgz", - "integrity": "sha512-THZcLzDGk3vJqjhAbLkLag43tiE3V0B7wVe98Xtl+1KyAsr+4iShg+9hke4pLZmrCJu0pUg0TrbhJmdqn/MKoA==", + "version": "11.3.2", + "resolved": "https://registry.npmjs.org/serve/-/serve-11.3.2.tgz", + "integrity": "sha512-yKWQfI3xbj/f7X1lTBg91fXBP0FqjJ4TEi+ilES5yzH0iKJpN5LjNb1YzIfQg9Rqn4ECUS2SOf2+Kmepogoa5w==", "dev": true, "requires": { "@zeit/schemas": "2.6.0", @@ -19528,7 +20131,7 @@ "chalk": "2.4.1", "clipboardy": "1.2.3", "compression": "1.7.3", - "serve-handler": "6.1.2", + "serve-handler": "6.1.3", "update-check": "1.5.2" }, "dependencies": { @@ -19558,9 +20161,9 @@ } }, "serve-handler": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.2.tgz", - "integrity": "sha512-RFh49wX7zJmmOVDcIjiDSJnMH+ItQEvyuYLYuDBVoA/xmQSCuj+uRmk1cmBB5QQlI3qOiWKp6p4DUGY+Z5AB2A==", + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.3.tgz", + "integrity": "sha512-FosMqFBNrLyeiIDvP1zgO6YoTzFYHxLDEIavhlmQ+knB2Z7l1t+kGLHkZIDN7UVWqQAmKI3D20A6F6jo3nDd4w==", "dev": true, "requires": { "bytes": "3.0.0", @@ -19719,9 +20322,9 @@ } }, "shallow-equal": { - "version": "1.0.0", - "resolved": "https://registry.yarnpkg.com/shallow-equal/-/shallow-equal-1.0.0.tgz", - "integrity": "sha1-UI0YOLPeWQq4dXsBGyXkMJAJRfc=" + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/shallow-equal/-/shallow-equal-1.2.1.tgz", + "integrity": "sha512-S4vJDjHHMBaiZuT9NPb616CSmLf618jawtv3sufLl6ivK8WocjAo58cXwbRV1cgqxH0Qbv+iUt6m05eqEa2IRA==" }, "shebang-command": { "version": "1.2.0", @@ -19850,20 +20453,53 @@ "dev": true }, "slash": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", - "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true }, "slice-ansi": { - "version": "2.0.0", - "resolved": "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-2.0.0.tgz", - "integrity": "sha1-U3O9uFWbRWduhUHGaRbN1iUWEuc=", + "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": { + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true + }, + "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" + } + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + } } }, "snapdragon": { @@ -20220,9 +20856,9 @@ } }, "state-toggle": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/state-toggle/-/state-toggle-1.0.2.tgz", - "integrity": "sha512-8LpelPGR0qQM4PnfLiplOQNJcIN1/r2Gy0xKB2zKnIW2YzPMt2sR4I/+gtPjhN7Svh9kw+zqEg2SFwpBO9iNiw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/state-toggle/-/state-toggle-1.0.3.tgz", + "integrity": "sha512-d/5Z4/2iiCnHw6Xzghyhb+GcmF89bxwgXG60wjIiZaxnymbyOmI8Hk4VqHXiVVp6u2ysaskFfXg3ekCj4WNftQ==", "dev": true }, "static-extend": { @@ -20738,14 +21374,15 @@ } }, "stringify-entities": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-1.3.2.tgz", - "integrity": "sha512-nrBAQClJAPN2p+uGCVJRPIPakKeKWZ9GtBCmormE7pWOSlHat7+x5A8gx85M7HM5Dt0BP3pP5RhVW77WdbJJ3A==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-3.0.1.tgz", + "integrity": "sha512-Lsk3ISA2++eJYqBMPKcr/8eby1I6L0gP0NlxF8Zja6c05yr/yCYyb2c9PwXjd08Ib3If1vn1rbs1H5ZtVuOfvQ==", "dev": true, "requires": { "character-entities-html4": "^1.0.0", "character-entities-legacy": "^1.0.0", "is-alphanumerical": "^1.0.0", + "is-decimal": "^1.0.2", "is-hexadecimal": "^1.0.0" } }, @@ -20886,41 +21523,80 @@ } }, "style-loader": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-1.0.0.tgz", - "integrity": "sha512-B0dOCFwv7/eY31a5PCieNwMgMhVGFe9w+rh7s/Bx8kfFkrth9zfTZquoYvdw8URgiqxObQKcpW51Ugz1HjfdZw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-1.2.1.tgz", + "integrity": "sha512-ByHSTQvHLkWE9Ir5+lGbVOXhxX10fbprhLvdg96wedFZb4NDekDPxVKv5Fwmio+QcMlkkNfuK+5W1peQ5CUhZg==", "dev": true, "requires": { - "loader-utils": "^1.2.3", - "schema-utils": "^2.0.1" + "loader-utils": "^2.0.0", + "schema-utils": "^2.6.6" }, "dependencies": { "ajv": { - "version": "6.10.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz", - "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==", + "version": "6.12.4", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.4.tgz", + "integrity": "sha512-eienB2c9qVQs2KWexhkrdMLVDoIQCz5KSeLxwg9Lzk4DOfBtIK9PQwwufcsn1jjGuf9WZmqPMbGxOzfcuphJCQ==", "dev": true, "requires": { - "fast-deep-equal": "^2.0.1", + "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "ajv-keywords": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.4.1.tgz", - "integrity": "sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true + }, + "emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", + "dev": true + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "json5": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz", + "integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, + "loader-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", + "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + } + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", "dev": true }, "schema-utils": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.4.1.tgz", - "integrity": "sha512-RqYLpkPZX5Oc3fw/kHHHyP56fg5Y+XBpIpV8nCg0znIALfq3OH+Ea9Hfeac9BAMwG5IICltiZ0vxFvJQONfA5w==", + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", + "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", "dev": true, "requires": { - "ajv": "^6.10.2", - "ajv-keywords": "^3.4.1" + "@types/json-schema": "^7.0.5", + "ajv": "^6.12.4", + "ajv-keywords": "^3.5.2" } } } @@ -20956,75 +21632,195 @@ } }, "stylelint": { - "version": "9.10.1", - "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-9.10.1.tgz", - "integrity": "sha512-9UiHxZhOAHEgeQ7oLGwrwoDR8vclBKlSX7r4fH0iuu0SfPwFaLkb1c7Q2j1cqg9P7IDXeAV2TvQML/fRQzGBBQ==", + "version": "13.7.0", + "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-13.7.0.tgz", + "integrity": "sha512-1wStd4zVetnlHO98VjcHQbjSDmvcA39smkZQMct2cf+hom40H0xlQNdzzbswoG/jGBh61/Ue9m7Lu99PY51O6A==", "dev": true, "requires": { - "autoprefixer": "^9.0.0", + "@stylelint/postcss-css-in-js": "^0.37.2", + "@stylelint/postcss-markdown": "^0.36.1", + "autoprefixer": "^9.8.6", "balanced-match": "^1.0.0", - "chalk": "^2.4.1", - "cosmiconfig": "^5.0.0", - "debug": "^4.0.0", - "execall": "^1.0.0", - "file-entry-cache": "^4.0.0", - "get-stdin": "^6.0.0", + "chalk": "^4.1.0", + "cosmiconfig": "^7.0.0", + "debug": "^4.1.1", + "execall": "^2.0.0", + "fast-glob": "^3.2.4", + "fastest-levenshtein": "^1.0.12", + "file-entry-cache": "^5.0.1", + "get-stdin": "^8.0.0", "global-modules": "^2.0.0", - "globby": "^9.0.0", + "globby": "^11.0.1", "globjoin": "^0.1.4", - "html-tags": "^2.0.0", - "ignore": "^5.0.4", - "import-lazy": "^3.1.0", + "html-tags": "^3.1.0", + "ignore": "^5.1.8", + "import-lazy": "^4.0.0", "imurmurhash": "^0.1.4", - "known-css-properties": "^0.11.0", - "leven": "^2.1.0", - "lodash": "^4.17.4", - "log-symbols": "^2.0.0", - "mathml-tag-names": "^2.0.1", - "meow": "^5.0.0", - "micromatch": "^3.1.10", + "known-css-properties": "^0.19.0", + "lodash": "^4.17.20", + "log-symbols": "^4.0.0", + "mathml-tag-names": "^2.1.3", + "meow": "^7.1.1", + "micromatch": "^4.0.2", "normalize-selector": "^0.2.0", - "pify": "^4.0.0", - "postcss": "^7.0.13", + "postcss": "^7.0.32", "postcss-html": "^0.36.0", - "postcss-jsx": "^0.36.0", - "postcss-less": "^3.1.0", - "postcss-markdown": "^0.36.0", + "postcss-less": "^3.1.4", "postcss-media-query-parser": "^0.2.3", - "postcss-reporter": "^6.0.0", "postcss-resolve-nested-selector": "^0.1.1", - "postcss-safe-parser": "^4.0.0", - "postcss-sass": "^0.3.5", - "postcss-scss": "^2.0.0", - "postcss-selector-parser": "^3.1.0", + "postcss-safe-parser": "^4.0.2", + "postcss-sass": "^0.4.4", + "postcss-scss": "^2.1.1", + "postcss-selector-parser": "^6.0.2", "postcss-syntax": "^0.36.2", - "postcss-value-parser": "^3.3.0", - "resolve-from": "^4.0.0", - "signal-exit": "^3.0.2", - "slash": "^2.0.0", + "postcss-value-parser": "^4.1.0", + "resolve-from": "^5.0.0", + "slash": "^3.0.0", "specificity": "^0.4.1", - "string-width": "^3.0.0", + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", "style-search": "^0.1.0", "sugarss": "^2.0.0", "svg-tags": "^1.0.0", - "table": "^5.0.0" + "table": "^6.0.1", + "v8-compile-cache": "^2.1.1", + "write-file-atomic": "^3.0.3" }, "dependencies": { + "@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==", + "dev": true + }, "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true + }, + "autoprefixer": { + "version": "9.8.6", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.8.6.tgz", + "integrity": "sha512-XrvP4VVHdRBCdX1S3WXVD8+RyG9qeb1D5Sn1DeLiG2xfSpzellk5k54xbUERJ3M5DggQxes39UGOTP8CFrEGbg==", + "dev": true, + "requires": { + "browserslist": "^4.12.0", + "caniuse-lite": "^1.0.30001109", + "colorette": "^1.2.1", + "normalize-range": "^0.1.2", + "num2fraction": "^1.2.2", + "postcss": "^7.0.32", + "postcss-value-parser": "^4.1.0" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "browserslist": { + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.14.1.tgz", + "integrity": "sha512-zyBTIHydW37pnb63c7fHFXUG6EcqWOqoMdDx6cdyaDFriZ20EoVxcE95S54N+heRqY8m8IUgB5zYta/gCwSaaA==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30001124", + "electron-to-chromium": "^1.3.562", + "escalade": "^3.0.2", + "node-releases": "^1.1.60" + } + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true }, "camelcase-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-4.2.0.tgz", - "integrity": "sha1-oqpfsa9oh1glnDLBQUJteJI7m3c=", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", + "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", "dev": true, "requires": { - "camelcase": "^4.1.0", - "map-obj": "^2.0.0", - "quick-lru": "^1.0.0" + "camelcase": "^5.3.1", + "map-obj": "^4.0.0", + "quick-lru": "^4.0.1" + } + }, + "caniuse-lite": { + "version": "1.0.30001124", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001124.tgz", + "integrity": "sha512-zQW8V3CdND7GHRH6rxm6s59Ww4g/qGWTheoboW9nfeMg7sUoopIfKCcNZUjwYRCOrvereh3kwDpZj4VLQ7zGtA==", + "dev": true + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "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" + } + }, + "cosmiconfig": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.0.tgz", + "integrity": "sha512-pondGvTuVYDk++upghXJabWzL6Kxu6f26ljFw64Swq9v6sQPUL3EUlVDV56diOjpCayKihL6hVe8exIACU4XcA==", + "dev": true, + "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" } }, "debug": { @@ -21037,148 +21833,172 @@ } }, "dir-glob": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.2.2.tgz", - "integrity": "sha512-f9LBi5QWzIW3I6e//uxZoLBlUt9kcp66qo0sSCxL6YZKc75R1c4MFCoe/LaZiBGmgujvQdxc5Bn3QhfyvK5Hsw==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", "dev": true, "requires": { - "path-type": "^3.0.0" + "path-type": "^4.0.0" } }, - "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "electron-to-chromium": { + "version": "1.3.562", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.562.tgz", + "integrity": "sha512-WhRe6liQ2q/w1MZc8mD8INkenHivuHdrr4r5EQHNomy3NJux+incP6M6lDMd0paShP3MD0WGe5R1TWmEClf+Bg==", "dev": true }, - "file-entry-cache": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-4.0.0.tgz", - "integrity": "sha512-AVSwsnbV8vH/UVbvgEhf3saVQXORNv0ZzSkvkhQIaia5Tia+JhGTaa/ePUSVoPHQyGayQNmYfkzFi3WZV5zcpA==", + "fast-glob": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.4.tgz", + "integrity": "sha512-kr/Oo6PX51265qeuCYsyGypiO5uJFgBS0jksyG7FUeCyQzNwYnzrNIMR1NXfkZXsMYXYLRAHgISHBz8gQcxKHQ==", "dev": true, "requires": { - "flat-cache": "^2.0.1" + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.0", + "merge2": "^1.3.0", + "micromatch": "^4.0.2", + "picomatch": "^2.2.1" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" } }, "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, "requires": { - "locate-path": "^2.0.0" - } - }, - "flat-cache": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", - "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", - "dev": true, - "requires": { - "flatted": "^2.0.0", - "rimraf": "2.6.3", - "write": "1.0.3" + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" } }, "get-stdin": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz", - "integrity": "sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-8.0.0.tgz", + "integrity": "sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg==", "dev": true }, - "globby": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-9.2.0.tgz", - "integrity": "sha512-ollPHROa5mcxDEkwg6bPt3QbEf4pDQSNtd6JPL1YvOvAo/7/0VAm9TccUeoTmarjPw4pfUthSCqcyfNB1I3ZSg==", + "glob-parent": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", + "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", "dev": true, "requires": { - "@types/glob": "^7.1.1", - "array-union": "^1.0.2", - "dir-glob": "^2.2.2", - "fast-glob": "^2.2.6", - "glob": "^7.1.3", - "ignore": "^4.0.3", - "pify": "^4.0.1", - "slash": "^2.0.0" - }, - "dependencies": { - "ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true - } + "is-glob": "^4.0.1" + } + }, + "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" } }, "ignore": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.4.tgz", - "integrity": "sha512-MzbUSahkTW1u7JpKKjY7LCARd1fU5W2rLdxlM4kdkayuCwZImjkpluF9CM1aLewYJguPDqewLam18Y6AU69A8A==", + "version": "5.1.8", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", + "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", "dev": true }, - "indent-string": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", - "integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=", - "dev": true - }, - "leven": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-2.1.0.tgz", - "integrity": "sha1-wuep93IJTe6dNCAq6KzORoeHVYA=", - "dev": true - }, - "load-json-file": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", - "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", + "import-fresh": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz", + "integrity": "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==", "dev": true, "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^4.0.0", - "pify": "^3.0.0", - "strip-bom": "^3.0.0" + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" }, "dependencies": { - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true } } }, + "indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, "locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, "requires": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" + "p-locate": "^4.1.0" } }, + "lodash": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", + "dev": true + }, "map-obj": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-2.0.0.tgz", - "integrity": "sha1-plzSkIepJZi4eRJXpSPgISIqwfk=", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.1.0.tgz", + "integrity": "sha512-glc9y00wgtwcDmp7GaE/0b0OnxpNJsVf3ael/An6Fe2Q51LLwN1er6sdomLRzz5h0+yMpiYLhWYF5R7HeqVd4g==", "dev": true }, "meow": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-5.0.0.tgz", - "integrity": "sha512-CbTqYU17ABaLefO8vCU153ZZlprKYWDljcndKKDCFcYQITzWCXZAVk4QMFZPgvzrnUQ3uItnIE/LoUOwrT15Ig==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/meow/-/meow-7.1.1.tgz", + "integrity": "sha512-GWHvA5QOcS412WCo8vwKDlTelGLsCGBVevQB5Kva961rmNfun0PCbv5+xta2kUMFJyR8/oWnn7ddeKdosbAPbA==", "dev": true, "requires": { - "camelcase-keys": "^4.0.0", - "decamelize-keys": "^1.0.0", - "loud-rejection": "^1.0.0", - "minimist-options": "^3.0.1", - "normalize-package-data": "^2.3.4", - "read-pkg-up": "^3.0.0", - "redent": "^2.0.0", - "trim-newlines": "^2.0.0", - "yargs-parser": "^10.0.0" + "@types/minimist": "^1.2.0", + "camelcase-keys": "^6.2.2", + "decamelize-keys": "^1.1.0", + "hard-rejection": "^2.1.0", + "minimist-options": "4.1.0", + "normalize-package-data": "^2.5.0", + "read-pkg-up": "^7.0.1", + "redent": "^3.0.0", + "trim-newlines": "^3.0.0", + "type-fest": "^0.13.1", + "yargs-parser": "^18.1.3" + } + }, + "micromatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", + "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", + "dev": true, + "requires": { + "braces": "^3.0.1", + "picomatch": "^2.0.5" } }, "ms": { @@ -21187,146 +22007,239 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, - "p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "node-releases": { + "version": "1.1.60", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.60.tgz", + "integrity": "sha512-gsO4vjEdQaTusZAEebUWp2a5d7dF5DYoIpDG7WySnk7BuZDW+GPpHXoXXuYawRBr/9t5q54tirPz79kFIWg4dA==", + "dev": true + }, + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", "dev": true, "requires": { - "p-try": "^1.0.0" + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" } }, "p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, "requires": { - "p-limit": "^1.1.0" + "p-limit": "^2.2.0" } }, - "p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", - "dev": true - }, - "pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "dev": true - }, - "postcss-selector-parser": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.1.tgz", - "integrity": "sha1-T4dfSvsMllc9XPTXQBGu4lCn6GU=", + "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==", "dev": true, "requires": { - "dot-prop": "^4.1.1", - "indexes-of": "^1.0.1", - "uniq": "^1.0.1" + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true + }, + "postcss": { + "version": "7.0.32", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.32.tgz", + "integrity": "sha512-03eXong5NLnNCD05xscnGKGDZ98CyzoqPSMjOe6SuoQY7Z2hIj0Ld1g/O/UQRuOle2aRtiIRDg9tDcTGAkLfKw==", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + }, + "dependencies": { + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + } + } + }, + "postcss-safe-parser": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-4.0.2.tgz", + "integrity": "sha512-Uw6ekxSWNLCPesSv/cmqf2bY/77z11O7jZGPax3ycZMFU/oi2DMH9i89AdHc1tRwFg/arFoEwX0IS3LCUxJh1g==", + "dev": true, + "requires": { + "postcss": "^7.0.26" } }, "postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz", + "integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==", "dev": true }, "read-pkg": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", - "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", "dev": true, "requires": { - "load-json-file": "^4.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^3.0.0" + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" + }, + "dependencies": { + "type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", + "dev": true + } } }, "read-pkg-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz", - "integrity": "sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc=", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", "dev": true, "requires": { - "find-up": "^2.0.0", - "read-pkg": "^3.0.0" + "find-up": "^4.1.0", + "read-pkg": "^5.2.0", + "type-fest": "^0.8.1" + }, + "dependencies": { + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true + } } }, "redent": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-2.0.0.tgz", - "integrity": "sha1-wbIAe0LVfrE4kHmzyDM2OdXhzKo=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", "dev": true, "requires": { - "indent-string": "^3.0.0", - "strip-indent": "^2.0.0" + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" } }, "resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "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==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", "dev": true, "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" } }, "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", "dev": true, "requires": { - "ansi-regex": "^4.1.0" + "ansi-regex": "^5.0.0" } }, "strip-indent": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-2.0.0.tgz", - "integrity": "sha1-XvjbKV0B5u1sv3qrlpmNeCJSe2g=", - "dev": true - }, - "trim-newlines": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-2.0.0.tgz", - "integrity": "sha1-tAPQuRvlDDMd/EuC7s6yLD3hbSA=", - "dev": true - }, - "write": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", - "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", "dev": true, "requires": { - "mkdirp": "^0.5.1" + "min-indent": "^1.0.0" } }, - "yargs-parser": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-10.1.0.tgz", - "integrity": "sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ==", + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", "dev": true, "requires": { - "camelcase": "^4.1.0" + "has-flag": "^3.0.0" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "trim-newlines": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.0.tgz", + "integrity": "sha512-C4+gOpvmxaSMKuEf9Qc134F1ZuOHVXKRbtEflf4NTtuuJDEIJ9p5PXsalL8SkeRw+qit1Mo+yuvMPAKwWg/1hA==", + "dev": true + }, + "type-fest": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", + "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", + "dev": true + }, + "write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "dev": true, + "requires": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" } } } }, "stylelint-config-adidas": { - "version": "1.2.1", - "resolved": "https://registry.yarnpkg.com/stylelint-config-adidas/-/stylelint-config-adidas-1.2.1.tgz", - "integrity": "sha1-+V8/GmNldKXRpnc4RBfBZShG08M=", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/stylelint-config-adidas/-/stylelint-config-adidas-1.3.0.tgz", + "integrity": "sha512-1B25n5jzE5Hrn0vnhcpqqJLslxSoGKmzhdm+W5kPyy6yeCGa9y+U5sg7rlwEvaOVNNozPkikkD4CAEAxygC7VQ==", "dev": true, "requires": { "stylelint-config-standard": "18.2.0" @@ -21351,15 +22264,15 @@ } }, "stylelint-config-recommended": { - "version": "2.1.0", - "resolved": "https://registry.yarnpkg.com/stylelint-config-recommended/-/stylelint-config-recommended-2.1.0.tgz", - "integrity": "sha1-9SbVx3HGgRGG2ertvtAhlf7jCFg=", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-2.2.0.tgz", + "integrity": "sha512-bZ+d4RiNEfmoR74KZtCKmsABdBJr4iXRiCso+6LtMJPw5rd/KnxUWTxht7TbafrTJK1YRjNgnN0iVZaJfc3xJA==", "dev": true }, "stylelint-config-recommended-scss": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/stylelint-config-recommended-scss/-/stylelint-config-recommended-scss-4.0.0.tgz", - "integrity": "sha512-aEy0ENUrH4ASgFCu2mMcqBUAX0l4CPXg0XucJXdW+I7mdqJ7ICddkxP1eamBNBZ1QToc/wsuLmTQcalk3qYpsw==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylelint-config-recommended-scss/-/stylelint-config-recommended-scss-4.2.0.tgz", + "integrity": "sha512-4bI5BYbabo/GCQ6LbRZx/ZlVkK65a1jivNNsD+ix/Lw0U3iAch+jQcvliGnnAX8SUPaZ0UqzNVNNAF3urswa7g==", "dev": true, "requires": { "stylelint-config-recommended": "^3.0.0" @@ -21375,30 +22288,36 @@ }, "stylelint-config-standard": { "version": "18.2.0", - "resolved": "https://registry.yarnpkg.com/stylelint-config-standard/-/stylelint-config-standard-18.2.0.tgz", - "integrity": "sha1-YoMUmrp/ZPGHMa748Kv7Nc9hngY=", + "resolved": "https://registry.npmjs.org/stylelint-config-standard/-/stylelint-config-standard-18.2.0.tgz", + "integrity": "sha512-07x0TaSIzvXlbOioUU4ORkCIM07kyIuojkbSVCyFWNVgXMXYHfhnQSCkqu+oHWJf3YADAnPGWzdJ53NxkoJ7RA==", "dev": true, "requires": { "stylelint-config-recommended": "^2.1.0" } }, "stylelint-scss": { - "version": "3.11.1", - "resolved": "https://registry.npmjs.org/stylelint-scss/-/stylelint-scss-3.11.1.tgz", - "integrity": "sha512-0FZNSfy5X2Or4VRA3Abwfrw1NHrI6jHT8ji9xSwP8Re2Kno0i90qbHwm8ohPO0kRB1RP9x1vCYBh4Tij+SZjIg==", + "version": "3.18.0", + "resolved": "https://registry.npmjs.org/stylelint-scss/-/stylelint-scss-3.18.0.tgz", + "integrity": "sha512-LD7+hv/6/ApNGt7+nR/50ft7cezKP2HM5rI8avIdGaUWre3xlHfV4jKO/DRZhscfuN+Ewy9FMhcTq0CcS0C/SA==", "dev": true, "requires": { "lodash": "^4.17.15", "postcss-media-query-parser": "^0.2.3", "postcss-resolve-nested-selector": "^0.1.1", "postcss-selector-parser": "^6.0.2", - "postcss-value-parser": "^4.0.2" + "postcss-value-parser": "^4.1.0" }, "dependencies": { "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", + "dev": true + }, + "postcss-value-parser": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz", + "integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==", "dev": true } } @@ -21450,9 +22369,9 @@ "dev": true }, "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { "has-flag": "^4.0.0" @@ -21562,15 +22481,73 @@ "dev": true }, "table": { - "version": "5.1.1", - "resolved": "https://registry.yarnpkg.com/table/-/table-5.1.1.tgz", - "integrity": "sha1-kgMBkvG3tRtu6rI+1BaGLke3CDc=", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/table/-/table-6.0.3.tgz", + "integrity": "sha512-8321ZMcf1B9HvVX/btKv8mMZahCjn2aYrDlpqHaBFCfnox64edeH9kEid0vTLTRR8gWR2A20aDgeuTTea4sVtw==", "dev": true, "requires": { - "ajv": "^6.6.1", - "lodash": "^4.17.11", - "slice-ansi": "2.0.0", - "string-width": "^2.1.1" + "ajv": "^6.12.4", + "lodash": "^4.17.20", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.0" + }, + "dependencies": { + "ajv": { + "version": "6.12.4", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.4.tgz", + "integrity": "sha512-eienB2c9qVQs2KWexhkrdMLVDoIQCz5KSeLxwg9Lzk4DOfBtIK9PQwwufcsn1jjGuf9WZmqPMbGxOzfcuphJCQ==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "lodash": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", + "dev": true + }, + "string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + } } }, "tapable": { @@ -21826,9 +22803,9 @@ "dev": true }, "tiny-invariant": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.0.6.tgz", - "integrity": "sha512-FOyLWWVjG+aC0UqG76V53yAWdXfH8bO6FNmyZOuUrzDzK8DI3/JRY25UD7+g49JWM1LXwymsKERB+DzI0dTEQA==" + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.1.0.tgz", + "integrity": "sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw==" }, "tiny-warning": { "version": "1.0.3", @@ -21979,15 +22956,15 @@ "dev": true }, "trim-trailing-lines": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/trim-trailing-lines/-/trim-trailing-lines-1.1.2.tgz", - "integrity": "sha512-MUjYItdrqqj2zpcHFTkMa9WAv4JHTI6gnRQGPFLrt5L9a6tRMiDnIqYl8JBvu2d2Tc3lWJKQwlGCp0K8AvCM+Q==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/trim-trailing-lines/-/trim-trailing-lines-1.1.3.tgz", + "integrity": "sha512-4ku0mmjXifQcTVfYDfR5lpgV7zVqPg6zV9rdZmwOPqq0+Zq19xDqEgagqVbc4pOOShbncuAOIs59R3+3gcF3ZA==", "dev": true }, "trough": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/trough/-/trough-1.0.4.tgz", - "integrity": "sha512-tdzBRDGWcI1OpPVmChbdSKhvSVurznZ8X36AYURAcl+0o2ldlCY2XPzyXNNxwJwwyIU+rIglTCG4kxtNKBQH7Q==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/trough/-/trough-1.0.5.tgz", + "integrity": "sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA==", "dev": true }, "true-case-path": { @@ -22006,9 +22983,9 @@ "dev": true }, "ts-jest": { - "version": "26.2.0", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-26.2.0.tgz", - "integrity": "sha512-9+y2qwzXdAImgLSYLXAb/Rhq9+K4rbt0417b8ai987V60g2uoNWBBmMkYgutI7D8Zhu+IbCSHbBtrHxB9d7xyA==", + "version": "26.3.0", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-26.3.0.tgz", + "integrity": "sha512-Jq2uKfx6bPd9+JDpZNMBJMdMQUC3sJ08acISj8NXlVgR2d5OqslEHOR2KHMgwymu8h50+lKIm0m0xj/ioYdW2Q==", "dev": true, "requires": { "@types/jest": "26.x", @@ -22024,147 +23001,6 @@ "yargs-parser": "18.x" }, "dependencies": { - "@jest/types": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.3.0.tgz", - "integrity": "sha512-BDPG23U0qDeAvU4f99haztXwdAg3hz4El95LkAM+tHAqqhiVzRpEGHHU8EDxT/AnxOrA65YjLBwDahdJ9pTLJQ==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - } - }, - "@types/istanbul-reports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", - "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", - "dev": true, - "requires": { - "@types/istanbul-lib-report": "*" - } - }, - "@types/yargs": { - "version": "15.0.5", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.5.tgz", - "integrity": "sha512-Dk/IDOPtOgubt/IaevIUbTgV7doaKkoorvOyYM2CMwuDyP89bekI7H4xLIwunNYiK9jhCkmc6pUrJk3cj2AB9w==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "ci-info": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", - "dev": true - }, - "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" - } - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "graceful-fs": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", - "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "is-ci": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", - "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", - "dev": true, - "requires": { - "ci-info": "^2.0.0" - } - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "jest-util": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-26.3.0.tgz", - "integrity": "sha512-4zpn6bwV0+AMFN0IYhH/wnzIQzRaYVrz1A8sYnRnj4UXDXbOVtWmlaZkO9mipFqZ13okIfN87aDoJWB7VH6hcw==", - "dev": true, - "requires": { - "@jest/types": "^26.3.0", - "@types/node": "*", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "is-ci": "^2.0.0", - "micromatch": "^4.0.2" - } - }, - "micromatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", - "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", - "dev": true, - "requires": { - "braces": "^3.0.1", - "picomatch": "^2.0.5" - } - }, "mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", @@ -22176,34 +23012,6 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", "dev": true - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - }, - "yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "dev": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } } } }, @@ -22397,13 +23205,13 @@ "dev": true }, "unherit": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/unherit/-/unherit-1.1.2.tgz", - "integrity": "sha512-W3tMnpaMG7ZY6xe/moK04U9fBhi6wEiCYHUW5Mop/wQHf12+79EQGwxYejNdhEz2mkqkBlGwm7pxmgBKMVUj0w==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/unherit/-/unherit-1.1.3.tgz", + "integrity": "sha512-Ft16BJcnapDKp0+J/rqFC3Rrk6Y/Ng4nzsC028k2jdDII/rdZ7Wd3pPT/6+vIIxRagwRc9K0IUX0Ra4fKvw+WQ==", "dev": true, "requires": { - "inherits": "^2.0.1", - "xtend": "^4.0.1" + "inherits": "^2.0.0", + "xtend": "^4.0.0" } }, "unicode-canonical-property-names-ecmascript": { @@ -22435,19 +23243,31 @@ "dev": true }, "unified": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/unified/-/unified-7.1.0.tgz", - "integrity": "sha512-lbk82UOIGuCEsZhPj8rNAkXSDXd6p0QLzIuSsCdxrqnqU56St4eyOB+AlXsVgVeRmetPTYydIuvFfpDIed8mqw==", + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/unified/-/unified-9.2.0.tgz", + "integrity": "sha512-vx2Z0vY+a3YoTj8+pttM3tiJHCwY5UFbYdiWrwBEbHmK8pvsPj2rtAX2BFfgXen8T39CJWblWRDT4L5WGXtDdg==", "dev": true, "requires": { - "@types/unist": "^2.0.0", - "@types/vfile": "^3.0.0", "bail": "^1.0.0", "extend": "^3.0.0", - "is-plain-obj": "^1.1.0", + "is-buffer": "^2.0.0", + "is-plain-obj": "^2.0.0", "trough": "^1.0.0", - "vfile": "^3.0.0", - "x-is-string": "^0.1.0" + "vfile": "^4.0.0" + }, + "dependencies": { + "is-buffer": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz", + "integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==", + "dev": true + }, + "is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true + } } }, "union-value": { @@ -22516,51 +23336,57 @@ } }, "unist-util-find-all-after": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/unist-util-find-all-after/-/unist-util-find-all-after-1.0.4.tgz", - "integrity": "sha512-CaxvMjTd+yF93BKLJvZnEfqdM7fgEACsIpQqz8vIj9CJnUb9VpyymFS3tg6TCtgrF7vfCJBF5jbT2Ox9CBRYRQ==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/unist-util-find-all-after/-/unist-util-find-all-after-3.0.1.tgz", + "integrity": "sha512-0GICgc++sRJesLwEYDjFVJPJttBpVQaTNgc6Jw0Jhzvfs+jtKePEMu+uD+PqkRUrAvGQqwhpDwLGWo1PK8PDEw==", "dev": true, "requires": { - "unist-util-is": "^3.0.0" + "unist-util-is": "^4.0.0" } }, "unist-util-is": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-3.0.0.tgz", - "integrity": "sha512-sVZZX3+kspVNmLWBPAB6r+7D9ZgAFPNWm66f7YNb420RlQSbn+n8rG8dGZSkrER7ZIXGQYNm5pqC3v3HopH24A==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-4.0.2.tgz", + "integrity": "sha512-Ofx8uf6haexJwI1gxWMGg6I/dLnF2yE+KibhD3/diOqY2TinLcqHXCV6OI5gFVn3xQqDH+u0M625pfKwIwgBKQ==", "dev": true }, "unist-util-remove-position": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-1.1.3.tgz", - "integrity": "sha512-CtszTlOjP2sBGYc2zcKA/CvNdTdEs3ozbiJ63IPBxh8iZg42SCCb8m04f8z2+V1aSk5a7BxbZKEdoDjadmBkWA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-2.0.1.tgz", + "integrity": "sha512-fDZsLYIe2uT+oGFnuZmy73K6ZxOPG/Qcm+w7jbEjaFcJgbQ6cqjs/eSPzXhsmGpAsWPkqZM9pYjww5QTn3LHMA==", "dev": true, "requires": { - "unist-util-visit": "^1.1.0" + "unist-util-visit": "^2.0.0" } }, "unist-util-stringify-position": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-1.1.2.tgz", - "integrity": "sha512-pNCVrk64LZv1kElr0N1wPiHEUoXNVFERp+mlTg/s9R5Lwg87f9bM/3sQB99w+N9D/qnM9ar3+AKDBwo/gm/iQQ==", - "dev": true - }, - "unist-util-visit": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-1.4.1.tgz", - "integrity": "sha512-AvGNk7Bb//EmJZyhtRUnNMEpId/AZ5Ph/KUpTI09WHQuDZHKovQ1oEv3mfmKpWKtoMzyMC4GLBm1Zy5k12fjIw==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-2.0.3.tgz", + "integrity": "sha512-3faScn5I+hy9VleOq/qNbAd6pAx7iH5jYBMS9I1HgQVijz/4mv5Bvw5iw1sC/90CODiKo81G/ps8AJrISn687g==", "dev": true, "requires": { - "unist-util-visit-parents": "^2.0.0" + "@types/unist": "^2.0.2" + } + }, + "unist-util-visit": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-2.0.3.tgz", + "integrity": "sha512-iJ4/RczbJMkD0712mGktuGpm/U4By4FfDonL7N/9tATGIF4imikjOuagyMY53tnZq3NP6BcmlrHhEKAfGWjh7Q==", + "dev": true, + "requires": { + "@types/unist": "^2.0.0", + "unist-util-is": "^4.0.0", + "unist-util-visit-parents": "^3.0.0" } }, "unist-util-visit-parents": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-2.1.2.tgz", - "integrity": "sha512-DyN5vD4NE3aSeB+PXYNKxzGsfocxp6asDc2XXE3b0ekO2BaRUpBicbbUygfSvYfUz1IkmjFR1YF7dPklraMZ2g==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-3.1.0.tgz", + "integrity": "sha512-0g4wbluTF93npyPrp/ymd3tCDTMnP0yo2akFD2FIBAYXq/Sga3lwaU1D8OYKbtpioaI6CkDcQ6fsMnmtzt7htw==", "dev": true, "requires": { - "unist-util-is": "^3.0.0" + "@types/unist": "^2.0.0", + "unist-util-is": "^4.0.0" } }, "universalify": { @@ -22886,15 +23712,16 @@ } }, "vfile": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/vfile/-/vfile-3.0.1.tgz", - "integrity": "sha512-y7Y3gH9BsUSdD4KzHsuMaCzRjglXN0W2EcMf0gpvu6+SbsGhMje7xDc8AEoeXy6mIwCKMI6BkjMsRjzQbhMEjQ==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-4.2.0.tgz", + "integrity": "sha512-a/alcwCvtuc8OX92rqqo7PflxiCgXRFjdyoGVuYV+qbgCb0GgZJRvIgCD4+U/Kl1yhaRsaTwksF88xbPyGsgpw==", "dev": true, "requires": { + "@types/unist": "^2.0.0", "is-buffer": "^2.0.0", "replace-ext": "1.0.0", - "unist-util-stringify-position": "^1.0.0", - "vfile-message": "^1.0.0" + "unist-util-stringify-position": "^2.0.0", + "vfile-message": "^2.0.0" }, "dependencies": { "is-buffer": { @@ -22906,18 +23733,19 @@ } }, "vfile-location": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-2.0.5.tgz", - "integrity": "sha512-Pa1ey0OzYBkLPxPZI3d9E+S4BmvfVwNAAXrrqGbwTVXWaX2p9kM1zZ+n35UtVM06shmWKH4RPRN8KI80qE3wNQ==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-3.1.0.tgz", + "integrity": "sha512-FCZ4AN9xMcjFIG1oGmZKo61PjwJHRVA+0/tPUP2ul4uIwjGGndIxavEMRpWn5p4xwm/ZsdXp9YNygf1ZyE4x8g==", "dev": true }, "vfile-message": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-1.1.1.tgz", - "integrity": "sha512-1WmsopSGhWt5laNir+633LszXvZ+Z/lxveBf6yhGsqnQIhlhzooZae7zV6YVM1Sdkw68dtAW3ow0pOdPANugvA==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-2.0.4.tgz", + "integrity": "sha512-DjssxRGkMvifUOJre00juHoP9DPWuzjxKuMDrhNbk2TdaYYBNMStsNhEOt3idrtI12VQYM/1+iM0KOzXi4pxwQ==", "dev": true, "requires": { - "unist-util-stringify-position": "^1.1.1" + "@types/unist": "^2.0.0", + "unist-util-stringify-position": "^2.0.0" } }, "vm-browserify": { @@ -23627,22 +24455,14 @@ "dev": true }, "whatwg-url": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.1.0.tgz", - "integrity": "sha512-vEIkwNi9Hqt4TV9RdnaBPNt+E2Sgmo3gePebCRgZ1R7g6d23+53zCTnuB0amKI4AXq6VM8jj2DUAa0S1vjJxkw==", + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.2.1.tgz", + "integrity": "sha512-ZmVCr6nfBeaMxEHALLEGy0LszYjpJqf6PVNQUQ1qd9Et+q7Jpygd4rGGDXgHjD8e99yLFseD69msHDM4YwPZ4A==", "dev": true, "requires": { "lodash.sortby": "^4.7.0", "tr46": "^2.0.2", - "webidl-conversions": "^5.0.0" - }, - "dependencies": { - "webidl-conversions": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz", - "integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==", - "dev": true - } + "webidl-conversions": "^6.1.0" } }, "which": { @@ -23997,12 +24817,6 @@ "integrity": "sha512-D3RuNkynyHmEJIpD2qrgVkc9DQ23OrN/moAwZX4L8DfvszsJxpjQuUq3LMx6HoYji9fbIOBY18XWBsAux1ZZUA==", "dev": true }, - "x-is-string": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/x-is-string/-/x-is-string-0.1.0.tgz", - "integrity": "sha1-R0tQhlrzpJqcRlfwWs0UVFj3fYI=", - "dev": true - }, "xdg-basedir": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-3.0.0.tgz", @@ -24039,6 +24853,12 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, + "yaml": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.0.tgz", + "integrity": "sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg==", + "dev": true + }, "yargs": { "version": "15.4.1", "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", diff --git a/package.json b/package.json index d0723914..b13cd0ba 100644 --- a/package.json +++ b/package.json @@ -22,41 +22,41 @@ "check": "npm run test & npm run lint & wait" }, "dependencies": { - "@fortawesome/fontawesome-free": "^5.11.2", - "@fortawesome/fontawesome-svg-core": "^1.2.25", - "@fortawesome/free-regular-svg-icons": "^5.11.2", - "@fortawesome/free-solid-svg-icons": "^5.11.2", + "@fortawesome/fontawesome-free": "^5.14.0", + "@fortawesome/fontawesome-svg-core": "^1.2.30", + "@fortawesome/free-regular-svg-icons": "^5.14.0", + "@fortawesome/free-solid-svg-icons": "^5.14.0", "@fortawesome/react-fontawesome": "^0.1.11", "array-filter": "^1.0.0", "array-map": "^0.0.0", "array-reduce": "^0.0.0", - "axios": "^0.19.0", - "bootstrap": "^4.3.1", - "bottlejs": "^1.7.2", - "bowser": "^2.9.0", - "chart.js": "^2.8.0", + "axios": "^0.20.0", + "bootstrap": "^4.5.2", + "bottlejs": "^2.0.0", + "bowser": "^2.10.0", + "chart.js": "^2.9.3", "classnames": "^2.2.6", - "compare-versions": "^3.5.1", + "compare-versions": "^3.6.0", "csvjson": "^5.1.0", - "event-source-polyfill": "^1.0.12", - "leaflet": "^1.5.1", - "moment": "^2.24.0", + "event-source-polyfill": "^1.0.17", + "leaflet": "^1.7.1", + "moment": "^2.27.0", "promise": "^8.0.3", - "qs": "^6.9.0", - "ramda": "^0.26.1", + "qs": "^6.9.4", + "ramda": "^0.27.1", "react": "^16.13.1", - "react-autosuggest": "^9.4.3", - "react-chartjs-2": "^2.8.0", - "react-color": "^2.17.4", - "react-copy-to-clipboard": "^5.0.1", + "react-autosuggest": "^10.0.2", + "react-chartjs-2": "^2.10.0", + "react-color": "^2.18.1", + "react-copy-to-clipboard": "^5.0.2", "react-datepicker": "~1.5.0", "react-dom": "^16.13.1", "react-external-link": "^1.1.1", - "react-leaflet": "^2.4.0", - "react-moment": "^0.9.5", - "react-redux": "^7.1.1", - "react-router-dom": "^5.1.2", - "react-swipeable": "^5.4.0", + "react-leaflet": "^2.7.0", + "react-moment": "^0.9.7", + "react-redux": "^7.2.1", + "react-router-dom": "^5.2.0", + "react-swipeable": "^5.5.1", "react-tagsinput": "^3.19.0", "reactstrap": "^8.0.1", "redux": "^4.0.4", @@ -73,7 +73,7 @@ "@stryker-mutator/typescript": "^3.2.4", "@stryker-mutator/jest-runner": "^3.2.4", "@svgr/webpack": "^4.3.3", - "@types/chart.js": "^2.8.0", + "@types/chart.js": "^2.9.24", "@types/classnames": "^2.2.10", "@types/enzyme": "^3.10.5", "@types/jest": "^26.0.10", @@ -116,11 +116,11 @@ "fs-extra": "^8.1.0", "html-webpack-plugin": "^4.0.0-beta.8", "identity-obj-proxy": "^3.0.0", - "jest": "^26.4.1", - "jest-pnp-resolver": "^1.2.1", + "jest": "^26.4.2", + "jest-pnp-resolver": "^1.2.2", "jest-resolve": "^26.4.0", "mini-css-extract-plugin": "^0.8.0", - "node-sass": "^4.12.0", + "node-sass": "^4.14.1", "object-assign": "^4.1.1", "ocular.js": "^0.1.0", "optimize-css-assets-webpack-plugin": "^5.0.3", @@ -131,21 +131,21 @@ "postcss-preset-env": "^6.7.0", "postcss-safe-parser": "^4.0.1", "raf": "^3.4.1", - "react-app-polyfill": "^1.0.4", - "react-dev-utils": "^9.1.0", + "react-app-polyfill": "^1.0.6", + "react-dev-utils": "^10.2.1", "resolve": "^1.12.0", - "sass-loader": "^8.0.0", - "serve": "^11.2.0", + "sass-loader": "^10.0.2", + "serve": "^11.3.2", "stryker-cli": "^1.0.0", - "style-loader": "^1.0.0", - "stylelint": "^9.10.1", - "stylelint-config-adidas": "^1.2.1", + "style-loader": "^1.2.1", + "stylelint": "^13.7.0", + "stylelint-config-adidas": "^1.3.0", "stylelint-config-adidas-bem": "^1.2.0", - "stylelint-config-recommended-scss": "^4.0.0", - "stylelint-scss": "^3.11.1", + "stylelint-config-recommended-scss": "^4.2.0", + "stylelint-scss": "^3.18.0", "sw-precache-webpack-plugin": "^0.11.5", "terser-webpack-plugin": "^2.1.2", - "ts-jest": "^26.0.0", + "ts-jest": "^26.3.0", "ts-mockery": "^1.2.0", "typescript": "^3.9.7", "url-loader": "^2.2.0", diff --git a/src/common/AsideMenu.scss b/src/common/AsideMenu.scss index 46a88786..dda916e9 100644 --- a/src/common/AsideMenu.scss +++ b/src/common/AsideMenu.scss @@ -18,7 +18,7 @@ $asideMenuMobileWidth: 280px; @media (min-width: $mdMin) { padding: 30px 15px 15px; - border-right: 1px solid #eee; + border-right: 1px solid #eeeeee; } @media (max-width: $smMax) { @@ -51,17 +51,17 @@ $asideMenuMobileWidth: 280px; } .aside-menu__item--selected { - color: #fff; + color: #ffffff; background-color: $mainColor; } .aside-menu__item--selected:hover { - color: #fff; + color: #ffffff; background-color: $mainColor; } .aside-menu__item--divider { - border-bottom: 1px solid #eee; + border-bottom: 1px solid #eeeeee; margin: 20px 0; } @@ -74,7 +74,7 @@ $asideMenuMobileWidth: 280px; } .aside-menu__item--danger:hover { - color: #fff; + color: #ffffff; background-color: $dangerColor; } diff --git a/src/common/react-tagsinput.scss b/src/common/react-tagsinput.scss index b1e5fcd9..aa541351 100644 --- a/src/common/react-tagsinput.scss +++ b/src/common/react-tagsinput.scss @@ -1,6 +1,6 @@ .react-tagsinput { - background-color: #fff; - border: 1px solid #ccc; + background-color: #ffffff; + border: 1px solid #cccccc; border-radius: .25rem; overflow: hidden; min-height: 2.6rem; @@ -22,7 +22,7 @@ margin: 0 5px 6px 0; padding: 6px 8px; line-height: 1; - color: #fff; + color: #ffffff; } .react-tagsinput-remove { @@ -33,7 +33,7 @@ .react-tagsinput-tag span:before { content: '\2715'; - color: #fff; + color: #ffffff; } .react-tagsinput-input { diff --git a/src/index.scss b/src/index.scss index be46288b..209b5b70 100644 --- a/src/index.scss +++ b/src/index.scss @@ -25,7 +25,7 @@ body, } .badge-main { - color: #fff; + color: #ffffff; background-color: $mainColor; } diff --git a/src/short-urls/UseExistingIfFoundInfoIcon.scss b/src/short-urls/UseExistingIfFoundInfoIcon.scss index 835e5d2d..ff21131f 100644 --- a/src/short-urls/UseExistingIfFoundInfoIcon.scss +++ b/src/short-urls/UseExistingIfFoundInfoIcon.scss @@ -2,6 +2,6 @@ margin-bottom: 0; padding: 10px 15px; font-size: 17.5px; - border-left: 5px solid #eee; + border-left: 5px solid #eeeeee; background-color: #f9f9f9; } diff --git a/src/tags/TagCard.scss b/src/tags/TagCard.scss index 799c83d3..4bff8b45 100644 --- a/src/tags/TagCard.scss +++ b/src/tags/TagCard.scss @@ -3,7 +3,7 @@ } .tag-card__header.tag-card__header { - background-color: #eee; + background-color: #eeeeee; } .tag-card__header.tag-card__header, diff --git a/src/utils/DateInput.scss b/src/utils/DateInput.scss index 616e74ab..9653c38b 100644 --- a/src/utils/DateInput.scss +++ b/src/utils/DateInput.scss @@ -10,7 +10,7 @@ } .date-input-container__input:not(:disabled) { - background-color: #fff !important; + background-color: #ffffff !important; } .date-input-container__icon { @@ -29,6 +29,6 @@ .react-datepicker__close-icon.react-datepicker__close-icon:after { right: .75rem; line-height: 11px; - background-color: #333; + background-color: #333333; font-size: 14px; } diff --git a/src/utils/base.scss b/src/utils/base.scss index 69fdacd1..392d1fbc 100644 --- a/src/utils/base.scss +++ b/src/utils/base.scss @@ -10,8 +10,8 @@ $xlgMin: 1200px; // Colors $mainColor: #4696e5; -$lightHoverColor: #eee; -$lightGrey: #ddd; +$lightHoverColor: #eeeeee; +$lightGrey: #dddddd; $dangerColor: #dc3545; $mediumGrey: #dee2e6; From d064eb5f9ef1088a4cb2aafec4b9db3290b0474d Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 6 Sep 2020 10:22:21 +0200 Subject: [PATCH 59/59] Fixed inconsistent type --- src/visits/VisitsStats.tsx | 2 +- src/visits/helpers/LineChartCard.tsx | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/visits/VisitsStats.tsx b/src/visits/VisitsStats.tsx index 67f5b0ee..b31cd5fc 100644 --- a/src/visits/VisitsStats.tsx +++ b/src/visits/VisitsStats.tsx @@ -125,7 +125,7 @@ const VisitsStats: FC = (
diff --git a/src/visits/helpers/LineChartCard.tsx b/src/visits/helpers/LineChartCard.tsx index 0eadf798..a600d364 100644 --- a/src/visits/helpers/LineChartCard.tsx +++ b/src/visits/helpers/LineChartCard.tsx @@ -12,7 +12,7 @@ import { Line } from 'react-chartjs-2'; import { always, cond, reverse } from 'ramda'; import moment from 'moment'; import { ChartData, ChartDataSets } from 'chart.js'; -import { NormalizedVisit, Stats, Visit } from '../types'; +import { NormalizedVisit, Stats } from '../types'; import { fillTheGaps } from '../../utils/helpers/visits'; import { useToggle } from '../../utils/helpers/hooks'; import { rangeOf } from '../../utils/utils'; @@ -22,7 +22,7 @@ import './LineChartCard.scss'; interface LineChartCardProps { title: string; highlightedLabel?: string; - visits: Visit[]; + visits: NormalizedVisit[]; highlightedVisits: NormalizedVisit[]; } @@ -66,7 +66,7 @@ const determineInitialStep = (oldestVisitDate: string): Step => { return matcher() ?? 'monthly'; }; -const groupVisitsByStep = (step: Step, visits: (Visit | NormalizedVisit)[]): Stats => visits.reduce( +const groupVisitsByStep = (step: Step, visits: NormalizedVisit[]): Stats => visits.reduce( (acc, visit) => { const key = STEP_TO_DATE_FORMAT[step](visit.date); @@ -77,7 +77,7 @@ const groupVisitsByStep = (step: Step, visits: (Visit | NormalizedVisit)[]): Sta {}, ); -const generateLabels = (step: Step, visits: Visit[]): string[] => { +const generateLabels = (step: Step, visits: NormalizedVisit[]): string[] => { const unit = STEP_TO_DATE_UNIT_MAP[step]; const formatter = STEP_TO_DATE_FORMAT[step]; const newerDate = moment(visits[0].date); @@ -91,7 +91,7 @@ const generateLabels = (step: Step, visits: Visit[]): string[] => { }; const generateLabelsAndGroupedVisits = ( - visits: Visit[], + visits: NormalizedVisit[], groupedVisitsWithGaps: Stats, step: Step, skipNoElements: boolean,