upgrade msw 1->2, fix network error case

In the upgrade I managed to dust off some tests that I'd been skipping
for quite a while. It turns out one of them was pointing out a real
problem: in the network error case, we didn't display the error to the
user properly. It's really sad this reaches our code as a `TypeError`,
but it is what it is.
This commit is contained in:
Scott Lamb 2023-12-17 23:37:07 -08:00
parent 3911334fee
commit e9a25322b5
7 changed files with 204 additions and 269 deletions

275
ui/package-lock.json generated
View File

@ -46,7 +46,7 @@
"eslint-plugin-react-refresh": "^0.4.5",
"eslint-plugin-vitest": "^0.3.18",
"http-proxy-middleware": "^2.0.4",
"msw": "^1.3.2",
"msw": "^2.0.0",
"prettier": "^2.6.0",
"ts-node": "^10.9.2",
"typescript": "^5.1.0",
@ -1905,6 +1905,33 @@
"optional": true,
"peer": true
},
"node_modules/@bundled-es-modules/cookie": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@bundled-es-modules/cookie/-/cookie-2.0.0.tgz",
"integrity": "sha512-Or6YHg/kamKHpxULAdSqhGqnWFneIXu1NKvvfBBzKGwpVsYuFIQ5aBPHDnnoR3ghW1nvSkALd+EF9iMtY7Vjxw==",
"dev": true,
"dependencies": {
"cookie": "^0.5.0"
}
},
"node_modules/@bundled-es-modules/js-levenshtein": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@bundled-es-modules/js-levenshtein/-/js-levenshtein-2.0.1.tgz",
"integrity": "sha512-DERMS3yfbAljKsQc0U2wcqGKUWpdFjwqWuoMugEJlqBnKO180/n+4SR/J8MRDt1AN48X1ovgoD9KrdVXcaa3Rg==",
"dev": true,
"dependencies": {
"js-levenshtein": "^1.1.6"
}
},
"node_modules/@bundled-es-modules/statuses": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@bundled-es-modules/statuses/-/statuses-1.0.1.tgz",
"integrity": "sha512-yn7BklA5acgcBr+7w064fGV+SGIFySjCKpqjcWgBAIfrAkY+4GQTJJHQMeT3V/sgz23VTEVV8TtOmkvJAhFVfg==",
"dev": true,
"dependencies": {
"statuses": "^2.0.1"
}
},
"node_modules/@cspotcode/source-map-support": {
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
@ -3963,44 +3990,29 @@
"integrity": "sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA=="
},
"node_modules/@mswjs/cookies": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/@mswjs/cookies/-/cookies-0.2.2.tgz",
"integrity": "sha512-mlN83YSrcFgk7Dm1Mys40DLssI1KdJji2CMKN8eOlBqsTADYzj2+jWzsANsUTFbxDMWPD5e9bfA1RGqBpS3O1g==",
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@mswjs/cookies/-/cookies-1.1.0.tgz",
"integrity": "sha512-0ZcCVQxifZmhwNBoQIrystCb+2sWBY2Zw8lpfJBPCHGCA/HWqehITeCRVIv4VMy8MPlaHo2w2pTHFV2pFfqKPw==",
"dev": true,
"dependencies": {
"@types/set-cookie-parser": "^2.4.0",
"set-cookie-parser": "^2.4.6"
},
"engines": {
"node": ">=14"
"node": ">=18"
}
},
"node_modules/@mswjs/interceptors": {
"version": "0.17.10",
"resolved": "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.17.10.tgz",
"integrity": "sha512-N8x7eSLGcmUFNWZRxT1vsHvypzIRgQYdG0rJey/rZCy6zT/30qDt8Joj7FxzGNLSwXbeZqJOMqDurp7ra4hgbw==",
"version": "0.25.13",
"resolved": "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.25.13.tgz",
"integrity": "sha512-xfjR81WwXPHwhDbqJRHlxYmboJuiSaIKpP4I5TJVFl/EmByOU13jOBT9hmEnxcjR3jvFYoqoNKt7MM9uqerj9A==",
"dev": true,
"dependencies": {
"@open-draft/until": "^1.0.3",
"@types/debug": "^4.1.7",
"@xmldom/xmldom": "^0.8.3",
"debug": "^4.3.3",
"headers-polyfill": "3.2.5",
"@open-draft/deferred-promise": "^2.2.0",
"@open-draft/logger": "^0.3.0",
"@open-draft/until": "^2.0.0",
"is-node-process": "^1.2.0",
"outvariant": "^1.2.1",
"strict-event-emitter": "^0.2.4",
"web-encoding": "^1.1.5"
"strict-event-emitter": "^0.5.1"
},
"engines": {
"node": ">=14"
}
},
"node_modules/@mswjs/interceptors/node_modules/strict-event-emitter": {
"version": "0.2.8",
"resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.2.8.tgz",
"integrity": "sha512-KDf/ujU8Zud3YaLtMCcTI4xkZlZVIYxTLr+XIULexP+77EEVWixeXroLUXQXiVtH4XH2W7jr/3PT1v3zBuvc3A==",
"dev": true,
"dependencies": {
"events": "^3.3.0"
"node": ">=18"
}
},
"node_modules/@mui/base": {
@ -4388,10 +4400,26 @@
"node": ">= 8"
}
},
"node_modules/@open-draft/deferred-promise": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@open-draft/deferred-promise/-/deferred-promise-2.2.0.tgz",
"integrity": "sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==",
"dev": true
},
"node_modules/@open-draft/logger": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/@open-draft/logger/-/logger-0.3.0.tgz",
"integrity": "sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==",
"dev": true,
"dependencies": {
"is-node-process": "^1.2.0",
"outvariant": "^1.4.0"
}
},
"node_modules/@open-draft/until": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@open-draft/until/-/until-1.0.3.tgz",
"integrity": "sha512-Aq58f5HiWdyDlFffbbSjAlv596h/cOnt2DO1w3DOC7OJ5EHs0hd/nycJfiu9RJbT6Yk6F1knnRRXNSpxoIVZ9Q==",
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@open-draft/until/-/until-2.1.0.tgz",
"integrity": "sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==",
"dev": true
},
"node_modules/@popperjs/core": {
@ -5154,15 +5182,6 @@
"integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==",
"dev": true
},
"node_modules/@types/debug": {
"version": "4.1.12",
"resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz",
"integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==",
"dev": true,
"dependencies": {
"@types/ms": "*"
}
},
"node_modules/@types/graceful-fs": {
"version": "4.1.9",
"resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz",
@ -5225,12 +5244,6 @@
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
"dev": true
},
"node_modules/@types/ms": {
"version": "0.7.34",
"resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.34.tgz",
"integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==",
"dev": true
},
"node_modules/@types/node": {
"version": "18.19.3",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.3.tgz",
@ -5288,15 +5301,6 @@
"integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==",
"dev": true
},
"node_modules/@types/set-cookie-parser": {
"version": "2.4.7",
"resolved": "https://registry.npmjs.org/@types/set-cookie-parser/-/set-cookie-parser-2.4.7.tgz",
"integrity": "sha512-+ge/loa0oTozxip6zmhRIk8Z/boU51wl9Q6QdLZcokIGMzY5lFXYy/x7Htj2HTC6/KZP1hUbZ1ekx8DYXICvWg==",
"dev": true,
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/stack-utils": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz",
@ -5305,6 +5309,12 @@
"optional": true,
"peer": true
},
"node_modules/@types/statuses": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@types/statuses/-/statuses-2.0.4.tgz",
"integrity": "sha512-eqNDvZsCNY49OAXB0Firg/Sc2BgoWsntsLUdybGFOhAfCD6QJ2n9HXUIHGqt5qjrxmMv4wS8WLAw43ZkKcJ8Pw==",
"dev": true
},
"node_modules/@types/yargs-parser": {
"version": "21.0.3",
"resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz",
@ -5766,22 +5776,6 @@
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
}
},
"node_modules/@xmldom/xmldom": {
"version": "0.8.10",
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz",
"integrity": "sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==",
"dev": true,
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/@zxing/text-encoding": {
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/@zxing/text-encoding/-/text-encoding-0.9.0.tgz",
"integrity": "sha512-U/4aVJ2mxI0aDNI8Uq0wEhMgY+u4CNtEb0om3+y3+niDAsoTCOB33UF0sxpzqzdqXLqmvc+vZyAt4O8pPdfkwA==",
"dev": true,
"optional": true
},
"node_modules/abab": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz",
@ -6846,9 +6840,9 @@
"dev": true
},
"node_modules/cookie": {
"version": "0.4.2",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz",
"integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==",
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz",
"integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==",
"dev": true,
"engines": {
"node": ">= 0.6"
@ -7994,15 +7988,6 @@
"integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==",
"dev": true
},
"node_modules/events": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
"integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==",
"dev": true,
"engines": {
"node": ">=0.8.x"
}
},
"node_modules/execa": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz",
@ -8621,9 +8606,9 @@
}
},
"node_modules/headers-polyfill": {
"version": "3.2.5",
"resolved": "https://registry.npmjs.org/headers-polyfill/-/headers-polyfill-3.2.5.tgz",
"integrity": "sha512-tUCGvt191vNSQgttSyJoibR+VO+I6+iCHIUdhzEMJKE+EAL8BwCN7fUOZlY4ofOelNHsK+gEjxB/B+9N3EWtdA==",
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/headers-polyfill/-/headers-polyfill-4.0.2.tgz",
"integrity": "sha512-EWGTfnTqAO2L/j5HZgoM/3z82L7necsJ0pO9Tp0X1wil3PDLrkypTBRgVO2ExehEEvUycejZD3FuRaXpZZc3kw==",
"dev": true
},
"node_modules/hoist-non-react-statics": {
@ -12993,29 +12978,31 @@
"dev": true
},
"node_modules/msw": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/msw/-/msw-1.3.2.tgz",
"integrity": "sha512-wKLhFPR+NitYTkQl5047pia0reNGgf0P6a1eTnA5aNlripmiz0sabMvvHcicE8kQ3/gZcI0YiPFWmYfowfm3lA==",
"version": "2.0.11",
"resolved": "https://registry.npmjs.org/msw/-/msw-2.0.11.tgz",
"integrity": "sha512-dAXFS2DxZX0uFqMPhS3oUAu8S/5IQ5qKKSwtXl3/dMTeML0C8JfSvbeWtowYg6pu4Iehgp5L/pHLrlIcG++y/A==",
"dev": true,
"hasInstallScript": true,
"dependencies": {
"@mswjs/cookies": "^0.2.2",
"@mswjs/interceptors": "^0.17.10",
"@open-draft/until": "^1.0.3",
"@bundled-es-modules/cookie": "^2.0.0",
"@bundled-es-modules/js-levenshtein": "^2.0.1",
"@bundled-es-modules/statuses": "^1.0.1",
"@mswjs/cookies": "^1.1.0",
"@mswjs/interceptors": "^0.25.13",
"@open-draft/until": "^2.1.0",
"@types/cookie": "^0.4.1",
"@types/js-levenshtein": "^1.1.1",
"chalk": "^4.1.1",
"@types/statuses": "^2.0.1",
"chalk": "^4.1.2",
"chokidar": "^3.4.2",
"cookie": "^0.4.2",
"graphql": "^16.8.1",
"headers-polyfill": "3.2.5",
"headers-polyfill": "^4.0.1",
"inquirer": "^8.2.0",
"is-node-process": "^1.2.0",
"js-levenshtein": "^1.1.6",
"node-fetch": "^2.6.7",
"outvariant": "^1.4.0",
"path-to-regexp": "^6.2.0",
"strict-event-emitter": "^0.4.3",
"strict-event-emitter": "^0.5.0",
"type-fest": "^2.19.0",
"yargs": "^17.3.1"
},
@ -13023,14 +13010,14 @@
"msw": "cli/index.js"
},
"engines": {
"node": ">=14"
"node": ">=18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/mswjs"
},
"peerDependencies": {
"typescript": ">= 4.4.x <= 5.2.x"
"typescript": ">= 4.7.x <= 5.2.x"
},
"peerDependenciesMeta": {
"typescript": {
@ -13150,48 +13137,6 @@
"integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
"dev": true
},
"node_modules/node-fetch": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
"dev": true,
"dependencies": {
"whatwg-url": "^5.0.0"
},
"engines": {
"node": "4.x || >=6.0.0"
},
"peerDependencies": {
"encoding": "^0.1.0"
},
"peerDependenciesMeta": {
"encoding": {
"optional": true
}
}
},
"node_modules/node-fetch/node_modules/tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
"dev": true
},
"node_modules/node-fetch/node_modules/webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
"dev": true
},
"node_modules/node-fetch/node_modules/whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
"dev": true,
"dependencies": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"
}
},
"node_modules/node-int64": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz",
@ -14472,12 +14417,6 @@
"semver": "bin/semver.js"
}
},
"node_modules/set-cookie-parser": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.6.0.tgz",
"integrity": "sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ==",
"dev": true
},
"node_modules/set-function-length": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz",
@ -14650,6 +14589,15 @@
"integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==",
"dev": true
},
"node_modules/statuses": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
"integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
"dev": true,
"engines": {
"node": ">= 0.8"
}
},
"node_modules/std-env": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/std-env/-/std-env-3.6.0.tgz",
@ -14669,9 +14617,9 @@
}
},
"node_modules/strict-event-emitter": {
"version": "0.4.6",
"resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.4.6.tgz",
"integrity": "sha512-12KWeb+wixJohmnwNFerbyiBrAlq5qJLwIt38etRtKtmmHyDSoGlIqFE9wx+4IwG0aDjI7GV8tc8ZccjWZZtTg==",
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.5.1.tgz",
"integrity": "sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==",
"dev": true
},
"node_modules/string_decoder": {
@ -15306,19 +15254,6 @@
"requires-port": "^1.0.0"
}
},
"node_modules/util": {
"version": "0.12.5",
"resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz",
"integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==",
"dev": true,
"dependencies": {
"inherits": "^2.0.3",
"is-arguments": "^1.0.4",
"is-generator-function": "^1.0.7",
"is-typed-array": "^1.1.3",
"which-typed-array": "^1.1.2"
}
},
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
@ -15742,18 +15677,6 @@
"defaults": "^1.0.3"
}
},
"node_modules/web-encoding": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/web-encoding/-/web-encoding-1.1.5.tgz",
"integrity": "sha512-HYLeVCdJ0+lBYV2FvNZmv3HJ2Nt0QYXqZojk3d9FJOLkwnuhzM9tmamh8d7HPM8QqjKH8DeHkFTx+CFlWpZZDA==",
"dev": true,
"dependencies": {
"util": "^0.12.3"
},
"optionalDependencies": {
"@zxing/text-encoding": "0.9.0"
}
},
"node_modules/webidl-conversions": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",

View File

@ -101,7 +101,7 @@
"eslint-plugin-react-refresh": "^0.4.5",
"eslint-plugin-vitest": "^0.3.18",
"http-proxy-middleware": "^2.0.4",
"msw": "^1.3.2",
"msw": "^2.0.0",
"prettier": "^2.6.0",
"ts-node": "^10.9.2",
"typescript": "^5.1.0",

View File

@ -5,13 +5,13 @@
import { screen } from "@testing-library/react";
import App from "./App";
import { renderWithCtx } from "./testutil";
import { rest } from "msw";
import { http, HttpResponse } from "msw";
import { setupServer } from "msw/node";
import { beforeAll, afterAll, afterEach, expect, test } from "vitest";
const server = setupServer(
rest.get("/api/", (req, res, ctx) => {
return res(ctx.status(503), ctx.text("server error"));
http.get("/api/", () => {
return HttpResponse.text("server error", { status: 503 });
})
);
beforeAll(() => server.listen({ onUnhandledRequest: "error" }));

View File

@ -5,7 +5,7 @@
import { screen } from "@testing-library/react";
import { utcToZonedTime } from "date-fns-tz";
import format from "date-fns/format";
import { rest } from "msw";
import { DefaultBodyType, delay, http, HttpResponse, PathParams } from "msw";
import { setupServer } from "msw/node";
import { Recording, VideoSampleEntry } from "../api";
import { renderWithCtx } from "../testutil";
@ -85,32 +85,32 @@ function TestFormat(time90k: number) {
}
const server = setupServer(
rest.get("/api/cameras/:camera/:streamType/recordings", (req, res, ctx) => {
const p = req.url.searchParams;
const range90k = [
parseInt(p.get("startTime90k")!, 10),
parseInt(p.get("endTime90k")!, 10),
];
if (range90k[0] === 42) {
return res(ctx.status(503), ctx.text("server error"));
}
if (range90k[0] === TEST_RANGE1[0]) {
return res(
ctx.json({
http.get<PathParams, DefaultBodyType, any>(
"/api/cameras/:camera/:streamType/recordings",
async ({ request }) => {
const url = new URL(request.url);
const p = url.searchParams;
const range90k = [
parseInt(p.get("startTime90k")!, 10),
parseInt(p.get("endTime90k")!, 10),
];
if (range90k[0] === 42) {
return HttpResponse.text("server error", { status: 503 });
}
if (range90k[0] === TEST_RANGE1[0]) {
return HttpResponse.json({
recordings: TEST_RECORDINGS1,
videoSampleEntries: TEST_VIDEO_SAMPLE_ENTRIES,
})
);
} else {
return res(
ctx.delay(2000), // 2 second delay
ctx.json({
});
} else {
await delay(2000); // 2 second delay
return HttpResponse.json({
recordings: TEST_RECORDINGS2,
videoSampleEntries: TEST_VIDEO_SAMPLE_ENTRIES,
})
);
});
}
}
})
)
);
beforeAll(() => server.listen({ onUnhandledRequest: "error" }));
afterEach(() => server.resetHandlers());
@ -169,7 +169,9 @@ test("slow replace", async () => {
).toBeInTheDocument();
// Then the second query result should show up.
expect(await screen.findByText(/27 Apr 2021 06:17:43/)).toBeInTheDocument();
expect(
await screen.findByText(/27 Apr 2021 06:17:43/, {}, { timeout: 2000 })
).toBeInTheDocument();
});
test("error", async () => {

View File

@ -2,62 +2,63 @@
// Copyright (C) 2021 The Moonfire NVR Authors; see AUTHORS and LICENSE.txt.
// SPDX-License-Identifier: GPL-v3.0-or-later WITH GPL-3.0-linking-exception
import { screen, waitFor } from "@testing-library/react";
import {
screen,
waitFor,
waitForElementToBeRemoved,
} from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { rest } from "msw";
import { delay, http, HttpResponse } from "msw";
import { setupServer } from "msw/node";
import Login from "./Login";
import { renderWithCtx } from "./testutil";
import { beforeAll, afterEach, afterAll, test, vi, expect } from "vitest";
import {
beforeAll,
afterEach,
afterAll,
test,
vi,
expect,
beforeEach,
} from "vitest";
// Set up a fake API backend.
const server = setupServer(
rest.post("/api/login", (req, res, ctx) => {
const { username, password } = req.body! as Record<string, string>;
http.post<any, Record<string, string>>("/api/login", async ({ request }) => {
const body = await request.json();
const { username, password } = body!;
console.log(
"/api/login post username=" + username + " password=" + password
);
if (username === "slamb" && password === "hunter2") {
return res(ctx.status(204));
return new HttpResponse(null, { status: 204 });
} else if (username === "delay") {
return res(ctx.delay("infinite"));
await delay("infinite");
return new HttpResponse(null);
} else if (username === "server-error") {
return res(ctx.status(503), ctx.text("server error"));
return HttpResponse.text("server error", { status: 503 });
} else if (username === "network-error") {
return res.networkError("network error");
return HttpResponse.error();
} else {
return res(ctx.status(401), ctx.text("bad credentials"));
return HttpResponse.text("bad credentials", { status: 401 });
}
})
);
beforeAll(() => server.listen({ onUnhandledRequest: "error" }));
afterEach(() => server.resetHandlers());
beforeEach(() => {
// Using fake timers allows tests to jump forward to when a snackbar goes away, without incurring
// extra real delay. msw only appears to work when `shouldAdvanceTime` is set though.
vi.useFakeTimers({
shouldAdvanceTime: true,
});
});
afterEach(() => {
vi.runOnlyPendingTimers();
vi.useRealTimers();
server.resetHandlers();
});
afterAll(() => server.close());
// TODO: fix this. It's meant to allow the snack bar timers to run quickly
// in tests, but fake timers seem to have problems:
//
// * react-scripts v4, @testing-library/react v11: worked
// * react-scripts v5, @testing-library/react v11:
// saw "was not wrapped in act" warnings, test failed
// (Seems like waitFor's internal advance calls aren't wrapped in act)
// * react-scripts v5, @testing-library/react v12:
// msw requests never complete
// https://github.com/mswjs/msw/issues/448#issuecomment-723438099 ?
// https://github.com/facebook/jest/issues/11103 ?
// https://github.com/facebook/jest/issues/13018 ?
//
// Argh!
// beforeEach(() => vi.useFakeTimers({
// legacyFakeTimers: true,
// }));
// afterEach(() => {
// act(() => {
// vi.runOnlyPendingTimers();
// vi.useRealTimers();
// });
// });
test("success", async () => {
const user = userEvent.setup();
const handleClose = vi.fn().mockName("handleClose");
@ -70,12 +71,7 @@ test("success", async () => {
await waitFor(() => expect(onSuccess).toHaveBeenCalledTimes(1));
});
// TODO: fix and re-enable this test.
// Currently it makes "CI=true npm run test" hang.
// I think the problem is that npmjs doesn't really support aborting requests,
// so the delay("infinite") request just sticks around, even though the fetch
// has been aborted. Maybe https://github.com/mswjs/msw/pull/585 will fix it.
test.skip("close while pending", async () => {
test("close while pending", async () => {
const handleClose = vi.fn().mockName("handleClose");
const onSuccess = vi.fn().mockName("handleOpen");
const { rerender } = renderWithCtx(
@ -94,9 +90,7 @@ test.skip("close while pending", async () => {
);
});
// TODO: fix and re-enable this test.
// It depends on the timers; see TODO above.
test.skip("bad credentials", async () => {
test("bad credentials", async () => {
const handleClose = vi.fn().mockName("handleClose");
const onSuccess = vi.fn().mockName("handleOpen");
renderWithCtx(
@ -108,9 +102,7 @@ test.skip("bad credentials", async () => {
expect(onSuccess).toHaveBeenCalledTimes(0);
});
// TODO: fix and re-enable this test.
// It depends on the timers; see TODO above.
test.skip("server error", async () => {
test("server error", async () => {
const handleClose = vi.fn().mockName("handleClose");
const onSuccess = vi.fn().mockName("handleOpen");
renderWithCtx(
@ -119,15 +111,12 @@ test.skip("server error", async () => {
await userEvent.type(screen.getByLabelText(/Username/), "server-error");
await userEvent.type(screen.getByLabelText(/Password/), "asdf{enter}");
await screen.findByText(/server error/);
await waitFor(() =>
expect(screen.queryByText(/server error/)).not.toBeInTheDocument()
);
vi.runOnlyPendingTimers();
await waitForElementToBeRemoved(() => screen.queryByText(/server error/));
expect(onSuccess).toHaveBeenCalledTimes(0);
});
// TODO: fix and re-enable this test.
// It depends on the timers; see TODO above.
test.skip("network error", async () => {
test("network error", async () => {
const handleClose = vi.fn().mockName("handleClose");
const onSuccess = vi.fn().mockName("handleOpen");
renderWithCtx(
@ -135,9 +124,9 @@ test.skip("network error", async () => {
);
await userEvent.type(screen.getByLabelText(/Username/), "network-error");
await userEvent.type(screen.getByLabelText(/Password/), "asdf{enter}");
await screen.findByText(/network error/);
await waitFor(() =>
expect(screen.queryByText(/network error/)).not.toBeInTheDocument()
);
// This is the text chosen by msw:
// https://github.com/mswjs/interceptors/blob/122a6533ce57d551dc3b59b3bb43a39026989b70/src/interceptors/fetch/index.ts#L187
await screen.findByText(/Failed to fetch/);
expect(onSuccess).toHaveBeenCalledTimes(0);
});

View File

@ -40,18 +40,37 @@ async function myfetch(
): Promise<FetchResult<Response>> {
let response;
try {
response = await fetch(window.location.origin + url, init);
// Note: `fetch` handles relative URLs on real browsers. Our test
// environment doesn't appear to simulate this properly. Even with the
// browser-like `jsdom` and `msw`'s interception, it uses node's native
// `Request`, which fails with a `TypeError` if given a relative URL.
// Resolve it ourselves here. Harmless in production, makes the tests work.
response = await fetch(new URL(url, window.location.origin), init);
} catch (e) {
if (!(e instanceof DOMException)) {
throw e;
}
if (e.name === "AbortError") {
return { status: "aborted" };
} else {
if (e instanceof TypeError) {
// One might expect this to indicate a logic flaw, but it can happen on a variety of
// conditions including network errors (as seen in Chrome, Firefox, and msw):
// <https://developer.mozilla.org/en-US/docs/Web/API/fetch#exceptions>
//
// It turns out the `DOMException` name of `NetworkEror` is just a decoy, I guess?
// Or possibly only used by the older `XMLHTTPRequest`.
// <https://webidl.spec.whatwg.org/#idl-DOMException-error-names>
// <https://developer.mozilla.org/en-US/docs/Web/API/DOMException#error_names>
// <https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/send>
return {
status: "error",
message: `network error: ${e.message}`,
message: `${e.name}: ${e.message}`,
};
} else if (e instanceof DOMException) {
if (e.name === "AbortError") {
return { status: "aborted" };
}
return {
status: "error",
message: `${e.name}: ${e.message}`,
};
} else {
throw e;
}
}
if (!response.ok) {

View File

@ -8,7 +8,9 @@ import { SnackbarProvider, useSnackbars } from "./snackbars";
import { beforeEach, afterEach, expect, test, vi } from "vitest";
// Mock out timers.
beforeEach(() => { vi.useFakeTimers(); });
beforeEach(() => {
vi.useFakeTimers();
});
afterEach(() => {
vi.runOnlyPendingTimers();
vi.useRealTimers();