diff --git a/web/jest.config.mjs b/web/jest.config.mjs index f17d93364..c0dbc1c1b 100644 --- a/web/jest.config.mjs +++ b/web/jest.config.mjs @@ -178,14 +178,17 @@ export default { // A map from regular expressions to paths to transformers transform: { - '\\.[jt]sx?$': 'babel-jest' - } + '\\.[jt]sx?$': 'babel-jest', + '^.+\\.svelte$': [ + 'svelte-jester', + { + preprocess: true + } + ] + }, // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation - // transformIgnorePatterns: [ - // "/node_modules/", - // "\\.pnp\\.[^\\/]+$" - // ], + transformIgnorePatterns: ['/node_modules/(?!svelte-material-icons).*/', '\\.pnp\\.[^\\/]+$'] // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them // unmockedModulePathPatterns: undefined, diff --git a/web/package-lock.json b/web/package-lock.json index 3c2acb2c2..86f785909 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -25,6 +25,8 @@ "@sveltejs/adapter-auto": "next", "@sveltejs/adapter-node": "next", "@sveltejs/kit": "next", + "@testing-library/jest-dom": "^5.16.5", + "@testing-library/svelte": "^3.2.1", "@types/bcrypt": "^5.0.0", "@types/cookie": "^0.4.1", "@types/fluent-ffmpeg": "^2.1.20", @@ -48,13 +50,19 @@ "svelte": "^3.44.0", "svelte-check": "^2.7.1", "svelte-jester": "^2.3.2", - "svelte-preprocess": "^4.10.6", + "svelte-preprocess": "^4.10.7", "tailwindcss": "^3.0.24", "tslib": "^2.3.1", "typescript": "^4.7.4", "vite": "^3.0.0" } }, + "node_modules/@adobe/css-tools": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.0.1.tgz", + "integrity": "sha512-+u76oB43nOHrF4DDWRLWDCtci7f3QJoEBigemIdIeTi1ODqjx6Tad9NCVnPRwewWlKkVab5PlK8DCtPTyX7S8g==", + "dev": true + }, "node_modules/@ampproject/remapping": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", @@ -2626,6 +2634,107 @@ } } }, + "node_modules/@testing-library/dom": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.17.1.tgz", + "integrity": "sha512-KnH2MnJUzmFNPW6RIKfd+zf2Wue8mEKX0M3cpX6aKl5ZXrJM1/c/Pc8c2xDNYQCnJO48Sm5ITbMXgqTr3h4jxQ==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^4.2.0", + "aria-query": "^5.0.0", + "chalk": "^4.1.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.4.4", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@testing-library/dom/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/dom/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/dom/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/jest-dom": { + "version": "5.16.5", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.16.5.tgz", + "integrity": "sha512-N5ixQ2qKpi5OLYfwQmUb/5mSV9LneAcaUfp32pn4yCnpb8r/Yz0pXFPck21dIicKmi+ta5WRAknkZCfA8refMA==", + "dev": true, + "dependencies": { + "@adobe/css-tools": "^4.0.1", + "@babel/runtime": "^7.9.2", + "@types/testing-library__jest-dom": "^5.9.1", + "aria-query": "^5.0.0", + "chalk": "^3.0.0", + "css.escape": "^1.5.1", + "dom-accessibility-api": "^0.5.6", + "lodash": "^4.17.15", + "redent": "^3.0.0" + }, + "engines": { + "node": ">=8", + "npm": ">=6", + "yarn": ">=1" + } + }, + "node_modules/@testing-library/jest-dom/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@testing-library/svelte": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@testing-library/svelte/-/svelte-3.2.1.tgz", + "integrity": "sha512-qP5nMAx78zt+a3y9Sws9BNQYP30cOQ/LXDYuAj7wNtw86b7AtB7TFAz6/Av9hFsW3IJHPBBIGff6utVNyq+F1g==", + "dev": true, + "dependencies": { + "@testing-library/dom": "^8.1.0" + }, + "engines": { + "node": ">= 10" + }, + "peerDependencies": { + "svelte": "3.x" + } + }, "node_modules/@tootallnate/once": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", @@ -2635,6 +2744,12 @@ "node": ">= 10" } }, + "node_modules/@types/aria-query": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-4.2.2.tgz", + "integrity": "sha512-HnYpAE1Y6kRyKM/XkEuiRQhTHvkzMBurTHnpFLYLBGPIylZNPs9jJcuOOYWxPLJCSEtmZT0Y8rHDokKN7rRTig==", + "dev": true + }, "node_modules/@types/babel__core": { "version": "7.1.19", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.19.tgz", @@ -2739,6 +2854,16 @@ "@types/istanbul-lib-report": "*" } }, + "node_modules/@types/jest": { + "version": "29.0.0", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.0.0.tgz", + "integrity": "sha512-X6Zjz3WO4cT39Gkl0lZ2baFRaEMqJl5NC1OjElkwtNzAlbkr2K/WJXkBkH5VP0zx4Hgsd2TZYdOEfvp2Dxia+Q==", + "dev": true, + "dependencies": { + "expect": "^29.0.0", + "pretty-format": "^29.0.0" + } + }, "node_modules/@types/jsdom": { "version": "20.0.0", "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-20.0.0.tgz", @@ -2823,6 +2948,15 @@ "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", "dev": true }, + "node_modules/@types/testing-library__jest-dom": { + "version": "5.14.5", + "resolved": "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.5.tgz", + "integrity": "sha512-SBwbxYoyPIvxHbeHxTZX2Pe/74F/tX2/D3mMvzabdeJ25bBojfW0TyB8BHrbq/9zaaKICJZjLP+8r6AeZMFCuQ==", + "dev": true, + "dependencies": { + "@types/jest": "*" + } + }, "node_modules/@types/tough-cookie": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.2.tgz", @@ -3260,6 +3394,15 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, + "node_modules/aria-query": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.0.2.tgz", + "integrity": "sha512-eigU3vhqSO+Z8BKDnVLN/ompjhf3pYzecKXz8+whRy+9gZu8n1TCGfwzQUUPnqdHl9ax1Hr9031orZ+UOEYr7Q==", + "dev": true, + "engines": { + "node": ">=6.0" + } + }, "node_modules/array-union": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", @@ -3866,6 +4009,12 @@ "node": ">= 8" } }, + "node_modules/css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", + "dev": true + }, "node_modules/cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", @@ -4133,6 +4282,12 @@ "node": ">=6.0.0" } }, + "node_modules/dom-accessibility-api": { + "version": "0.5.14", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.14.tgz", + "integrity": "sha512-NMt+m9zFMPZe0JcY9gN224Qvk6qLIdqex29clBvc/y75ZBX9YA9wNK3frsYvu2DI1xcCIwxwnX+TlsJ2DSOADg==", + "dev": true + }, "node_modules/domexception": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz", @@ -5601,6 +5756,15 @@ "node": ">=0.8.19" } }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -6660,6 +6824,15 @@ "node": ">=10" } }, + "node_modules/lz-string": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.4.4.tgz", + "integrity": "sha512-0ckx7ZHRPqb0oUm8zNr+90mtf9DQB60H1wMCjBtfi62Kl3a7JbHob6gA2bC+xRvZoOL+1hzUK8jeuEIQE8svEQ==", + "dev": true, + "bin": { + "lz-string": "bin/bin.js" + } + }, "node_modules/magic-string": { "version": "0.26.2", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.26.2.tgz", @@ -7621,6 +7794,19 @@ "node": ">=8.10.0" } }, + "node_modules/redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "dependencies": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -9091,6 +9277,12 @@ } }, "dependencies": { + "@adobe/css-tools": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.0.1.tgz", + "integrity": "sha512-+u76oB43nOHrF4DDWRLWDCtci7f3QJoEBigemIdIeTi1ODqjx6Tad9NCVnPRwewWlKkVab5PlK8DCtPTyX7S8g==", + "dev": true + }, "@ampproject/remapping": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", @@ -10964,12 +11156,97 @@ "svelte-hmr": "^0.14.12" } }, + "@testing-library/dom": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.17.1.tgz", + "integrity": "sha512-KnH2MnJUzmFNPW6RIKfd+zf2Wue8mEKX0M3cpX6aKl5ZXrJM1/c/Pc8c2xDNYQCnJO48Sm5ITbMXgqTr3h4jxQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^4.2.0", + "aria-query": "^5.0.0", + "chalk": "^4.1.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.4.4", + "pretty-format": "^27.0.2" + }, + "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 + }, + "pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + } + }, + "react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true + } + } + }, + "@testing-library/jest-dom": { + "version": "5.16.5", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.16.5.tgz", + "integrity": "sha512-N5ixQ2qKpi5OLYfwQmUb/5mSV9LneAcaUfp32pn4yCnpb8r/Yz0pXFPck21dIicKmi+ta5WRAknkZCfA8refMA==", + "dev": true, + "requires": { + "@adobe/css-tools": "^4.0.1", + "@babel/runtime": "^7.9.2", + "@types/testing-library__jest-dom": "^5.9.1", + "aria-query": "^5.0.0", + "chalk": "^3.0.0", + "css.escape": "^1.5.1", + "dom-accessibility-api": "^0.5.6", + "lodash": "^4.17.15", + "redent": "^3.0.0" + }, + "dependencies": { + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + } + } + }, + "@testing-library/svelte": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@testing-library/svelte/-/svelte-3.2.1.tgz", + "integrity": "sha512-qP5nMAx78zt+a3y9Sws9BNQYP30cOQ/LXDYuAj7wNtw86b7AtB7TFAz6/Av9hFsW3IJHPBBIGff6utVNyq+F1g==", + "dev": true, + "requires": { + "@testing-library/dom": "^8.1.0" + } + }, "@tootallnate/once": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", "dev": true }, + "@types/aria-query": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-4.2.2.tgz", + "integrity": "sha512-HnYpAE1Y6kRyKM/XkEuiRQhTHvkzMBurTHnpFLYLBGPIylZNPs9jJcuOOYWxPLJCSEtmZT0Y8rHDokKN7rRTig==", + "dev": true + }, "@types/babel__core": { "version": "7.1.19", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.19.tgz", @@ -11074,6 +11351,16 @@ "@types/istanbul-lib-report": "*" } }, + "@types/jest": { + "version": "29.0.0", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.0.0.tgz", + "integrity": "sha512-X6Zjz3WO4cT39Gkl0lZ2baFRaEMqJl5NC1OjElkwtNzAlbkr2K/WJXkBkH5VP0zx4Hgsd2TZYdOEfvp2Dxia+Q==", + "dev": true, + "requires": { + "expect": "^29.0.0", + "pretty-format": "^29.0.0" + } + }, "@types/jsdom": { "version": "20.0.0", "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-20.0.0.tgz", @@ -11157,6 +11444,15 @@ "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", "dev": true }, + "@types/testing-library__jest-dom": { + "version": "5.14.5", + "resolved": "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.5.tgz", + "integrity": "sha512-SBwbxYoyPIvxHbeHxTZX2Pe/74F/tX2/D3mMvzabdeJ25bBojfW0TyB8BHrbq/9zaaKICJZjLP+8r6AeZMFCuQ==", + "dev": true, + "requires": { + "@types/jest": "*" + } + }, "@types/tough-cookie": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.2.tgz", @@ -11451,6 +11747,12 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, + "aria-query": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.0.2.tgz", + "integrity": "sha512-eigU3vhqSO+Z8BKDnVLN/ompjhf3pYzecKXz8+whRy+9gZu8n1TCGfwzQUUPnqdHl9ax1Hr9031orZ+UOEYr7Q==", + "dev": true + }, "array-union": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", @@ -11908,6 +12210,12 @@ "which": "^2.0.1" } }, + "css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", + "dev": true + }, "cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", @@ -12111,6 +12419,12 @@ "esutils": "^2.0.2" } }, + "dom-accessibility-api": { + "version": "0.5.14", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.14.tgz", + "integrity": "sha512-NMt+m9zFMPZe0JcY9gN224Qvk6qLIdqex29clBvc/y75ZBX9YA9wNK3frsYvu2DI1xcCIwxwnX+TlsJ2DSOADg==", + "dev": true + }, "domexception": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz", @@ -13102,6 +13416,12 @@ "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true }, + "indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true + }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -13909,6 +14229,12 @@ "yallist": "^4.0.0" } }, + "lz-string": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.4.4.tgz", + "integrity": "sha512-0ckx7ZHRPqb0oUm8zNr+90mtf9DQB60H1wMCjBtfi62Kl3a7JbHob6gA2bC+xRvZoOL+1hzUK8jeuEIQE8svEQ==", + "dev": true + }, "magic-string": { "version": "0.26.2", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.26.2.tgz", @@ -14562,6 +14888,16 @@ "picomatch": "^2.2.1" } }, + "redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "requires": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + } + }, "regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", diff --git a/web/package.json b/web/package.json index ff6063db6..63631834b 100644 --- a/web/package.json +++ b/web/package.json @@ -20,6 +20,8 @@ "@sveltejs/adapter-auto": "next", "@sveltejs/adapter-node": "next", "@sveltejs/kit": "next", + "@testing-library/jest-dom": "^5.16.5", + "@testing-library/svelte": "^3.2.1", "@types/bcrypt": "^5.0.0", "@types/cookie": "^0.4.1", "@types/fluent-ffmpeg": "^2.1.20", @@ -43,7 +45,7 @@ "svelte": "^3.44.0", "svelte-check": "^2.7.1", "svelte-jester": "^2.3.2", - "svelte-preprocess": "^4.10.6", + "svelte-preprocess": "^4.10.7", "tailwindcss": "^3.0.24", "tslib": "^2.3.1", "typescript": "^4.7.4", diff --git a/web/src/lib/__mocks__/jsdom-url.mock.ts b/web/src/lib/__mocks__/jsdom-url.mock.ts new file mode 100644 index 000000000..b915c8438 --- /dev/null +++ b/web/src/lib/__mocks__/jsdom-url.mock.ts @@ -0,0 +1,8 @@ +const createObjectURLMock = jest.fn(); + +Object.defineProperty(URL, 'createObjectURL', { + writable: true, + value: createObjectURLMock +}); + +export { createObjectURLMock }; diff --git a/web/src/lib/components/album-page/__tests__/album-card.spec.ts b/web/src/lib/components/album-page/__tests__/album-card.spec.ts new file mode 100644 index 000000000..ff89bb55d --- /dev/null +++ b/web/src/lib/components/album-page/__tests__/album-card.spec.ts @@ -0,0 +1,144 @@ +import { jest, describe, it } from '@jest/globals'; +import { render, RenderResult, waitFor, fireEvent } from '@testing-library/svelte'; +import { createObjectURLMock } from '$lib/__mocks__/jsdom-url.mock'; +import { api, ThumbnailFormat } from '@api'; +import { albumFactory } from '@test-data'; +import AlbumCard from '../album-card.svelte'; +import '@testing-library/jest-dom'; + +jest.mock('@api'); + +const apiMock: jest.MockedObject = api as jest.MockedObject; + +describe('AlbumCard component', () => { + let sut: RenderResult; + + it.each([ + { + album: albumFactory.build({ albumThumbnailAssetId: null, shared: false, assetCount: 0 }), + count: 0, + shared: false + }, + { + album: albumFactory.build({ albumThumbnailAssetId: null, shared: true, assetCount: 0 }), + count: 0, + shared: true + }, + { + album: albumFactory.build({ albumThumbnailAssetId: null, shared: false, assetCount: 5 }), + count: 5, + shared: false + }, + { + album: albumFactory.build({ albumThumbnailAssetId: null, shared: true, assetCount: 2 }), + count: 2, + shared: true + } + ])( + 'shows album data without thumbnail with count $count - shared: $shared', + async ({ album, count, shared }) => { + sut = render(AlbumCard, { album }); + + const albumImgElement = sut.getByTestId('album-image'); + const albumNameElement = sut.getByTestId('album-name'); + const albumDetailsElement = sut.getByTestId('album-details'); + const detailsText = `${count} items` + (shared ? ' . Shared' : ''); + // TODO: is this a bug? + expect(albumImgElement).toHaveAttribute('src', '/api/asset/thumbnail/null?format=WEBP'); + expect(albumImgElement).toHaveAttribute('alt', album.id); + + await waitFor(() => expect(albumImgElement).toHaveAttribute('src', 'no-thumbnail.png')); + + expect(albumImgElement).toHaveAttribute('alt', album.id); + expect(apiMock.assetApi.getAssetThumbnail).not.toHaveBeenCalled(); + + expect(albumNameElement).toHaveTextContent(album.albumName); + expect(albumDetailsElement).toHaveTextContent(new RegExp(detailsText)); + } + ); + + it('shows album data and and loads the thumbnail image when available', async () => { + const thumbnailBlob = new Blob(); + const thumbnailUrl = 'blob:thumbnailUrlOne'; + apiMock.assetApi.getAssetThumbnail.mockResolvedValue({ + data: thumbnailBlob, + config: {}, + headers: {}, + status: 200, + statusText: '' + }); + createObjectURLMock.mockReturnValueOnce(thumbnailUrl); + + const album = albumFactory.build({ + albumThumbnailAssetId: 'thumbnailIdOne', + shared: false, + albumName: 'some album name' + }); + sut = render(AlbumCard, { album }); + + const albumImgElement = sut.getByTestId('album-image'); + const albumNameElement = sut.getByTestId('album-name'); + const albumDetailsElement = sut.getByTestId('album-details'); + // TODO: is this expected? + expect(albumImgElement).toHaveAttribute( + 'src', + '/api/asset/thumbnail/thumbnailIdOne?format=WEBP' + ); + expect(albumImgElement).toHaveAttribute('alt', album.id); + + await waitFor(() => expect(albumImgElement).toHaveAttribute('src', thumbnailUrl)); + + expect(albumImgElement).toHaveAttribute('alt', album.id); + expect(apiMock.assetApi.getAssetThumbnail).toHaveBeenCalledTimes(1); + expect(apiMock.assetApi.getAssetThumbnail).toHaveBeenCalledWith( + 'thumbnailIdOne', + ThumbnailFormat.Jpeg, + { responseType: 'blob' } + ); + expect(createObjectURLMock).toHaveBeenCalledWith(thumbnailBlob); + + expect(albumNameElement).toHaveTextContent('some album name'); + expect(albumDetailsElement).toHaveTextContent('0 items'); + }); + + describe('with rendered component - no thumbnail', () => { + const album = Object.freeze(albumFactory.build({ albumThumbnailAssetId: null })); + + beforeEach(async () => { + sut = render(AlbumCard, { album }); + + const albumImgElement = sut.getByTestId('album-image'); + await waitFor(() => expect(albumImgElement).toHaveAttribute('src', 'no-thumbnail.png')); + }); + + it('dispatches custom "click" event with the album in context', async () => { + const onClickHandler = jest.fn(); + sut.component.$on('click', onClickHandler); + const albumCardElement = sut.getByTestId('album-card'); + + await fireEvent.click(albumCardElement); + expect(onClickHandler).toHaveBeenCalledTimes(1); + expect(onClickHandler).toHaveBeenCalledWith(expect.objectContaining({ detail: album })); + }); + + it('dispatches custom "click" event on context menu click with mouse coordinates', async () => { + const onClickHandler = jest.fn(); + sut.component.$on('showalbumcontextmenu', onClickHandler); + + const contextMenuBtnParent = sut.getByTestId('context-button-parent'); + + await fireEvent( + contextMenuBtnParent, + new MouseEvent('click', { + clientX: 123, + clientY: 456 + }) + ); + + expect(onClickHandler).toHaveBeenCalledTimes(1); + expect(onClickHandler).toHaveBeenCalledWith( + expect.objectContaining({ detail: { x: 123, y: 456 } }) + ); + }); + }); +}); diff --git a/web/src/lib/components/album-page/album-card.svelte b/web/src/lib/components/album-page/album-card.svelte index 5665e6b9b..62c762459 100644 --- a/web/src/lib/components/album-page/album-card.svelte +++ b/web/src/lib/components/album-page/album-card.svelte @@ -53,11 +53,13 @@
dispatchClick('click', album)} + data-testid="album-card" >
-

+

{album.albumName}

- +

{album.assetCount} items

{#if album.shared}