diff --git a/package-lock.json b/package-lock.json
index d245e94863ef06a128918335899739311cdc9f23..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,6 +43,7 @@
         "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",
@@ -522,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",
@@ -1654,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",
@@ -2653,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",
@@ -2718,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",
@@ -3517,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",
@@ -3580,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",
@@ -3621,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",
@@ -3793,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",
@@ -4545,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",
@@ -4646,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",
@@ -4818,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",
@@ -4863,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",
@@ -5077,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",
@@ -5089,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",
@@ -5168,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",
@@ -5177,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",
@@ -5192,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",
@@ -5204,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",
@@ -5222,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",
@@ -5237,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",
@@ -5912,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",
@@ -6254,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",
@@ -6681,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",
@@ -6916,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",
@@ -7180,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",
@@ -7444,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",
@@ -7923,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",
@@ -8725,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 07bf44ca2594d40ec79da3aed7a08f14fe06dd14..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,6 +51,7 @@
     "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",
diff --git a/src/components/BaseComponents/Menu.vue b/src/components/BaseComponents/Menu.vue
index a44cedcafc8e80e96e3c8cf8deea826889f8d58a..6d32979e421861b9f676bf8c4382c7374ab6a3af 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">
@@ -105,7 +105,7 @@ function toUserProfile() {
 
 function toLogout() {
     userStore.clearUserInfo();
-    router.push('/login')
+    router.push('login')
 }
 
 
diff --git a/src/components/BaseComponents/__tests__/Menu.spec.ts b/src/components/BaseComponents/__tests__/Menu.spec.ts
index 299b57553bde1b662366707418a89b3e677834ab..e467002cfc35f0fd4f5a127a50b8f4d4bc357b7e 100644
--- a/src/components/BaseComponents/__tests__/Menu.spec.ts
+++ b/src/components/BaseComponents/__tests__/Menu.spec.ts
@@ -1,40 +1,123 @@
-// Import necessary libraries and mocks
-import { describe, it, expect, vi } from 'vitest';
+import { describe, it, expect, beforeEach } from 'vitest';
 import { mount } from '@vue/test-utils';
-import NavBar from '@/components/BaseComponents/Menu.vue';
-import { createRouter, createWebHistory } from 'vue-router';
-
-// Use the minimal route setup for testing
-const routes = [
-  { path: '/', name: 'home', component: { template: '<div>Home</div>' } },
-  { path: '/login', name: 'login', component: { template: '<div>Login</div>' } },
-];
-const router = createRouter({
-  history: createWebHistory('/'),
-  routes,
-});
+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;
 
-// Mock store setup
-const mockStore = {
-  isLoggedIn: false,
-  firstname: '',
-  clearUserInfo: vi.fn(),
-};
-vi.mock('@/stores/UserStore', () => ({
-  useUserInfoStore: () => mockStore
-}));
-
-// Test the NavBar component
-describe('NavBar', () => {
-  it('renders navbar and checks for logo and initial links', async () => {
-    const wrapper = mount(NavBar, {
-      global: {
-        plugins: [router]
+  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();
       }
     });
-    expect(wrapper.find('#navBar').exists()).toBe(true);
-    expect(wrapper.find('#logoImg').attributes('src')).toBe('/src/assets/Sparesti-logo.png');
-    expect(wrapper.find('#logo').text()).toContain('Sparesti');
-    expect(wrapper.findAll('.nav-item').length).toBeGreaterThan(0);
+
+  });
+
+  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/InputFields/__tests__/BaseInput.spec.ts b/src/components/InputFields/__tests__/BaseInput.spec.ts
index 01ae12b718a826e062e98b1d0f6b53dfa5377aed..90f881ef89744bd7c300e04ddc9ed3bf19bc64f8 100644
--- a/src/components/InputFields/__tests__/BaseInput.spec.ts
+++ b/src/components/InputFields/__tests__/BaseInput.spec.ts
@@ -18,6 +18,4 @@ describe('InputField.vue', () => {
     expect(wrapper.emitted().inputChangeEvent).toBeTruthy();
     expect(wrapper.emitted().inputChangeEvent[0]).toEqual(['Test Value']);
   });
-
-  // Add more test cases for other functionalities as needed
 });
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/UserProfile/UserProfileLayout.vue b/src/components/UserProfile/UserProfileLayout.vue
index 161c8b9550540e6122a65c8217937356dae6b23f..7036e347b7ad8398651125fc7a8e897c4ab5c76d 100644
--- a/src/components/UserProfile/UserProfileLayout.vue
+++ b/src/components/UserProfile/UserProfileLayout.vue
@@ -1,4 +1,5 @@
 <script setup lang="ts">
+import { ref } from "vue";
 import { useRouter } from "vue-router";
 import { useUserInfoStore } from "../../stores/UserStore";
 
@@ -8,8 +9,15 @@ let cardTitles = ["Spain tour", "Food waste", "Coffee", "Concert", "New book", "
 
 let points = 0;
 let streak = 0;
+let firstname = ref("");
+let lastname = ref("");
+
+const router = useRouter();
+const userStore = useUserInfoStore();
+firstname.value = userStore.firstname;
+lastname.value = userStore.lastname;
+
 
-const router = useRouter()
 const toRoadmap = () => {
   router.push('/');
 };
@@ -35,7 +43,7 @@ const toUpdateUserSettings = () => {
               </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
index bbe3d449d08693b9a13ca4ebd9a870e045d3f9c3..f3028b0cb27deeb0de90684578082eeb735fe6ea 100644
--- a/src/components/UserProfile/__tests__/UserProfileLayout.spec.ts
+++ b/src/components/UserProfile/__tests__/UserProfileLayout.spec.ts
@@ -1,47 +1,86 @@
-import { describe, it, expect, vi } from 'vitest';
+import { describe, it, expect, beforeEach } from 'vitest';
 import { mount } from '@vue/test-utils';
-import DashboardComponent from '@/components/UserProfile/UserProfileLayout.vue'; // Update with your actual import
-
-// Correctly mocking 'vue-router'
-vi.mock('vue-router', async (importOriginal) => {
-  const actual = await importOriginal(); // Import the actual vue-router module
-  return {
-    ...actual, // Spread all exports
-    // Optionally override specific exports if needed
-  };
-});
+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;
 
-describe('DashboardComponent', () => {
-  // Now you can import and use createRouter and createWebHistory
-  const { createRouter, createWebHistory } = require('vue-router');
+  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();
+      }
+    });
 
-  const router = createRouter({
-    history: createWebHistory(),
-    routes: [{ path: '/', name: 'home' }, { path: '/update-user', name: 'update-user' }]
   });
 
-  it('renders correctly', () => {
-    const wrapper = mount(DashboardComponent, {
-      global: {
-        plugins: [router]
-      }
+  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');
     });
+  });
 
-    // Check if the component renders
-    expect(wrapper.find('.container').exists()).toBe(true);
-    expect(wrapper.find('h1').text()).toBe('Andy Horwitz');
-    expect(wrapper.findAll('.card').length).toBeGreaterThan(0); // Checks if any cards are rendered
+  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');
+    });
   });
+  
 
-  it('navigates to roadmap page', async () => {
-    const wrapper = mount(DashboardComponent, {
-      global: {
-        plugins: [router]
-      }
+  describe('UserStore Actions', () => {
+    it('updates user information correctly', () => {
+      store.setUserInfo({ firstname: 'John', lastname: 'Smith' });
+
+      expect(store.firstname).toBe('John');
+      expect(store.lastname).toBe('Smith');
     });
 
-    await router.isReady(); // Wait for router to be ready
-    await wrapper.find('.stretched-link').trigger('click'); // Simulate clicking the link that calls toRoadmap
-    expect(router.currentRoute.value.path).toBe('/');
+    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('');
+    });
   });
-});
\ No newline at end of file
+});
diff --git a/src/router/index.ts b/src/router/index.ts
index 1bec708d93ee2784117b5a1caa24da5852299a0a..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',