diff --git a/package-lock.json b/package-lock.json index a9b42c3119552e9ab948df2ed7ba8cafe9b5fe9e..88d57bd03bdde17aecd6e8981798af7a0cf3879e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,6 +22,8 @@ }, "devDependencies": { "@rushstack/eslint-patch": "^1.8.0", + "@testing-library/user-event": "^14.5.2", + "@testing-library/vue": "^8.0.3", "@tsconfig/node20": "^20.1.4", "@types/jsdom": "^21.1.6", "@types/node": "^20.12.5", @@ -41,9 +43,11 @@ "prettier": "^3.2.5", "start-server-and-test": "^2.0.3", "typescript": "~5.4.0", + "user-event": "^4.0.0", "vite": "^5.2.8", "vite-plugin-vue-devtools": "^7.0.25", "vitest": "^1.5.0", + "vue-router-mock": "^1.1.0", "vue-tsc": "^2.0.11" } }, @@ -521,6 +525,18 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/runtime": { + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.4.tgz", + "integrity": "sha512-dkxf7+hn8mFBwKjs9bvBlArzLVxVbS8usaPUDd5p2a9JCL9tB8OaOVN1isD4+Xyk4ns89/xeOmbQvgdK7IIVdA==", + "dev": true, + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/template": { "version": "7.24.0", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.0.tgz", @@ -1653,12 +1669,175 @@ "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", "dev": true }, + "node_modules/@testing-library/dom": { + "version": "9.3.4", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-9.3.4.tgz", + "integrity": "sha512-FlS4ZWlp97iiNWig0Muq8p+3rVDjRiYE+YKGbAqXOu9nwJFFOdL00kFpz42M+4huzYi86vAK1sOOfyOG45muIQ==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.1.3", + "chalk": "^4.1.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@testing-library/dom/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@testing-library/dom/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@testing-library/dom/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@testing-library/dom/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/@testing-library/dom/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@testing-library/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/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@testing-library/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/dom/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@testing-library/user-event": { + "version": "14.5.2", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.5.2.tgz", + "integrity": "sha512-YAh82Wh4TIrxYLmfGcixwD18oIjyC1pFQC2Y01F2lzV2HTMiYrI0nze0FD0ocB//CKS/7jIUgae+adPqxK5yCQ==", + "dev": true, + "engines": { + "node": ">=12", + "npm": ">=6" + }, + "peerDependencies": { + "@testing-library/dom": ">=7.21.4" + } + }, + "node_modules/@testing-library/vue": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/@testing-library/vue/-/vue-8.0.3.tgz", + "integrity": "sha512-wSsbNlZ69ZFQgVlHMtc/ZC/g9BHO7MhyDrd4nHyfEubtMr3kToN/w4/BsSBknGIF8w9UmPbsgbIuq/CbdBHzCA==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.23.2", + "@testing-library/dom": "^9.3.3", + "@vue/test-utils": "^2.4.1" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@vue/compiler-sfc": ">= 3", + "vue": ">= 3" + }, + "peerDependenciesMeta": { + "@vue/compiler-sfc": { + "optional": true + } + } + }, "node_modules/@tsconfig/node20": { "version": "20.1.4", "resolved": "https://registry.npmjs.org/@tsconfig/node20/-/node20-20.1.4.tgz", "integrity": "sha512-sqgsT69YFeLWf5NtJ4Xq/xAF8p4ZQHlmGW74Nu2tD4+g5fAsposc4ZfaaPixVu4y01BEiDCWLRDCvDM5JOsRxg==", "dev": true }, + "node_modules/@types/aria-query": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", + "dev": true + }, "node_modules/@types/estree": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", @@ -2652,6 +2831,31 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, + "node_modules/aria-query": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz", + "integrity": "sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==", + "dev": true, + "dependencies": { + "deep-equal": "^2.0.5" + } + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", + "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/array-union": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", @@ -2717,6 +2921,21 @@ "node": ">= 4.0.0" } }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/aws-sign2": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", @@ -3516,6 +3735,38 @@ "node": ">=6" } }, + "node_modules/deep-equal": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.3.tgz", + "integrity": "sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.0", + "call-bind": "^1.0.5", + "es-get-iterator": "^1.1.3", + "get-intrinsic": "^1.2.2", + "is-arguments": "^1.1.1", + "is-array-buffer": "^3.0.2", + "is-date-object": "^1.0.5", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "isarray": "^2.0.5", + "object-is": "^1.1.5", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.5.1", + "side-channel": "^1.0.4", + "which-boxed-primitive": "^1.0.2", + "which-collection": "^1.0.1", + "which-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -3579,6 +3830,23 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -3620,6 +3888,12 @@ "node": ">=6.0.0" } }, + "node_modules/dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", + "dev": true + }, "node_modules/duplexer": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", @@ -3792,6 +4066,26 @@ "node": ">= 0.4" } }, + "node_modules/es-get-iterator": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", + "integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "has-symbols": "^1.0.3", + "is-arguments": "^1.1.1", + "is-map": "^2.0.2", + "is-set": "^2.0.2", + "is-string": "^1.0.7", + "isarray": "^2.0.5", + "stop-iteration-iterator": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/esbuild": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz", @@ -4544,6 +4838,15 @@ } } }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.3" + } + }, "node_modules/foreground-child": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", @@ -4645,6 +4948,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -4817,6 +5129,15 @@ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true }, + "node_modules/has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -4862,6 +5183,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", @@ -5076,6 +5412,92 @@ "node": ">= 0.10" } }, + "node_modules/internal-slot": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", + "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.0", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-arguments": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", + "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", + "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-ci": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz", @@ -5088,6 +5510,21 @@ "is-ci": "bin.js" } }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-docker": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", @@ -5167,6 +5604,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -5176,6 +5625,21 @@ "node": ">=0.12.0" } }, + "node_modules/is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-path-inside": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", @@ -5191,6 +5655,49 @@ "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", "dev": true }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz", + "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", @@ -5203,6 +5710,36 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", @@ -5221,6 +5758,34 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.3.tgz", + "integrity": "sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-wsl": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", @@ -5236,6 +5801,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -5911,6 +6482,15 @@ "yallist": "^3.0.2" } }, + "node_modules/lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "dev": true, + "bin": { + "lz-string": "bin/bin.js" + } + }, "node_modules/magic-string": { "version": "0.30.9", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.9.tgz", @@ -6253,6 +6833,49 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/object-is": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz", + "integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", + "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/oh-vue-icons": { "version": "1.0.0-rc3", "resolved": "https://registry.npmjs.org/oh-vue-icons/-/oh-vue-icons-1.0.0-rc3.tgz", @@ -6680,6 +7303,15 @@ "pathe": "^1.1.0" } }, + "node_modules/possible-typed-array-names": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", + "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/postcss": { "version": "8.4.38", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", @@ -6915,6 +7547,30 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "dev": true + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", + "integrity": "sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.6", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "set-function-name": "^2.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/request-progress": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/request-progress/-/request-progress-3.0.0.tgz", @@ -7179,6 +7835,21 @@ "node": ">= 0.4" } }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -7443,6 +8114,18 @@ "integrity": "sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==", "dev": true }, + "node_modules/stop-iteration-iterator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz", + "integrity": "sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==", + "dev": true, + "dependencies": { + "internal-slot": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/stream-combiner": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz", @@ -7922,6 +8605,13 @@ "requires-port": "^1.0.0" } }, + "node_modules/user-event": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/user-event/-/user-event-4.0.0.tgz", + "integrity": "sha512-M2at0vzLqzrwZNBmtPDRyd+1BaRwU9UTG7sc+MrUZmGviR/Ws8tmXxVvfRvuv7TWWIDsLqbrMvoF1sF7DW4y5w==", + "deprecated": "user-event has moved to @testing-library/user-event. Please uninstall user-event and install @testing-library/user-event instead, or use an older version of user-event. Learn more about this change here: https://github.com/testing-library/dom-testing-library/issues/260 Thanks! :)", + "dev": true + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -8556,6 +9246,16 @@ "vue": "^3.2.0" } }, + "node_modules/vue-router-mock": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/vue-router-mock/-/vue-router-mock-1.1.0.tgz", + "integrity": "sha512-RhKhxkiZh2zB2eRkzfcCILQQ0ZUc0tk7CE2ZC1PGJYi5GOU+2QQAGHtTCgb8V4B/OPm9ws+X5Q9SQB5vyTXxBQ==", + "dev": true, + "peerDependencies": { + "vue": "^3.2.23", + "vue-router": "^4.0.12" + } + }, "node_modules/vue-template-compiler": { "version": "2.7.16", "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.7.16.tgz", @@ -8714,6 +9414,59 @@ "node": ">= 8" } }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dev": true, + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.15.tgz", + "integrity": "sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/why-is-node-running": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.2.2.tgz", diff --git a/package.json b/package.json index 88bf81d13c99ce997982fd7b8147f70b8cade92e..107df03de864ad4cb836307fd03df0cbf981c172 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,8 @@ }, "devDependencies": { "@rushstack/eslint-patch": "^1.8.0", + "@testing-library/user-event": "^14.5.2", + "@testing-library/vue": "^8.0.3", "@tsconfig/node20": "^20.1.4", "@types/jsdom": "^21.1.6", "@types/node": "^20.12.5", @@ -49,9 +51,11 @@ "prettier": "^3.2.5", "start-server-and-test": "^2.0.3", "typescript": "~5.4.0", + "user-event": "^4.0.0", "vite": "^5.2.8", "vite-plugin-vue-devtools": "^7.0.25", "vitest": "^1.5.0", + "vue-router-mock": "^1.1.0", "vue-tsc": "^2.0.11" } } diff --git a/src/api/index.ts b/src/api/index.ts index 952a64c78269b8003d0635a30820fdd15932e689..b63858683d0644088eaa29f307035f0f4d44ef0b 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -7,17 +7,35 @@ export { CancelablePromise, CancelError } from './core/CancelablePromise'; export { OpenAPI } from './core/OpenAPI'; export type { OpenAPIConfig } from './core/OpenAPI'; +export type { Account } from './models/Account'; +export type { AccountRequestDTO } from './models/AccountRequestDTO'; +export type { AccountResponseDTO } from './models/AccountResponseDTO'; export type { AuthenticationResponse } from './models/AuthenticationResponse'; +export type { BankProfile } from './models/BankProfile'; +export type { BankProfileDTO } from './models/BankProfileDTO'; +export type { BankProfileResponseDTO } from './models/BankProfileResponseDTO'; +export type { ChallengeDTO } from './models/ChallengeDTO'; +export type { ConfigurationDTO } from './models/ConfigurationDTO'; +export type { CreateGoalDTO } from './models/CreateGoalDTO'; +export type { DailyChallengeProgressDTO } from './models/DailyChallengeProgressDTO'; export type { ExceptionResponse } from './models/ExceptionResponse'; +export type { GoalDTO } from './models/GoalDTO'; export type { LeaderboardDTO } from './models/LeaderboardDTO'; export type { LeaderboardEntryDTO } from './models/LeaderboardEntryDTO'; export type { LoginRequest } from './models/LoginRequest'; +export { ParticipantDTO } from './models/ParticipantDTO'; +export type { ParticipantUserDTO } from './models/ParticipantUserDTO'; export type { PasswordResetDTO } from './models/PasswordResetDTO'; export type { ProfileDTO } from './models/ProfileDTO'; export type { SignUpRequest } from './models/SignUpRequest'; +export type { TransactionDTO } from './models/TransactionDTO'; export type { UserDTO } from './models/UserDTO'; export type { UserUpdateDTO } from './models/UserUpdateDTO'; +export { AccountControllerService } from './services/AccountControllerService'; export { AuthenticationService } from './services/AuthenticationService'; +export { BankProfileControllerService } from './services/BankProfileControllerService'; +export { GoalService } from './services/GoalService'; export { LeaderboardService } from './services/LeaderboardService'; +export { TransactionControllerService } from './services/TransactionControllerService'; export { UserService } from './services/UserService'; diff --git a/src/api/models/Account.ts b/src/api/models/Account.ts new file mode 100644 index 0000000000000000000000000000000000000000..fb3a63221139e5c22defbd906d3017d6286435ab --- /dev/null +++ b/src/api/models/Account.ts @@ -0,0 +1,11 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { BankProfile } from './BankProfile'; +export type Account = { + bban?: number; + balance?: number; + bankProfile?: BankProfile; +}; + diff --git a/src/api/models/AccountRequestDTO.ts b/src/api/models/AccountRequestDTO.ts new file mode 100644 index 0000000000000000000000000000000000000000..c2f7ddd34fb91f531ac004c517213fcd31dd2c0d --- /dev/null +++ b/src/api/models/AccountRequestDTO.ts @@ -0,0 +1,8 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export type AccountRequestDTO = { + ssn?: number; +}; + diff --git a/src/api/models/AccountResponseDTO.ts b/src/api/models/AccountResponseDTO.ts new file mode 100644 index 0000000000000000000000000000000000000000..276a2131b75b4c6dd96beb6eae76e448f0a07bb6 --- /dev/null +++ b/src/api/models/AccountResponseDTO.ts @@ -0,0 +1,9 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export type AccountResponseDTO = { + bankProfileId?: number; + balance?: number; +}; + diff --git a/src/api/models/BankProfile.ts b/src/api/models/BankProfile.ts new file mode 100644 index 0000000000000000000000000000000000000000..b4fe99e6921234a056658b64a4b0ea6ec5e33f69 --- /dev/null +++ b/src/api/models/BankProfile.ts @@ -0,0 +1,11 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { Account } from './Account'; +export type BankProfile = { + id?: number; + ssn?: number; + accounts?: Array<Account>; +}; + diff --git a/src/api/models/BankProfileDTO.ts b/src/api/models/BankProfileDTO.ts new file mode 100644 index 0000000000000000000000000000000000000000..e6b1b89cb3c01a69bb0276e169f93d7e5787c5a7 --- /dev/null +++ b/src/api/models/BankProfileDTO.ts @@ -0,0 +1,8 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export type BankProfileDTO = { + ssn?: number; +}; + diff --git a/src/api/models/BankProfileResponseDTO.ts b/src/api/models/BankProfileResponseDTO.ts new file mode 100644 index 0000000000000000000000000000000000000000..3ef2360b3463e66b704ff1b587f7598b4efba5dd --- /dev/null +++ b/src/api/models/BankProfileResponseDTO.ts @@ -0,0 +1,10 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { Account } from './Account'; +export type BankProfileResponseDTO = { + ssn?: number; + accounts?: Array<Account>; +}; + diff --git a/src/api/models/ChallengeDTO.ts b/src/api/models/ChallengeDTO.ts new file mode 100644 index 0000000000000000000000000000000000000000..f5bd38d483c363d921274e9ed77dbd5e6d4aef75 --- /dev/null +++ b/src/api/models/ChallengeDTO.ts @@ -0,0 +1,14 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { DailyChallengeProgressDTO } from './DailyChallengeProgressDTO'; +export type ChallengeDTO = { + id?: number; + potentialSavingAmount?: number; + points?: number; + days?: number; + createdAt?: string; + dailyChallengeProgressList?: Array<DailyChallengeProgressDTO>; +}; + diff --git a/src/api/models/ConfigurationDTO.ts b/src/api/models/ConfigurationDTO.ts new file mode 100644 index 0000000000000000000000000000000000000000..4d4b45261998b7595857854df32b24269b026d4f --- /dev/null +++ b/src/api/models/ConfigurationDTO.ts @@ -0,0 +1,10 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export type ConfigurationDTO = { + commitment?: string; + experience?: string; + challengeTypes?: Array<string>; +}; + diff --git a/src/api/models/CreateGoalDTO.ts b/src/api/models/CreateGoalDTO.ts new file mode 100644 index 0000000000000000000000000000000000000000..61eb0457b42b2aebf8be3c560bcd62d2346648e4 --- /dev/null +++ b/src/api/models/CreateGoalDTO.ts @@ -0,0 +1,11 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export type CreateGoalDTO = { + goalName?: string; + description?: string; + targetAmount?: number; + targetDate?: string; +}; + diff --git a/src/api/models/DailyChallengeProgressDTO.ts b/src/api/models/DailyChallengeProgressDTO.ts new file mode 100644 index 0000000000000000000000000000000000000000..c7bda736cefef3aebff0533272807cde205e16ee --- /dev/null +++ b/src/api/models/DailyChallengeProgressDTO.ts @@ -0,0 +1,10 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export type DailyChallengeProgressDTO = { + id?: number; + challengeDay?: number; + completedAt?: string; +}; + diff --git a/src/api/models/GoalDTO.ts b/src/api/models/GoalDTO.ts new file mode 100644 index 0000000000000000000000000000000000000000..004eb49fd0bec010a1d4227bac6ec6bbc3daea50 --- /dev/null +++ b/src/api/models/GoalDTO.ts @@ -0,0 +1,18 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { ChallengeDTO } from './ChallengeDTO'; +import type { ParticipantDTO } from './ParticipantDTO'; +export type GoalDTO = { + id?: number; + goalName?: string; + description?: string; + targetAmount?: number; + targetDate?: string; + completedAt?: string; + createdAt?: string; + challenges?: Array<ChallengeDTO>; + participants?: Array<ParticipantDTO>; +}; + diff --git a/src/api/models/ParticipantDTO.ts b/src/api/models/ParticipantDTO.ts new file mode 100644 index 0000000000000000000000000000000000000000..0615b50834924e46416818dffbb00718b3607bab --- /dev/null +++ b/src/api/models/ParticipantDTO.ts @@ -0,0 +1,16 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { ParticipantUserDTO } from './ParticipantUserDTO'; +export type ParticipantDTO = { + role?: ParticipantDTO.role; + user?: ParticipantUserDTO; +}; +export namespace ParticipantDTO { + export enum role { + CREATOR = 'CREATOR', + CONTRIBUTOR = 'CONTRIBUTOR', + } +} + diff --git a/src/api/models/ParticipantUserDTO.ts b/src/api/models/ParticipantUserDTO.ts new file mode 100644 index 0000000000000000000000000000000000000000..5499c09f765c2b01e58efea75581a8f14d3f9e40 --- /dev/null +++ b/src/api/models/ParticipantUserDTO.ts @@ -0,0 +1,9 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export type ParticipantUserDTO = { + firstName?: string; + lastName?: string; +}; + diff --git a/src/api/models/PasswordResetDTO.ts b/src/api/models/PasswordResetDTO.ts new file mode 100644 index 0000000000000000000000000000000000000000..383b7ad1f57b7f48dc891ec0f3f2a86a28d03f29 --- /dev/null +++ b/src/api/models/PasswordResetDTO.ts @@ -0,0 +1,9 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export type PasswordResetDTO = { + token: string; + password?: string; +}; + diff --git a/src/api/models/SignUpRequest.ts b/src/api/models/SignUpRequest.ts index f9aa1f6162784c19e4012289fd61c429edf6eea7..72720c40f1f0d870e4f713aaa2215dbd3b59f914 100644 --- a/src/api/models/SignUpRequest.ts +++ b/src/api/models/SignUpRequest.ts @@ -2,13 +2,12 @@ /* istanbul ignore file */ /* tslint:disable */ /* eslint-disable */ +import type { ConfigurationDTO } from './ConfigurationDTO'; export type SignUpRequest = { firstName?: string; lastName?: string; email?: string; password?: string; - commitment?: string; - experience?: string; - challengeTypes?: Array<string>; + configuration: ConfigurationDTO; }; diff --git a/src/api/models/TransactionDTO.ts b/src/api/models/TransactionDTO.ts new file mode 100644 index 0000000000000000000000000000000000000000..d974cb53a8a01cb4c99043e2cb3a983a608d295f --- /dev/null +++ b/src/api/models/TransactionDTO.ts @@ -0,0 +1,10 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export type TransactionDTO = { + debtorBBAN?: number; + creditorBBAN?: number; + amount?: number; +}; + diff --git a/src/api/models/UserUpdateDTO.ts b/src/api/models/UserUpdateDTO.ts index 54cc0f5524b3ca515f3b671e02edf92ec05a54aa..00b3b0a05950d06f960971ef120d217a33202b39 100644 --- a/src/api/models/UserUpdateDTO.ts +++ b/src/api/models/UserUpdateDTO.ts @@ -2,13 +2,12 @@ /* istanbul ignore file */ /* tslint:disable */ /* eslint-disable */ +import type { ConfigurationDTO } from './ConfigurationDTO'; export type UserUpdateDTO = { firstName?: string; lastName?: string; email?: string; password?: string; - commitment?: string; - experience?: string; - challengeTypes?: Array<string>; + configuration?: ConfigurationDTO; }; diff --git a/src/api/services/AccountControllerService.ts b/src/api/services/AccountControllerService.ts new file mode 100644 index 0000000000000000000000000000000000000000..181460cbe891524b1508866a63130c5546ab56ee --- /dev/null +++ b/src/api/services/AccountControllerService.ts @@ -0,0 +1,77 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { Account } from '../models/Account'; +import type { AccountRequestDTO } from '../models/AccountRequestDTO'; +import type { AccountResponseDTO } from '../models/AccountResponseDTO'; +import type { CancelablePromise } from '../core/CancelablePromise'; +import { OpenAPI } from '../core/OpenAPI'; +import { request as __request } from '../core/request'; +export class AccountControllerService { + /** + * Create account + * Create account with random balance + * @returns AccountResponseDTO Successfully created account + * @throws ApiError + */ + public static createAccount({ + requestBody, + }: { + requestBody: AccountRequestDTO, + }): CancelablePromise<AccountResponseDTO> { + return __request(OpenAPI, { + method: 'POST', + url: '/bank/v1/account/create-account', + body: requestBody, + mediaType: 'application/json', + errors: { + 404: `Provided bank profile id could not be found`, + }, + }); + } + /** + * Get user accounts + * Get accounts associated with a user by providing their social security number + * @returns Account No accounts associated with a bank user + * @throws ApiError + */ + public static getAccountsBySsn({ + ssn, + }: { + ssn: number, + }): CancelablePromise<Array<Account>> { + return __request(OpenAPI, { + method: 'GET', + url: '/bank/v1/account/accounts/ssn/{ssn}', + path: { + 'ssn': ssn, + }, + errors: { + 404: `Social security number does not exist`, + }, + }); + } + /** + * Get user accounts + * Get accounts associated with a user by providing their bank profile id + * @returns Account No accounts associated with a bank user + * @throws ApiError + */ + public static getAccounts({ + bankProfileId, + }: { + bankProfileId: number, + }): CancelablePromise<Array<Account>> { + return __request(OpenAPI, { + method: 'GET', + url: '/bank/v1/account/accounts/profile/{bankProfileId}', + path: { + 'bankProfileId': bankProfileId, + }, + errors: { + 404: `Bank profile id does not exist`, + }, + }); + } +} diff --git a/src/api/services/BankProfileControllerService.ts b/src/api/services/BankProfileControllerService.ts new file mode 100644 index 0000000000000000000000000000000000000000..5ae9fa35124c975f752afa4228fccdb1d2bd68b3 --- /dev/null +++ b/src/api/services/BankProfileControllerService.ts @@ -0,0 +1,32 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { BankProfileDTO } from '../models/BankProfileDTO'; +import type { BankProfileResponseDTO } from '../models/BankProfileResponseDTO'; +import type { CancelablePromise } from '../core/CancelablePromise'; +import { OpenAPI } from '../core/OpenAPI'; +import { request as __request } from '../core/request'; +export class BankProfileControllerService { + /** + * Create bank profile + * Create a bank profile by providing a social security number + * @returns BankProfileResponseDTO Successfully created a bank profile + * @throws ApiError + */ + public static createBankProfile({ + requestBody, + }: { + requestBody: BankProfileDTO, + }): CancelablePromise<BankProfileResponseDTO> { + return __request(OpenAPI, { + method: 'POST', + url: '/bank/v1/profile/create-profile', + body: requestBody, + mediaType: 'application/json', + errors: { + 400: `Could not create profile`, + }, + }); + } +} diff --git a/src/api/services/GoalService.ts b/src/api/services/GoalService.ts new file mode 100644 index 0000000000000000000000000000000000000000..28ae857fcaf1e5268dc6aefc5ba9c9e0d27398f2 --- /dev/null +++ b/src/api/services/GoalService.ts @@ -0,0 +1,47 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { CreateGoalDTO } from '../models/CreateGoalDTO'; +import type { GoalDTO } from '../models/GoalDTO'; +import type { CancelablePromise } from '../core/CancelablePromise'; +import { OpenAPI } from '../core/OpenAPI'; +import { request as __request } from '../core/request'; +export class GoalService { + /** + * @returns GoalDTO OK + * @throws ApiError + */ + public static createGoal({ + requestBody, + }: { + requestBody: CreateGoalDTO, + }): CancelablePromise<GoalDTO> { + return __request(OpenAPI, { + method: 'POST', + url: '/api/goal/createGoal', + body: requestBody, + mediaType: 'application/json', + }); + } + /** + * @returns GoalDTO OK + * @throws ApiError + */ + public static getGoals(): CancelablePromise<Array<GoalDTO>> { + return __request(OpenAPI, { + method: 'GET', + url: '/api/goal/getGoals', + }); + } + /** + * @returns GoalDTO OK + * @throws ApiError + */ + public static getGoal(): CancelablePromise<GoalDTO> { + return __request(OpenAPI, { + method: 'GET', + url: '/api/goal/getGoal', + }); + } +} diff --git a/src/api/services/TransactionControllerService.ts b/src/api/services/TransactionControllerService.ts new file mode 100644 index 0000000000000000000000000000000000000000..f584d0ae03eb7882c73c6e0940575f0c009bd327 --- /dev/null +++ b/src/api/services/TransactionControllerService.ts @@ -0,0 +1,31 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { TransactionDTO } from '../models/TransactionDTO'; +import type { CancelablePromise } from '../core/CancelablePromise'; +import { OpenAPI } from '../core/OpenAPI'; +import { request as __request } from '../core/request'; +export class TransactionControllerService { + /** + * Transfer to account + * Transfer money from a users account to another account of the same user + * @returns TransactionDTO No accounts associated with a bank user + * @throws ApiError + */ + public static transferToSelf({ + requestBody, + }: { + requestBody: TransactionDTO, + }): CancelablePromise<TransactionDTO> { + return __request(OpenAPI, { + method: 'POST', + url: '/bank/v1/transaction/norwegian-domestic-payment-to-self', + body: requestBody, + mediaType: 'application/json', + errors: { + 404: `Bank profile id does not exist`, + }, + }); + } +} diff --git a/src/components/BaseComponents/Menu.vue b/src/components/BaseComponents/Menu.vue index a44cedcafc8e80e96e3c8cf8deea826889f8d58a..19451c8205366d367b81b2c926b2270789a362ce 100644 --- a/src/components/BaseComponents/Menu.vue +++ b/src/components/BaseComponents/Menu.vue @@ -1,7 +1,7 @@ <template> <nav id="navBar" class="navbar navbar-expand-xl"> <div class="container-fluid"> - <a class="navbar-brand" href="/" @click="toHome"> + <a class="navbar-brand" href="/" @click="toHome" id="home"> <img id="logoImg" src="/src/assets/Sparesti-logo.png" alt="Sparesti-logo" width="60"> <span id="logo" class="text-white">Sparesti</span> </a> @@ -47,7 +47,7 @@ <li><a class="dropdown-item text-white dropdown-username-link" href="#" @click="toSetting"><img src="@/assets/icons/admin.svg">Admin table</a></li> <li><a class="dropdown-item text-white dropdown-username-link" href="#" - @click="toLogout"><img src="@/assets/icons/logout.svg">Log out</a></li> + @click="toLogout" data-testid="logout"><img src="@/assets/icons/logout.svg">Log out</a></li> </ul> </li> <li v-else class="nav-item"> @@ -98,6 +98,9 @@ function toFeedback() { router.push('/feedback') } +function toFriends() { + router.push('/friends') +} function toUserProfile() { router.push('/profile') @@ -105,7 +108,7 @@ function toUserProfile() { function toLogout() { userStore.clearUserInfo(); - router.push('/login') + router.push('login') } diff --git a/src/components/BaseComponents/__tests__/Footer.spec.ts b/src/components/BaseComponents/__tests__/Footer.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..25968d67433031c9353e3aaab288b1032fc8085e --- /dev/null +++ b/src/components/BaseComponents/__tests__/Footer.spec.ts @@ -0,0 +1,12 @@ +import { describe, it, expect } from 'vitest' +import { mount } from '@vue/test-utils' +import FooterComponent from '@/components/BaseComponents/Footer.vue' + +describe('FooterComponent', () => { + it('renders properly and includes the correct copyright notice', () => { + const wrapper = mount(FooterComponent) + const footer = wrapper.find('#footer') + expect(footer.exists()).toBe(true) + expect(footer.text()).toContain('© 2024 Copyright: Anders Høvik, Andreas Svendsrud, Henrik Dybdal, Henrik Sandok, Jens Aanestad, Victor Kaste, Viktor Grevskott') + }) +}) diff --git a/src/components/BaseComponents/__tests__/Menu.spec.ts b/src/components/BaseComponents/__tests__/Menu.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..e467002cfc35f0fd4f5a127a50b8f4d4bc357b7e --- /dev/null +++ b/src/components/BaseComponents/__tests__/Menu.spec.ts @@ -0,0 +1,123 @@ +import { describe, it, expect, beforeEach } from 'vitest'; +import { mount } from '@vue/test-utils'; +import { createRouter, createMemoryHistory } from 'vue-router'; +import { createPinia, setActivePinia } from 'pinia'; +import { useUserInfoStore } from '@/stores/UserStore'; +import MyComponent from '@/components/BaseComponents/Menu.vue'; // Adjust path as needed +import router from '@/router/index'; // Adjust path as needed +import { access } from 'fs'; +import { render, screen } from '@testing-library/vue'; +import userEvent from '@testing-library/user-event'; + +describe('Menu and Router Tests', () => { + let store, mockRouter; + + beforeEach(() => { + // Create a fresh Pinia and Router instance before each test + setActivePinia(createPinia()); + store = useUserInfoStore(); + mockRouter = createRouter({ + history: createMemoryHistory(), + routes: router.getRoutes(), + }); + router.beforeEach((to, from, next) => { + const isAuthenticated = store.accessToken; + if (to.matched.some(record => record.meta.requiresAuth) && !isAuthenticated) { + next({ name: 'login' }); + } else { + next(); + } + }); + + }); + + describe('Component Rendering', () => { + it('renders Menu correctly with data from the store', () => { + store.setUserInfo({ firstname: 'Jane', lastname: 'Doe', accessToken: 'thisIsATestToken' }); + + const wrapper = mount(MyComponent, { + global: { + plugins: [mockRouter], + }, + }); + + expect(wrapper.text()).toContain('Jane'); + }); + }); + + describe('Navigation Guards', () => { + it('redirects an unauthenticated user to login when accessing a protected route', async () => { + store.$patch({ accessToken: '' }); + + router.push('/profile'); + await router.isReady(); + + expect(router.currentRoute.value.name).toBe('login'); + }); + + it('allows an authenticated user to visit a protected route', async () => { + store.$patch({ accessToken: 'valid-token' }); + + mockRouter.push('/profile'); + + await mockRouter.isReady(); + + expect(mockRouter.currentRoute.value.name).toBe('profile'); + }); + }); + + + describe('UserStore Actions', () => { + it('updates user information correctly', () => { + store.setUserInfo({ firstname: 'John', lastname: 'Smith' }); + + expect(store.firstname).toBe('John'); + expect(store.lastname).toBe('Smith'); + }); + + it('clears user information correctly', () => { + store.setUserInfo({ firstname: 'John', lastname: 'Smith', accessToken: 'thisIsATestToken'}); + store.clearUserInfo(); + + expect(store.firstname).toBe(''); + expect(store.lastname).toBe(''); + expect(store.accessToken).toBe(''); + }); + }); + + describe('Menu Actions', () => { + it('logout clears userstore', async () => { + store.setUserInfo({ firstname: 'John', lastname: 'Smith', accessToken: 'thisIsATestToken'}); + + render(MyComponent, { + global: { + plugins: [mockRouter], + }, + }); + await userEvent.click(screen.getByTestId('logout')); + + expect(store.firstname).toBe(''); + expect(store.lastname).toBe(''); + expect(store.accessToken).toBe(''); + }); + + it('home redirects to home', async () => { + store.setUserInfo({ firstname: 'John', lastname: 'Smith', accessToken: 'thisIsATestToken'}); + + const { container } = render(MyComponent, { + global: { + plugins: [mockRouter], + }, + }); + + // Assuming there's an element with id="home-link" that you want to click + const homeLink = container.querySelector('#home'); // Use the actual ID here + if (homeLink) { + await userEvent.click(homeLink); + await mockRouter.isReady(); + } + + expect(mockRouter.currentRoute.value.name).toBe('home'); // Assuming 'Home' is the route name for '/' + }); + }); +}); diff --git a/src/components/Buttons/__tests__/Button1.spec.ts b/src/components/Buttons/__tests__/Button1.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..e1c2a39182663c7d37365f9973ff4953fe8d1768 --- /dev/null +++ b/src/components/Buttons/__tests__/Button1.spec.ts @@ -0,0 +1,18 @@ +import { describe, it, expect } from 'vitest' +import { mount } from '@vue/test-utils' +import ButtonComponent from '@/components/Buttons/Button1.vue' + +describe('ButtonComponent', () => { + it('displays the passed buttonText prop', () => { + const buttonText = 'Click Me!' + const wrapper = mount(ButtonComponent, { + props: { + buttonText + } + }) + + const button = wrapper.find('#buttonStyle') + expect(button.exists()).toBe(true) + expect(button.text()).toBe(buttonText) + }) +}) diff --git a/src/components/Buttons/__tests__/ShopButton.spec.ts b/src/components/Buttons/__tests__/ShopButton.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..25858cb46e529322a5da1386f31aab636bff64f9 --- /dev/null +++ b/src/components/Buttons/__tests__/ShopButton.spec.ts @@ -0,0 +1,28 @@ +import { describe, it, expect } from 'vitest' +import { mount } from '@vue/test-utils' +import ImageButtonComponent from '@/components/Buttons/ShopButton.vue' + +describe('ImageButtonComponent', () => { + it('renders the button with the correct text and image', () => { + const buttonText = 'Add Coin' + const wrapper = mount(ImageButtonComponent, { + props: { + buttonText + }, + global: { + stubs: { + // This stubs out all <router-link> and <router-view> components used in the app. + 'RouterLink': true, + 'RouterView': true + } + } + }) + + const button = wrapper.find('#buttonStyle') + expect(button.exists()).toBe(true) + expect(button.text()).toContain('+Add Coin') + const image = button.find('img') + expect(image.exists()).toBe(true) + expect(image.attributes('src')).toBe('/src/assets/items/pigcoin.png') + }) +}) diff --git a/src/components/InputFields/BaseInput.vue b/src/components/InputFields/BaseInput.vue index 8e20e43d95e8ac8cf46e6253d1ef39c4ea52d351..19a7c2e0188220bde30f3c5bdd60fc9e5ba3a931 100644 --- a/src/components/InputFields/BaseInput.vue +++ b/src/components/InputFields/BaseInput.vue @@ -63,7 +63,7 @@ const onInputEvent = (event: any) => { :required="required" /> <div class="valid-feedback">{{ validMessage }}</div> - <div class="invalid-feedback">{{ invalidMessage }}</div> + <div class="invalid-feedback" id="invalid">{{ invalidMessage }}</div> </div> </template> diff --git a/src/components/InputFields/__tests__/BaseInput.spec.ts b/src/components/InputFields/__tests__/BaseInput.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..90f881ef89744bd7c300e04ddc9ed3bf19bc64f8 --- /dev/null +++ b/src/components/InputFields/__tests__/BaseInput.spec.ts @@ -0,0 +1,21 @@ +import { describe, it, expect } from 'vitest' +import { mount } from '@vue/test-utils'; +import InputField from '@/components/InputFields/BaseInput.vue'; + +describe('InputField.vue', () => { + it('emits inputChangeEvent when input event is triggered', async () => { + const wrapper = mount(InputField, { + props: { + label: 'Test Label', + inputId: 'testId', + modelValue: '' + } + }); + + const input = wrapper.find('input'); + await input.setValue('Test Value'); + + expect(wrapper.emitted().inputChangeEvent).toBeTruthy(); + expect(wrapper.emitted().inputChangeEvent[0]).toEqual(['Test Value']); + }); +}); diff --git a/src/components/LeaderboardComponents/Leaderboard.vue b/src/components/LeaderboardComponents/Leaderboard.vue index 9003a25a4c37050a30eafc00cb1551c4885b0dc7..735ef4c014946080061da6e7c1e991b3b6a347eb 100644 --- a/src/components/LeaderboardComponents/Leaderboard.vue +++ b/src/components/LeaderboardComponents/Leaderboard.vue @@ -20,7 +20,7 @@ <tbody id="line">`</tbody> <tbody v-if="!userInLeaderboard"> <tr></tr> - <tr v-for="(entry, index) in leaderboardExtra" :key="entry.user.id" :class="{ 'is-user-5': entry.user.firstName === 'User' }"> + <tr v-for="(entry, index) in leaderboardExtra" :key="entry.user.id" :class="{ 'is-user-5': entry.user.firstName === userStore.firstname }"> <td class="number">{{ entry.rank }}</td> <td class="name" @click="navigateToUserProfile(entry.user.id)">{{ entry.user.firstName }}</td> <td class="points">{{ entry.score }}</td> diff --git a/src/components/LeaderboardComponents/__tests__/Leaderboard.spec.ts b/src/components/LeaderboardComponents/__tests__/Leaderboard.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..1f069a058b885113e8e7f86f97834b561d90d2a9 --- /dev/null +++ b/src/components/LeaderboardComponents/__tests__/Leaderboard.spec.ts @@ -0,0 +1,66 @@ +import { describe, it, expect, beforeEach, vi } from 'vitest'; +import { mount } from '@vue/test-utils'; +import { createRouter, createMemoryHistory } from 'vue-router'; +import { createPinia, setActivePinia } from 'pinia'; +import Leaderboard from '@/components/LeaderboardComponents/Leaderboard.vue'; +import { useUserInfoStore } from '@/stores/UserStore'; +import router from '@/router/index'; + +describe('Leaderboard', () => { + let wrapper, store, mockRouter; + + const leaderboard = [ + { user: { id: 1, firstName: 'Alice', email: 'alice@example.com' }, rank: 1, score: 50 }, + { user: { id: 2, firstName: 'Bob', email: 'bob@example.com' }, rank: 2, score: 45 } + ]; + + const leaderboardExtra = [ + { user: { id: 3, firstName: 'Charlie', email: 'charlie@example.com' }, rank: 1, score: 40 } + ]; + + beforeEach(() => { + setActivePinia(createPinia()); + store = useUserInfoStore(); + store.$state = { email: 'alice@example.com' }; // Setting initial state + mockRouter = createRouter({ + history: createMemoryHistory(), + routes: router.getRoutes(), + }); + + router.beforeEach((to, from, next) => { + const isAuthenticated = store.accessToken; + if (to.matched.some(record => record.meta.requiresAuth) && !isAuthenticated) { + next({ name: 'login' }); + } else { + next(); + } + }); + + wrapper = mount(Leaderboard, { + props: { leaderboard, leaderboardExtra }, + global: { + plugins: [mockRouter], + stubs: ['router-link', 'router-view'] + } + }); + }); + + it('renders all entries from the leaderboard and leaderboardExtra props', () => { + const rows = wrapper.findAll('tbody > tr'); + expect(rows.length).toBe(2); + }); + + it('correctly determines if the user is in the leaderboard', () => { + expect(wrapper.vm.userInLeaderboard).toBe(true); + }); + + it('shows the gold medal image only for the first entry', () => { + const medals = wrapper.findAll('.gold-medal'); + expect(medals.length).toBe(1); // Only the first entry should have a gold medal + }); + + it('applies the is-user-5 class based on user firstName', () => { + store.$state.firstname = 'User'; // Change state to match the condition + expect(wrapper.find('.is-user-5').exists()).toBe(false); // Check if the class is applied + }); +}); diff --git a/src/components/Login/LoginForm.vue b/src/components/Login/LoginForm.vue index 3401af96d2977c1f508c15d2dc47e3e9748a2c6d..8e502fa066902eae0a3040410ff3052648cdf2bd 100644 --- a/src/components/Login/LoginForm.vue +++ b/src/components/Login/LoginForm.vue @@ -32,7 +32,14 @@ const handleSubmit = async () => { console.log(emailRef.value) console.log(passwordRef.value) + formRef.value.classList.add("was-validated") + + const form = formRef.value; + if (!form.checkValidity()) { + return; + } + const loginUserPayload: LoginRequest = { email: emailRef.value, password: passwordRef.value diff --git a/src/components/Login/LoginLink.vue b/src/components/Login/LoginLink.vue index 0f23a2edbeed887995e5db725c0aac37092effa6..8f1db76b8746d93bbdb0ec2c16526d93fc55149b 100644 --- a/src/components/Login/LoginLink.vue +++ b/src/components/Login/LoginLink.vue @@ -3,7 +3,7 @@ </script> <template> - <p>Already have an account? <RouterLink to="/login">Login</RouterLink></p> + <p>Already have an account? <RouterLink to="/login" id="login">Login</RouterLink></p> </template> <style scoped> diff --git a/src/components/Login/__tests__/LoginForm.spec.ts b/src/components/Login/__tests__/LoginForm.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..399d96c794edc716ef1749a30801147067a99b2c --- /dev/null +++ b/src/components/Login/__tests__/LoginForm.spec.ts @@ -0,0 +1,120 @@ +import { describe, it, expect, beforeEach } from 'vitest'; +import { mount } from '@vue/test-utils'; +import { createRouter, createMemoryHistory } from 'vue-router'; +import { createPinia, setActivePinia } from 'pinia'; +import { useUserInfoStore } from '@/stores/UserStore'; +import MyComponent from '@/components/Login/LoginForm.vue'; // Adjust path as needed +import router from '@/router/index'; // Adjust path as needed +import { access } from 'fs'; +import { render, fireEvent, cleanup, screen } from '@testing-library/vue'; +import userEvent from '@testing-library/user-event'; + +describe('Menu and Router Tests', () => { + let store: any, mockRouter: any; + + beforeEach(() => { + // Create a fresh Pinia and Router instance before each test + setActivePinia(createPinia()); + store = useUserInfoStore(); + mockRouter = createRouter({ + history: createMemoryHistory(), + routes: router.getRoutes(), + }); + router.beforeEach((to, from, next) => { + const isAuthenticated = store.accessToken; + if (to.matched.some(record => record.meta.requiresAuth) && !isAuthenticated) { + next({ name: 'login' }); + } else { + next(); + } + }); + + }); + + describe('Component Rendering', () => { + it('renders form correctly', () => { + store.setUserInfo({ firstname: 'Jane', lastname: 'Doe', accessToken: 'thisIsATestToken' }); + + const wrapper = mount(MyComponent, { + global: { + plugins: [mockRouter], + }, + }); + + expect(wrapper.text()).toContain('email'); + expect(wrapper.text()).toContain('password'); + }); + }); + + describe('Navigation Guards', () => { + it('redirects an unauthenticated user to login when accessing a protected route', async () => { + store.$patch({ accessToken: '' }); + + router.push('/'); + await router.isReady(); + + expect(router.currentRoute.value.name).toBe('login'); + }); + + it('allows an unauthenticated user to visit signup', async () => { + store.$patch({ accessToken: 'valid-token' }); + + mockRouter.push('/sign-up'); + + await mockRouter.isReady(); + + expect(mockRouter.currentRoute.value.name).toBe('sign up'); + }); + }); + + + describe('Input fields', () => { + it('updates user credetials correctly', async () => { + const { getByPlaceholderText } = render(MyComponent); + + const emailInput = getByPlaceholderText('Enter your email'); + const passwordInput = getByPlaceholderText('Enter password'); + await fireEvent.update(emailInput, 'user@example.com'); + await fireEvent.update(passwordInput, 'Password1'); + + expect(emailInput.value).toBe('user@example.com'); + expect(passwordInput.value).toBe('Password1'); + }); + + it('Password error msg', async () => { + const { container } = render(MyComponent, { + global: { + plugins: [mockRouter], + }, + }); + + const errorMsg = container.querySelector('#invalid'); // Use the actual ID here + expect(errorMsg?.textContent === "Password must be between 4 and 16 characters and contain one capital letter, small letter and a number") + }); + + it('logout should have empty store at application start', () => { + expect(store.firstname).toBe(''); + expect(store.lastname).toBe(''); + expect(store.accessToken).toBe(''); + }); + }); + + describe('Menu Actions', () => { + it('signup redirects to signup', async () => { + const { container } = render(MyComponent, { + global: { + plugins: [mockRouter], + }, + }); + + // Assuming there's an element with id="home-link" that you want to click + const signupLink = container.querySelector('#signup'); // Use the actual ID here + if (signupLink) { + await userEvent.click(signupLink); + await mockRouter.isReady(); + } + + expect(mockRouter.currentRoute.value.name).toBe('sign up'); // Assuming 'Home' is the route name for '/' + }); + }); +}); diff --git a/src/components/Login/__tests__/LoginLink.spec.ts b/src/components/Login/__tests__/LoginLink.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..acb566e492bf595bd940ce5e09f0f335f6766070 --- /dev/null +++ b/src/components/Login/__tests__/LoginLink.spec.ts @@ -0,0 +1,73 @@ +import { describe, it, expect, beforeEach } from 'vitest'; +import { render } from '@testing-library/vue'; +import { createPinia, setActivePinia } from 'pinia'; +import { createRouter, createMemoryHistory } from 'vue-router'; +import LoginPrompt from '@/components/Login/LoginLink.vue'; +import { useUserInfoStore } from '@/stores/UserStore'; +import router from '@/router/index'; +import { render, screen } from '@testing-library/vue'; +import userEvent from '@testing-library/user-event'; + +describe('LoginPrompt', () => { + let store, mockRouter; + + beforeEach(() => { + // Create a fresh Pinia and Router instance before each test + setActivePinia(createPinia()); + store = useUserInfoStore(); + mockRouter = createRouter({ + history: createMemoryHistory(), + routes: router.getRoutes(), + }); + router.beforeEach((to, from, next) => { + const isAuthenticated = store.accessToken; + if (to.matched.some(record => record.meta.requiresAuth) && !isAuthenticated) { + next({ name: 'login' }); + } else { + next(); + } + }); + }); + + + it('renders login link correctly', async () => { + const router = createRouter({ + history: createMemoryHistory(), + routes: [{ path: '/login', component: { template: 'Login Page' } }], + }); + + const { getByText } = render(LoginPrompt, { + global: { + plugins: [router], + }, + }); + + await router.isReady(); // Ensure the router is ready before asserting + + const loginLink = getByText('Login'); + expect(loginLink).toBeDefined(); // Check if the 'Login' link is rendered + }); + + it('navigates to the login page when the login link is clicked', async () => { + const mockRouter = createRouter({ + history: createMemoryHistory(), + routes: [{ path: '/login', name: 'login', component: { template: 'Login Page' } }], + }); + + const { container } = render(LoginPrompt, { + global: { + plugins: [mockRouter], + }, + }); + + await mockRouter.isReady(); // Ensure the router is ready before asserting + + const loginLink = container.querySelector('#login'); // Use the actual ID here + if (loginLink) { + await userEvent.click(loginLink); + await mockRouter.isReady(); + } + + expect(mockRouter.currentRoute.value.path).toBe('/login'); // Check if the router navigated to the login page + }, 10000); +}); diff --git a/src/components/NewsComponents/__tests__/NewsComponent.spec.ts b/src/components/NewsComponents/__tests__/NewsComponent.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..7f5f15c28c46180c819f1b3fe25e8b2ea1718862 --- /dev/null +++ b/src/components/NewsComponents/__tests__/NewsComponent.spec.ts @@ -0,0 +1,56 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; +import { mount } from '@vue/test-utils'; +import MyComponent from '@/components/NewsComponents/NewsComponent.vue'; // Adjust the import path according to your setup + +// Mocking the global fetch API +global.fetch = vi.fn(() => + Promise.resolve({ + json: () => Promise.resolve({ + articles: [ + { + urlToImage: 'example-image.jpg', + title: 'Test Title', + description: 'Test Description', + url: 'http://example.com' + } + ] + }) + }) +); + +describe('MyComponent', () => { + let wrapper; + + beforeEach(() => { + vi.useFakeTimers(); // Set up fake timers + vi.spyOn(global, 'setInterval'); // Spy on setInterval + + // Setting up the wrapper before each test + wrapper = mount(MyComponent); + }); + + afterEach(() => { + // Clearing all mocks and timers after each test + vi.clearAllMocks(); + vi.restoreAllMocks(); // Restore original implementations + vi.runOnlyPendingTimers(); + vi.useRealTimers(); // Use real timers again + }); + + it('fetches news and updates articles data on component mount', async () => { + await vi.advanceTimersByTime(0); // Fast-forward any timers (like setInterval) + expect(fetch).toHaveBeenCalledTimes(1); + expect(wrapper.vm.articles).toEqual([ + { + urlToImage: 'example-image.jpg', + title: 'Test Title', + description: 'Test Description', + url: 'http://example.com' + } + ]); + }); + + it('sets up an interval to fetch news every 5 minutes', () => { + expect(setInterval).toHaveBeenCalledWith(expect.any(Function), 300000); + }); +}); diff --git a/src/components/SignUp/SignUpLink.vue b/src/components/SignUp/SignUpLink.vue index f7c354b6b9d64f7cc776d64f5336d739a95fa116..e16871208dcc1d42fd4dc7783587772215d8e1c9 100644 --- a/src/components/SignUp/SignUpLink.vue +++ b/src/components/SignUp/SignUpLink.vue @@ -3,7 +3,7 @@ </script> <template> - <p>Don't have an account? <RouterLink to="/sign-up">Sign up</RouterLink></p> + <p>Don't have an account? <RouterLink to="/sign-up" id="signup">Sign up</RouterLink></p> </template> <style scoped> diff --git a/src/components/SignUp/__tests__/SignUpForm.spec.ts b/src/components/SignUp/__tests__/SignUpForm.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..0146a6e93ee78315a2bf670c1e3871e473bb6ffb --- /dev/null +++ b/src/components/SignUp/__tests__/SignUpForm.spec.ts @@ -0,0 +1,125 @@ +import { describe, it, expect, beforeEach } from 'vitest'; +import { mount } from '@vue/test-utils'; +import { createRouter, createMemoryHistory } from 'vue-router'; +import { createPinia, setActivePinia } from 'pinia'; +import { useUserInfoStore } from '@/stores/UserStore'; +import MyComponent from '@/components/SignUp/SignUpForm.vue'; // Adjust path as needed +import router from '@/router/index'; // Adjust path as needed +import { access } from 'fs'; +import { render, fireEvent, cleanup, screen } from '@testing-library/vue'; +import userEvent from '@testing-library/user-event'; + +describe('Menu and Router Tests', () => { + let store: any, mockRouter: any; + + beforeEach(() => { + // Create a fresh Pinia and Router instance before each test + setActivePinia(createPinia()); + store = useUserInfoStore(); + mockRouter = createRouter({ + history: createMemoryHistory(), + routes: router.getRoutes(), + }); + router.beforeEach((to, from, next) => { + const isAuthenticated = store.accessToken; + if (to.matched.some(record => record.meta.requiresAuth) && !isAuthenticated) { + next({ name: 'login' }); + } else { + next(); + } + }); + + }); + + describe('Component Rendering', () => { + it('renders form correctly', () => { + const wrapper = mount(MyComponent, { + global: { + plugins: [mockRouter], + }, + }); + + expect(wrapper.text()).toContain('First name'); + expect(wrapper.text()).toContain('Surname'); + expect(wrapper.text()).toContain('Email'); + }); + }); + + describe('Navigation Guards', () => { + it('redirects an unauthenticated user to login when accessing a protected route', async () => { + store.$patch({ accessToken: '' }); + + router.push('/'); + await router.isReady(); + + expect(router.currentRoute.value.name).toBe('login'); + }); + + it('allows an unauthenticated user to visit login', async () => { + store.$patch({ accessToken: 'valid-token' }); + + mockRouter.push('/login'); + + await mockRouter.isReady(); + + expect(mockRouter.currentRoute.value.name).toBe('login'); + }); + }); + + + describe('Input fields', () => { + it('updates user credetials correctly', async () => { + const { getByPlaceholderText } = render(MyComponent); + + const firstInput = getByPlaceholderText('Enter your first name'); + const lastInput = getByPlaceholderText('Enter your surname'); + const emailInput = getByPlaceholderText('Enter your email'); + const passwordInput = getByPlaceholderText('Enter password'); + await fireEvent.update(firstInput, 'Alice'); + await fireEvent.update(lastInput, 'Alicon'); + await fireEvent.update(emailInput, 'user@example.com'); + await fireEvent.update(passwordInput, 'Password1'); + + expect(firstInput.value).toBe('Alice'); + expect(lastInput.value).toBe('Alicon'); + expect(emailInput.value).toBe('user@example.com'); + expect(passwordInput.value).toBe('Password1'); + }); + + it('Password error msg', async () => { + const { container } = render(MyComponent, { + global: { + plugins: [mockRouter], + }, + }); + + const errorMsg = container.querySelector('#invalid'); // Use the actual ID here + expect(errorMsg?.textContent === "Password must be between 4 and 16 characters and contain one capital letter, small letter and a number") + }); + + it('logout should have empty store at application start', () => { + expect(store.firstname).toBe(''); + expect(store.lastname).toBe(''); + expect(store.accessToken).toBe(''); + }); + }); + + describe('Menu Actions', () => { + it('signup redirects to signup', async () => { + const { container } = render(MyComponent, { + global: { + plugins: [mockRouter], + }, + }); + + // Assuming there's an element with id="home-link" that you want to click + const signupLink = container.querySelector('#login'); // Use the actual ID here + if (signupLink) { + await userEvent.click(signupLink); + await mockRouter.isReady(); + } + + expect(mockRouter.currentRoute.value.name).toBe('login'); // Assuming 'Home' is the route name for '/' + }); + }); +}); diff --git a/src/components/SignUp/__tests__/SignUpLink.spec.ts b/src/components/SignUp/__tests__/SignUpLink.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..4701d55d2f7d7c4690b0b24cc5f687b8c0fc2285 --- /dev/null +++ b/src/components/SignUp/__tests__/SignUpLink.spec.ts @@ -0,0 +1,71 @@ +import { describe, it, expect, beforeEach } from 'vitest'; +import { render } from '@testing-library/vue'; +import { createPinia, setActivePinia } from 'pinia'; +import { createRouter, createMemoryHistory } from 'vue-router'; +import LoginPrompt from '@/components/SignUp/SignUpLink.vue'; +import { useUserInfoStore } from '@/stores/UserStore'; +import router from '@/router/index'; +import { render, screen } from '@testing-library/vue'; +import userEvent from '@testing-library/user-event'; + +describe('LoginPrompt', () => { + let store, mockRouter; + + beforeEach(() => { + // Create a fresh Pinia and Router instance before each test + setActivePinia(createPinia()); + store = useUserInfoStore(); + mockRouter = createRouter({ + history: createMemoryHistory(), + routes: router.getRoutes(), + }); + router.beforeEach((to, from, next) => { + const isAuthenticated = store.accessToken; + if (to.matched.some(record => record.meta.requiresAuth) && !isAuthenticated) { + next({ name: 'login' }); + } else { + next(); + } + }); + }); + + + it('renders login link correctly', async () => { + const router = createRouter({ + history: createMemoryHistory(), + routes: [{ path: '/signup', component: { template: 'Signup Page' } }], + }); + + const { getByText } = render(LoginPrompt, { + global: { + plugins: [router], + }, + }); + + const loginLink = getByText('Sign up'); + expect(loginLink).toBeDefined(); // Check if the 'Login' link is rendered + }); + + it('navigates to the login page when the login link is clicked', async () => { + const mockRouter = createRouter({ + history: createMemoryHistory(), + routes: [{ path: '/login', name: 'login', component: { template: 'Login Page' } }], + }); + + const { container } = render(LoginPrompt, { + global: { + plugins: [mockRouter], + }, + }); + + await mockRouter.isReady(); // Ensure the router is ready before asserting + + const signupLink = container.querySelector('#signup'); // Use the actual ID here + if (signupLink) { + await userEvent.click(signupLink); + await mockRouter.isReady(); + } + + expect(mockRouter.currentRoute.value.path).toBe('/sign-up'); // Check if the router navigated to the login page + }, 10000); +}); diff --git a/src/components/UpdateUserComponents/UpdateUserLayout.vue b/src/components/UpdateUserComponents/UpdateUserLayout.vue index 511ece58743fbe4502419a4646f6247c680d2530..838d8544930828865399e42f0559f0353f34e206 100644 --- a/src/components/UpdateUserComponents/UpdateUserLayout.vue +++ b/src/components/UpdateUserComponents/UpdateUserLayout.vue @@ -115,7 +115,7 @@ onMounted(() => { </div> </div> <br> - <h5 class="user-name">Yuki Hayashi</h5> + <h3 class="user-name">Yuki Hayashi</h3> <h6 class="user-email">yuki@Maxwell.com</h6> </div> </div> @@ -273,7 +273,7 @@ body { border-radius: 100px; } -.account-settings .user-profile h5.user-name { +.account-settings .user-profile h3.user-name { margin: 0 0 0.5rem 0; } diff --git a/src/components/UserProfile/UserProfileLayout.vue b/src/components/UserProfile/UserProfileLayout.vue index db2c4bca1ccc4af534c3a5aacd5dd162657ffaec..7036e347b7ad8398651125fc7a8e897c4ab5c76d 100644 --- a/src/components/UserProfile/UserProfileLayout.vue +++ b/src/components/UserProfile/UserProfileLayout.vue @@ -1,7 +1,5 @@ <script setup lang="ts"> - -import Menu from "@/components/BaseComponents/Menu.vue"; -import Footer from "@/components/BaseComponents/Footer.vue"; +import { ref } from "vue"; import { useRouter } from "vue-router"; import { useUserInfoStore } from "../../stores/UserStore"; @@ -11,15 +9,23 @@ let cardTitles = ["Spain tour", "Food waste", "Coffee", "Concert", "New book", " let points = 0; let streak = 0; +let firstname = ref(""); +let lastname = ref(""); -let route = useRouter() -function toRoadmap() { - route.push('/roadmap') -} +const router = useRouter(); +const userStore = useUserInfoStore(); +firstname.value = userStore.firstname; +lastname.value = userStore.lastname; -function toUpdateUserSettings() { - route.push('/update-user') -} + +const toRoadmap = () => { + router.push('/'); +}; + +// Function to navigate to update user settings +const toUpdateUserSettings = () => { + router.push('/update-user'); +}; </script> <template> @@ -32,12 +38,12 @@ function toUpdateUserSettings() { <img src="https://bootdey.com/img/Content/avatar/avatar3.png" alt="Generic placeholder image" class="img-fluid img-thumbnail mt-4 mb-2" style="width: 150px; z-index: 1"> <button type="button" data-mdb-button-init data-mdb-ripple-init class="btn btn-outline-primary" - data-mdb-ripple-color="dark" style="z-index: 1;" @click="toUpdateUserSettings"> + data-mdb-ripple-color="dark" style="z-index: 1;" id="toUpdate" @click="toUpdateUserSettings"> Edit profile </button> </div> <div class="ms-3" style="margin-top: 130px;"> - <h1>Andy Horwitz</h1> + <h1>{{ firstname }} {{ lastname }}</h1> </div> </div> <div class="p-4 text-black" style="background-color: #f8f9fa;"> diff --git a/src/components/UserProfile/__tests__/UserProfileLayout.spec.ts b/src/components/UserProfile/__tests__/UserProfileLayout.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..f3028b0cb27deeb0de90684578082eeb735fe6ea --- /dev/null +++ b/src/components/UserProfile/__tests__/UserProfileLayout.spec.ts @@ -0,0 +1,86 @@ +import { describe, it, expect, beforeEach } from 'vitest'; +import { mount } from '@vue/test-utils'; +import { createRouter, createMemoryHistory } from 'vue-router'; +import { createPinia, setActivePinia } from 'pinia'; +import { useUserInfoStore } from '@/stores/UserStore'; +import MyComponent from '@/components/UserProfile/UserProfileLayout.vue'; // Adjust path as needed +import router from '@/router/index'; // Adjust path as needed +import { access } from 'fs'; + +describe('MyComponent and Router Tests', () => { + let store, mockRouter; + + beforeEach(() => { + // Create a fresh Pinia and Router instance before each test + setActivePinia(createPinia()); + store = useUserInfoStore(); + mockRouter = createRouter({ + history: createMemoryHistory(), + routes: router.getRoutes(), + }); + router.beforeEach((to, from, next) => { + const isAuthenticated = store.accessToken; + if (to.matched.some(record => record.meta.requiresAuth) && !isAuthenticated) { + next({ name: 'login' }); + } else { + next(); + } + }); + + }); + + describe('Component Rendering', () => { + it('renders MyComponent correctly with data from the store', () => { + // Mock user information + store.setUserInfo({ firstname: 'Jane', lastname: 'Doe', accessToken: 'thisIsATestToken' }); + + const wrapper = mount(MyComponent, { + global: { + plugins: [mockRouter], + }, + }); + + // Check for text or elements that depend on user info + expect(wrapper.text()).toContain('Jane'); + expect(wrapper.text()).toContain('Doe'); + }); + }); + + describe('Navigation Guards', () => { + it('redirects an unauthenticated user to login when accessing a protected route', async () => { + // Simulate the user being unauthenticated + store.$patch({ accessToken: '' }); + + router.push('/profile'); + await router.isReady(); + + expect(router.currentRoute.value.name).toBe('login'); + }); + + it('allows an authenticated user to visit a protected route', async () => { + store.$patch({ accessToken: 'valid-token' }); // Token is present + mockRouter.push('/profile'); + await mockRouter.isReady(); + expect(mockRouter.currentRoute.value.name).toBe('profile'); + }); + }); + + + describe('UserStore Actions', () => { + it('updates user information correctly', () => { + store.setUserInfo({ firstname: 'John', lastname: 'Smith' }); + + expect(store.firstname).toBe('John'); + expect(store.lastname).toBe('Smith'); + }); + + it('clears user information correctly', () => { + store.setUserInfo({ firstname: 'John', lastname: 'Smith', accessToken: 'thisIsATestToken'}); + store.clearUserInfo(); + + expect(store.firstname).toBe(''); + expect(store.lastname).toBe(''); + expect(store.accessToken).toBe(''); + }); + }); +}); diff --git a/src/router/index.ts b/src/router/index.ts index 70bd7ba69d8236b5ae338cec2490cb7c47ed272e..189079a9a1423941d18bd1b0c5725c0568c530f3 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -35,7 +35,7 @@ const routes = [ component: () => import('@/views/TestView.vue'), }, { - path: '/profile', + path: 'profile', name: 'profile', component: UserProfileView }, @@ -59,11 +59,6 @@ const routes = [ name: 'shop', component: () => import('@/views/ShopView.vue'), }, - { - path: 'profile', - name: 'profile', - component: UserProfileView - }, { path: '/budget-overview', name: 'budget overview', @@ -161,7 +156,7 @@ const routes = [ ]; const router = createRouter({ - history: createWebHistory(import.meta.env.BASE_URL), + history: createWebHistory(import.meta.env.BASE_URL || '/'), routes, scrollBehavior() { return { top: 0 }; diff --git a/tsconfig.vitest.json b/tsconfig.vitest.json index 571995d11e6acb21020f2570fb6a034609ee654e..0f0268e051af3a98dd7ea5656c937c3012d22623 100644 --- a/tsconfig.vitest.json +++ b/tsconfig.vitest.json @@ -4,8 +4,8 @@ "compilerOptions": { "composite": true, "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.vitest.tsbuildinfo", - + "lib": [], - "types": ["node", "jsdom"] + "types": ["node", "jsdom", "vitest/globals"] } } diff --git a/vitest.config.ts b/vitest.config.ts index f5088576a014873999241d0a3d0fe0313984f2b3..c29610e1b27cd789d15fedc6576e22276cab6a63 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -10,7 +10,8 @@ export default mergeConfig( exclude: [...configDefaults.exclude, 'e2e/**'], root: fileURLToPath(new URL('./', import.meta.url)), coverage: { - provider: 'v8' + provider: 'v8', + exclude: ['src/views/'] } } })