diff --git a/eslint.config.js b/eslint.config.ts
similarity index 95%
rename from eslint.config.js
rename to eslint.config.ts
index 06e45d1..0ccbd1a 100644
--- a/eslint.config.js
+++ b/eslint.config.ts
@@ -25,6 +25,7 @@ export default typescriptEslint.config(
rules: {
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-require-imports": "off",
+ "@typescript-eslint/no-unused-vars": "warn",
},
},
{
diff --git a/package-lock.json b/package-lock.json
index 6f168eb..814b9a3 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -27,6 +27,7 @@
"eslint-plugin-vue": "^10.5.1",
"globals": "^16.4.0",
"happy-dom": "^20.0.5",
+ "jiti": "^2.6.1",
"npm-run-all2": "^8.0.4",
"prettier": "3.6.2",
"resize-observer-polyfill": "^1.5.1",
@@ -4786,6 +4787,16 @@
"@pkgjs/parseargs": "^0.11.0"
}
},
+ "node_modules/jiti": {
+ "version": "2.6.1",
+ "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz",
+ "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==",
+ "devOptional": true,
+ "license": "MIT",
+ "bin": {
+ "jiti": "lib/jiti-cli.mjs"
+ }
+ },
"node_modules/js-beautify": {
"version": "1.15.4",
"resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.15.4.tgz",
diff --git a/package.json b/package.json
index 1f38e6d..311e6e3 100644
--- a/package.json
+++ b/package.json
@@ -38,6 +38,7 @@
"eslint-plugin-vue": "^10.5.1",
"globals": "^16.4.0",
"happy-dom": "^20.0.5",
+ "jiti": "^2.6.1",
"npm-run-all2": "^8.0.4",
"prettier": "3.6.2",
"resize-observer-polyfill": "^1.5.1",
diff --git a/src/components/HeaderNavBar.vue b/src/components/HeaderNavBar.vue
index 6cd7416..2c3dfee 100644
--- a/src/components/HeaderNavBar.vue
+++ b/src/components/HeaderNavBar.vue
@@ -7,12 +7,7 @@ import { RouterLink } from "vue-router";
-
+
Home
@@ -26,7 +21,12 @@ import { RouterLink } from "vue-router";
About
+
+
+ Settings
+
+
-
\ No newline at end of file
+
diff --git a/src/router/index.ts b/src/router/index.ts
index 591cd69..5adb202 100644
--- a/src/router/index.ts
+++ b/src/router/index.ts
@@ -14,6 +14,11 @@ const router = createRouter({
name: "about",
component: () => import("../views/AboutView.vue"),
},
+ {
+ path: "/settings",
+ name: "settings",
+ component: () => import("~/views/SettingView.vue"),
+ },
],
});
diff --git a/src/views/SettingView.vue b/src/views/SettingView.vue
new file mode 100644
index 0000000..822c917
--- /dev/null
+++ b/src/views/SettingView.vue
@@ -0,0 +1,5 @@
+
+
+ Settings
+
+
diff --git a/tests/components/HeardeNavBar.spec.ts b/tests/components/HeardeNavBar.spec.ts
index 444eb17..1526c7f 100644
--- a/tests/components/HeardeNavBar.spec.ts
+++ b/tests/components/HeardeNavBar.spec.ts
@@ -1,116 +1,138 @@
-import { describe, it, expect, beforeEach } from "vitest"
-import { mount } from "@vue/test-utils"
-import { createRouter, createWebHistory } from "vue-router"
-import { createVuetify } from "vuetify"
-import * as components from 'vuetify/components'
-import * as directives from 'vuetify/directives'
-import HeaderNavBar from "../../src/components/HeaderNavBar.vue"
+import { describe, it, expect, beforeEach } from "vitest";
+import { mount } from "@vue/test-utils";
+import { createRouter, createWebHistory } from "vue-router";
+import { createVuetify } from "vuetify";
+import * as components from "vuetify/components";
+import * as directives from "vuetify/directives";
+import HeaderNavBar from "../../src/components/HeaderNavBar.vue";
-globalThis.ResizeObserver = require('resize-observer-polyfill');
+globalThis.ResizeObserver = require("resize-observer-polyfill");
// Mock routes for testing
const routes = [
{ path: "/", component: { template: "
Home
" } },
- { path: "/about", component: { template: "About
" } }
-]
+ { path: "/about", component: { template: "About
" } },
+ { path: "/settings", component: { template: "Settings
" } },
+];
const router = createRouter({
history: createWebHistory(),
- routes
+ routes,
});
const vuetify = createVuetify({
components,
directives,
-})
+});
describe("HeaderNavBar", () => {
- let wrapper: any
+ let wrapper: any;
beforeEach(() => {
- wrapper = mount({
- template: `
+ wrapper = mount(
+ {
+ template: `
`,
- }, {
- global: {
- components: {
- HeaderNavBar,
- },
- plugins: [router, vuetify]
+ },
+ {
+ global: {
+ components: {
+ HeaderNavBar,
+ },
+ plugins: [router, vuetify],
+ },
}
- })
- })
+ );
+ });
it("renders the component", () => {
- expect(wrapper.exists()).toBe(true)
- expect(wrapper.findComponent({ name: "VAppBar" }).exists()).toBe(true)
- })
+ expect(wrapper.exists()).toBe(true);
+ expect(wrapper.findComponent({ name: "VAppBar" }).exists()).toBe(true);
+ });
- it("renders both navigation buttons", () => {
+ it("renders all navigation buttons", () => {
const homeButton = wrapper.find("[data-role='homeNavigation']");
const aboutButton = wrapper.find("[data-role='aboutNavigation']");
+ const settingsButton = wrapper.find("[data-role='settingsNavigation']");
expect(homeButton.exists()).toBe(true);
expect(aboutButton.exists()).toBe(true);
+ expect(settingsButton.exists()).toBe(true);
expect(homeButton.text()).toContain("Home");
expect(aboutButton.text()).toContain("About");
+ expect(settingsButton.text()).toContain("Settings");
});
it("has correct icons on buttons", () => {
const homeButton = wrapper.find("[data-role='homeNavigation']");
const aboutButton = wrapper.find("[data-role='aboutNavigation']");
+ const settingsButton = wrapper.find("[data-role='settingsNavigation']");
- expect(homeButton.find("i").attributes("class")).toContain("mdi-home-outline");
- expect(aboutButton.find("i").attributes("class")).toContain("mdi-chat-question-outline");
+ expect(homeButton.find("i").attributes("class")).toContain("mdi-home-outline");
+ expect(aboutButton.find("i").attributes("class")).toContain("mdi-chat-question-outline");
+ expect(settingsButton.find("i").attributes("class")).toContain("mdi-cog-outline");
});
it("has correct RouterLink paths", () => {
const routerLinks = wrapper.findAllComponents({ name: "RouterLink" });
-
- expect(routerLinks).toHaveLength(2);
+
+ expect(routerLinks).toHaveLength(3);
expect(routerLinks[0].props("to")).toBe("/");
expect(routerLinks[1].props("to")).toBe("/about");
+ expect(routerLinks[2].props("to")).toBe("/settings");
});
it("applies correct button variants based on active route", async () => {
// Navigate to home route
- await router.push("/")
- await wrapper.vm.$nextTick()
+ await router.push("/");
+ await wrapper.vm.$nextTick();
- // Check home button is elevated (active) and about is outlined (inactive)
+ // Check home button is elevated and the rest are outlined
const homeButton = wrapper.find("[data-role='homeNavigation']");
const aboutButton = wrapper.find("[data-role='aboutNavigation']");
+ const settingsButton = wrapper.find("[data-role='settingsNavigation']");
- expect(homeButton.find("button").attributes("class")).toContain("elevated");
- expect(aboutButton.find("button").attributes("class")).toContain("outlined");
+ expect(homeButton.find("button").attributes("class")).toContain("elevated");
+ expect(aboutButton.find("button").attributes("class")).toContain("outlined");
+ expect(settingsButton.find("button").attributes("class")).toContain("outlined");
- // // Navigate to about route
+ // Navigate to about route
await router.push("/about");
await wrapper.vm.$nextTick();
- // // Check about button is now elevated and home is outlined
+ // Check about button is now elevated and the rest are outlined
expect(homeButton.find("button").attributes("class")).toContain("outlined");
- expect(aboutButton.find("button").attributes("class")).toContain("elevated");
+ expect(aboutButton.find("button").attributes("class")).toContain("elevated");
+ expect(settingsButton.find("button").attributes("class")).toContain("outlined");
+
+ // Navigate to settings route
+ await router.push("/settings");
+ await wrapper.vm.$nextTick();
+
+ // Check about button is now elevated and the rest are outlined
+ expect(homeButton.find("button").attributes("class")).toContain("outlined");
+ expect(aboutButton.find("button").attributes("class")).toContain("outlined");
+ expect(settingsButton.find("button").attributes("class")).toContain("elevated");
});
it("has correct button styling classes", () => {
const routerLinks = wrapper.findAllComponents({ name: "RouterLink" });
for (const link of routerLinks) {
- expect(link.classes()).toContain("mx-2")
- expect(link.classes()).toContain("text-decoration-none")
+ expect(link.classes()).toContain("mx-2");
+ expect(link.classes()).toContain("text-decoration-none");
}
- const buttons = wrapper.findAllComponents({ name: "v-btn" });
+ const buttons = wrapper.findAllComponents({ name: "v-btn" });
for (const button of buttons) {
- expect(button.attributes("class")).toContain("v-btn--size-large");
- expect(button.attributes("class")).toContain("-primary");
- }
+ expect(button.attributes("class")).toContain("v-btn--size-large");
+ expect(button.attributes("class")).toContain("-primary");
+ }
});
it("has proper app bar structure", () => {
@@ -126,11 +148,11 @@ describe("HeaderNavBar", () => {
it("maintains accessibility attributes", () => {
const homeButton = wrapper.find("[data-role='homeNavigation']");
- expect(homeButton.attributes("data-role")).toBe('homeNavigation');
+ expect(homeButton.attributes("data-role")).toBe("homeNavigation");
// Check that buttons are actual button elements for screen readers
const buttons = wrapper.findAll("button");
- expect(buttons).toHaveLength(2);
+ expect(buttons).toHaveLength(3);
for (const button of buttons) {
expect(button.element.tagName).toBe("BUTTON");
}
diff --git a/tests/router/index.spec.ts b/tests/router/index.spec.ts
index 8cad8ba..c1ace64 100644
--- a/tests/router/index.spec.ts
+++ b/tests/router/index.spec.ts
@@ -1,168 +1,141 @@
-
import { describe, it, expect, beforeEach } from "vitest";
import router from "~/router/index";
describe("Router Integration Tests", () => {
+ const testRoutes = [
+ { path: "/", name: "home", lazyLoaded: false },
+ { path: "/about", name: "about", lazyLoaded: true },
+ { path: "/settings", name: "settings", lazyLoaded: true },
+ ];
+
beforeEach(async () => {
await router.push("/");
await router.isReady();
});
- it("creates a router instance", () => {
- expect(router).toBeDefined();
- expect(router).toHaveProperty("currentRoute");
- expect(router).toHaveProperty("push");
- expect(router).toHaveProperty("replace");
- });
+ describe("General", () => {
+ it("has hash history mode", () => {
+ expect(router.options.history.base).toContain("#");
+ });
- it("has hash history mode", () => {
- expect(router.options.history.base).toContain("#");
- });
+ it("has 3 routes configured", () => {
+ const routes = router.getRoutes();
+ expect(routes).toHaveLength(3);
+ });
- it("has 2 routes configured", () => {
- const routes = router.getRoutes();
- expect(routes).toHaveLength(2);
- });
+ it("creates a router instance", () => {
+ expect(router).toBeDefined();
+ expect(router).toHaveProperty("currentRoute");
+ expect(router).toHaveProperty("push");
+ expect(router).toHaveProperty("replace");
+ });
- it("has home route at root path", () => {
- const routes = router.getRoutes();
- const homeRoute = routes.find(route => route.path === "/");
+ it("does not have undefined routes", () => {
+ expect(router.hasRoute("nonexistent")).toBe(false);
+ });
- expect(homeRoute).toBeDefined();
- expect(homeRoute?.name).toBe("home");
- });
-
- it("has about route", () => {
- const routes = router.getRoutes();
- const aboutRoute = routes.find(route => route.path === "/about");
-
- expect(aboutRoute).toBeDefined();
- expect(aboutRoute?.name).toBe("about");
- });
-
- it("navigates to home route", async () => {
- await router.push("/");
- await router.isReady();
-
- expect(router.currentRoute.value.path).toBe("/");
- expect(router.currentRoute.value.name).toBe("home");
- });
-
- it("navigates to about route", async () => {
- await router.push("/about");
- await router.isReady();
-
- expect(router.currentRoute.value.path).toBe("/about");
- expect(router.currentRoute.value.name).toBe("about");
- });
-
- it("navigates using route name for home", async () => {
- await router.push({ name: "home" });
- await router.isReady();
-
- expect(router.currentRoute.value.name).toBe("home");
- expect(router.currentRoute.value.path).toBe("/");
- });
-
- it("navigates using route name for about", async () => {
- await router.push({ name: "about" });
- await router.isReady();
-
- expect(router.currentRoute.value.name).toBe("about");
- expect(router.currentRoute.value.path).toBe("/about");
- });
-
- it("resolves home route correctly", () => {
- const resolved = router.resolve("/");
-
- expect(resolved.name).toBe("home");
- expect(resolved.path).toBe("/");
- });
-
- it("resolves about route correctly", () => {
- const resolved = router.resolve("/about");
-
- expect(resolved.name).toBe("about");
- expect(resolved.path).toBe("/about");
- });
-
- it("has HomeView component for home route", () => {
- const routes = router.getRoutes();
- const homeRoute = routes.find(route => route.path === "/");
-
- expect(homeRoute?.components?.default).toBeDefined();
- });
-
- it("has lazy loaded component for about route", () => {
- const routes = router.getRoutes();
- const aboutRoute = routes.find(route => route.path === "/about");
-
- expect(aboutRoute?.components).toBeDefined();
- });
-
- it("checks if home route exists", () => {
- expect(router.hasRoute("home")).toBe(true);
- });
-
- it("checks if about route exists", () => {
- expect(router.hasRoute("about")).toBe(true);
- });
-
- it("does not have undefined routes", () => {
- expect(router.hasRoute("nonexistent")).toBe(false);
- });
-
- it("maintains navigation history", async () => {
- await router.push("/");
- await router.push("/about");
-
- expect(router.currentRoute.value.path).toBe("/about");
-
- router.back();
- await router.isReady();
-
- expect(router.back).toBeDefined();
- });
-
- it("handles programmatic navigation with replace", async () => {
- await router.push("/");
- await router.replace("/about");
- await router.isReady();
-
- expect(router.currentRoute.value.path).toBe("/about");
- });
-
- it("matches routes case-sensitively", () => {
- const routes = router.getRoutes();
- const upperCaseRoute = routes.find(route => route.path === "/ABOUT");
-
- expect(upperCaseRoute).toBeUndefined();
- });
-
- it("provides route metadata access", () => {
- const routes = router.getRoutes();
-
- for (const route of routes) {
- expect(route).toHaveProperty("path");
- expect(route).toHaveProperty("name");
- expect(route).toHaveProperty("meta");
- }
- });
-
- it("is ready after initialization", async () => {
- const ready = await router.isReady();
-
- // Note: isReady() returns void when resolved
- expect(ready).toBeUndefined();
- });
-
- it("handles navigation to current route", async () => {
- await router.push("/");
-
- try {
+ it("maintains navigation history", async () => {
await router.push("/");
- } catch (error: any) {
- expect(error.message).toContain("Avoided redundant navigation to current location");
- }
+ await router.push("/about");
+
+ expect(router.currentRoute.value.path).toBe("/about");
+
+ router.back();
+ await router.isReady();
+
+ expect(router.back).toBeDefined();
+ });
+
+ it("handles programmatic navigation with replace", async () => {
+ await router.push("/");
+ await router.replace("/about");
+ await router.isReady();
+
+ expect(router.currentRoute.value.path).toBe("/about");
+ });
+
+ it("matches routes case-sensitively", () => {
+ const routes = router.getRoutes();
+ const upperCaseRoute = routes.find(route => route.path === "/ABOUT");
+
+ expect(upperCaseRoute).toBeUndefined();
+ });
+
+ it("provides route metadata access", () => {
+ const routes = router.getRoutes();
+
+ for (const route of routes) {
+ expect(route).toHaveProperty("path");
+ expect(route).toHaveProperty("name");
+ expect(route).toHaveProperty("meta");
+ }
+ });
+
+ it("is ready after initialization", async () => {
+ const ready = await router.isReady();
+
+ // Note: isReady() returns void when resolved
+ expect(ready).toBeUndefined();
+ });
+
+ it("handles navigation to current route", async () => {
+ await router.push("/");
+
+ try {
+ await router.push("/");
+ } catch (error: any) {
+ expect(error.message).toContain("Avoided redundant navigation to current location");
+ }
+ });
});
+
+ for (const r of testRoutes) {
+ describe(r.name, () => {
+ it("has route", () => {
+ const routes = router.getRoutes();
+ const currentRoute = routes.find(route => route.path === r.path);
+
+ expect(currentRoute).toBeDefined();
+ expect(currentRoute?.name).toBe(r.name);
+ });
+
+ it("checks if route exists", () => {
+ expect(router.hasRoute(r.name)).toBe(true);
+ });
+
+ it("navigates to route", async () => {
+ await router.push(r.path);
+ await router.isReady();
+
+ expect(router.currentRoute.value.path).toBe(r.path);
+ expect(router.currentRoute.value.name).toBe(r.name);
+ });
+
+ it("navigates using route name", async () => {
+ await router.push({ name: r.name });
+ await router.isReady();
+
+ expect(router.currentRoute.value.name).toBe(r.name);
+ expect(router.currentRoute.value.path).toBe(r.path);
+ });
+
+ it("resolves route correctly", () => {
+ const resolved = router.resolve(r.path);
+
+ expect(resolved.name).toBe(r.name);
+ expect(resolved.path).toBe(r.path);
+ });
+
+ it("has (lazy) loaded component", () => {
+ const routes = router.getRoutes();
+ const currentRoute = routes.find(route => route.path === r.path);
+
+ if (r.lazyLoaded) {
+ expect(currentRoute?.components).toBeDefined();
+ } else {
+ expect(currentRoute?.components?.default).toBeDefined();
+ }
+ });
+ });
+ }
});
diff --git a/tests/views/SettingsView.spec.ts b/tests/views/SettingsView.spec.ts
new file mode 100644
index 0000000..1d41a29
--- /dev/null
+++ b/tests/views/SettingsView.spec.ts
@@ -0,0 +1,10 @@
+import { describe, expect, it } from "vitest";
+import { mount, VueWrapper } from "@vue/test-utils";
+import SettingView from "~/views/SettingView.vue";
+
+describe("SettingView.vue", () => {
+ const wrapper: VueWrapper = mount(SettingView, {});
+ it("renders without crashing", () => {
+ expect(wrapper.exists()).toBe(true);
+ });
+});