diff --git a/.github/workflows/deploy-preview.yml b/.github/workflows/deploy-preview.yml index 76b02a48..b958cc37 100644 --- a/.github/workflows/deploy-preview.yml +++ b/.github/workflows/deploy-preview.yml @@ -19,7 +19,7 @@ jobs: node-version: 16.15 - name: Build run: | - npm ci --force && \ + npm ci && \ node ./scripts/set-homepage.js /shlink-web-client/${GITHUB_HEAD_REF#refs/heads/} && \ rm src/service-worker.ts && \ npm run build diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index 52d82e65..1ab257a4 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -16,7 +16,7 @@ jobs: with: node-version: 16.15 - name: Generate release assets - run: npm ci --force && VERSION=${GITHUB_REF#refs/tags/v} npm run build:dist + run: npm ci && VERSION=${GITHUB_REF#refs/tags/v} npm run build:dist - name: Publish release with assets uses: docker://antonyurchenko/git-release:latest env: diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e72bcf5..1e8eafcf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), * [#671](https://github.com/shlinkio/shlink-web-client/pull/671) Added proper color-scheme in root element based on selected theme. ### Changed -* *Nothing* +* [#688](https://github.com/shlinkio/shlink-web-client/pull/688) Finalized migration from enzyme to react-testing-library. ### Deprecated * *Nothing* diff --git a/Dockerfile b/Dockerfile index 4365eab9..d907daa8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,8 +2,7 @@ FROM node:16.15-alpine as node COPY . /shlink-web-client ARG VERSION="latest" ENV VERSION ${VERSION} -# TODO Remove --force once enzyme was removed -RUN cd /shlink-web-client && npm ci --force && NODE_ENV=production npm run build +RUN cd /shlink-web-client && npm ci && NODE_ENV=production npm run build FROM nginx:1.21-alpine LABEL maintainer="Alejandro Celaya " diff --git a/config/jest/setupBeforeEnzyme.js b/config/jest/setupBeforeEnzyme.js deleted file mode 100644 index 71c20acd..00000000 --- a/config/jest/setupBeforeEnzyme.js +++ /dev/null @@ -1,4 +0,0 @@ -import * as util from 'util'; - -global.TextEncoder = util.TextEncoder; -global.TextDecoder = util.TextDecoder; diff --git a/config/jest/setupEnzyme.js b/config/jest/setupEnzyme.js deleted file mode 100644 index 1d262ca5..00000000 --- a/config/jest/setupEnzyme.js +++ /dev/null @@ -1,4 +0,0 @@ -import Enzyme from 'enzyme'; -import Adapter from '@wojtekmaj/enzyme-adapter-react-17'; - -Enzyme.configure({ adapter: new Adapter() }); diff --git a/jest.config.js b/jest.config.js index 7a2f7fe3..1e37de5d 100644 --- a/jest.config.js +++ b/jest.config.js @@ -15,7 +15,6 @@ module.exports = { lines: 90, }, }, - setupFiles: ['/config/jest/setupBeforeEnzyme.js', '/config/jest/setupEnzyme.js'], setupFilesAfterEnv: ['/config/jest/setupTests.ts'], testMatch: ['/test/**/*.test.{ts,tsx}'], testEnvironment: 'jsdom', diff --git a/package-lock.json b/package-lock.json index 55db1790..7beed8ef 100644 --- a/package-lock.json +++ b/package-lock.json @@ -59,7 +59,6 @@ "@testing-library/jest-dom": "^5.16.4", "@testing-library/react": "^13.1.1", "@testing-library/user-event": "^14.1.1", - "@types/enzyme": "^3.10.11", "@types/jest": "^27.4.1", "@types/json2csv": "^5.0.3", "@types/leaflet": "^1.7.9", @@ -72,11 +71,9 @@ "@types/react-dom": "^18.0.3", "@types/react-tag-autocomplete": "^6.1.1", "@types/uuid": "^8.3.4", - "@wojtekmaj/enzyme-adapter-react-17": "0.6.5", "adm-zip": "^0.5.9", "babel-jest": "^28.0.3", "chalk": "^5.0.1", - "enzyme": "^3.11.0", "eslint": "^8.12.0", "identity-obj-proxy": "^3.0.0", "jest": "^28.0.3", @@ -5513,15 +5510,6 @@ "@types/node": "*" } }, - "node_modules/@types/cheerio": { - "version": "0.22.22", - "resolved": "https://registry.npmjs.org/@types/cheerio/-/cheerio-0.22.22.tgz", - "integrity": "sha512-05DYX4zU96IBfZFY+t3Mh88nlwSMtmmzSYaQkKN48T495VV1dkHSah6qYyDTN5ngaS0i0VonH37m+RuzSM0YiA==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/connect": { "version": "3.4.35", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", @@ -5541,16 +5529,6 @@ "@types/node": "*" } }, - "node_modules/@types/enzyme": { - "version": "3.10.11", - "resolved": "https://registry.npmjs.org/@types/enzyme/-/enzyme-3.10.11.tgz", - "integrity": "sha512-LEtC7zXsQlbGXWGcnnmOI7rTyP+i1QzQv4Va91RKXDEukLDaNyxu0rXlfMiGEhJwfgTPCTb0R+Pnlj//oM9e/w==", - "dev": true, - "dependencies": { - "@types/cheerio": "*", - "@types/react": "*" - } - }, "node_modules/@types/eslint": { "version": "7.29.0", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-7.29.0.tgz", @@ -6662,89 +6640,6 @@ "@xtuc/long": "4.2.2" } }, - "node_modules/@wojtekmaj/enzyme-adapter-react-17": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/@wojtekmaj/enzyme-adapter-react-17/-/enzyme-adapter-react-17-0.6.5.tgz", - "integrity": "sha512-ChIObUiXXYUiqzXPqOai+p6KF5dlbItpDDYsftUOQiAiygbMDlLeJIjynC6ZrJIa2U2MpRp4YJmtR2GQyIHjgA==", - "dev": true, - "dependencies": { - "@wojtekmaj/enzyme-adapter-utils": "^0.1.1", - "enzyme-shallow-equal": "^1.0.0", - "has": "^1.0.0", - "object.assign": "^4.1.0", - "object.values": "^1.1.0", - "prop-types": "^15.7.0", - "react-is": "^17.0.2", - "react-test-renderer": "^17.0.0" - }, - "peerDependencies": { - "enzyme": "^3.0.0", - "react": "^17.0.0-0", - "react-dom": "^17.0.0-0" - } - }, - "node_modules/@wojtekmaj/enzyme-adapter-react-17/node_modules/@wojtekmaj/enzyme-adapter-utils": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/@wojtekmaj/enzyme-adapter-utils/-/enzyme-adapter-utils-0.1.4.tgz", - "integrity": "sha512-ARGIQSIIv3oBia1m5Ihn1VU0FGmft6KPe39SBKTb8p7LSXO23YI4kNtc4M/cKoIY7P+IYdrZcgMObvedyjoSQA==", - "dev": true, - "dependencies": { - "function.prototype.name": "^1.1.0", - "has": "^1.0.0", - "object.fromentries": "^2.0.0", - "prop-types": "^15.7.0" - }, - "peerDependencies": { - "react": "^17.0.0-0" - } - }, - "node_modules/@wojtekmaj/enzyme-adapter-react-17/node_modules/enzyme-shallow-equal": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/enzyme-shallow-equal/-/enzyme-shallow-equal-1.0.4.tgz", - "integrity": "sha512-MttIwB8kKxypwHvRynuC3ahyNc+cFbR8mjVIltnmzQ0uKGqmsfO4bfBuLxb0beLNPhjblUEYvEbsg+VSygvF1Q==", - "dev": true, - "dependencies": { - "has": "^1.0.3", - "object-is": "^1.1.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/@wojtekmaj/enzyme-adapter-react-17/node_modules/react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "dev": true - }, - "node_modules/@wojtekmaj/enzyme-adapter-react-17/node_modules/react-test-renderer": { - "version": "17.0.1", - "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-17.0.1.tgz", - "integrity": "sha512-/dRae3mj6aObwkjCcxZPlxDFh73XZLgvwhhyON2haZGUEhiaY5EjfAdw+d/rQmlcFwdTpMXCSGVk374QbCTlrA==", - "dev": true, - "dependencies": { - "object-assign": "^4.1.1", - "react-is": "^17.0.1", - "react-shallow-renderer": "^16.13.1", - "scheduler": "^0.20.1" - }, - "peerDependencies": { - "react": "17.0.1" - } - }, - "node_modules/@wojtekmaj/enzyme-adapter-react-17/node_modules/react-test-renderer/node_modules/react-shallow-renderer": { - "version": "16.14.1", - "resolved": "https://registry.npmjs.org/react-shallow-renderer/-/react-shallow-renderer-16.14.1.tgz", - "integrity": "sha512-rkIMcQi01/+kxiTE9D3fdS959U1g7gs+/rborw++42m1O9FAQiNI/UNRZExVUoAOprn4umcXf+pFRou8i4zuBg==", - "dev": true, - "dependencies": { - "object-assign": "^4.1.1", - "react-is": "^16.12.0 || ^17.0.0" - }, - "peerDependencies": { - "react": "^16.0.0 || ^17.0.0" - } - }, "node_modules/@xtuc/ieee754": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", @@ -7164,12 +7059,6 @@ "node": ">=6.0" } }, - "node_modules/array-filter": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-filter/-/array-filter-1.0.0.tgz", - "integrity": "sha1-uveeYubvTCpMC4MSMtr/7CUfnYM=", - "dev": true - }, "node_modules/array-flatten": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", @@ -8289,61 +8178,6 @@ "integrity": "sha512-tzWzvgePgLORb9/3a0YenggReLKAIb2owL03H2Xdoe5pKcUyWRSEQ8xfCar8t2SIAuEDwtmx2da1YB52YuHQMQ==", "dev": true }, - "node_modules/cheerio": { - "version": "1.0.0-rc.3", - "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.3.tgz", - "integrity": "sha512-0td5ijfUPuubwLUu0OBoe98gZj8C/AA+RW3v67GPlGOrvxWjZmBXiBCRU+I8VEiNyJzjth40POfHiz2RB3gImA==", - "dev": true, - "dependencies": { - "css-select": "~1.2.0", - "dom-serializer": "~0.1.1", - "entities": "~1.1.1", - "htmlparser2": "^3.9.1", - "lodash": "^4.15.0", - "parse5": "^3.0.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cheerio/node_modules/css-select": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", - "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", - "dev": true, - "dependencies": { - "boolbase": "~1.0.0", - "css-what": "2.1", - "domutils": "1.5.1", - "nth-check": "~1.0.1" - } - }, - "node_modules/cheerio/node_modules/dom-serializer": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.1.tgz", - "integrity": "sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA==", - "dev": true, - "dependencies": { - "domelementtype": "^1.3.0", - "entities": "^1.1.1" - } - }, - "node_modules/cheerio/node_modules/domutils": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", - "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", - "dev": true, - "dependencies": { - "dom-serializer": "0", - "domelementtype": "1" - } - }, - "node_modules/cheerio/node_modules/entities": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", - "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==", - "dev": true - }, "node_modules/chokidar": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", @@ -8967,15 +8801,6 @@ "node": ">=8.0.0" } }, - "node_modules/css-what": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz", - "integrity": "sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==", - "dev": true, - "engines": { - "node": "*" - } - }, "node_modules/css.escape": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", @@ -9426,12 +9251,6 @@ "node": ">=8" } }, - "node_modules/discontinuous-range": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/discontinuous-range/-/discontinuous-range-1.0.0.tgz", - "integrity": "sha1-44Mx8IRLukm5qctxx3FYWqsbxlo=", - "dev": true - }, "node_modules/dlv": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", @@ -9533,15 +9352,6 @@ "node": ">=12" } }, - "node_modules/domhandler": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", - "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", - "dev": true, - "dependencies": { - "domelementtype": "1" - } - }, "node_modules/domutils": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", @@ -9685,52 +9495,6 @@ "integrity": "sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw==", "dev": true }, - "node_modules/enzyme": { - "version": "3.11.0", - "resolved": "https://registry.npmjs.org/enzyme/-/enzyme-3.11.0.tgz", - "integrity": "sha512-Dw8/Gs4vRjxY6/6i9wU0V+utmQO9kvh9XLnz3LIudviOnVYDEe2ec+0k+NQoMamn1VrjKgCUOWj5jG/5M5M0Qw==", - "dev": true, - "dependencies": { - "array.prototype.flat": "^1.2.3", - "cheerio": "^1.0.0-rc.3", - "enzyme-shallow-equal": "^1.0.1", - "function.prototype.name": "^1.1.2", - "has": "^1.0.3", - "html-element-map": "^1.2.0", - "is-boolean-object": "^1.0.1", - "is-callable": "^1.1.5", - "is-number-object": "^1.0.4", - "is-regex": "^1.0.5", - "is-string": "^1.0.5", - "is-subset": "^0.1.1", - "lodash.escape": "^4.0.1", - "lodash.isequal": "^4.5.0", - "object-inspect": "^1.7.0", - "object-is": "^1.0.2", - "object.assign": "^4.1.0", - "object.entries": "^1.1.1", - "object.values": "^1.1.1", - "raf": "^3.4.1", - "rst-selector-parser": "^2.2.3", - "string.prototype.trim": "^1.2.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/enzyme-shallow-equal": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/enzyme-shallow-equal/-/enzyme-shallow-equal-1.0.1.tgz", - "integrity": "sha512-hGA3i1so8OrYOZSM9whlkNmVHOicJpsjgTzC+wn2JMJXhq1oO4kA4bJ5MsfzSIcC71aLDKzJ6gZpIxrqt3QTAQ==", - "dev": true, - "dependencies": { - "has": "^1.0.3", - "object-is": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz", @@ -11844,38 +11608,12 @@ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", "dev": true }, - "node_modules/function.prototype.name": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.2.tgz", - "integrity": "sha512-C8A+LlHBJjB2AdcRPorc5JvJ5VUoWlXdEHLOJdCI7kjHEtGTpHQUiqMvCIKUwIsGwZX2jZJy761AXsn356bJQg==", - "dev": true, - "dependencies": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1", - "functions-have-names": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/functional-red-black-tree": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", "dev": true }, - "node_modules/functions-have-names": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.1.tgz", - "integrity": "sha512-j48B/ZI7VKs3sgeI2cZp7WXWmZXu7Iq5pl5/vptV5N2mq+DGFuS/ulaDjtaoLpYzuD6u8UgrUKHfgo7fDTSiBA==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -12240,18 +11978,6 @@ "wbuf": "^1.1.0" } }, - "node_modules/html-element-map": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/html-element-map/-/html-element-map-1.2.0.tgz", - "integrity": "sha512-0uXq8HsuG1v2TmQ8QkIhzbrqeskE4kn52Q18QJ9iAA/SnHoEKXWiUxHQtclRsCFWEUD2So34X+0+pZZu862nnw==", - "dev": true, - "dependencies": { - "array-filter": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/html-encoding-sniffer": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", @@ -12288,40 +12014,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/htmlparser2": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", - "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==", - "dev": true, - "dependencies": { - "domelementtype": "^1.3.1", - "domhandler": "^2.3.0", - "domutils": "^1.5.1", - "entities": "^1.1.1", - "inherits": "^2.0.1", - "readable-stream": "^3.1.1" - } - }, - "node_modules/htmlparser2/node_modules/entities": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", - "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==", - "dev": true - }, - "node_modules/htmlparser2/node_modules/readable-stream": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz", - "integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/http-deceiver": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", @@ -12959,12 +12651,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-subset": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-subset/-/is-subset-0.1.1.tgz", - "integrity": "sha1-ilkRfZMt4d4A8kX83TnOQ/HpOaY=", - "dev": true - }, "node_modules/is-symbol": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", @@ -17172,24 +16858,12 @@ "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=", "dev": true }, - "node_modules/lodash.escape": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-4.0.1.tgz", - "integrity": "sha1-yQRGkMIeBClL6qUXcS/e0fqI3pg=", - "dev": true - }, "node_modules/lodash.flatmap": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.flatmap/-/lodash.flatmap-4.5.0.tgz", "integrity": "sha1-74y/QI9uSCaGYzRTBcaswLd4cC4=", "dev": true }, - "node_modules/lodash.flattendeep": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", - "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=", - "dev": true - }, "node_modules/lodash.get": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", @@ -17201,12 +16875,6 @@ "integrity": "sha1-Cwih3PaDl8OXhVwyOXg4Mt90A9E=", "dev": true }, - "node_modules/lodash.isequal": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", - "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=", - "dev": true - }, "node_modules/lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", @@ -17681,12 +17349,6 @@ "mkdirp": "bin/cmd.js" } }, - "node_modules/moo": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/moo/-/moo-0.5.1.tgz", - "integrity": "sha512-I1mnb5xn4fO80BH9BLcF0yLypy2UKl+Cb01Fu0hJRkJjlCRtxZMWkTdAtDd5ZqCOxtCkhmRwyI57vWT+1iZ67w==", - "dev": true - }, "node_modules/moo-color": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/moo-color/-/moo-color-1.0.3.tgz", @@ -17766,31 +17428,6 @@ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, - "node_modules/nearley": { - "version": "2.19.1", - "resolved": "https://registry.npmjs.org/nearley/-/nearley-2.19.1.tgz", - "integrity": "sha512-xq47GIUGXxU9vQg7g/y1o1xuKnkO7ev4nRWqftmQrLkfnE/FjRqDaGOUakM8XHPn/6pW3bGjU2wgoJyId90rqg==", - "dev": true, - "dependencies": { - "commander": "^2.19.0", - "moo": "^0.5.0", - "railroad-diagrams": "^1.0.0", - "randexp": "0.4.6", - "semver": "^5.4.1" - }, - "bin": { - "nearley-railroad": "bin/nearley-railroad.js", - "nearley-test": "bin/nearley-test.js", - "nearley-unparse": "bin/nearley-unparse.js", - "nearleyc": "bin/nearleyc.js" - } - }, - "node_modules/nearley/node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - }, "node_modules/negotiator": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", @@ -18445,15 +18082,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/parse5": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-3.0.3.tgz", - "integrity": "sha512-rgO9Zg5LLLkfJF9E6CCmXlSE4UVceloys8JrFqCcHloC3usd/kJCyPDwH2SOlzix2j3xaP9sUX3e8+kvkuleAA==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -19566,30 +19194,11 @@ "performance-now": "^2.1.0" } }, - "node_modules/railroad-diagrams": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz", - "integrity": "sha1-635iZ1SN3t+4mcG5Dlc3RVnN234=", - "dev": true - }, "node_modules/ramda": { "version": "0.27.2", "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.27.2.tgz", "integrity": "sha512-SbiLPU40JuJniHexQSAgad32hfwd+DRUdwF2PlVuI5RZD0/vahUco7R8vD86J/tcEKKF9vZrUVwgtmGCqlCKyA==" }, - "node_modules/randexp": { - "version": "0.4.6", - "resolved": "https://registry.npmjs.org/randexp/-/randexp-0.4.6.tgz", - "integrity": "sha512-80WNmd9DA0tmZrw9qQa62GPPWfuXJknrmVmLcxvq4uZBdYqb1wYoKTmnlGUchvVWe0XiLupYkBoXVOxz3C8DYQ==", - "dev": true, - "dependencies": { - "discontinuous-range": "1.0.0", - "ret": "~0.1.10" - }, - "engines": { - "node": ">=0.12" - } - }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -23099,15 +22708,6 @@ "node": ">=4" } }, - "node_modules/ret": { - "version": "0.1.15", - "resolved": "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz", - "integrity": "sha1-uKSCXVvbH8P29Twrwz+BOIaBx7w=", - "dev": true, - "engines": { - "node": ">=0.12" - } - }, "node_modules/retry": { "version": "0.13.1", "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", @@ -23187,16 +22787,6 @@ "randombytes": "^2.1.0" } }, - "node_modules/rst-selector-parser": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/rst-selector-parser/-/rst-selector-parser-2.2.3.tgz", - "integrity": "sha1-gbIw6i/MYGbInjRy3nlChdmwPZE=", - "dev": true, - "dependencies": { - "lodash.flattendeep": "^4.4.0", - "nearley": "^2.7.10" - } - }, "node_modules/run-async": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", @@ -23277,16 +22867,6 @@ "node": ">=10" } }, - "node_modules/scheduler": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz", - "integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==", - "dev": true, - "dependencies": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" - } - }, "node_modules/schema-utils": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", @@ -24095,23 +23675,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/string.prototype.trim": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.1.tgz", - "integrity": "sha512-MjGFEeqixw47dAMFMtgUro/I0+wNqZB5GKXGt1fFr24u3TzDXCPu7J9Buppzoe3r/LqkSDLDDJzE15RGWDGAVw==", - "dev": true, - "dependencies": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1", - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/string.prototype.trimend": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", @@ -30720,15 +30283,6 @@ "@types/node": "*" } }, - "@types/cheerio": { - "version": "0.22.22", - "resolved": "https://registry.npmjs.org/@types/cheerio/-/cheerio-0.22.22.tgz", - "integrity": "sha512-05DYX4zU96IBfZFY+t3Mh88nlwSMtmmzSYaQkKN48T495VV1dkHSah6qYyDTN5ngaS0i0VonH37m+RuzSM0YiA==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, "@types/connect": { "version": "3.4.35", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", @@ -30748,16 +30302,6 @@ "@types/node": "*" } }, - "@types/enzyme": { - "version": "3.10.11", - "resolved": "https://registry.npmjs.org/@types/enzyme/-/enzyme-3.10.11.tgz", - "integrity": "sha512-LEtC7zXsQlbGXWGcnnmOI7rTyP+i1QzQv4Va91RKXDEukLDaNyxu0rXlfMiGEhJwfgTPCTb0R+Pnlj//oM9e/w==", - "dev": true, - "requires": { - "@types/cheerio": "*", - "@types/react": "*" - } - }, "@types/eslint": { "version": "7.29.0", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-7.29.0.tgz", @@ -31673,76 +31217,6 @@ "@xtuc/long": "4.2.2" } }, - "@wojtekmaj/enzyme-adapter-react-17": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/@wojtekmaj/enzyme-adapter-react-17/-/enzyme-adapter-react-17-0.6.5.tgz", - "integrity": "sha512-ChIObUiXXYUiqzXPqOai+p6KF5dlbItpDDYsftUOQiAiygbMDlLeJIjynC6ZrJIa2U2MpRp4YJmtR2GQyIHjgA==", - "dev": true, - "requires": { - "@wojtekmaj/enzyme-adapter-utils": "^0.1.1", - "enzyme-shallow-equal": "^1.0.0", - "has": "^1.0.0", - "object.assign": "^4.1.0", - "object.values": "^1.1.0", - "prop-types": "^15.7.0", - "react-is": "^17.0.2", - "react-test-renderer": "^17.0.0" - }, - "dependencies": { - "@wojtekmaj/enzyme-adapter-utils": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/@wojtekmaj/enzyme-adapter-utils/-/enzyme-adapter-utils-0.1.4.tgz", - "integrity": "sha512-ARGIQSIIv3oBia1m5Ihn1VU0FGmft6KPe39SBKTb8p7LSXO23YI4kNtc4M/cKoIY7P+IYdrZcgMObvedyjoSQA==", - "dev": true, - "requires": { - "function.prototype.name": "^1.1.0", - "has": "^1.0.0", - "object.fromentries": "^2.0.0", - "prop-types": "^15.7.0" - } - }, - "enzyme-shallow-equal": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/enzyme-shallow-equal/-/enzyme-shallow-equal-1.0.4.tgz", - "integrity": "sha512-MttIwB8kKxypwHvRynuC3ahyNc+cFbR8mjVIltnmzQ0uKGqmsfO4bfBuLxb0beLNPhjblUEYvEbsg+VSygvF1Q==", - "dev": true, - "requires": { - "has": "^1.0.3", - "object-is": "^1.1.2" - } - }, - "react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "dev": true - }, - "react-test-renderer": { - "version": "17.0.1", - "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-17.0.1.tgz", - "integrity": "sha512-/dRae3mj6aObwkjCcxZPlxDFh73XZLgvwhhyON2haZGUEhiaY5EjfAdw+d/rQmlcFwdTpMXCSGVk374QbCTlrA==", - "dev": true, - "requires": { - "object-assign": "^4.1.1", - "react-is": "^17.0.1", - "react-shallow-renderer": "^16.13.1", - "scheduler": "^0.20.1" - }, - "dependencies": { - "react-shallow-renderer": { - "version": "16.14.1", - "resolved": "https://registry.npmjs.org/react-shallow-renderer/-/react-shallow-renderer-16.14.1.tgz", - "integrity": "sha512-rkIMcQi01/+kxiTE9D3fdS959U1g7gs+/rborw++42m1O9FAQiNI/UNRZExVUoAOprn4umcXf+pFRou8i4zuBg==", - "dev": true, - "requires": { - "object-assign": "^4.1.1", - "react-is": "^16.12.0 || ^17.0.0" - } - } - } - } - } - }, "@xtuc/ieee754": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", @@ -32058,12 +31532,6 @@ "@babel/runtime-corejs3": "^7.10.2" } }, - "array-filter": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-filter/-/array-filter-1.0.0.tgz", - "integrity": "sha1-uveeYubvTCpMC4MSMtr/7CUfnYM=", - "dev": true - }, "array-flatten": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", @@ -32890,60 +32358,6 @@ "integrity": "sha512-tzWzvgePgLORb9/3a0YenggReLKAIb2owL03H2Xdoe5pKcUyWRSEQ8xfCar8t2SIAuEDwtmx2da1YB52YuHQMQ==", "dev": true }, - "cheerio": { - "version": "1.0.0-rc.3", - "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.3.tgz", - "integrity": "sha512-0td5ijfUPuubwLUu0OBoe98gZj8C/AA+RW3v67GPlGOrvxWjZmBXiBCRU+I8VEiNyJzjth40POfHiz2RB3gImA==", - "dev": true, - "requires": { - "css-select": "~1.2.0", - "dom-serializer": "~0.1.1", - "entities": "~1.1.1", - "htmlparser2": "^3.9.1", - "lodash": "^4.15.0", - "parse5": "^3.0.1" - }, - "dependencies": { - "css-select": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", - "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", - "dev": true, - "requires": { - "boolbase": "~1.0.0", - "css-what": "2.1", - "domutils": "1.5.1", - "nth-check": "~1.0.1" - } - }, - "dom-serializer": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.1.tgz", - "integrity": "sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA==", - "dev": true, - "requires": { - "domelementtype": "^1.3.0", - "entities": "^1.1.1" - } - }, - "domutils": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", - "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", - "dev": true, - "requires": { - "dom-serializer": "0", - "domelementtype": "1" - } - }, - "entities": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", - "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==", - "dev": true - } - } - }, "chokidar": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", @@ -33448,12 +32862,6 @@ "source-map": "^0.6.1" } }, - "css-what": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz", - "integrity": "sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==", - "dev": true - }, "css.escape": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", @@ -33794,12 +33202,6 @@ "path-type": "^4.0.0" } }, - "discontinuous-range": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/discontinuous-range/-/discontinuous-range-1.0.0.tgz", - "integrity": "sha1-44Mx8IRLukm5qctxx3FYWqsbxlo=", - "dev": true - }, "dlv": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", @@ -33897,15 +33299,6 @@ "webidl-conversions": "^7.0.0" } }, - "domhandler": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", - "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", - "dev": true, - "requires": { - "domelementtype": "1" - } - }, "domutils": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", @@ -34029,46 +33422,6 @@ "integrity": "sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw==", "dev": true }, - "enzyme": { - "version": "3.11.0", - "resolved": "https://registry.npmjs.org/enzyme/-/enzyme-3.11.0.tgz", - "integrity": "sha512-Dw8/Gs4vRjxY6/6i9wU0V+utmQO9kvh9XLnz3LIudviOnVYDEe2ec+0k+NQoMamn1VrjKgCUOWj5jG/5M5M0Qw==", - "dev": true, - "requires": { - "array.prototype.flat": "^1.2.3", - "cheerio": "^1.0.0-rc.3", - "enzyme-shallow-equal": "^1.0.1", - "function.prototype.name": "^1.1.2", - "has": "^1.0.3", - "html-element-map": "^1.2.0", - "is-boolean-object": "^1.0.1", - "is-callable": "^1.1.5", - "is-number-object": "^1.0.4", - "is-regex": "^1.0.5", - "is-string": "^1.0.5", - "is-subset": "^0.1.1", - "lodash.escape": "^4.0.1", - "lodash.isequal": "^4.5.0", - "object-inspect": "^1.7.0", - "object-is": "^1.0.2", - "object.assign": "^4.1.0", - "object.entries": "^1.1.1", - "object.values": "^1.1.1", - "raf": "^3.4.1", - "rst-selector-parser": "^2.2.3", - "string.prototype.trim": "^1.2.1" - } - }, - "enzyme-shallow-equal": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/enzyme-shallow-equal/-/enzyme-shallow-equal-1.0.1.tgz", - "integrity": "sha512-hGA3i1so8OrYOZSM9whlkNmVHOicJpsjgTzC+wn2JMJXhq1oO4kA4bJ5MsfzSIcC71aLDKzJ6gZpIxrqt3QTAQ==", - "dev": true, - "requires": { - "has": "^1.0.3", - "object-is": "^1.0.2" - } - }, "error-ex": { "version": "1.3.2", "resolved": "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz", @@ -35641,29 +34994,12 @@ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", "dev": true }, - "function.prototype.name": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.2.tgz", - "integrity": "sha512-C8A+LlHBJjB2AdcRPorc5JvJ5VUoWlXdEHLOJdCI7kjHEtGTpHQUiqMvCIKUwIsGwZX2jZJy761AXsn356bJQg==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1", - "functions-have-names": "^1.2.0" - } - }, "functional-red-black-tree": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", "dev": true }, - "functions-have-names": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.1.tgz", - "integrity": "sha512-j48B/ZI7VKs3sgeI2cZp7WXWmZXu7Iq5pl5/vptV5N2mq+DGFuS/ulaDjtaoLpYzuD6u8UgrUKHfgo7fDTSiBA==", - "dev": true - }, "gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -35928,15 +35264,6 @@ "wbuf": "^1.1.0" } }, - "html-element-map": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/html-element-map/-/html-element-map-1.2.0.tgz", - "integrity": "sha512-0uXq8HsuG1v2TmQ8QkIhzbrqeskE4kn52Q18QJ9iAA/SnHoEKXWiUxHQtclRsCFWEUD2So34X+0+pZZu862nnw==", - "dev": true, - "requires": { - "array-filter": "^1.0.0" - } - }, "html-encoding-sniffer": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", @@ -35964,39 +35291,6 @@ "integrity": "sha512-vy7ClnArOZwCnqZgvv+ddgHgJiAFXe3Ge9ML5/mBctVJoUoYPCdxVucOywjDARn6CVoh3dRSFdPHy2sX80L0Wg==", "dev": true }, - "htmlparser2": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", - "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==", - "dev": true, - "requires": { - "domelementtype": "^1.3.1", - "domhandler": "^2.3.0", - "domutils": "^1.5.1", - "entities": "^1.1.1", - "inherits": "^2.0.1", - "readable-stream": "^3.1.1" - }, - "dependencies": { - "entities": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", - "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==", - "dev": true - }, - "readable-stream": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz", - "integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - } - } - }, "http-deceiver": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", @@ -36437,12 +35731,6 @@ "has-tostringtag": "^1.0.0" } }, - "is-subset": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-subset/-/is-subset-0.1.1.tgz", - "integrity": "sha1-ilkRfZMt4d4A8kX83TnOQ/HpOaY=", - "dev": true - }, "is-symbol": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", @@ -39650,24 +38938,12 @@ "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=", "dev": true }, - "lodash.escape": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-4.0.1.tgz", - "integrity": "sha1-yQRGkMIeBClL6qUXcS/e0fqI3pg=", - "dev": true - }, "lodash.flatmap": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.flatmap/-/lodash.flatmap-4.5.0.tgz", "integrity": "sha1-74y/QI9uSCaGYzRTBcaswLd4cC4=", "dev": true }, - "lodash.flattendeep": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", - "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=", - "dev": true - }, "lodash.get": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", @@ -39679,12 +38955,6 @@ "integrity": "sha1-Cwih3PaDl8OXhVwyOXg4Mt90A9E=", "dev": true }, - "lodash.isequal": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", - "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=", - "dev": true - }, "lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", @@ -40049,12 +39319,6 @@ "minimist": "^1.2.5" } }, - "moo": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/moo/-/moo-0.5.1.tgz", - "integrity": "sha512-I1mnb5xn4fO80BH9BLcF0yLypy2UKl+Cb01Fu0hJRkJjlCRtxZMWkTdAtDd5ZqCOxtCkhmRwyI57vWT+1iZ67w==", - "dev": true - }, "moo-color": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/moo-color/-/moo-color-1.0.3.tgz", @@ -40125,27 +39389,6 @@ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, - "nearley": { - "version": "2.19.1", - "resolved": "https://registry.npmjs.org/nearley/-/nearley-2.19.1.tgz", - "integrity": "sha512-xq47GIUGXxU9vQg7g/y1o1xuKnkO7ev4nRWqftmQrLkfnE/FjRqDaGOUakM8XHPn/6pW3bGjU2wgoJyId90rqg==", - "dev": true, - "requires": { - "commander": "^2.19.0", - "moo": "^0.5.0", - "railroad-diagrams": "^1.0.0", - "randexp": "0.4.6", - "semver": "^5.4.1" - }, - "dependencies": { - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - } - } - }, "negotiator": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", @@ -40641,15 +39884,6 @@ "lines-and-columns": "^1.1.6" } }, - "parse5": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-3.0.3.tgz", - "integrity": "sha512-rgO9Zg5LLLkfJF9E6CCmXlSE4UVceloys8JrFqCcHloC3usd/kJCyPDwH2SOlzix2j3xaP9sUX3e8+kvkuleAA==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, "parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -41429,27 +40663,11 @@ "performance-now": "^2.1.0" } }, - "railroad-diagrams": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz", - "integrity": "sha1-635iZ1SN3t+4mcG5Dlc3RVnN234=", - "dev": true - }, "ramda": { "version": "0.27.2", "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.27.2.tgz", "integrity": "sha512-SbiLPU40JuJniHexQSAgad32hfwd+DRUdwF2PlVuI5RZD0/vahUco7R8vD86J/tcEKKF9vZrUVwgtmGCqlCKyA==" }, - "randexp": { - "version": "0.4.6", - "resolved": "https://registry.npmjs.org/randexp/-/randexp-0.4.6.tgz", - "integrity": "sha512-80WNmd9DA0tmZrw9qQa62GPPWfuXJknrmVmLcxvq4uZBdYqb1wYoKTmnlGUchvVWe0XiLupYkBoXVOxz3C8DYQ==", - "dev": true, - "requires": { - "discontinuous-range": "1.0.0", - "ret": "~0.1.10" - } - }, "randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -43956,12 +43174,6 @@ "signal-exit": "^3.0.2" } }, - "ret": { - "version": "0.1.15", - "resolved": "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz", - "integrity": "sha1-uKSCXVvbH8P29Twrwz+BOIaBx7w=", - "dev": true - }, "retry": { "version": "0.13.1", "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", @@ -44021,16 +43233,6 @@ } } }, - "rst-selector-parser": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/rst-selector-parser/-/rst-selector-parser-2.2.3.tgz", - "integrity": "sha1-gbIw6i/MYGbInjRy3nlChdmwPZE=", - "dev": true, - "requires": { - "lodash.flattendeep": "^4.4.0", - "nearley": "^2.7.10" - } - }, "run-async": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", @@ -44096,16 +43298,6 @@ "xmlchars": "^2.2.0" } }, - "scheduler": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz", - "integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==", - "dev": true, - "requires": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" - } - }, "schema-utils": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", @@ -44782,17 +43974,6 @@ } } }, - "string.prototype.trim": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.1.tgz", - "integrity": "sha512-MjGFEeqixw47dAMFMtgUro/I0+wNqZB5GKXGt1fFr24u3TzDXCPu7J9Buppzoe3r/LqkSDLDDJzE15RGWDGAVw==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1", - "function-bind": "^1.1.1" - } - }, "string.prototype.trimend": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", diff --git a/package.json b/package.json index 6f86668e..84f8d26c 100644 --- a/package.json +++ b/package.json @@ -75,7 +75,6 @@ "@testing-library/jest-dom": "^5.16.4", "@testing-library/react": "^13.1.1", "@testing-library/user-event": "^14.1.1", - "@types/enzyme": "^3.10.11", "@types/jest": "^27.4.1", "@types/json2csv": "^5.0.3", "@types/leaflet": "^1.7.9", @@ -88,11 +87,9 @@ "@types/react-dom": "^18.0.3", "@types/react-tag-autocomplete": "^6.1.1", "@types/uuid": "^8.3.4", - "@wojtekmaj/enzyme-adapter-react-17": "0.6.5", "adm-zip": "^0.5.9", "babel-jest": "^28.0.3", "chalk": "^5.0.1", - "enzyme": "^3.11.0", "eslint": "^8.12.0", "identity-obj-proxy": "^3.0.0", "jest": "^28.0.3", diff --git a/src/tags/helpers/EditTagModal.tsx b/src/tags/helpers/EditTagModal.tsx index b03a00a7..4b6af3d7 100644 --- a/src/tags/helpers/EditTagModal.tsx +++ b/src/tags/helpers/EditTagModal.tsx @@ -34,7 +34,7 @@ export const EditTagModal = ({ getColorForKey }: ColorGenerator) => ( return ( -
+ Edit tag diff --git a/src/tags/helpers/Tag.tsx b/src/tags/helpers/Tag.tsx index 2e116a80..61ab3057 100644 --- a/src/tags/helpers/Tag.tsx +++ b/src/tags/helpers/Tag.tsx @@ -19,6 +19,8 @@ export const Tag: FC = ({ text, children, clearable, className = '', c onClick={onClick} > {children ?? text} - {clearable && ×} + {clearable && ( + × + )} ); diff --git a/src/visits/VisitsStats.tsx b/src/visits/VisitsStats.tsx index f2ec12c2..1980ded8 100644 --- a/src/visits/VisitsStats.tsx +++ b/src/visits/VisitsStats.tsx @@ -139,7 +139,7 @@ export const VisitsStats: FC = ({ } if (isEmpty(visits)) { - return There are no visits matching current filter :(; + return There are no visits matching current filter; } return ( diff --git a/test/tags/TagCard.test.tsx b/test/tags/TagCard.test.tsx index d0e9297c..18dcb66c 100644 --- a/test/tags/TagCard.test.tsx +++ b/test/tags/TagCard.test.tsx @@ -2,15 +2,15 @@ import { screen } from '@testing-library/react'; import { MemoryRouter } from 'react-router-dom'; import { Mock } from 'ts-mockery'; import { TagCard as createTagCard } from '../../src/tags/TagCard'; -import { ColorGenerator } from '../../src/utils/services/ColorGenerator'; import { ReachableServer } from '../../src/servers/data'; import { renderWithEvents } from '../__helpers__/setUpTest'; +import { colorGeneratorMock } from '../utils/services/__mocks__/ColorGenerator.mock'; describe('', () => { const TagCard = createTagCard( ({ isOpen }) => DeleteTagConfirmModal {isOpen ? '[Open]' : '[Closed]'}, ({ isOpen }) => EditTagModal {isOpen ? '[Open]' : '[Closed]'}, - Mock.of({ getColorForKey: () => '' }), + colorGeneratorMock, ); const setUp = (tag = 'ssr') => renderWithEvents( diff --git a/test/tags/helpers/EditTagModal.test.tsx b/test/tags/helpers/EditTagModal.test.tsx index 6140caf6..d3f11979 100644 --- a/test/tags/helpers/EditTagModal.test.tsx +++ b/test/tags/helpers/EditTagModal.test.tsx @@ -1,46 +1,34 @@ -import { shallow, ShallowWrapper } from 'enzyme'; +import { screen, waitFor } from '@testing-library/react'; import { Mock } from 'ts-mockery'; -import { Button, Input, Modal, ModalHeader, Popover } from 'reactstrap'; -import { HexColorPicker } from 'react-colorful'; import { TagEdition } from '../../../src/tags/reducers/tagEdit'; import { EditTagModal as createEditTagModal } from '../../../src/tags/helpers/EditTagModal'; import { ColorGenerator } from '../../../src/utils/services/ColorGenerator'; -import { Result } from '../../../src/utils/Result'; import { ProblemDetailsError } from '../../../src/api/types'; -import { ShlinkApiError } from '../../../src/api/ShlinkApiError'; +import { renderWithEvents } from '../../__helpers__/setUpTest'; describe('', () => { - const EditTagModal = createEditTagModal(Mock.of({ getColorForKey: jest.fn(() => 'red') })); + const EditTagModal = createEditTagModal(Mock.of({ getColorForKey: jest.fn(() => 'green') })); const editTag = jest.fn().mockReturnValue(Promise.resolve()); const tagEdited = jest.fn().mockReturnValue(Promise.resolve()); const toggle = jest.fn(); - let wrapper: ShallowWrapper; - const createWrapper = (tagEdit: Partial = {}) => { + const setUp = (tagEdit: Partial = {}) => { const edition = Mock.of(tagEdit); - - wrapper = shallow( + return renderWithEvents( , ); - - return wrapper; }; afterEach(jest.clearAllMocks); - afterEach(() => wrapper?.unmount()); - it('allows modal to be toggled with different mechanisms', () => { - const wrapper = createWrapper(); - const modal = wrapper.find(Modal); - const modalHeader = wrapper.find(ModalHeader); - const cancelBtn = wrapper.find(Button).findWhere((btn) => btn.prop('type') === 'button'); + it('allows modal to be toggled with different mechanisms', async () => { + const { user } = setUp(); expect(toggle).not.toHaveBeenCalled(); - (modal.prop('toggle') as Function)(); - (modalHeader.prop('toggle') as Function)(); - cancelBtn.simulate('click'); + await user.click(screen.getByLabelText('Close')); + await user.click(screen.getByRole('button', { name: 'Cancel' })); - expect(toggle).toHaveBeenCalledTimes(3); + expect(toggle).toHaveBeenCalledTimes(2); expect(editTag).not.toHaveBeenCalled(); expect(tagEdited).not.toHaveBeenCalled(); }); @@ -48,62 +36,54 @@ describe('', () => { it.each([ [true, 'Saving...'], [false, 'Save'], - ])('renders submit button in expected state', (editing, expectedText) => { - const wrapper = createWrapper({ editing }); - const submitBtn = wrapper.find(Button).findWhere((btn) => btn.prop('color') === 'primary'); - - expect(submitBtn.html()).toContain(expectedText); - expect(submitBtn.prop('disabled')).toEqual(editing); + ])('renders submit button in expected state', (editing, name) => { + setUp({ editing }); + expect(screen.getByRole('button', { name })).toBeInTheDocument(); }); it.each([ [true, 1], [false, 0], ])('displays error result in case of error', (error, expectedResultCount) => { - const wrapper = createWrapper({ error, errorData: Mock.all() }); - const result = wrapper.find(Result); - const apiError = wrapper.find(ShlinkApiError); - - expect(result).toHaveLength(expectedResultCount); - expect(apiError).toHaveLength(expectedResultCount); + setUp({ error, errorData: Mock.all() }); + expect(screen.queryAllByText('Something went wrong while editing the tag :(')).toHaveLength(expectedResultCount); }); - it('updates tag value when text changes', () => { - const wrapper = createWrapper(); + it('updates tag value when text changes', async () => { + const { user } = setUp(); + const getInput = () => screen.getByPlaceholderText('Tag'); - expect(wrapper.find(Input).prop('value')).toEqual('foo'); - wrapper.find(Input).simulate('change', { target: { value: 'bar' } }); - expect(wrapper.find(Input).prop('value')).toEqual('bar'); + expect(getInput()).toHaveValue('foo'); + await user.clear(getInput()); + await user.type(getInput(), 'bar'); + expect(getInput()).toHaveValue('bar'); }); it('invokes all functions on form submit', async () => { - const wrapper = createWrapper(); - const form = wrapper.find('form'); + const { user } = setUp(); expect(editTag).not.toHaveBeenCalled(); expect(tagEdited).not.toHaveBeenCalled(); - await form.simulate('submit', { preventDefault: jest.fn() }); + await user.click(screen.getByRole('button', { name: 'Save' })); expect(editTag).toHaveBeenCalled(); expect(tagEdited).toHaveBeenCalled(); }); - it('changes color when changing on color picker', () => { - const wrapper = createWrapper(); + it('changes color when changing on color picker', async () => { + const { user } = setUp(); + const colorBtn = screen.getByRole('img', { hidden: true }); + // const initialColor = colorBtn.parentElement?.style.backgroundColor; - expect(wrapper.find(HexColorPicker).prop('color')).toEqual('red'); - wrapper.find(HexColorPicker).simulate('change', 'blue'); - expect(wrapper.find(HexColorPicker).prop('color')).toEqual('blue'); - }); + await user.click(colorBtn); + await waitFor(() => screen.getByRole('tooltip')); + await user.click(screen.getByLabelText('Hue')); + await user.click(screen.getByLabelText('Color')); + await user.click(colorBtn); + await waitFor(() => expect(screen.queryByRole('tooltip')).not.toBeInTheDocument()); - it('allows toggling popover with different mechanisms', () => { - const wrapper = createWrapper(); - - expect(wrapper.find(Popover).prop('isOpen')).toEqual(false); - (wrapper.find(Popover).prop('toggle') as Function)(); - expect(wrapper.find(Popover).prop('isOpen')).toEqual(true); - wrapper.find('div').simulate('click'); - expect(wrapper.find(Popover).prop('isOpen')).toEqual(false); + // I need to figure this one out + // await waitFor(() => expect(initialColor).not.toEqual(colorBtn.parentElement?.style.backgroundColor)); }); }); diff --git a/test/tags/helpers/Tag.test.tsx b/test/tags/helpers/Tag.test.tsx index e8ecbaf6..edd4d20c 100644 --- a/test/tags/helpers/Tag.test.tsx +++ b/test/tags/helpers/Tag.test.tsx @@ -75,7 +75,7 @@ describe('', () => { container.firstElementChild && await user.click(container.firstElementChild); expect(onClick).toHaveBeenCalledTimes(1); - await user.click(screen.getByLabelText('Close')); + await user.click(screen.getByLabelText(/^Remove/)); expect(onClose).toHaveBeenCalledTimes(1); }); @@ -86,7 +86,7 @@ describe('', () => { ])('includes a close component when the tag is clearable', (clearable, expectedCloseBtnAmount, expectedCursor) => { const { container } = setUp('foo', clearable); - expect(screen.queryAllByLabelText('Close')).toHaveLength(expectedCloseBtnAmount); + expect(screen.queryAllByLabelText(/^Remove/)).toHaveLength(expectedCloseBtnAmount); expect(container.firstChild).toHaveAttribute('style', expect.stringContaining(`cursor: ${expectedCursor}`)); }); diff --git a/test/tags/helpers/TagsSelector.test.tsx b/test/tags/helpers/TagsSelector.test.tsx index 143e32da..e6bed7ec 100644 --- a/test/tags/helpers/TagsSelector.test.tsx +++ b/test/tags/helpers/TagsSelector.test.tsx @@ -1,77 +1,81 @@ -import { shallow, ShallowWrapper } from 'enzyme'; +import { screen } from '@testing-library/react'; import { Mock } from 'ts-mockery'; import { TagsSelector as createTagsSelector } from '../../../src/tags/helpers/TagsSelector'; -import { ColorGenerator } from '../../../src/utils/services/ColorGenerator'; import { TagsList } from '../../../src/tags/reducers/tagsList'; import { Settings } from '../../../src/settings/reducers/settings'; +import { renderWithEvents } from '../../__helpers__/setUpTest'; +import { colorGeneratorMock } from '../../utils/services/__mocks__/ColorGenerator.mock'; describe('', () => { const onChange = jest.fn(); - const TagsSelector = createTagsSelector(Mock.all()); + const TagsSelector = createTagsSelector(colorGeneratorMock); const tags = ['foo', 'bar']; const tagsList = Mock.of({ tags: [...tags, 'baz'] }); - let wrapper: ShallowWrapper; + const setUp = () => renderWithEvents( + ()} + listTags={jest.fn()} + onChange={onChange} + />, + ); - beforeEach(jest.clearAllMocks); - beforeEach(() => { - wrapper = shallow( - ()} - listTags={jest.fn()} - onChange={onChange} - />, - ); - }); + afterEach(jest.clearAllMocks); - afterEach(() => wrapper?.unmount()); - - it('has expected props', () => { - expect(wrapper.prop('placeholderText')).toEqual('Add tags to the URL'); - expect(wrapper.prop('allowNew')).toEqual(true); - expect(wrapper.prop('addOnBlur')).toEqual(true); - expect(wrapper.prop('minQueryLength')).toEqual(1); + it('has an input for tags', () => { + setUp(); + expect(screen.getByPlaceholderText('Add tags to the URL')).toBeInTheDocument(); }); it('contains expected tags', () => { - expect(wrapper.prop('tags')).toEqual([ - { - id: 'foo', - name: 'foo', - }, - { - id: 'bar', - name: 'bar', - }, - ]); + setUp(); + + expect(screen.getByText('foo')).toBeInTheDocument(); + expect(screen.getByText('bar')).toBeInTheDocument(); }); - it('contains expected suggestions', () => { - expect(wrapper.prop('suggestions')).toEqual([ - { - id: 'baz', - name: 'baz', - }, - ]); + it('contains expected suggestions', async () => { + const { container, user } = setUp(); + + expect(container.querySelector('.react-tags__suggestions')).not.toBeInTheDocument(); + expect(screen.queryByText('baz')).not.toBeInTheDocument(); + + await user.type(screen.getByPlaceholderText('Add tags to the URL'), 'ba'); + + expect(container.querySelector('.react-tags__suggestions')).toBeInTheDocument(); + expect(screen.getByText('baz')).toBeInTheDocument(); }); it.each([ ['The-New-Tag', [...tags, 'the-new-tag']], - ['comma,separated,tags', [...tags, 'comma', 'separated', 'tags']], ['foo', tags], - ])('invokes onChange when new tags are added', (newTag, expectedTags) => { - wrapper.simulate('addition', { name: newTag }); + ])('invokes onChange when new tags are added', async (newTag, expectedTags) => { + const { user } = setUp(); + expect(onChange).not.toHaveBeenCalled(); + await user.type(screen.getByPlaceholderText('Add tags to the URL'), newTag); + await user.type(screen.getByPlaceholderText('Add tags to the URL'), '{Enter}'); expect(onChange).toHaveBeenCalledWith(expectedTags); }); - it.each([ - [0, 'bar'], - [1, 'foo'], - ])('invokes onChange when tags are deleted', (index, expected) => { - wrapper.simulate('delete', index); + it('splits tags when several comma-separated ones are pasted', async () => { + const { user } = setUp(); + expect(onChange).not.toHaveBeenCalled(); + await user.click(screen.getByPlaceholderText('Add tags to the URL')); + await user.paste('comma,separated,tags'); + await user.type(screen.getByPlaceholderText('Add tags to the URL'), '{Enter}'); + expect(onChange).toHaveBeenCalledWith([...tags, 'comma', 'separated', 'tags']); + }); + + it.each([ + ['foo', 'bar'], + ['bar', 'foo'], + ])('invokes onChange when tags are deleted', async (removedLabel, expected) => { + const { user } = setUp(); + + await user.click(screen.getByLabelText(`Remove ${removedLabel}`)); expect(onChange).toHaveBeenCalledWith([expected]); }); }); diff --git a/test/utils/DropdownBtnMenu.test.tsx b/test/utils/DropdownBtnMenu.test.tsx index 42f43f00..d7ef86b8 100644 --- a/test/utils/DropdownBtnMenu.test.tsx +++ b/test/utils/DropdownBtnMenu.test.tsx @@ -1,48 +1,39 @@ -import { shallow, ShallowWrapper } from 'enzyme'; +import { screen } from '@testing-library/react'; import { Mock } from 'ts-mockery'; -import { ButtonDropdown, DropdownMenu, DropdownToggle } from 'reactstrap'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { faEllipsisV as menuIcon } from '@fortawesome/free-solid-svg-icons'; import { DropdownBtnMenu, DropdownBtnMenuProps } from '../../src/utils/DropdownBtnMenu'; +import { renderWithEvents } from '../__helpers__/setUpTest'; describe('', () => { - let wrapper: ShallowWrapper; - const createWrapper = (props: Partial) => { - wrapper = shallow((props)}>the children); - - return wrapper; - }; - - afterAll(() => wrapper?.unmount()); + const setUp = (props: Partial = {}) => renderWithEvents( + ({ toggle: jest.fn(), ...props })}>the children, + ); it('renders expected components', () => { - const wrapper = createWrapper({}); - const toggle = wrapper.find(DropdownToggle); - const icon = wrapper.find(FontAwesomeIcon); + setUp(); + const toggle = screen.getByRole('button'); - expect(wrapper.find(ButtonDropdown)).toHaveLength(1); - expect(toggle).toHaveLength(1); - expect(toggle.prop('size')).toEqual('sm'); - expect(toggle.prop('caret')).toEqual(true); - expect(toggle.prop('outline')).toEqual(true); - expect(toggle.prop('className')).toEqual('dropdown-btn-menu__dropdown-toggle'); - expect(icon).toHaveLength(1); - expect(icon.prop('icon')).toEqual(menuIcon); + expect(toggle).toBeInTheDocument(); + expect(toggle).toHaveClass('btn-sm'); + expect(toggle).toHaveClass('dropdown-btn-menu__dropdown-toggle'); + expect(screen.getByRole('img', { hidden: true })).toBeInTheDocument(); }); it('renders expected children', () => { - const menu = createWrapper({}).find(DropdownMenu); - - expect(menu.prop('children')).toEqual('the children'); + setUp(); + expect(screen.getByText('the children')).toBeInTheDocument(); }); it.each([ [undefined, true], [true, true], [false, false], - ])('renders menu to right when expected', (right, expectedRight) => { - const wrapper = createWrapper({ right }); + ])('renders menu to the end when expected', (right, expectedEnd) => { + setUp({ right }); - expect(wrapper.find(DropdownMenu).prop('end')).toEqual(expectedRight); + if (expectedEnd) { + expect(screen.getByRole('menu', { hidden: true })).toHaveClass('dropdown-menu-end'); + } else { + expect(screen.getByRole('menu', { hidden: true })).not.toHaveClass('dropdown-menu-end'); + } }); }); diff --git a/test/utils/dates/DateRangeRow.test.tsx b/test/utils/dates/DateRangeRow.test.tsx index 79c957c6..a4ab3e70 100644 --- a/test/utils/dates/DateRangeRow.test.tsx +++ b/test/utils/dates/DateRangeRow.test.tsx @@ -1,39 +1,34 @@ -import { shallow, ShallowWrapper } from 'enzyme'; +import { screen } from '@testing-library/react'; import { DateRangeRow } from '../../../src/utils/dates/DateRangeRow'; -import { DateInput } from '../../../src/utils/DateInput'; +import { renderWithEvents } from '../../__helpers__/setUpTest'; describe('', () => { - let wrapper: ShallowWrapper; const onEndDateChange = jest.fn(); const onStartDateChange = jest.fn(); + const setUp = () => renderWithEvents( + , + ); - beforeEach(() => { - wrapper = shallow(); - }); - afterEach(() => { - wrapper.unmount(); - jest.clearAllMocks(); - }); + afterEach(jest.clearAllMocks); it('renders two date inputs', () => { - const dateInput = wrapper.find(DateInput); - - expect(dateInput).toHaveLength(2); + setUp(); + expect(screen.getAllByRole('textbox')).toHaveLength(2); }); - it('invokes start date callback when change event is triggered on first input', () => { - const dateInput = wrapper.find(DateInput).first(); + it('invokes start date callback when change event is triggered on first input', async () => { + const { user } = setUp(); expect(onStartDateChange).not.toHaveBeenCalled(); - dateInput.simulate('change'); + await user.type(screen.getByPlaceholderText('Since...'), '2020-05-05'); expect(onStartDateChange).toHaveBeenCalled(); }); - it('invokes end date callback when change event is triggered on second input', () => { - const dateInput = wrapper.find(DateInput).last(); + it('invokes end date callback when change event is triggered on second input', async () => { + const { user } = setUp(); expect(onEndDateChange).not.toHaveBeenCalled(); - dateInput.simulate('change'); + await user.type(screen.getByPlaceholderText('Until...'), '2022-05-05'); expect(onEndDateChange).toHaveBeenCalled(); }); }); diff --git a/test/visits/VisitsStats.test.tsx b/test/visits/VisitsStats.test.tsx index 0c7d3de4..8bd2fb8f 100644 --- a/test/visits/VisitsStats.test.tsx +++ b/test/visits/VisitsStats.test.tsx @@ -1,116 +1,83 @@ -import { shallow, ShallowWrapper } from 'enzyme'; -import { Progress } from 'reactstrap'; -import { sum } from 'ramda'; +import { screen } from '@testing-library/react'; import { Mock } from 'ts-mockery'; -import { Route } from 'react-router-dom'; +import { Router } from 'react-router-dom'; +import { createMemoryHistory } from 'history'; import { VisitsStats } from '../../src/visits/VisitsStats'; -import { Message } from '../../src/utils/Message'; import { Visit, VisitsInfo } from '../../src/visits/types'; -import { LineChartCard } from '../../src/visits/charts/LineChartCard'; -import { VisitsTable } from '../../src/visits/VisitsTable'; -import { Result } from '../../src/utils/Result'; import { Settings } from '../../src/settings/reducers/settings'; import { SelectedServer } from '../../src/servers/data'; -import { SortableBarChartCard } from '../../src/visits/charts/SortableBarChartCard'; -import { DoughnutChartCard } from '../../src/visits/charts/DoughnutChartCard'; -import { ExportBtn } from '../../src/utils/ExportBtn'; +import { renderWithEvents } from '../__helpers__/setUpTest'; +import { rangeOf } from '../../src/utils/utils'; describe('', () => { - const visits = [Mock.all(), Mock.all(), Mock.all()]; - - let wrapper: ShallowWrapper; + const visits = rangeOf(3, () => Mock.of({ date: '2020-01-01' })); const getVisitsMock = jest.fn(); const exportCsv = jest.fn(); + const setUp = (visitsInfo: Partial, activeRoute = '/by-time') => { + const history = createMemoryHistory(); + history.push(activeRoute); - const createComponent = (visitsInfo: Partial) => { - wrapper = shallow( - (visitsInfo)} - cancelGetVisits={() => {}} - settings={Mock.all()} - exportCsv={exportCsv} - selectedServer={Mock.all()} - />, + return renderWithEvents( + + (visitsInfo)} + cancelGetVisits={() => {}} + settings={Mock.all()} + exportCsv={exportCsv} + selectedServer={Mock.all()} + /> + , ); - - return wrapper; }; - afterEach(() => wrapper?.unmount()); - it('renders a preloader when visits are loading', () => { - const wrapper = createComponent({ loading: true, visits: [] }); - const loadingMessage = wrapper.find(Message); - const progress = wrapper.find(Progress); + setUp({ loading: true, visits: [] }); - expect(loadingMessage).toHaveLength(1); - expect(loadingMessage.html()).toContain('Loading...'); - expect(progress).toHaveLength(0); + expect(screen.getByText('Loading...')).toBeInTheDocument(); + expect(screen.queryByText(/^This is going to take a while/)).not.toBeInTheDocument(); }); it('renders a warning and progress bar when loading large amounts of visits', () => { - const wrapper = createComponent({ loading: true, loadingLarge: true, visits: [], progress: 25 }); - const loadingMessage = wrapper.find(Message); - const progress = wrapper.find(Progress); + setUp({ loading: true, loadingLarge: true, visits: [], progress: 25 }); - expect(loadingMessage).toHaveLength(1); - expect(loadingMessage.html()).toContain('This is going to take a while... :S'); - expect(progress).toHaveLength(1); - expect(progress.prop('value')).toEqual(25); + expect(screen.queryByText('Loading...')).not.toBeInTheDocument(); + expect(screen.getByText(/^This is going to take a while/)).toBeInTheDocument(); + expect(screen.getByRole('progressbar')).toHaveAttribute('aria-valuenow', '25'); }); it('renders an error message when visits could not be loaded', () => { - const wrapper = createComponent({ loading: false, error: true, visits: [] }); - const errorMessage = wrapper.find(Result).filterWhere((result) => result.prop('type') === 'error'); - - expect(errorMessage).toHaveLength(1); - expect(errorMessage.html()).toContain('An error occurred while loading visits :('); + setUp({ loading: false, error: true, visits: [] }); + expect(screen.getByText('An error occurred while loading visits :(')).toBeInTheDocument(); }); it('renders a message when visits are loaded but the list is empty', () => { - const wrapper = createComponent({ loading: false, error: false, visits: [] }); - const message = wrapper.find(Message); - - expect(message).toHaveLength(1); - expect(message.html()).toContain('There are no visits matching current filter :('); + setUp({ loading: false, error: false, visits: [] }); + expect(screen.getByText('There are no visits matching current filter')).toBeInTheDocument(); }); - it('renders expected amount of charts', () => { - const wrapper = createComponent({ loading: false, error: false, visits }); - const total = sum(wrapper.find(Route).map((element) => { - const ElementComponents = () => element.prop('element'); - // @ts-expect-error Wrapped element - const wrappedElement = shallow(); - - const charts = wrappedElement.find(DoughnutChartCard); - const sortableCharts = wrappedElement.find(SortableBarChartCard); - const lineChart = wrappedElement.find(LineChartCard); - const table = wrappedElement.find(VisitsTable); - - return charts.length + sortableCharts.length + lineChart.length + table.length; - })); - - expect(total).toEqual(7); + it.each([ + ['/by-time', 2], + ['/by-context', 4], + ['/by-location', 3], + ['/list', 1], + ])('renders expected amount of charts', (route, expectedCharts) => { + const { container } = setUp({ loading: false, error: false, visits }, route); + expect(container.querySelectorAll('.card')).toHaveLength(expectedCharts); }); - it('holds the map button content generator on cities chart extraHeaderContent', () => { - const wrapper = createComponent({ loading: false, error: false, visits }); - const ElementComponent = () => wrapper.find(Route).findWhere((element) => element.prop('path') === 'by-location') - .prop('element'); - const citiesChart = shallow().find(SortableBarChartCard).find('[title="Cities"]'); - const extraHeaderContent = citiesChart.prop('extraHeaderContent'); - - expect(extraHeaderContent).toHaveLength(1); - expect(typeof extraHeaderContent).toEqual('function'); + it('holds the map button on cities chart header', () => { + setUp({ loading: false, error: false, visits }, '/by-location'); + expect( + screen.getAllByRole('img', { hidden: true }).some((icon) => icon.classList.contains('fa-map-location-dot')), + ).toEqual(true); }); - it('exports CSV when export btn is clicked', () => { - const wrapper = createComponent({ visits }); - const exportBtn = wrapper.find(ExportBtn).last(); + it('exports CSV when export btn is clicked', async () => { + const { user } = setUp({ visits }); - expect(exportBtn).toHaveLength(1); - exportBtn.simulate('click'); + expect(exportCsv).not.toHaveBeenCalled(); + await user.click(screen.getByRole('button', { name: /Export/ })); expect(exportCsv).toHaveBeenCalled(); }); }); diff --git a/test/visits/VisitsTable.test.tsx b/test/visits/VisitsTable.test.tsx index 4f5fc10e..5bf886ea 100644 --- a/test/visits/VisitsTable.test.tsx +++ b/test/visits/VisitsTable.test.tsx @@ -1,168 +1,141 @@ -import { shallow, ShallowWrapper } from 'enzyme'; +import { screen, waitFor } from '@testing-library/react'; import { Mock } from 'ts-mockery'; import { VisitsTable, VisitsTableProps } 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'; import { ReachableServer, SelectedServer } from '../../src/servers/data'; import { SemVer } from '../../src/utils/helpers/version'; +import { renderWithEvents } from '../__helpers__/setUpTest'; describe('', () => { const matchMedia = () => Mock.of({ matches: false }); const setSelectedVisits = jest.fn(); - let wrapper: ShallowWrapper; - const wrapperFactory = (props: Partial = {}) => { - wrapper = shallow( - ()} - {...props} - matchMedia={matchMedia} - setSelectedVisits={setSelectedVisits} - />, - ); - - return wrapper; - }; - const createWrapper = (visits: NormalizedVisit[], selectedVisits: NormalizedVisit[] = []) => wrapperFactory( + const setUpFactory = (props: Partial = {}) => renderWithEvents( + ()} + {...props} + matchMedia={matchMedia} + setSelectedVisits={setSelectedVisits} + />, + ); + const setUp = (visits: NormalizedVisit[], selectedVisits: NormalizedVisit[] = []) => setUpFactory( { visits, selectedVisits }, ); - const createOrphanVisitsWrapper = (isOrphanVisits: boolean, version: SemVer) => wrapperFactory({ + const setUpForOrphanVisits = (isOrphanVisits: boolean, version: SemVer) => setUpFactory({ isOrphanVisits, selectedServer: Mock.of({ printableVersion: version, version }), }); - const createServerVersionWrapper = (version: SemVer) => wrapperFactory({ + const setUpForServerVersion = (version: SemVer) => setUpFactory({ selectedServer: Mock.of({ printableVersion: version, version }), }); - const createWrapperWithBots = () => wrapperFactory({ + const setUpWithBots = () => setUpFactory({ selectedServer: Mock.of({ printableVersion: '2.7.0', version: '2.7.0' }), visits: [ - Mock.of({ potentialBot: false }), - Mock.of({ potentialBot: true }), + Mock.of({ potentialBot: false, date: '2022-05-05' }), + Mock.of({ potentialBot: true, date: '2022-05-05' }), ], }); afterEach(jest.resetAllMocks); - afterEach(() => wrapper?.unmount()); it.each([ - ['2.6.0' as SemVer, ['Date', 'Country', 'City', 'Browser', 'OS', 'Referrer']], - ['2.7.0' as SemVer, ['fa-robot', 'Date', 'Country', 'City', 'Browser', 'OS', 'Referrer']], - ])('renders columns as expected', (version, expectedColumns) => { - const wrapper = createServerVersionWrapper(version); - const th = wrapper.find('thead').find('th'); - - expect(th).toHaveLength(expectedColumns.length + 1); - expectedColumns.forEach((column, index) => { - expect(th.at(index + 1).html()).toContain(column); - }); + ['2.6.0' as SemVer, 6], + ['2.7.0' as SemVer, 7], + ])('renders expected amount of columns', (version, expectedColumns) => { + setUpForServerVersion(version); + expect(screen.getAllByRole('columnheader')).toHaveLength(expectedColumns + 1); }); it('shows warning when no visits are found', () => { - const wrapper = createWrapper([]); - const td = wrapper.find('tbody').find('td'); - - expect(td).toHaveLength(1); - expect(td.text()).toContain('No visits found with current filtering'); + setUp([]); + expect(screen.getByText('No visits found with current filtering')).toBeInTheDocument(); }); it.each([ - [50, 3], - [21, 2], - [30, 2], - [60, 3], - [115, 6], - ])('renders the expected amount of pages', (visitsCount, expectedAmountOfPages) => { - const wrapper = createWrapper( - rangeOf(visitsCount, () => Mock.of({ browser: '', date: '', referer: '' })), + [50, 5, 1], + [21, 4, 1], + [30, 4, 1], + [60, 5, 1], + [115, 7, 2], // This one will have ellipsis + ])('renders the expected amount of pages', (visitsCount, expectedAmountOfPageItems, expectedDisabledItems) => { + const { container } = setUp( + rangeOf(visitsCount, () => Mock.of({ browser: '', date: '2022-01-01', referer: '' })), ); - const tr = wrapper.find('tbody').find('tr'); - const paginator = wrapper.find(SimplePaginator); - - expect(tr).toHaveLength(20); - expect(paginator.prop('pagesCount')).toEqual(expectedAmountOfPages); + expect(container.querySelectorAll('.page-item')).toHaveLength(expectedAmountOfPageItems); + expect(container.querySelectorAll('.disabled')).toHaveLength(expectedDisabledItems); }); it.each( rangeOf(20, (value) => [value]), )('does not render footer when there is only one page to render', (visitsCount) => { - const wrapper = createWrapper( - rangeOf(visitsCount, () => Mock.of({ browser: '', date: '', referer: '' })), + const { container } = setUp( + rangeOf(visitsCount, () => Mock.of({ browser: '', date: '2022-01-01', referer: '' })), ); - const tr = wrapper.find('tbody').find('tr'); - const paginator = wrapper.find(SimplePaginator); - expect(tr).toHaveLength(visitsCount); - expect(paginator).toHaveLength(0); + expect(container.querySelector('tfoot')).not.toBeInTheDocument(); + expect(screen.queryByLabelText('pagination')).not.toBeInTheDocument(); }); - it('selected rows are highlighted', () => { - const visits = rangeOf(10, () => Mock.of({ browser: '', date: '', referer: '' })); - const wrapper = createWrapper( - visits, - [visits[1], visits[2]], - ); + it('selected rows are highlighted', async () => { + const visits = rangeOf(10, () => Mock.of({ browser: '', date: '2022-01-01', referer: '' })); + const { container, user } = setUp(visits, [visits[1], visits[2]]); - expect(wrapper.find('.text-primary')).toHaveLength(3); - expect(wrapper.find('.table-active')).toHaveLength(2); + // Initial situation + expect(container.querySelectorAll('.table-active')).toHaveLength(2); // Select one extra - wrapper.find('tr').at(5).simulate('click'); + await user.click(screen.getAllByRole('row')[5]); expect(setSelectedVisits).toHaveBeenCalledWith([visits[1], visits[2], visits[4]]); // Deselect one - wrapper.find('tr').at(3).simulate('click'); + await user.click(screen.getAllByRole('row')[3]); expect(setSelectedVisits).toHaveBeenCalledWith([visits[1]]); // Select all - wrapper.find('thead').find('th').at(0).simulate('click'); + await user.click(screen.getAllByRole('columnheader')[0]); expect(setSelectedVisits).toHaveBeenCalledWith(visits); }); - it('orders visits when column is clicked', () => { - const wrapper = createWrapper(rangeOf(9, (index) => Mock.of({ + it('orders visits when column is clicked', async () => { + const { user } = setUp(rangeOf(9, (index) => Mock.of({ browser: '', - date: `${9 - index}`, + date: `2022-01-0${10 - index}`, referer: `${index}`, country: `Country_${index}`, }))); + const getFirstColumnValue = () => screen.getAllByRole('row')[2]?.querySelectorAll('td')[2]?.textContent; + const clickColumn = async (index: number) => user.click(screen.getAllByRole('columnheader')[index]); - expect(wrapper.find('tbody').find('tr').at(0).find('td') - .at(2) - .text()).toContain('Country_1'); - wrapper.find('thead').find('th').at(1).simulate('click'); // Date column ASC - expect(wrapper.find('tbody').find('tr').at(0).find('td') - .at(2) - .text()).toContain('Country_9'); - wrapper.find('thead').find('th').at(6).simulate('click'); // Referer column - ASC - expect(wrapper.find('tbody').find('tr').at(0).find('td') - .at(2) - .text()).toContain('Country_1'); - wrapper.find('thead').find('th').at(6).simulate('click'); // Referer column - DESC - expect(wrapper.find('tbody').find('tr').at(0).find('td') - .at(2) - .text()).toContain('Country_9'); - wrapper.find('thead').find('th').at(6).simulate('click'); // Referer column - reset - expect(wrapper.find('tbody').find('tr').at(0).find('td') - .at(2) - .text()).toContain('Country_1'); + expect(getFirstColumnValue()).toContain('Country_1'); + await clickColumn(1); // Date column ASC + expect(getFirstColumnValue()).toContain('Country_9'); + await clickColumn(6); // Referer column - ASC + expect(getFirstColumnValue()).toContain('Country_1'); + await clickColumn(6); // Referer column - DESC + expect(getFirstColumnValue()).toContain('Country_9'); + await clickColumn(6); // Referer column - reset + expect(getFirstColumnValue()).toContain('Country_1'); }); - it('filters list when writing in search box', () => { - const wrapper = createWrapper([ - ...rangeOf(7, () => Mock.of({ browser: 'aaa', date: 'aaa', referer: 'aaa' })), - ...rangeOf(2, () => Mock.of({ browser: 'bbb', date: 'bbb', referer: 'bbb' })), + it('filters list when writing in search box', async () => { + const { user } = setUp([ + ...rangeOf(7, () => Mock.of({ browser: 'aaa', date: '2022-01-01', referer: 'aaa' })), + ...rangeOf(2, () => Mock.of({ browser: 'bbb', date: '2022-01-01', referer: 'bbb' })), ]); - const searchField = wrapper.find(SearchField); + const searchField = screen.getByPlaceholderText('Search...'); + const searchText = async (text: string) => { + await user.clear(searchField); + text.length > 0 && await user.type(searchField, text); + }; - expect(wrapper.find('tbody').find('tr')).toHaveLength(7 + 2); - searchField.simulate('change', 'aa'); - expect(wrapper.find('tbody').find('tr')).toHaveLength(7); - searchField.simulate('change', 'bb'); - expect(wrapper.find('tbody').find('tr')).toHaveLength(2); - searchField.simulate('change', ''); - expect(wrapper.find('tbody').find('tr')).toHaveLength(7 + 2); + expect(screen.getAllByRole('row')).toHaveLength(9 + 2); + await searchText('aa'); + await waitFor(() => expect(screen.getAllByRole('row')).toHaveLength(7 + 2)); + await searchText('bb'); + await waitFor(() => expect(screen.getAllByRole('row')).toHaveLength(2 + 2)); + await searchText(''); + await waitFor(() => expect(screen.getAllByRole('row')).toHaveLength(9 + 2)); }); it.each([ @@ -171,20 +144,15 @@ describe('', () => { [true, '2.7.0' as SemVer, 9], [false, '2.7.0' as SemVer, 8], ])('displays proper amount of columns for orphan and non-orphan visits', (isOrphanVisits, version, expectedCols) => { - const wrapper = createOrphanVisitsWrapper(isOrphanVisits, version); - const rowsWithColspan = wrapper.find('[colSpan]'); - const cols = wrapper.find('th'); - - expect(cols).toHaveLength(expectedCols); - expect(rowsWithColspan).toHaveLength(2); - rowsWithColspan.forEach((row) => expect(row.prop('colSpan')).toEqual(expectedCols)); + setUpForOrphanVisits(isOrphanVisits, version); + expect(screen.getAllByRole('columnheader')).toHaveLength(expectedCols); }); it('displays bots icon when a visit is a potential bot', () => { - const wrapper = createWrapperWithBots(); - const rows = wrapper.find('tbody').find('tr'); + setUpWithBots(); + const [,, nonBotVisitRow, botVisitRow] = screen.getAllByRole('row'); - expect(rows.at(0).find('td').at(1).text()).not.toContain('FontAwesomeIcon'); - expect(rows.at(1).find('td').at(1).text()).toContain('FontAwesomeIcon'); + expect(nonBotVisitRow.querySelectorAll('td')[1]).toBeEmptyDOMElement(); + expect(botVisitRow.querySelectorAll('td')[1]).not.toBeEmptyDOMElement(); }); });