diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0304ddbe..09130ba7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -30,8 +30,9 @@ jobs: # Checks - name: Lint run: npm run lint - # - name: Test - # run: npm test + # Test + - name: Test + run: npm test # Build - name: Build run: npm run build diff --git a/package-lock.json b/package-lock.json index 2ddd3089..4ef4b029 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,6 +23,8 @@ "conventional-changelog": "^3.1.25", "http-proxy-middleware": "^2.0.1", "husky": "^7.0.4", + "jest-watch-typeahead": "^0.6.5", + "jest-websocket-mock": "^2.3.0", "lint-staged": "^12.3.1", "moving-letters": "^1.0.1", "nth-check": ">=2.0.1", @@ -11665,88 +11667,24 @@ "dev": true }, "node_modules/jest-watch-typeahead": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/jest-watch-typeahead/-/jest-watch-typeahead-1.0.0.tgz", - "integrity": "sha512-jxoszalAb394WElmiJTFBMzie/RDCF+W7Q29n5LzOPtcoQoHWfdUtHFkbhgf5NwWe8uMOxvKb/g7ea7CshfkTw==", + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/jest-watch-typeahead/-/jest-watch-typeahead-0.6.5.tgz", + "integrity": "sha512-GIbV6h37/isatMDtqZlA8Q5vC6T3w+5qdvtF+3LIkPc58zEWzbKmTHvlUIp3wvBm400RzrQWcVPcsAJqKWu7XQ==", "dev": true, "dependencies": { "ansi-escapes": "^4.3.1", "chalk": "^4.0.0", "jest-regex-util": "^27.0.0", "jest-watcher": "^27.0.0", - "slash": "^4.0.0", - "string-length": "^5.0.1", - "strip-ansi": "^7.0.1" + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">=10" }, "peerDependencies": { - "jest": "^27.0.0" - } - }, - "node_modules/jest-watch-typeahead/node_modules/ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/jest-watch-typeahead/node_modules/char-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-2.0.0.tgz", - "integrity": "sha512-oGu2QekBMXgyQNWPDRQ001bjvDnZe4/zBTz37TMbiKz1NbNiyiH5hRkobe7npRN6GfbGbxMYFck/vQ1r9c1VMA==", - "dev": true, - "engines": { - "node": ">=12.20" - } - }, - "node_modules/jest-watch-typeahead/node_modules/slash": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", - "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/jest-watch-typeahead/node_modules/string-length": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-5.0.1.tgz", - "integrity": "sha512-9Ep08KAMUn0OadnVaBuRdE2l615CQ508kr0XMadjClfYpdCyvrbFp6Taebo8yyxokQ4viUd/xPPUA4FGgUa0ow==", - "dev": true, - "dependencies": { - "char-regex": "^2.0.0", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/jest-watch-typeahead/node_modules/strip-ansi": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", - "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", - "dev": true, - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" + "jest": "^26.0.0 || ^27.0.0" } }, "node_modules/jest-watcher": { @@ -11792,6 +11730,81 @@ "@types/yargs-parser": "*" } }, + "node_modules/jest-websocket-mock": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/jest-websocket-mock/-/jest-websocket-mock-2.3.0.tgz", + "integrity": "sha512-kXhRRApRdT4hLG/4rhsfcR0Ke0OzqIsDj0P5t0dl5aiAftShSgoRqp/0pyjS5bh+b9GrIzmfkrV2cn9LxxvSvA==", + "dev": true, + "dependencies": { + "jest-diff": "^27.0.2", + "mock-socket": "^9.1.0" + } + }, + "node_modules/jest-websocket-mock/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-websocket-mock/node_modules/diff-sequences": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.5.1.tgz", + "integrity": "sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ==", + "dev": true, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-websocket-mock/node_modules/jest-diff": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.5.1.tgz", + "integrity": "sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^27.5.1", + "jest-get-type": "^27.5.1", + "pretty-format": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-websocket-mock/node_modules/jest-get-type": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz", + "integrity": "sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==", + "dev": true, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-websocket-mock/node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-websocket-mock/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/jest-worker": { "version": "27.4.5", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.4.5.tgz", @@ -12864,6 +12877,15 @@ "mkdirp": "bin/cmd.js" } }, + "node_modules/mock-socket": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/mock-socket/-/mock-socket-9.1.2.tgz", + "integrity": "sha512-XKZkCnQ9ISOlTnaPg4LYYSMj7+6i78HyadYzLA5JM4465ibLdjappZD9Csnqc3Tfzep/eEK/LCJ29BTaLHoB1A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, "node_modules/modify-values": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/modify-values/-/modify-values-1.0.1.tgz", @@ -15502,6 +15524,27 @@ } } }, + "node_modules/react-scripts/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/react-scripts/node_modules/char-regex": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-2.0.1.tgz", + "integrity": "sha512-oSvEeo6ZUD7NepqAat3RqoucZ5SeqLJgOvVIwkafu6IP3V0pO38s/ypdVUmDDK6qIIHNlYHJAKX9E7R7HoKElw==", + "dev": true, + "engines": { + "node": ">=12.20" + } + }, "node_modules/react-scripts/node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -15518,6 +15561,27 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/react-scripts/node_modules/jest-watch-typeahead": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/jest-watch-typeahead/-/jest-watch-typeahead-1.0.0.tgz", + "integrity": "sha512-jxoszalAb394WElmiJTFBMzie/RDCF+W7Q29n5LzOPtcoQoHWfdUtHFkbhgf5NwWe8uMOxvKb/g7ea7CshfkTw==", + "dev": true, + "dependencies": { + "ansi-escapes": "^4.3.1", + "chalk": "^4.0.0", + "jest-regex-util": "^27.0.0", + "jest-watcher": "^27.0.0", + "slash": "^4.0.0", + "string-length": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "jest": "^27.0.0" + } + }, "node_modules/react-scripts/node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -15581,6 +15645,18 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/react-scripts/node_modules/slash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", + "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/react-scripts/node_modules/source-map": { "version": "0.7.3", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", @@ -15590,6 +15666,37 @@ "node": ">= 8" } }, + "node_modules/react-scripts/node_modules/string-length": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-5.0.1.tgz", + "integrity": "sha512-9Ep08KAMUn0OadnVaBuRdE2l615CQ508kr0XMadjClfYpdCyvrbFp6Taebo8yyxokQ4viUd/xPPUA4FGgUa0ow==", + "dev": true, + "dependencies": { + "char-regex": "^2.0.0", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/react-scripts/node_modules/strip-ansi": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", + "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/react-scripts/node_modules/type-fest": { "version": "2.8.0", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.8.0.tgz", @@ -27894,57 +28001,18 @@ } }, "jest-watch-typeahead": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/jest-watch-typeahead/-/jest-watch-typeahead-1.0.0.tgz", - "integrity": "sha512-jxoszalAb394WElmiJTFBMzie/RDCF+W7Q29n5LzOPtcoQoHWfdUtHFkbhgf5NwWe8uMOxvKb/g7ea7CshfkTw==", + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/jest-watch-typeahead/-/jest-watch-typeahead-0.6.5.tgz", + "integrity": "sha512-GIbV6h37/isatMDtqZlA8Q5vC6T3w+5qdvtF+3LIkPc58zEWzbKmTHvlUIp3wvBm400RzrQWcVPcsAJqKWu7XQ==", "dev": true, "requires": { "ansi-escapes": "^4.3.1", "chalk": "^4.0.0", "jest-regex-util": "^27.0.0", "jest-watcher": "^27.0.0", - "slash": "^4.0.0", - "string-length": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "dependencies": { - "ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", - "dev": true - }, - "char-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-2.0.0.tgz", - "integrity": "sha512-oGu2QekBMXgyQNWPDRQ001bjvDnZe4/zBTz37TMbiKz1NbNiyiH5hRkobe7npRN6GfbGbxMYFck/vQ1r9c1VMA==", - "dev": true - }, - "slash": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", - "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", - "dev": true - }, - "string-length": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-5.0.1.tgz", - "integrity": "sha512-9Ep08KAMUn0OadnVaBuRdE2l615CQ508kr0XMadjClfYpdCyvrbFp6Taebo8yyxokQ4viUd/xPPUA4FGgUa0ow==", - "dev": true, - "requires": { - "char-regex": "^2.0.0", - "strip-ansi": "^7.0.1" - } - }, - "strip-ansi": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", - "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", - "dev": true, - "requires": { - "ansi-regex": "^6.0.1" - } - } + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0" } }, "jest-watcher": { @@ -27986,6 +28054,65 @@ } } }, + "jest-websocket-mock": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/jest-websocket-mock/-/jest-websocket-mock-2.3.0.tgz", + "integrity": "sha512-kXhRRApRdT4hLG/4rhsfcR0Ke0OzqIsDj0P5t0dl5aiAftShSgoRqp/0pyjS5bh+b9GrIzmfkrV2cn9LxxvSvA==", + "dev": true, + "requires": { + "jest-diff": "^27.0.2", + "mock-socket": "^9.1.0" + }, + "dependencies": { + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + }, + "diff-sequences": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.5.1.tgz", + "integrity": "sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ==", + "dev": true + }, + "jest-diff": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.5.1.tgz", + "integrity": "sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "diff-sequences": "^27.5.1", + "jest-get-type": "^27.5.1", + "pretty-format": "^27.5.1" + } + }, + "jest-get-type": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz", + "integrity": "sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==", + "dev": true + }, + "pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + } + }, + "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 + } + } + }, "jest-worker": { "version": "27.4.5", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.4.5.tgz", @@ -28790,6 +28917,12 @@ "minimist": "^1.2.5" } }, + "mock-socket": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/mock-socket/-/mock-socket-9.1.2.tgz", + "integrity": "sha512-XKZkCnQ9ISOlTnaPg4LYYSMj7+6i78HyadYzLA5JM4465ibLdjappZD9Csnqc3Tfzep/eEK/LCJ29BTaLHoB1A==", + "dev": true + }, "modify-values": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/modify-values/-/modify-values-1.0.1.tgz", @@ -30590,6 +30723,18 @@ "source-map": "^0.7.3" } }, + "ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true + }, + "char-regex": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-2.0.1.tgz", + "integrity": "sha512-oSvEeo6ZUD7NepqAat3RqoucZ5SeqLJgOvVIwkafu6IP3V0pO38s/ypdVUmDDK6qIIHNlYHJAKX9E7R7HoKElw==", + "dev": true + }, "find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -30600,6 +30745,21 @@ "path-exists": "^4.0.0" } }, + "jest-watch-typeahead": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/jest-watch-typeahead/-/jest-watch-typeahead-1.0.0.tgz", + "integrity": "sha512-jxoszalAb394WElmiJTFBMzie/RDCF+W7Q29n5LzOPtcoQoHWfdUtHFkbhgf5NwWe8uMOxvKb/g7ea7CshfkTw==", + "dev": true, + "requires": { + "ansi-escapes": "^4.3.1", + "chalk": "^4.0.0", + "jest-regex-util": "^27.0.0", + "jest-watcher": "^27.0.0", + "slash": "^4.0.0", + "string-length": "^5.0.1", + "strip-ansi": "^7.0.1" + } + }, "locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -30638,12 +30798,37 @@ "ajv-keywords": "^3.5.2" } }, + "slash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", + "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", + "dev": true + }, "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 }, + "string-length": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-5.0.1.tgz", + "integrity": "sha512-9Ep08KAMUn0OadnVaBuRdE2l615CQ508kr0XMadjClfYpdCyvrbFp6Taebo8yyxokQ4viUd/xPPUA4FGgUa0ow==", + "dev": true, + "requires": { + "char-regex": "^2.0.0", + "strip-ansi": "^7.0.1" + } + }, + "strip-ansi": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", + "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", + "dev": true, + "requires": { + "ansi-regex": "^6.0.1" + } + }, "type-fest": { "version": "2.8.0", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.8.0.tgz", diff --git a/package.json b/package.json index dad3bd9b..663646fd 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,8 @@ "conventional-changelog": "^3.1.25", "http-proxy-middleware": "^2.0.1", "husky": "^7.0.4", + "jest-watch-typeahead": "^0.6.5", + "jest-websocket-mock": "^2.3.0", "lint-staged": "^12.3.1", "moving-letters": "^1.0.1", "nth-check": ">=2.0.1", @@ -72,5 +74,12 @@ }, "lint-staged": { "src/**/*.{js,jsx,json,css,md}": "prettier --no-error-on-unmatched-pattern --write" + }, + "jest": { + "extraGlobals": [ + "Math", + "localStorage" + ], + "clearMocks": true } } diff --git a/src/__util__/AllTheProviders.jsx b/src/__util__/AllTheProviders.jsx new file mode 100644 index 00000000..9c08ce5b --- /dev/null +++ b/src/__util__/AllTheProviders.jsx @@ -0,0 +1,17 @@ +import { BrowserRouter } from 'react-router-dom' + +import { SettingsProvider } from '../context/SettingsContext' +import { WalletProvider } from '../context/WalletContext' +import { WebsocketProvider } from '../context/WebsocketContext' + +export const AllTheProviders = ({ children }) => { + return ( + + + + {children} + + + + ) +} diff --git a/src/components/App.test.jsx b/src/components/App.test.jsx new file mode 100644 index 00000000..bbac056d --- /dev/null +++ b/src/components/App.test.jsx @@ -0,0 +1,95 @@ +import React from 'react' +import { render, screen } from '@testing-library/react' +import { act } from 'react-dom/test-utils' +import user from '@testing-library/user-event' + +import { AllTheProviders } from '../__util__/AllTheProviders' + +import App from './App' + +it('should display Onboarding screen initially', () => { + act(() => { + render(, { + wrapper: AllTheProviders, + }) + }) + + // Onboarding screen + expect(screen.getByText('Get started')).toBeInTheDocument() + expect(screen.getByText('Skip intro')).toBeInTheDocument() + + // Wallets screen shown after Intro is skipped + expect(screen.queryByText('Your wallets')).not.toBeInTheDocument() + + act(() => { + const skipIntro = screen.getByText('Skip intro') + user.click(skipIntro) + }) + + expect(screen.getByText('Your wallets')).toBeInTheDocument() +}) + +it('should display Wallets screen directly when Onboarding screen has been shown', () => { + global.__DEV__.addToAppSettings({ showOnboarding: false }) + + act(() => { + render(, { + wrapper: AllTheProviders, + }) + }) + + // Wallets screen + expect(screen.getByText('Your wallets')).toBeInTheDocument() + expect(screen.getByText('Create new wallet')).toBeInTheDocument() + + // footer + expect(screen.getByText('Docs')).toBeInTheDocument() + expect(screen.getByText('Features')).toBeInTheDocument() + expect(screen.getByText('GitHub')).toBeInTheDocument() + expect(screen.getByText('Twitter')).toBeInTheDocument() +}) + +it('should display a modal with alpha warning information', () => { + global.__DEV__.addToAppSettings({ showOnboarding: false }) + + act(() => { + render(, { + wrapper: AllTheProviders, + }) + }) + + expect(screen.getByText('Read this before using.')).toBeInTheDocument() + expect( + screen.queryByText(/While JoinMarket is tried and tested, this user interface is not./) + ).not.toBeInTheDocument() + + act(() => { + const readThis = screen.getByText('Read this before using.') + user.click(readThis) + }) + + expect(screen.getByText(/While JoinMarket is tried and tested, this user interface is not./)).toBeInTheDocument() + expect(screen.getByText('Fine with me.')).toBeInTheDocument() +}) + +it('should display a websocket connection indicator', async () => { + global.__DEV__.addToAppSettings({ showOnboarding: false }) + + act(() => { + render(, { + wrapper: AllTheProviders, + }) + }) + + expect(screen.getByText('•').classList.contains('text-danger')).toBe(true) + expect(screen.getByText('•').classList.contains('text-success')).toBe(false) + expect(screen.getByText('Disconnected')).toBeInTheDocument() + expect(screen.queryByText('Connected')).not.toBeInTheDocument() + + await global.__DEV__.JM_WEBSOCKET_SERVER_MOCK.connected + + expect(screen.queryByText('Disconnected')).not.toBeInTheDocument() + expect(screen.getByText('Connected')).toBeInTheDocument() + expect(screen.getByText('•').classList.contains('text-success')).toBe(true) + expect(screen.getByText('•').classList.contains('text-danger')).toBe(false) +}) diff --git a/src/components/Wallets.jsx b/src/components/Wallets.jsx index caf44cda..78143d3e 100644 --- a/src/components/Wallets.jsx +++ b/src/components/Wallets.jsx @@ -60,7 +60,7 @@ export default function Wallets({ startWallet, stopWallet }) { {isLoading && (
)} {walletList?.map((wallet, index) => ( diff --git a/src/components/Wallets.test.jsx b/src/components/Wallets.test.jsx new file mode 100644 index 00000000..21bdf14d --- /dev/null +++ b/src/components/Wallets.test.jsx @@ -0,0 +1,95 @@ +import React from 'react' +import { render, screen, waitForElementToBeRemoved } from '@testing-library/react' +import { act } from 'react-dom/test-utils' + +import * as apiMock from '../libs/JmWalletApi' +import { AllTheProviders } from '../__util__/AllTheProviders' + +import Wallets from './Wallets' + +jest.mock('../libs/JmWalletApi') + +it('should render without errors', () => { + apiMock.getWalletAll.mockResolvedValueOnce(new Promise((r) => setTimeout(r, 1_000))) + + act(() => { + render(, { + wrapper: AllTheProviders, + }) + }) + + expect(screen.getByText('Your wallets')).toBeInTheDocument() + expect(screen.getByText('Loading wallets')).toBeInTheDocument() + expect(screen.getByText('Create new wallet')).toBeInTheDocument() +}) + +it('should display error message when loading wallets fails', async () => { + apiMock.getWalletAll.mockResolvedValueOnce({ + ok: false, + }) + + act(() => { + render(, { + wrapper: AllTheProviders, + }) + }) + + expect(screen.getByText('Loading wallets')).toBeInTheDocument() + expect(screen.getByText('Create new wallet')).toBeInTheDocument() + + await waitForElementToBeRemoved(screen.getByText('Loading wallets')) + + expect(screen.getByText('Loading wallets failed.')).toBeInTheDocument() + expect(screen.getByText('Create new wallet')).toBeInTheDocument() +}) + +it('should display big call-to-action button if no wallet has been created yet', async () => { + apiMock.getWalletAll.mockResolvedValueOnce({ + ok: true, + json: () => Promise.resolve({ wallets: [] }), + }) + + act(() => { + render(, { + wrapper: AllTheProviders, + }) + }) + + expect(screen.getByText('Loading wallets')).toBeInTheDocument() + + const callToActionButtonBefore = screen.getByText('Create new wallet') + expect(callToActionButtonBefore.classList.contains('btn')).toBe(true) + expect(callToActionButtonBefore.classList.contains('btn-lg')).toBe(false) + + await waitForElementToBeRemoved(screen.getByText('Loading wallets')) + + expect(screen.getByText('It looks like you do not have a wallet, yet.')).toBeInTheDocument() + + const callToActionButtonAfter = screen.getByText('Create new wallet') + expect(callToActionButtonAfter.classList.contains('btn-lg')).toBe(true) +}) + +it('should display login for available wallets', async () => { + apiMock.getWalletAll.mockResolvedValueOnce({ + ok: true, + json: () => Promise.resolve({ wallets: ['wallet0.jmdat', 'wallet1.jmdat'] }), + }) + + act(() => { + render(, { + wrapper: AllTheProviders, + }) + }) + + expect(screen.getByText('Loading wallets')).toBeInTheDocument() + await waitForElementToBeRemoved(screen.getByText('Loading wallets')) + + expect(screen.queryByText('It looks like you do not have a wallet, yet.')).not.toBeInTheDocument() + + expect(screen.getByText('wallet0')).toBeInTheDocument() + expect(screen.getByText('wallet1')).toBeInTheDocument() + + const callToActionButton = screen.getByText('Create new wallet') + expect(callToActionButton.classList.contains('btn')).toBe(true) + expect(callToActionButton.classList.contains('btn-lg')).toBe(false) +}) diff --git a/src/context/WebsocketContext.jsx b/src/context/WebsocketContext.jsx index 9dfc009b..5e57da5d 100644 --- a/src/context/WebsocketContext.jsx +++ b/src/context/WebsocketContext.jsx @@ -33,7 +33,7 @@ const createWebSocket = () => { console.error('websocket error', error) } - if (process.env.NODE_ENV !== 'production') { + if (process.env.NODE_ENV === 'development') { websocket.onopen = () => { console.debug('websocket connection openend') } diff --git a/src/setupTests.js b/src/setupTests.js new file mode 100644 index 00000000..2a29d310 --- /dev/null +++ b/src/setupTests.js @@ -0,0 +1,36 @@ +// react-testing-library renders your components to document.body, +// this adds jest-dom's custom assertions +import '@testing-library/jest-dom' +import { WS as WebSocketServer } from 'jest-websocket-mock' + +global.JM = { + SETTINGS_STORE_KEY: 'jm-settings', + THEMES: ['light', 'dark'], + THEME_ROOT_ATTR: 'data-theme', + PUBLIC_PATH: '', +} + +global.__DEV__ = {} + +global.__DEV__.addToAppSettings = () => { + global.localStorage.setItem( + global.JM.SETTINGS_STORE_KEY, + JSON.stringify( + Object.assign({}, global.localStorage.getItem(global.JM.SETTINGS_STORE_KEY) || {}, { + showOnboarding: false, + }) + ) + ) +} +;(function setupWebsocketServerMock() { + global.__DEV__.JM_WEBSOCKET_SERVER_MOCK = new WebSocketServer('ws://localhost/jmws', { jsonProtocol: true }) + + afterEach(() => { + // gracefully close all open connections and reset the environment between test runs + WebSocketServer.clean() + }) + + afterAll(() => { + global.__DEV__.JM_WEBSOCKET_SERVER_MOCK.close() + }) +})()