diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 559fe98e1..395beb025 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -55,6 +55,7 @@ "husky": "^9.0.11", "jsdom": "^26.0.0", "lodash": "^4.17.21", + "msw": "^2.7.0", "postcss-preset-mantine": "^1.14.4", "postcss-simple-vars": "^7.0.1", "prettier": "^3.2.5", @@ -1912,6 +1913,63 @@ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "dev": true }, + "node_modules/@bundled-es-modules/cookie": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@bundled-es-modules/cookie/-/cookie-2.0.1.tgz", + "integrity": "sha512-8o+5fRPLNbjbdGRRmJj3h6Hh1AQJf2dk3qQ/5ZFb+PXkRNiSoMGGUKlsgLfrxneb72axVJyIYji64E2+nNfYyw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cookie": "^0.7.2" + } + }, + "node_modules/@bundled-es-modules/cookie/node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.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, + "license": "ISC", + "dependencies": { + "statuses": "^2.0.1" + } + }, + "node_modules/@bundled-es-modules/tough-cookie": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@bundled-es-modules/tough-cookie/-/tough-cookie-0.1.6.tgz", + "integrity": "sha512-dvMHbL464C0zI+Yqxbz6kZ5TOEp7GLW+pry/RWndAR8MJQAXZ2rPmIs8tziTZjeIyhSNZgZbCePtfSbdWqStJw==", + "dev": true, + "license": "ISC", + "dependencies": { + "@types/tough-cookie": "^4.0.5", + "tough-cookie": "^4.1.4" + } + }, + "node_modules/@bundled-es-modules/tough-cookie/node_modules/tough-cookie": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", + "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/@canvas/image-data": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@canvas/image-data/-/image-data-1.0.0.tgz", @@ -2675,6 +2733,80 @@ "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", "dev": true }, + "node_modules/@inquirer/confirm": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.3.tgz", + "integrity": "sha512-fuF9laMmHoOgWapF9h9hv6opA5WvmGFHsTYGCmuFxcghIhEhb3dN0CdQR4BUMqa2H506NCj8cGX4jwMsE4t6dA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.1.4", + "@inquirer/type": "^3.0.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + } + }, + "node_modules/@inquirer/core": { + "version": "10.1.4", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.1.4.tgz", + "integrity": "sha512-5y4/PUJVnRb4bwWY67KLdebWOhOc7xj5IP2J80oWXa64mVag24rwQ1VAdnj7/eDY/odhguW0zQ1Mp1pj6fO/2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/figures": "^1.0.9", + "@inquirer/type": "^3.0.2", + "ansi-escapes": "^4.3.2", + "cli-width": "^4.1.0", + "mute-stream": "^2.0.0", + "signal-exit": "^4.1.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^6.2.0", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/core/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@inquirer/figures": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.9.tgz", + "integrity": "sha512-BXvGj0ehzrngHTPTDqUoDT3NXL8U0RxUk2zJm2A66RhCEIWdtU1v6GuUqNAgArW4PQ9CinqIWyHdQgdwOj06zQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/type": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.2.tgz", + "integrity": "sha512-ZhQ4TvhwHZF+lGhQ2O/rsjo80XoZR5/5qhOY3t6FJuX5XBg5Be8YzYTvaUGJnc12AUGI2nr4QSUE4PhKSigx7g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + } + }, "node_modules/@istanbuljs/schema": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", @@ -2888,6 +3020,24 @@ "react": "^18.x || ^19.x" } }, + "node_modules/@mswjs/interceptors": { + "version": "0.37.5", + "resolved": "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.37.5.tgz", + "integrity": "sha512-AAwRb5vXFcY4L+FvZ7LZusDuZ0vEe0Zm8ohn1FM6/X7A3bj4mqmkAcGRWuvC2JwSygNwHAAmMnAI73vPHeqsHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@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.4.3", + "strict-event-emitter": "^0.5.1" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -2923,6 +3073,31 @@ "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, + "license": "MIT" + }, + "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, + "license": "MIT", + "dependencies": { + "is-node-process": "^1.2.0", + "outvariant": "^1.4.0" + } + }, + "node_modules/@open-draft/until": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@open-draft/until/-/until-2.1.0.tgz", + "integrity": "sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==", + "dev": true, + "license": "MIT" + }, "node_modules/@polka/url": { "version": "1.0.0-next.25", "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.25.tgz", @@ -3729,6 +3904,20 @@ "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", "dev": true }, + "node_modules/@types/statuses": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/statuses/-/statuses-2.0.5.tgz", + "integrity": "sha512-jmIUGWrAiwu3dZpxntxieC+1n/5c3mjrImkmOSQ2NC5uP6cYO4aAZDdSmRcI5C1oiTmqlZGHC+/NmJrKogbP5A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/tough-cookie": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", + "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/trusted-types": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", @@ -5330,6 +5519,49 @@ "node": ">=8" } }, + "node_modules/cli-width": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 12" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/clsx": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", @@ -5961,6 +6193,13 @@ "integrity": "sha512-oJRPo82XEqtQAobHpJIR3zW5YO3sSRRkPz2an4yxi1UvqhsGm54vR/wzTFV74a3soDOJ8CKW7ajOOX5ESzddwg==", "dev": true }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, "node_modules/end-of-stream": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", @@ -6777,6 +7016,16 @@ "node": ">=6.9.0" } }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, "node_modules/get-func-name": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", @@ -6957,6 +7206,16 @@ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true }, + "node_modules/graphql": { + "version": "16.10.0", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.10.0.tgz", + "integrity": "sha512-AjqGKbDGUFRKIRCP9tCKiIGHyriz2oHEbPIbEtcSLSs4YjReZOIPQQWek4+6hjw62H9QShXHyaGivGiYVLeYFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" + } + }, "node_modules/has-bigints": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", @@ -7038,6 +7297,13 @@ "node": ">= 0.4" } }, + "node_modules/headers-polyfill": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/headers-polyfill/-/headers-polyfill-4.0.3.tgz", + "integrity": "sha512-IScLbePpkvO846sIwOtOTDjutRMWdXdJmXdMvk6gCBHxFO8d+QKOQedyZSxFTTFYRSmlgSTDtXqqq4pcenBXLQ==", + "dev": true, + "license": "MIT" + }, "node_modules/html-encoding-sniffer": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", @@ -7384,6 +7650,16 @@ "node": ">=0.10.0" } }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -7415,6 +7691,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-node-process": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-node-process/-/is-node-process-1.2.0.tgz", + "integrity": "sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==", + "dev": true, + "license": "MIT" + }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -8351,6 +8634,74 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, + "node_modules/msw": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/msw/-/msw-2.7.0.tgz", + "integrity": "sha512-BIodwZ19RWfCbYTxWTUfTXc+sg4OwjCAgxU1ZsgmggX/7S3LdUifsbUPJs61j0rWb19CZRGY5if77duhc0uXzw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@bundled-es-modules/cookie": "^2.0.1", + "@bundled-es-modules/statuses": "^1.0.1", + "@bundled-es-modules/tough-cookie": "^0.1.6", + "@inquirer/confirm": "^5.0.0", + "@mswjs/interceptors": "^0.37.0", + "@open-draft/deferred-promise": "^2.2.0", + "@open-draft/until": "^2.1.0", + "@types/cookie": "^0.6.0", + "@types/statuses": "^2.0.4", + "graphql": "^16.8.1", + "headers-polyfill": "^4.0.2", + "is-node-process": "^1.2.0", + "outvariant": "^1.4.3", + "path-to-regexp": "^6.3.0", + "picocolors": "^1.1.1", + "strict-event-emitter": "^0.5.1", + "type-fest": "^4.26.1", + "yargs": "^17.7.2" + }, + "bin": { + "msw": "cli/index.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/mswjs" + }, + "peerDependencies": { + "typescript": ">= 4.8.x" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/msw/node_modules/type-fest": { + "version": "4.32.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.32.0.tgz", + "integrity": "sha512-rfgpoi08xagF3JSdtJlCwMq9DGNDE0IMh3Mkpc1wUypg9vPi786AiqeBBKcqvIkq42azsBM85N490fyZjeUftw==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mute-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz", + "integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, "node_modules/nanoid": { "version": "3.3.8", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", @@ -8535,6 +8886,13 @@ "node": ">= 0.8.0" } }, + "node_modules/outvariant": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/outvariant/-/outvariant-1.4.3.tgz", + "integrity": "sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==", + "dev": true, + "license": "MIT" + }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -8623,6 +8981,13 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, + "node_modules/path-to-regexp": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", + "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", + "dev": true, + "license": "MIT" + }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -8648,9 +9013,9 @@ } }, "node_modules/picocolors": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", - "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "dev": true, "license": "ISC" }, @@ -9017,6 +9382,19 @@ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" }, + "node_modules/psl": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", + "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "funding": { + "url": "https://github.com/sponsors/lupomontero" + } + }, "node_modules/pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", @@ -9037,6 +9415,13 @@ "node": ">=6" } }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "dev": true, + "license": "MIT" + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -9469,6 +9854,16 @@ "jsesc": "bin/jsesc" } }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", @@ -9479,6 +9874,13 @@ "node": ">=0.10.0" } }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true, + "license": "MIT" + }, "node_modules/resolve": { "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", @@ -10078,6 +10480,16 @@ "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, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/std-env": { "version": "3.7.0", "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.7.0.tgz", @@ -10099,6 +10511,13 @@ "bare-events": "^2.2.0" } }, + "node_modules/strict-event-emitter": { + "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, + "license": "MIT" + }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -10109,6 +10528,21 @@ "safe-buffer": "~5.2.0" } }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/string.prototype.matchall": { "version": "4.0.11", "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.11.tgz", @@ -10833,6 +11267,16 @@ "node": ">=8" } }, + "node_modules/universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, "node_modules/upath": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", @@ -10883,6 +11327,17 @@ "punycode": "^2.1.0" } }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, "node_modules/use-callback-ref": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", @@ -12029,6 +12484,21 @@ "workbox-core": "7.1.0" } }, + "node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -12079,6 +12549,16 @@ "node": ">=0.4.0" } }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", @@ -12097,6 +12577,35 @@ "node": ">= 14" } }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", @@ -12108,6 +12617,19 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/yoctocolors-cjs": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.2.tgz", + "integrity": "sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } } } } diff --git a/frontend/package.json b/frontend/package.json index 78b9f8ad4..80a945f21 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -59,6 +59,7 @@ "husky": "^9.0.11", "jsdom": "^26.0.0", "lodash": "^4.17.21", + "msw": "^2.7.0", "postcss-preset-mantine": "^1.14.4", "postcss-simple-vars": "^7.0.1", "prettier": "^3.2.5", diff --git a/frontend/src/App/app.test.tsx b/frontend/src/App/app.test.tsx index db9895305..06aafaf86 100644 --- a/frontend/src/App/app.test.tsx +++ b/frontend/src/App/app.test.tsx @@ -1,9 +1,18 @@ +import { http } from "msw"; +import { HttpResponse } from "msw"; import { describe, it } from "vitest"; import { render } from "@/tests"; +import server from "@/tests/mocks/node"; import App from "."; describe("App", () => { it("should render without crash", () => { + server.use( + http.get("/api/system/searches", () => { + return HttpResponse.json({}); + }), + ); + render(); }); }); diff --git a/frontend/src/pages/Blacklist/Movies/index.test.tsx b/frontend/src/pages/Blacklist/Movies/index.test.tsx new file mode 100644 index 000000000..a2fc491e4 --- /dev/null +++ b/frontend/src/pages/Blacklist/Movies/index.test.tsx @@ -0,0 +1,72 @@ +/* eslint-disable camelcase */ +import { http } from "msw"; +import { HttpResponse } from "msw"; +import { render, screen, waitFor } from "@/tests"; +import server from "@/tests/mocks/node"; +import BlacklistMoviesView from "."; + +describe("Blacklist Movies", () => { + it("should render with blacklisted movies", async () => { + server.use( + http.get("/api/system/settings", () => { + return HttpResponse.json({ + general: { + theme: "auto", + }, + }); + }), + ); + + server.use( + http.get("/api/movies/blacklist", () => { + // TODO: Replace with Factory + return HttpResponse.json({ + data: [ + { + title: "Batman vs Teenage Mutant Ninja Turtles", + radarrId: 50, + provider: "yifysubtitles", + subs_id: + "https://yifysubtitles.ch/subtitles/batman-vs-teenage-mutant-ninja-turtles-2019-english-yify-19252", + language: { + name: "English", + code2: "en", + code3: "eng", + forced: false, + hi: false, + }, + timestamp: "28 seconds ago", + parsed_timestamp: "01/23/25 05:39:36", + }, + ], + }); + }), + ); + + render(); + + await waitFor(() => { + expect(screen.getByText("yifysubtitles")).toBeInTheDocument(); + }); + }); + + it("should render without blacklisted movies", async () => { + server.use( + http.get("/api/movies/blacklist", () => { + return HttpResponse.json({ + data: [], + }); + }), + ); + + render(); + + await waitFor(() => { + expect( + screen.getByText("No blacklisted movies subtitles"), + ).toBeInTheDocument(); + }); + + server.resetHandlers(); + }); +}); diff --git a/frontend/src/pages/Blacklist/Series/index.test.tsx b/frontend/src/pages/Blacklist/Series/index.test.tsx new file mode 100644 index 000000000..4ea012fb6 --- /dev/null +++ b/frontend/src/pages/Blacklist/Series/index.test.tsx @@ -0,0 +1,62 @@ +/* eslint-disable camelcase */ +import { http } from "msw"; +import { HttpResponse } from "msw"; +import { render, screen, waitFor } from "@/tests"; +import server from "@/tests/mocks/node"; +import BlacklistSeriesView from "."; + +describe("Blacklist Series", () => { + it("should render without blacklisted series", async () => { + server.use( + http.get("/api/episodes/blacklist", () => { + return HttpResponse.json({ + data: [], + }); + }), + ); + + render(); + + await waitFor(() => { + expect( + screen.getByText("No blacklisted series subtitles"), + ).toBeInTheDocument(); + }); + }); + + it("should render with blacklisted series", async () => { + server.use( + http.get("/api/episodes/blacklist", () => { + // TODO: Replace with Factory + return HttpResponse.json({ + data: [ + { + seriesTitle: "Dragon Ball DAIMA", + episode_number: "1x14", + episodeTitle: "Taboo", + sonarrSeriesId: 56, + provider: "animetosho", + subs_id: + "https://animetosho.org/storage/attach/0022fd50/2293072.xz", + language: { + name: "English", + code2: "en", + code3: "eng", + forced: false, + hi: false, + }, + timestamp: "now", + parsed_timestamp: "01/24/25 01:38:03", + }, + ], + }); + }), + ); + + render(); + + await waitFor(() => { + expect(screen.getByText("animetosho")).toBeInTheDocument(); + }); + }); +}); diff --git a/frontend/src/pages/Blacklist/blacklist.test.tsx b/frontend/src/pages/Blacklist/blacklist.test.tsx deleted file mode 100644 index 4360c473c..000000000 --- a/frontend/src/pages/Blacklist/blacklist.test.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { renderTest, RenderTestCase } from "@/tests/render"; -import BlacklistMoviesView from "./Movies"; -import BlacklistSeriesView from "./Series"; - -const cases: RenderTestCase[] = [ - { - name: "movie page", - ui: BlacklistMoviesView, - }, - { - name: "series page", - ui: BlacklistSeriesView, - }, -]; - -renderTest("Blacklist", cases); diff --git a/frontend/src/pages/History/Movies/index.test.tsx b/frontend/src/pages/History/Movies/index.test.tsx new file mode 100644 index 000000000..f31c093bc --- /dev/null +++ b/frontend/src/pages/History/Movies/index.test.tsx @@ -0,0 +1,33 @@ +import { http } from "msw"; +import { HttpResponse } from "msw"; +import { render } from "@/tests"; +import server from "@/tests/mocks/node"; +import MoviesHistoryView from "."; + +describe("History Movies", () => { + it("should render with movies", async () => { + server.use( + http.get("/api/movies/history", () => { + return HttpResponse.json({ + data: [], + }); + }), + ); + server.use( + http.get("/api/providers", () => { + return HttpResponse.json({ + data: [], + }); + }), + ); + server.use( + http.get("/api/system/languages", () => { + return HttpResponse.json({}); + }), + ); + + render(); + + // TODO: Assert + }); +}); diff --git a/frontend/src/pages/History/Series/index.test.tsx b/frontend/src/pages/History/Series/index.test.tsx new file mode 100644 index 000000000..b60cb5e9e --- /dev/null +++ b/frontend/src/pages/History/Series/index.test.tsx @@ -0,0 +1,21 @@ +import { http } from "msw"; +import { HttpResponse } from "msw"; +import { render } from "@/tests"; +import server from "@/tests/mocks/node"; +import SeriesHistoryView from "."; + +describe("History Series", () => { + it("should render with series", async () => { + server.use( + http.get("/api/episodes/history", () => { + return HttpResponse.json({ + data: [], + }); + }), + ); + + render(); + + // TODO: Assert + }); +}); diff --git a/frontend/src/pages/History/Statistics/HistoryStats.test.tsx b/frontend/src/pages/History/Statistics/HistoryStats.test.tsx new file mode 100644 index 000000000..66fc88403 --- /dev/null +++ b/frontend/src/pages/History/Statistics/HistoryStats.test.tsx @@ -0,0 +1,37 @@ +import { http } from "msw"; +import { HttpResponse } from "msw"; +import { render } from "@/tests"; +import server from "@/tests/mocks/node"; +import HistoryStats from "./HistoryStats"; + +describe("History Stats", () => { + it("should render without stats", async () => { + server.use( + http.get("/api/providers", () => { + return HttpResponse.json({ + data: [], + }); + }), + ); + server.use( + http.get("/api/system/languages", () => { + return HttpResponse.json({}); + }), + ); + server.use( + http.get("/api/history/stats", () => { + return HttpResponse.json({ + series: [], + }); + }), + ); + + server.use( + http.get("/api/system/providers", () => { + return HttpResponse.json({}); + }), + ); + + render(); + }); +}); diff --git a/frontend/src/pages/History/history.test.tsx b/frontend/src/pages/History/history.test.tsx deleted file mode 100644 index 277a268fb..000000000 --- a/frontend/src/pages/History/history.test.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { renderTest, RenderTestCase } from "@/tests/render"; -import HistoryStats from "./Statistics/HistoryStats"; -import MoviesHistoryView from "./Movies"; -import SeriesHistoryView from "./Series"; - -const cases: RenderTestCase[] = [ - { - name: "movie page", - ui: MoviesHistoryView, - }, - { - name: "series page", - ui: SeriesHistoryView, - }, - { - name: "statistics page", - ui: HistoryStats, - }, -]; - -renderTest("History", cases); diff --git a/frontend/src/pages/Movies/movies.test.tsx b/frontend/src/pages/Movies/movies.test.tsx index c4ac8133a..673483bfa 100644 --- a/frontend/src/pages/Movies/movies.test.tsx +++ b/frontend/src/pages/Movies/movies.test.tsx @@ -1,16 +1,46 @@ -import { describe } from "vitest"; -import { render } from "@/tests"; +import { http } from "msw"; +import { HttpResponse } from "msw"; +import { beforeEach, describe, it } from "vitest"; +import { render, screen } from "@/tests"; +import server from "@/tests/mocks/node"; import MovieMassEditor from "./Editor"; import MovieView from "."; describe("Movies page", () => { + beforeEach(() => { + server.use( + http.get("/api/movies", () => { + return HttpResponse.json({ + data: [], + }); + }), + ); + }); + it("should render", () => { render(); }); }); describe("Movies editor page", () => { + beforeEach(() => { + server.use( + http.get("/api/movies", () => { + return HttpResponse.json({ + data: [], + }); + }), + ); + server.use( + http.get("/api/system/languages/profiles", () => { + return HttpResponse.json([]); + }), + ); + }); + it("should render", () => { render(); + + expect(screen.getByText("Actions")).toBeInTheDocument(); }); }); diff --git a/frontend/src/pages/Series/series.test.tsx b/frontend/src/pages/Series/series.test.tsx index b8fd9fad5..decb7fd7c 100644 --- a/frontend/src/pages/Series/series.test.tsx +++ b/frontend/src/pages/Series/series.test.tsx @@ -1,15 +1,43 @@ -import { describe } from "vitest"; +import { http } from "msw"; +import { HttpResponse } from "msw"; +import { beforeEach, describe, it } from "vitest"; import { render } from "@/tests"; +import server from "@/tests/mocks/node"; import SeriesMassEditor from "./Editor"; import SeriesView from "."; describe("Series page", () => { + beforeEach(() => { + server.use( + http.get("/api/series", () => { + return HttpResponse.json({ + data: [], + }); + }), + ); + }); + it("should render", () => { render(); }); }); describe("Series editor page", () => { + beforeEach(() => { + server.use( + http.get("/api/series", () => { + return HttpResponse.json({ + data: [], + }); + }), + ); + server.use( + http.get("/api/system/languages/profiles", () => { + return HttpResponse.json([]); + }), + ); + }); + it("should render", () => { render(); }); diff --git a/frontend/src/pages/Settings/components/Section.test.tsx b/frontend/src/pages/Settings/components/Section.test.tsx index 535bd8be2..90322488b 100644 --- a/frontend/src/pages/Settings/components/Section.test.tsx +++ b/frontend/src/pages/Settings/components/Section.test.tsx @@ -5,6 +5,7 @@ import { Section } from "./Section"; describe("Settings section", () => { const header = "Section Header"; + it("should show header", () => { render(
); diff --git a/frontend/src/pages/Settings/settings.test.tsx b/frontend/src/pages/Settings/settings.test.tsx index 71aa74158..6dfb6e195 100644 --- a/frontend/src/pages/Settings/settings.test.tsx +++ b/frontend/src/pages/Settings/settings.test.tsx @@ -1,11 +1,11 @@ +import { http } from "msw"; +import { HttpResponse } from "msw"; +import server from "@/tests/mocks/node"; import { renderTest, RenderTestCase } from "@/tests/render"; import SettingsGeneralView from "./General"; import SettingsLanguagesView from "./Languages"; -import SettingsNotificationsView from "./Notifications"; import SettingsProvidersView from "./Providers"; -import SettingsRadarrView from "./Radarr"; import SettingsSchedulerView from "./Scheduler"; -import SettingsSonarrView from "./Sonarr"; import SettingsSubtitlesView from "./Subtitles"; import SettingsUIView from "./UI"; @@ -17,27 +17,32 @@ const cases: RenderTestCase[] = [ { name: "languages page", ui: SettingsLanguagesView, - }, - { - name: "notifications page", - ui: SettingsNotificationsView, - }, + setupEach: () => { + server.use( + http.get("/api/system/languages", () => { + return HttpResponse.json({}); + }), + ); + server.use( + http.get("/api/system/languages/profiles", () => { + return HttpResponse.json({ + data: [], + }); + }), + ); + }, + }, + // TODO: Test Notifications Page { name: "providers page", ui: SettingsProvidersView, }, - { - name: "radarr page", - ui: SettingsRadarrView, - }, + // TODO: Test Radarr Page { name: "scheduler page", ui: SettingsSchedulerView, }, - { - name: "sonarr page", - ui: SettingsSonarrView, - }, + // TODO: Test Sonarr Page { name: "subtitles page", ui: SettingsSubtitlesView, diff --git a/frontend/src/pages/System/Announcements/index.test.tsx b/frontend/src/pages/System/Announcements/index.test.tsx new file mode 100644 index 000000000..cc7d18e75 --- /dev/null +++ b/frontend/src/pages/System/Announcements/index.test.tsx @@ -0,0 +1,21 @@ +import { http } from "msw"; +import { HttpResponse } from "msw"; +import { render } from "@/tests"; +import server from "@/tests/mocks/node"; +import SystemAnnouncementsView from "."; + +describe("System Announcements", () => { + it("should render with announcements", async () => { + server.use( + http.get("/api/system/announcements", () => { + return HttpResponse.json({ + data: [], + }); + }), + ); + + render(); + + // TODO: Assert + }); +}); diff --git a/frontend/src/pages/System/Backups/index.test.tsx b/frontend/src/pages/System/Backups/index.test.tsx new file mode 100644 index 000000000..2b82177c6 --- /dev/null +++ b/frontend/src/pages/System/Backups/index.test.tsx @@ -0,0 +1,21 @@ +import { http } from "msw"; +import { HttpResponse } from "msw"; +import { render } from "@/tests"; +import server from "@/tests/mocks/node"; +import SystemBackupsView from "."; + +describe("System Backups", () => { + it("should render with backups", async () => { + server.use( + http.get("/api/system/backups", () => { + return HttpResponse.json({ + data: [], + }); + }), + ); + + render(); + + // TODO: Assert + }); +}); diff --git a/frontend/src/pages/System/Logs/index.test.tsx b/frontend/src/pages/System/Logs/index.test.tsx new file mode 100644 index 000000000..b70336b79 --- /dev/null +++ b/frontend/src/pages/System/Logs/index.test.tsx @@ -0,0 +1,21 @@ +import { http } from "msw"; +import { HttpResponse } from "msw"; +import { render } from "@/tests"; +import server from "@/tests/mocks/node"; +import SystemLogsView from "."; + +describe("System Logs", () => { + it("should render with logs", async () => { + server.use( + http.get("/api/system/logs", () => { + return HttpResponse.json({ + data: [], + }); + }), + ); + + render(); + + // TODO: Assert + }); +}); diff --git a/frontend/src/pages/System/Providers/index.test.tsx b/frontend/src/pages/System/Providers/index.test.tsx new file mode 100644 index 000000000..2e2f4ffdc --- /dev/null +++ b/frontend/src/pages/System/Providers/index.test.tsx @@ -0,0 +1,21 @@ +import { http } from "msw"; +import { HttpResponse } from "msw"; +import { render } from "@/tests"; +import server from "@/tests/mocks/node"; +import SystemProvidersView from "."; + +describe("System Providers", () => { + it("should render with providers", async () => { + server.use( + http.get("/api/providers", () => { + return HttpResponse.json({ + data: [], + }); + }), + ); + + render(); + + // TODO: Assert + }); +}); diff --git a/frontend/src/pages/System/Releases/index.test.tsx b/frontend/src/pages/System/Releases/index.test.tsx new file mode 100644 index 000000000..6e8dfed27 --- /dev/null +++ b/frontend/src/pages/System/Releases/index.test.tsx @@ -0,0 +1,21 @@ +import { http } from "msw"; +import { HttpResponse } from "msw"; +import { render } from "@/tests"; +import server from "@/tests/mocks/node"; +import SystemReleasesView from "."; + +describe("System Releases", () => { + it("should render with releases", async () => { + server.use( + http.get("/api/system/releases", () => { + return HttpResponse.json({ + data: [], + }); + }), + ); + + render(); + + // TODO: Assert + }); +}); diff --git a/frontend/src/pages/System/Status/index.test.tsx b/frontend/src/pages/System/Status/index.test.tsx new file mode 100644 index 000000000..988114d2e --- /dev/null +++ b/frontend/src/pages/System/Status/index.test.tsx @@ -0,0 +1,29 @@ +import { http } from "msw"; +import { HttpResponse } from "msw"; +import { render } from "@/tests"; +import server from "@/tests/mocks/node"; +import SystemStatusView from "."; + +describe("System Status", () => { + it("should render with status", async () => { + server.use( + http.get("/api/system/status", () => { + return HttpResponse.json({ + data: [], + }); + }), + ); + + server.use( + http.get("/api/system/health", () => { + return HttpResponse.json({ + data: [], + }); + }), + ); + + render(); + + // TODO: Assert + }); +}); diff --git a/frontend/src/pages/System/Tasks/index.test.tsx b/frontend/src/pages/System/Tasks/index.test.tsx new file mode 100644 index 000000000..b6c3710af --- /dev/null +++ b/frontend/src/pages/System/Tasks/index.test.tsx @@ -0,0 +1,21 @@ +import { http } from "msw"; +import { HttpResponse } from "msw"; +import { render } from "@/tests"; +import server from "@/tests/mocks/node"; +import SystemTasksView from "."; + +describe("System Tasks", () => { + it("should render with tasks", async () => { + server.use( + http.get("/api/system/tasks", () => { + return HttpResponse.json({ + data: [], + }); + }), + ); + + render(); + + // TODO: Assert + }); +}); diff --git a/frontend/src/pages/System/system.test.tsx b/frontend/src/pages/System/system.test.tsx deleted file mode 100644 index 813654a7b..000000000 --- a/frontend/src/pages/System/system.test.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import SystemAnnouncementsView from "@/pages/System/Announcements"; -import { renderTest, RenderTestCase } from "@/tests/render"; -import SystemBackupsView from "./Backups"; -import SystemLogsView from "./Logs"; -import SystemProvidersView from "./Providers"; -import SystemReleasesView from "./Releases"; -import SystemStatusView from "./Status"; -import SystemTasksView from "./Tasks"; - -const cases: RenderTestCase[] = [ - { - name: "backups page", - ui: SystemBackupsView, - }, - { - name: "logs page", - ui: SystemLogsView, - }, - { - name: "providers page", - ui: SystemProvidersView, - }, - { - name: "releases page", - ui: SystemReleasesView, - }, - { - name: "status page", - ui: SystemStatusView, - }, - { - name: "tasks page", - ui: SystemTasksView, - }, - { - name: "announcements page", - ui: SystemAnnouncementsView, - }, -]; - -renderTest("System", cases); diff --git a/frontend/src/pages/Wanted/Movies/index.test.tsx b/frontend/src/pages/Wanted/Movies/index.test.tsx new file mode 100644 index 000000000..8974573a5 --- /dev/null +++ b/frontend/src/pages/Wanted/Movies/index.test.tsx @@ -0,0 +1,21 @@ +import { http } from "msw"; +import { HttpResponse } from "msw"; +import { render } from "@/tests"; +import server from "@/tests/mocks/node"; +import WantedMoviesView from "."; + +describe("Wanted Movies", () => { + it("should render with wanted movies", async () => { + server.use( + http.get("/api/movies/wanted", () => { + return HttpResponse.json({ + data: [], + }); + }), + ); + + render(); + + // TODO: Assert + }); +}); diff --git a/frontend/src/pages/Wanted/Series/index.test.tsx b/frontend/src/pages/Wanted/Series/index.test.tsx new file mode 100644 index 000000000..8387dabb9 --- /dev/null +++ b/frontend/src/pages/Wanted/Series/index.test.tsx @@ -0,0 +1,21 @@ +import { http } from "msw"; +import { HttpResponse } from "msw"; +import { render } from "@/tests"; +import server from "@/tests/mocks/node"; +import WantedSeriesView from "."; + +describe("Wanted Series", () => { + it("should render with wanted series", async () => { + server.use( + http.get("/api/episodes/wanted", () => { + return HttpResponse.json({ + data: [], + }); + }), + ); + + render(); + + // TODO: Assert + }); +}); diff --git a/frontend/src/pages/Wanted/wanted.test.tsx b/frontend/src/pages/Wanted/wanted.test.tsx deleted file mode 100644 index 36e72c4bb..000000000 --- a/frontend/src/pages/Wanted/wanted.test.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { renderTest, RenderTestCase } from "@/tests/render"; -import WantedMoviesView from "./Movies"; -import WantedSeriesView from "./Series"; - -const cases: RenderTestCase[] = [ - { - name: "movie page", - ui: WantedMoviesView, - }, - { - name: "series page", - ui: WantedSeriesView, - }, -]; - -renderTest("Wanted", cases); diff --git a/frontend/src/pages/views/MassEditor.tsx b/frontend/src/pages/views/MassEditor.tsx index eac9dcb94..57221888a 100644 --- a/frontend/src/pages/views/MassEditor.tsx +++ b/frontend/src/pages/views/MassEditor.tsx @@ -57,14 +57,6 @@ function MassEditor(props: MassEditorProps) { ]; }, [profileOptions.options]); - const getKey = useCallback((value: Language.Profile | null) => { - if (value) { - return value.name; - } - - return "Clear"; - }, []); - const { mutateAsync } = mutation; /** @@ -136,7 +128,6 @@ function MassEditor(props: MassEditorProps) { placeholder="Change Profile" withCheckIcon={false} options={profileOptionsWithAction} - getkey={getKey} disabled={selections.length === 0} comboboxProps={{ store: combobox, diff --git a/frontend/src/tests/mocks/node.ts b/frontend/src/tests/mocks/node.ts new file mode 100644 index 000000000..7826431e6 --- /dev/null +++ b/frontend/src/tests/mocks/node.ts @@ -0,0 +1,5 @@ +import { setupServer } from "msw/node"; + +const server = setupServer(); + +export default server; diff --git a/frontend/src/tests/render.tsx b/frontend/src/tests/render.tsx index e0031e903..991c8c9b9 100644 --- a/frontend/src/tests/render.tsx +++ b/frontend/src/tests/render.tsx @@ -4,10 +4,17 @@ import { render } from "."; export interface RenderTestCase { name: string; ui: FunctionComponent; + setupEach?: () => void; } export function renderTest(name: string, cases: RenderTestCase[]) { describe(name, () => { + beforeEach(() => { + cases.forEach((element) => { + element.setupEach?.(); + }); + }); + cases.forEach((element) => { it(`${element.name.toLowerCase()} should render`, () => { render(); diff --git a/frontend/src/tests/setup.ts b/frontend/src/tests/setup.ts deleted file mode 100644 index 0f6df68ba..000000000 --- a/frontend/src/tests/setup.ts +++ /dev/null @@ -1,30 +0,0 @@ -/* eslint-disable @typescript-eslint/no-empty-function */ - -import { vitest } from "vitest"; -import "@testing-library/jest-dom"; - -// From https://stackoverflow.com/questions/39830580/jest-test-fails-typeerror-window-matchmedia-is-not-a-function -Object.defineProperty(window, "matchMedia", { - writable: true, - value: vitest.fn().mockImplementation((query) => ({ - matches: false, - media: query, - onchange: null, - addListener: vitest.fn(), // Deprecated - removeListener: vitest.fn(), // Deprecated - addEventListener: vitest.fn(), - removeEventListener: vitest.fn(), - dispatchEvent: vitest.fn(), - })), -}); - -// From https://github.com/mantinedev/mantine/blob/master/configuration/jest/jsdom.mocks.js -class ResizeObserver { - observe() {} - unobserve() {} - disconnect() {} -} - -window.ResizeObserver = ResizeObserver; - -window.scrollTo = () => {}; diff --git a/frontend/src/tests/setup.tsx b/frontend/src/tests/setup.tsx new file mode 100644 index 000000000..52310b906 --- /dev/null +++ b/frontend/src/tests/setup.tsx @@ -0,0 +1,70 @@ +/* eslint-disable @typescript-eslint/no-empty-function */ + +import { http } from "msw"; +import { HttpResponse } from "msw"; +import { vi, vitest } from "vitest"; +import "@testing-library/jest-dom"; +import queryClient from "@/apis/queries"; +import server from "./mocks/node"; + +vi.mock("recharts", async () => { + const OriginalRechartsModule = await vi.importActual("recharts"); + + return { + ...OriginalRechartsModule, + ResponsiveContainer: ({ children }: { children: React.ReactNode }) => ( +
{children}
+ ), + }; +}); + +// From https://stackoverflow.com/questions/39830580/jest-test-fails-typeerror-window-matchmedia-is-not-a-function +Object.defineProperty(window, "matchMedia", { + writable: true, + value: vitest.fn().mockImplementation((query) => ({ + matches: false, + media: query, + onchange: null, + addListener: vitest.fn(), // Deprecated + removeListener: vitest.fn(), // Deprecated + addEventListener: vitest.fn(), + removeEventListener: vitest.fn(), + dispatchEvent: vitest.fn(), + })), +}); + +// From https://github.com/mantinedev/mantine/blob/master/configuration/jest/jsdom.mocks.js +class ResizeObserver { + observe() {} + unobserve() {} + disconnect() {} +} + +window.ResizeObserver = ResizeObserver; + +window.scrollTo = () => {}; + +beforeAll(() => { + server.listen({ onUnhandledRequest: "error" }); +}); + +beforeEach(() => { + server.resetHandlers(); + server.use( + http.get("/api/system/settings", () => { + return HttpResponse.json({ + general: { + theme: "auto", + }, + }); + }), + ); +}); + +afterEach(() => { + server.resetHandlers(); + + queryClient.clear(); +}); + +afterAll(() => server.close()); diff --git a/frontend/src/utilities/console.ts b/frontend/src/utilities/console.ts index 64da1330c..372207db3 100644 --- a/frontend/src/utilities/console.ts +++ b/frontend/src/utilities/console.ts @@ -6,6 +6,10 @@ import { isProdEnv } from "."; type LoggerType = "info" | "warning" | "error"; export function LOG(type: LoggerType, msg: string, ...payload: any[]) { + if (import.meta.env.MODE === "test") { + return; + } + if (!isProdEnv) { let logger = console.log; if (type === "warning") { diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index fbae83c88..3eecb807d 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -132,7 +132,7 @@ export default defineConfig(async ({ mode, command }) => { test: { globals: true, environment: "jsdom", - setupFiles: "./src/tests/setup.ts", + setupFiles: "./src/tests/setup.tsx", }, server: { proxy: {