diff --git a/package-lock.json b/package-lock.json index 9d35485b..6cd3dc29 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,7 +24,7 @@ "@gfx/zopfli": "^1.0.15", "@testing-library/dom": "^10.4.0", "@testing-library/jest-dom": "^6.6.3", - "@testing-library/react": "^12.1.5", + "@testing-library/react": "^16.3.0", "autoprefixer": "^10.4.21", "babel-core": "^7.0.0-bridge.0", "babel-loader": "^9.2.1", @@ -40,9 +40,9 @@ "mini-css-extract-plugin": "^2.9.2", "postcss": "^8.5.6", "postcss-loader": "^8.1.1", - "react": "^17.0.2", + "react": "^18.3.1", "react-aria-modal": "^5.0.2", - "react-dom": "^17.0.2", + "react-dom": "^18.3.1", "react-router-dom": "^5.3.4", "react-tooltip": "^5.29.1", "style-loader": "^4.0.0", @@ -3592,151 +3592,31 @@ } }, "node_modules/@testing-library/react": { - "version": "12.1.5", - "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-12.1.5.tgz", - "integrity": "sha512-OfTXCJUFgjd/digLUuPxa0+/3ZxsQmE7ub9kcbW/wi96Bh3o/p5vrETcBGfP17NWPGqeYYl5LTRpwyGoMC4ysg==", + "version": "16.3.0", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.0.tgz", + "integrity": "sha512-kFSyxiEDwv1WLl2fgsq6pPBbw5aWKrsY2/noi1Id0TK0UParSF62oFQFGHXIyaG4pp2tEub/Zlel+fjjZILDsw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/runtime": "^7.12.5", - "@testing-library/dom": "^8.0.0", - "@types/react-dom": "<18.0.0" + "@babel/runtime": "^7.12.5" }, "engines": { - "node": ">=12" + "node": ">=18" }, "peerDependencies": { - "react": "<18.0.0", - "react-dom": "<18.0.0" - } - }, - "node_modules/@testing-library/react/node_modules/@testing-library/dom": { - "version": "8.20.1", - "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.20.1.tgz", - "integrity": "sha512-/DiOQ5xBxgdYRC8LNk7U+RWat0S3qRLeIw3ZIkMQ9kkVlRmwD/Eg8k8CqIpD6GW7u20JIUOfMKbxtiLutpjQ4g==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.10.4", - "@babel/runtime": "^7.12.5", - "@types/aria-query": "^5.0.1", - "aria-query": "5.1.3", - "chalk": "^4.1.0", - "dom-accessibility-api": "^0.5.9", - "lz-string": "^1.5.0", - "pretty-format": "^27.0.2" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@testing-library/react/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@testing-library/react/node_modules/aria-query": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz", - "integrity": "sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==", - "dev": true, - "dependencies": { - "deep-equal": "^2.0.5" - } - }, - "node_modules/@testing-library/react/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@testing-library/react/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@testing-library/react/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@testing-library/react/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@testing-library/react/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/@testing-library/react/node_modules/pretty-format/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/@testing-library/react/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/@testing-library/react/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" + "@testing-library/dom": "^10.0.0", + "@types/react": "^18.0.0 || ^19.0.0", + "@types/react-dom": "^18.0.0 || ^19.0.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" }, - "engines": { - "node": ">=8" + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } } }, "node_modules/@tsconfig/node10": { @@ -3907,41 +3787,6 @@ "integrity": "sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==", "dev": true }, - "node_modules/@types/prop-types": { - "version": "15.7.15", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", - "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", - "dev": true, - "peer": true - }, - "node_modules/@types/react": { - "version": "17.0.87", - "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.87.tgz", - "integrity": "sha512-wpg9AbtJ6agjA+BKYmhG6dRWEU/2DHYwMzCaBzsz137ft6IyuqZ5fI4ic1DWL4DrI03Zy78IyVE6ucrXl0mu4g==", - "dev": true, - "peer": true, - "dependencies": { - "@types/prop-types": "*", - "@types/scheduler": "^0.16", - "csstype": "^3.0.2" - } - }, - "node_modules/@types/react-dom": { - "version": "17.0.26", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.26.tgz", - "integrity": "sha512-Z+2VcYXJwOqQ79HreLU/1fyQ88eXSSFh6I3JdrEHQIfYSI0kCQpTGvOrbE6jFGGYXKsHuwY9tBa/w5Uo6KzrEg==", - "dev": true, - "peerDependencies": { - "@types/react": "^17.0.0" - } - }, - "node_modules/@types/scheduler": { - "version": "0.16.8", - "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz", - "integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==", - "dev": true, - "peer": true - }, "node_modules/@types/stack-utils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", @@ -5788,13 +5633,6 @@ "node": ">=18" } }, - "node_modules/csstype": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "dev": true, - "peer": true - }, "node_modules/data-uri-to-buffer": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.0.tgz", @@ -5934,44 +5772,6 @@ } } }, - "node_modules/deep-equal": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.3.tgz", - "integrity": "sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==", - "dev": true, - "dependencies": { - "array-buffer-byte-length": "^1.0.0", - "call-bind": "^1.0.5", - "es-get-iterator": "^1.1.3", - "get-intrinsic": "^1.2.2", - "is-arguments": "^1.1.1", - "is-array-buffer": "^3.0.2", - "is-date-object": "^1.0.5", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.2", - "isarray": "^2.0.5", - "object-is": "^1.1.5", - "object-keys": "^1.1.1", - "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.5.1", - "side-channel": "^1.0.4", - "which-boxed-primitive": "^1.0.2", - "which-collection": "^1.0.1", - "which-typed-array": "^1.1.13" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/deep-equal/node_modules/isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true - }, "node_modules/deep-is": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", @@ -6322,32 +6122,6 @@ "node": ">= 0.4" } }, - "node_modules/es-get-iterator": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", - "integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.3", - "has-symbols": "^1.0.3", - "is-arguments": "^1.1.1", - "is-map": "^2.0.2", - "is-set": "^2.0.2", - "is-string": "^1.0.7", - "isarray": "^2.0.5", - "stop-iteration-iterator": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/es-get-iterator/node_modules/isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true - }, "node_modules/es-iterator-helpers": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.2.1.tgz", @@ -8129,22 +7903,6 @@ "node": ">= 0.10" } }, - "node_modules/is-arguments": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz", - "integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==", - "dev": true, - "dependencies": { - "call-bound": "^1.0.2", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-array-buffer": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", @@ -11332,22 +11090,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/object-is": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", - "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/object-keys": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", @@ -11965,14 +11707,13 @@ } }, "node_modules/react": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz", - "integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "dev": true, "license": "MIT", "dependencies": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" + "loose-envify": "^1.1.0" }, "engines": { "node": ">=0.10.0" @@ -11994,18 +11735,17 @@ } }, "node_modules/react-dom": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz", - "integrity": "sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "dev": true, "license": "MIT", "dependencies": { "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "scheduler": "^0.20.2" + "scheduler": "^0.23.2" }, "peerDependencies": { - "react": "17.0.2" + "react": "^18.3.1" } }, "node_modules/react-is": { @@ -12485,14 +12225,13 @@ } }, "node_modules/scheduler": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz", - "integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==", + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", "dev": true, "license": "MIT", "dependencies": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" + "loose-envify": "^1.1.0" } }, "node_modules/schema-utils": { @@ -16919,115 +16658,12 @@ } }, "@testing-library/react": { - "version": "12.1.5", - "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-12.1.5.tgz", - "integrity": "sha512-OfTXCJUFgjd/digLUuPxa0+/3ZxsQmE7ub9kcbW/wi96Bh3o/p5vrETcBGfP17NWPGqeYYl5LTRpwyGoMC4ysg==", + "version": "16.3.0", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.0.tgz", + "integrity": "sha512-kFSyxiEDwv1WLl2fgsq6pPBbw5aWKrsY2/noi1Id0TK0UParSF62oFQFGHXIyaG4pp2tEub/Zlel+fjjZILDsw==", "dev": true, "requires": { - "@babel/runtime": "^7.12.5", - "@testing-library/dom": "^8.0.0", - "@types/react-dom": "<18.0.0" - }, - "dependencies": { - "@testing-library/dom": { - "version": "8.20.1", - "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.20.1.tgz", - "integrity": "sha512-/DiOQ5xBxgdYRC8LNk7U+RWat0S3qRLeIw3ZIkMQ9kkVlRmwD/Eg8k8CqIpD6GW7u20JIUOfMKbxtiLutpjQ4g==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.10.4", - "@babel/runtime": "^7.12.5", - "@types/aria-query": "^5.0.1", - "aria-query": "5.1.3", - "chalk": "^4.1.0", - "dom-accessibility-api": "^0.5.9", - "lz-string": "^1.5.0", - "pretty-format": "^27.0.2" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "aria-query": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz", - "integrity": "sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==", - "dev": true, - "requires": { - "deep-equal": "^2.0.5" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "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" - }, - "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 - } - } - }, - "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 - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } + "@babel/runtime": "^7.12.5" } }, "@tsconfig/node10": { @@ -17198,39 +16834,6 @@ "integrity": "sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==", "dev": true }, - "@types/prop-types": { - "version": "15.7.15", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", - "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", - "dev": true, - "peer": true - }, - "@types/react": { - "version": "17.0.87", - "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.87.tgz", - "integrity": "sha512-wpg9AbtJ6agjA+BKYmhG6dRWEU/2DHYwMzCaBzsz137ft6IyuqZ5fI4ic1DWL4DrI03Zy78IyVE6ucrXl0mu4g==", - "dev": true, - "peer": true, - "requires": { - "@types/prop-types": "*", - "@types/scheduler": "^0.16", - "csstype": "^3.0.2" - } - }, - "@types/react-dom": { - "version": "17.0.26", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.26.tgz", - "integrity": "sha512-Z+2VcYXJwOqQ79HreLU/1fyQ88eXSSFh6I3JdrEHQIfYSI0kCQpTGvOrbE6jFGGYXKsHuwY9tBa/w5Uo6KzrEg==", - "dev": true, - "requires": {} - }, - "@types/scheduler": { - "version": "0.16.8", - "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz", - "integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==", - "dev": true, - "peer": true - }, "@types/stack-utils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", @@ -18512,13 +18115,6 @@ "rrweb-cssom": "^0.8.0" } }, - "csstype": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "dev": true, - "peer": true - }, "data-uri-to-buffer": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.0.tgz", @@ -18615,40 +18211,6 @@ "dev": true, "requires": {} }, - "deep-equal": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.3.tgz", - "integrity": "sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==", - "dev": true, - "requires": { - "array-buffer-byte-length": "^1.0.0", - "call-bind": "^1.0.5", - "es-get-iterator": "^1.1.3", - "get-intrinsic": "^1.2.2", - "is-arguments": "^1.1.1", - "is-array-buffer": "^3.0.2", - "is-date-object": "^1.0.5", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.2", - "isarray": "^2.0.5", - "object-is": "^1.1.5", - "object-keys": "^1.1.1", - "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.5.1", - "side-channel": "^1.0.4", - "which-boxed-primitive": "^1.0.2", - "which-collection": "^1.0.1", - "which-typed-array": "^1.1.13" - }, - "dependencies": { - "isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true - } - } - }, "deep-is": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", @@ -18910,31 +18472,6 @@ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==" }, - "es-get-iterator": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", - "integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.3", - "has-symbols": "^1.0.3", - "is-arguments": "^1.1.1", - "is-map": "^2.0.2", - "is-set": "^2.0.2", - "is-string": "^1.0.7", - "isarray": "^2.0.5", - "stop-iteration-iterator": "^1.0.0" - }, - "dependencies": { - "isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true - } - } - }, "es-iterator-helpers": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.2.1.tgz", @@ -20188,16 +19725,6 @@ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" }, - "is-arguments": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz", - "integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==", - "dev": true, - "requires": { - "call-bound": "^1.0.2", - "has-tostringtag": "^1.0.2" - } - }, "is-array-buffer": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", @@ -22479,16 +22006,6 @@ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==" }, - "object-is": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", - "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - } - }, "object-keys": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", @@ -22904,13 +22421,12 @@ } }, "react": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz", - "integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "dev": true, "requires": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" + "loose-envify": "^1.1.0" } }, "react-aria-modal": { @@ -22924,14 +22440,13 @@ } }, "react-dom": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz", - "integrity": "sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "dev": true, "requires": { "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "scheduler": "^0.20.2" + "scheduler": "^0.23.2" } }, "react-is": { @@ -23296,13 +22811,12 @@ } }, "scheduler": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz", - "integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==", + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", "dev": true, "requires": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" + "loose-envify": "^1.1.0" } }, "schema-utils": { diff --git a/package.json b/package.json index 871c76e3..1970f3cc 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "@gfx/zopfli": "^1.0.15", "@testing-library/dom": "^10.4.0", "@testing-library/jest-dom": "^6.6.3", - "@testing-library/react": "^12.1.5", + "@testing-library/react": "^16.3.0", "autoprefixer": "^10.4.21", "babel-core": "^7.0.0-bridge.0", "babel-loader": "^9.2.1", @@ -35,9 +35,9 @@ "mini-css-extract-plugin": "^2.9.2", "postcss": "^8.5.6", "postcss-loader": "^8.1.1", - "react": "^17.0.2", + "react": "^18.3.1", "react-aria-modal": "^5.0.2", - "react-dom": "^17.0.2", + "react-dom": "^18.3.1", "react-router-dom": "^5.3.4", "react-tooltip": "^5.29.1", "style-loader": "^4.0.0", diff --git a/src/components/__tests__/__snapshots__/page-url-details.test.jsx.snap b/src/components/__tests__/__snapshots__/page-url-details.test.jsx.snap index 718c2672..9e750b98 100644 --- a/src/components/__tests__/__snapshots__/page-url-details.test.jsx.snap +++ b/src/components/__tests__/__snapshots__/page-url-details.test.jsx.snap @@ -34,9 +34,7 @@ exports[`PageUrlDetails Component shows separate URL histories for each version / earth-science-conference-convenes - - - - @@ -99,9 +95,7 @@ exports[`PageUrlDetails Component shows separate URL histories for each version / earth-science-conference-convenes - - - - @@ -177,9 +169,7 @@ exports[`PageUrlDetails Component shows the versions' URL if it differs from the / something - - @@ -216,9 +206,7 @@ exports[`PageUrlDetails Component shows the versions' redirects 1`] = ` / earth-science-conference-convenes - - - - diff --git a/src/components/__tests__/change-view.test.jsx b/src/components/__tests__/change-view.test.jsx index b4c36aaf..d7f0f2b4 100644 --- a/src/components/__tests__/change-view.test.jsx +++ b/src/components/__tests__/change-view.test.jsx @@ -1,6 +1,6 @@ /* eslint-env jest */ -import { render, screen, fireEvent } from '@testing-library/react'; +import { render, screen, fireEvent, act } from '@testing-library/react'; import { ApiContext } from '../api-context'; import ChangeView, { defaultDiffType, diffTypeStorage } from '../change-view/change-view'; import layeredStorage from '../../scripts/layered-storage'; @@ -86,13 +86,13 @@ describe('change-view', () => { describe('initial diffType', () => { describe('when a diffType has been stored in layeredStorage and is relevant to the pages being compared', () => { - it('sets state.diffType to the stored value', () => { + it('sets state.diffType to the stored value', async () => { const storedDiffType = 'CHANGES_ONLY_TEXT'; layeredStorage.setItem(diffTypeStorage, storedDiffType); renderBasicChangeView({ mediaType: 'text/html' }); - screen.getByText(`diffType="${storedDiffType}"`); + await screen.findByText(`diffType="${storedDiffType}"`); }); describe('when a diffType has been stored in layeredStorage and is is NOT relevant to the pages being compared', () => { @@ -222,19 +222,24 @@ describe('change-view', () => { // sanity check expect(newType).not.toEqual(defaultDiffType); - const diffSelector = screen.getByLabelText('Comparison:'); - diffSelector.value = newType; - fireEvent.change(diffSelector); + await act(() => { + const diffSelector = screen.getByLabelText('Comparison:'); + diffSelector.value = newType; + fireEvent.change(diffSelector); + }); + screen.getByText(`diffType="${newType}"`); }); - it('stores the new diffType in layeredStorage', () => { + it('stores the new diffType in layeredStorage', async () => { renderBasicChangeView({ mediaType: 'text/html' }); const newType = diffTypesFor('text/html')[0].value; - const diffSelector = screen.getByLabelText('Comparison:'); - diffSelector.value = newType; - fireEvent.change(diffSelector); + await act(() => { + const diffSelector = screen.getByLabelText('Comparison:'); + diffSelector.value = newType; + fireEvent.change(diffSelector); + }); expect(layeredStorage.getItem(diffTypeStorage)).toBe(newType); }); diff --git a/src/components/__tests__/diff-view.test.jsx b/src/components/__tests__/diff-view.test.jsx index bbc94075..d8447dbc 100644 --- a/src/components/__tests__/diff-view.test.jsx +++ b/src/components/__tests__/diff-view.test.jsx @@ -31,6 +31,8 @@ describe('diff-view', () => { ); expect(container).not.toBeEmptyDOMElement(); + await screen.findByText(/loading/i); + await waitFor(() => expect(screen.queryByText(/loading/i)).toBeNull()); }); it('renders an alert if there are no changes in the diff', async () => { @@ -47,7 +49,7 @@ describe('diff-view', () => { ); - await waitFor(() => screen.getByRole('alert')); + await screen.findByRole('alert'); }); it('renders no alert if there are changes in the diff', async () => { @@ -63,7 +65,7 @@ describe('diff-view', () => { ); await waitFor(() => expect(mockApi.getDiff).toHaveBeenCalled()); + await screen.findByText('Hi'); expect(screen.queryByRole('alert')).toBeNull(); - screen.getByText('Hi'); }); }); diff --git a/src/components/__tests__/login-form.test.jsx b/src/components/__tests__/login-form.test.jsx index 421a392d..e5e655e9 100644 --- a/src/components/__tests__/login-form.test.jsx +++ b/src/components/__tests__/login-form.test.jsx @@ -1,6 +1,6 @@ /* eslint-env jest */ -import { render, screen, fireEvent, waitFor } from '@testing-library/react'; +import { render, screen, fireEvent, waitFor, act } from '@testing-library/react'; import LoginPanel from '../login-form/login-form'; import WebMonitoringDb from '../../services/web-monitoring-db'; import { ApiContext } from '../api-context'; @@ -35,10 +35,14 @@ describe('login-form', () => { screen.getByRole('button', { name: 'Cancel' }); }); - it('Calls props.cancelLogin when the cancel button is clicked', () => { + it('Calls props.cancelLogin when the cancel button is clicked', async () => { const cancelLogin = jest.fn(); render(); - fireEvent.click(screen.getByRole('button', { name: 'Cancel' })); + + await act(() => { + fireEvent.click(screen.getByRole('button', { name: 'Cancel' })); + }); + expect(cancelLogin).toHaveBeenCalled(); }); @@ -51,9 +55,11 @@ describe('login-form', () => { ); - fireEvent.change(screen.getByLabelText(/e-?mail/i), { target: { value: 'aaa@aaa.aaa' } }); - fireEvent.change(screen.getByLabelText(/password/i), { target: { value: 'password' } }); - fireEvent.click(screen.getByRole('button', { name: 'Log In' })); + await act(() => { + fireEvent.change(screen.getByLabelText(/e-?mail/i), { target: { value: 'aaa@aaa.aaa' } }); + fireEvent.change(screen.getByLabelText(/password/i), { target: { value: 'password' } }); + fireEvent.click(screen.getByRole('button', { name: 'Log In' })); + }); expect(api.logIn).toHaveBeenCalledWith('aaa@aaa.aaa', 'password'); }); @@ -67,9 +73,11 @@ describe('login-form', () => { ); - fireEvent.change(screen.getByLabelText(/e-?mail/i), { target: { value: 'aaa@aaa.aaa' } }); - fireEvent.change(screen.getByLabelText(/password/i), { target: { value: 'password' } }); - fireEvent.click(screen.getByRole('button', { name: 'Log In' })); + await act(() => { + fireEvent.change(screen.getByLabelText(/e-?mail/i), { target: { value: 'aaa@aaa.aaa' } }); + fireEvent.change(screen.getByLabelText(/password/i), { target: { value: 'password' } }); + fireEvent.click(screen.getByRole('button', { name: 'Log In' })); + }); await waitFor(() => expect(onLogin).toHaveBeenCalledWith({ id: 5 })); }); @@ -83,9 +91,11 @@ describe('login-form', () => { ); - fireEvent.change(screen.getByLabelText(/e-?mail/i), { target: { value: 'aaa@aaa.aaa' } }); - fireEvent.change(screen.getByLabelText(/password/i), { target: { value: 'password' } }); - fireEvent.click(screen.getByRole('button', { name: 'Log In' })); + await act(() => { + fireEvent.change(screen.getByLabelText(/e-?mail/i), { target: { value: 'aaa@aaa.aaa' } }); + fireEvent.change(screen.getByLabelText(/password/i), { target: { value: 'password' } }); + fireEvent.click(screen.getByRole('button', { name: 'Log In' })); + }); await screen.findByText('Login unsuccessful'); }); @@ -98,8 +108,10 @@ describe('login-form', () => { ); - fireEvent.change(screen.getByLabelText(/e-?mail/i), { target: { value: 'aaa@aaa.aaa' } }); - fireEvent.click(screen.getByRole('button', { name: 'Log In' })); + await act(() => { + fireEvent.change(screen.getByLabelText(/e-?mail/i), { target: { value: 'aaa@aaa.aaa' } }); + fireEvent.click(screen.getByRole('button', { name: 'Log In' })); + }); expect(api.logIn).not.toHaveBeenCalled(); await screen.findByText('Please enter an e-mail and password.'); diff --git a/src/components/__tests__/page-details.test.jsx b/src/components/__tests__/page-details.test.jsx index 8fefa305..cb2698d1 100644 --- a/src/components/__tests__/page-details.test.jsx +++ b/src/components/__tests__/page-details.test.jsx @@ -1,5 +1,5 @@ /* eslint-env jest */ -import { render, waitFor } from '@testing-library/react'; +import { render, waitFor, screen } from '@testing-library/react'; import PageDetails from '../page-details/page-details'; import simplePage from '../../__mocks__/simple-page.json'; import { ApiContext } from '../api-context'; @@ -45,15 +45,15 @@ describe('page-details', () => { }); }; - it('can render', () => { + it('can render', async () => { const mockApi = createMockApi(); - const { container } = render( + render( ); - expect(container).not.toBeEmptyDOMElement(); + await screen.findByText(simplePage.title); }); it('shows correct title', async () => { diff --git a/src/components/__tests__/page-list.test.jsx b/src/components/__tests__/page-list.test.jsx index 940c1295..4d7aecdb 100644 --- a/src/components/__tests__/page-list.test.jsx +++ b/src/components/__tests__/page-list.test.jsx @@ -1,6 +1,6 @@ /* eslint-env jest */ -import { fireEvent, render, screen } from '@testing-library/react'; +import { fireEvent, render, screen, act } from '@testing-library/react'; import PageList from '../page-list/page-list'; import simplePages from '../../__mocks__/simple-pages.json'; @@ -56,25 +56,29 @@ describe('page-list', () => { expect(screen.queryByText(/loading/i)).toBeNull(); }); - it('opens a new window when a user control clicks on a page row', () => { + it('opens a new window when a user control clicks on a page row', async () => { global.open = jest.fn(); render(); const page = simplePages[0]; - const row = screen.getByRole('link', { name: page.url }).closest('tr'); - fireEvent.click(row, { ctrlKey : true }); + await act(() => { + const row = screen.getByRole('link', { name: page.url }).closest('tr'); + fireEvent.click(row, { ctrlKey : true }); + }); expect(global.open.mock.calls[0][0]).toBe(`/page/${page.uuid}`); expect(global.open.mock.calls[0][1]).toBe('_blank'); }); - it('opens a new window when a user command clicks on a page row', () => { + it('opens a new window when a user command clicks on a page row', async () => { global.open = jest.fn(); render(); const page = simplePages[0]; - const row = screen.getByRole('link', { name: page.url }).closest('tr'); - fireEvent.click(row, { metaKey : true }); + await act(() => { + const row = screen.getByRole('link', { name: page.url }).closest('tr'); + fireEvent.click(row, { metaKey : true }); + }); expect(global.open.mock.calls.length).toBe(1); }); diff --git a/src/components/__tests__/search-bar.test.jsx b/src/components/__tests__/search-bar.test.jsx index 13a4e9b7..cfcc528d 100644 --- a/src/components/__tests__/search-bar.test.jsx +++ b/src/components/__tests__/search-bar.test.jsx @@ -1,6 +1,6 @@ /* eslint-env jest */ -import { render, screen, fireEvent } from '@testing-library/react'; +import { render, screen, fireEvent, act } from '@testing-library/react'; import SearchBar from '../search-bar/search-bar'; import { DateTime } from 'luxon'; @@ -14,13 +14,15 @@ describe('search-bar', () => { expect(searchBarInput).toBeInTheDocument(); }); - it('Handles search queries with a protocol correctly', () => { + it('Handles search queries with a protocol correctly', async () => { const onSearch = jest.fn(); render(); - const searchBarInput = screen.getByPlaceholderText('Search for a URL...'); - fireEvent.change(searchBarInput, { target: { value: 'http://epa' } }); - jest.runAllTimers(); + await act(() => { + const searchBarInput = screen.getByPlaceholderText('Search for a URL...'); + fireEvent.change(searchBarInput, { target: { value: 'http://epa' } }); + jest.runAllTimers(); + }); expect(onSearch).toHaveBeenCalledWith({ url: 'http://epa*', @@ -29,13 +31,15 @@ describe('search-bar', () => { }); }); - it('Handles search queries without a protocol correctly', () => { + it('Handles search queries without a protocol correctly', async () => { const onSearch = jest.fn(); render(); - const searchBarInput = screen.getByPlaceholderText('Search for a URL...'); - fireEvent.change(searchBarInput, { target: { value: 'epa' } }); - jest.runAllTimers(); + await act(() => { + const searchBarInput = screen.getByPlaceholderText('Search for a URL...'); + fireEvent.change(searchBarInput, { target: { value: 'epa' } }); + jest.runAllTimers(); + }); expect(onSearch).toHaveBeenCalledWith({ url: '*//epa*', @@ -44,12 +48,14 @@ describe('search-bar', () => { }); }); - it('Handles date range search queries for startDate', () => { + it('Handles date range search queries for startDate', async () => { const onSearch = jest.fn(); render(); - const startDateInput = screen.getByLabelText(/from date/i); - fireEvent.change(startDateInput, { target: { value: '2025-06-01' } }); + await act(() => { + const startDateInput = screen.getByLabelText(/from date/i); + fireEvent.change(startDateInput, { target: { value: '2025-06-01' } }); + }); expect(onSearch).toHaveBeenCalledWith({ url: null, @@ -58,12 +64,14 @@ describe('search-bar', () => { }); }); - it('Handles date range search queries for endDate', () => { + it('Handles date range search queries for endDate', async () => { const onSearch = jest.fn(); render(); - const endDateInput = screen.getByLabelText(/to date/i); - fireEvent.change(endDateInput, { target: { value: '2025-06-01' } }); + await act(() => { + const endDateInput = screen.getByLabelText(/to date/i); + fireEvent.change(endDateInput, { target: { value: '2025-06-01' } }); + }); expect(onSearch).toHaveBeenCalledWith({ url: null, diff --git a/src/components/change-view/change-view.jsx b/src/components/change-view/change-view.jsx index 3600aace..e5cc1826 100644 --- a/src/components/change-view/change-view.jsx +++ b/src/components/change-view/change-view.jsx @@ -56,6 +56,10 @@ export default class ChangeView extends Component { return null; } + get userCanAnnotate () { + return this.props.user?.permissions?.includes('annotate') ?? false; + } + constructor (props) { super(props); @@ -70,6 +74,8 @@ export default class ChangeView extends Component { updating: false, }; + this.currentlyLoadingChange = null; + this.handleFromVersionChange = this.handleFromVersionChange.bind(this); this.handleToVersionChange = this.handleToVersionChange.bind(this); this.handleDiffTypeChange = this.handleDiffTypeChange.bind(this); @@ -82,11 +88,20 @@ export default class ChangeView extends Component { } componentDidMount () { - this._getChange(); + if (this.userCanAnnotate) { + this._getChange(); + } + } + + componentWillUnmount () { + this.currentlyLoadingChange = null; } componentDidUpdate (previousProps) { - if (this.props.from !== previousProps.from || this.props.to !== previousProps.to) { + if ( + this.userCanAnnotate + && (this.props.from !== previousProps.from || this.props.to !== previousProps.to) + ) { this._getChange(this.props.from, this.props.to); } @@ -115,14 +130,6 @@ export default class ChangeView extends Component { render () { const { page } = this.props; - /** - * TODO: Update `userCanAnnotate` to reflect real user permissions once implemented. - * `canAnnotate` doesn't exist yet, so always defaults to null. - * Effectively hiding the annotation for everyone until permissions are implemented. - * `canAnnotate` is arbitrary and DOES NOT reflect any intended permissions model or setup. - * https://github.com/edgi-govdata-archiving/web-monitoring-ui/issues/120 - */ - const userCanAnnotate = this.props.user?.canAnnotate || null; if (!page || !page.versions) { // if haz no page, don't render return (
); @@ -130,7 +137,7 @@ export default class ChangeView extends Component { return (
- {userCanAnnotate ? this.renderSubmission() : null} + {this.userCanAnnotate ? this.renderSubmission() : null}
{ // only update state.change if what we want is still the same // and we don't already have it - if (!changeMatches(change, this.state.change) && changeMatches(change, this.props)) { + if (!changeMatches(change, this.state.change) && changeMatches(change, this.currentlyLoadingChange)) { this.setState({ annotation: Object.assign({}, change.current_annotation), change diff --git a/src/components/diff-view.jsx b/src/components/diff-view.jsx index 6d69fcbc..2e1f79ff 100644 --- a/src/components/diff-view.jsx +++ b/src/components/diff-view.jsx @@ -31,6 +31,21 @@ import styles from '../css/base.css'; export default class DiffView extends Component { static contextType = ApiContext; + // Tracks the diff that was most recently requested for loading. Prevents + // repeated calls causing multiple loads of the same data, and ensures that + // results only for the most recent diff is used (so if the diff props are + // changed while the component is loading, the data from the previous load + // will be discarded because it no longer matches `_loadingDiff`). + _loadingDiff = null; + + state = { + // Will be requested via the API based on props. + diffData: null, + // Tracks previous diff-related props so that we know only to reload data + // if props related to the diff API call have changed. + previousDiff: null, + }; + static getDerivedStateFromProps (props, state) { // Clear out stale diff data before trying to render if (!specifiesSameDiff(props, state.previousDiff)) { @@ -42,19 +57,15 @@ export default class DiffView extends Component { return null; } - constructor (props) { - super(props); - this._loadingDiff = null; - this.state = { - diffData: null, - previousDiff: null - }; - } - componentDidMount () { this._loadDiffIfNeeded(this.props); } + componentWillUnmount () { + // Ensure any in-flight API calls are discarded and do not update state. + this._loadingDiff = null; + } + componentDidUpdate () { this._loadDiffIfNeeded(this.props); } @@ -198,34 +209,29 @@ export default class DiffView extends Component { } this._loadingDiff = specifier; - if (!diffTypes[diffType].diffService) { - return Promise.all([ + let dataLoad; + if (diffTypes[diffType].diffService) { + dataLoad = this.context.api.getDiff( + page.uuid, + a.uuid, + b.uuid, + diffTypes[diffType].diffService, + diffTypes[diffType].options + ); + } + else { + dataLoad = Promise.all([ fetch(a.body_url, { mode: 'cors' }), fetch(b.body_url, { mode: 'cors' }) - ]) - .then(([rawA, rawB]) => { - return { raw: true, rawA, rawB }; - }) - .catch(error => error) - .then(data => { - this._loadingDiff = specifier; - this.setState({ diffData: data }); - }); + ]); } - this.context.api.getDiff( - page.uuid, - a.uuid, - b.uuid, - diffTypes[diffType].diffService, - diffTypes[diffType].options - ) - .catch(error => { - return error; - }) + dataLoad + .catch(error => error) .then(data => { - this._loadingDiff = specifier; - this.setState({ diffData: data }); + if (this._loadingDiff === specifier) { + this.setState({ diffData: data }); + } }); } } diff --git a/src/components/page-details/page-details.jsx b/src/components/page-details/page-details.jsx index dcddfaa4..bdc83fe9 100644 --- a/src/components/page-details/page-details.jsx +++ b/src/components/page-details/page-details.jsx @@ -29,6 +29,9 @@ import pageStyles from './page-details.css'; export default class PageDetails extends Component { static contextType = ApiContext; + isMounted = false; + state = { page: null }; + static getDerivedStateFromProps (props, state) { // Clear existing content when switching pages if (state.page && state.page.uuid !== props.match.params.pageId) { @@ -37,19 +40,14 @@ export default class PageDetails extends Component { return null; } - constructor (props) { - super(props); - this.state = { page: null }; - this._annotateChange = this._annotateChange.bind(this); - this._navigateToChange = this._navigateToChange.bind(this); - } - componentDidMount () { + this.isMounted = true; window.addEventListener('keydown', this); this._loadPage(this.props.match.params.pageId); } componentWillUnmount () { + this.isMounted = false; window.removeEventListener('keydown', this); this.setTitle(true); } @@ -87,14 +85,14 @@ export default class PageDetails extends Component { * @param {string} toVersion ID of the `to` version of the change * @param {Object} annotation */ - _annotateChange (fromVersion, toVersion, annotation) { + _annotateChange = (fromVersion, toVersion, annotation) => { return this.context.api.annotateChange( this.state.page.uuid, fromVersion, toVersion, annotation ); - } + }; render () { if (this.state.error) { @@ -323,6 +321,10 @@ export default class PageDetails extends Component { */ Promise.resolve(fromList || this.context.api.getPage(pageId)) .then(page => { + if (!this.isMounted) { + console.debug('PageDetails was unmounted while loading.'); + return; + } // If we redirected to a different page ID, store a special object so // we can redirect on render. if (page.uuid !== pageId) { @@ -335,7 +337,7 @@ export default class PageDetails extends Component { }); }) .catch(error => { - this.setState({ error }); + if (this.isMounted) this.setState({ error }); }); } @@ -372,8 +374,8 @@ export default class PageDetails extends Component { return `/page/${pageId}/${changeId}`; } - _navigateToChange (from, to, page, replace = false) { + _navigateToChange = (from, to, page, replace = false) => { const url = this._getChangeUrl(from, to, page); this.props.history[replace ? 'replace' : 'push'](url); - } + }; } diff --git a/src/scripts/main.jsx b/src/scripts/main.jsx index 587d2bc1..f2088f5d 100644 --- a/src/scripts/main.jsx +++ b/src/scripts/main.jsx @@ -1,10 +1,8 @@ import 'normalize.css'; import '../css/styles.css'; import '../css/global.css'; -import ReactDOM from 'react-dom'; +import { createRoot } from 'react-dom/client'; import WebMonitoringUi from '../components/web-monitoring-ui'; -ReactDOM.render( - , - document.getElementById('web-monitoring-ui-root') -); +const root = createRoot(document.getElementById('web-monitoring-ui-root')); +root.render();