Moved the navbar into self contained component
This commit is contained in:
parent
55d4781dde
commit
c5abac2029
4 changed files with 174 additions and 27 deletions
|
|
@ -32,6 +32,7 @@
|
||||||
"@vue/tsconfig": "^0.7.0",
|
"@vue/tsconfig": "^0.7.0",
|
||||||
"jsdom": "^26.1.0",
|
"jsdom": "^26.1.0",
|
||||||
"npm-run-all2": "^8.0.4",
|
"npm-run-all2": "^8.0.4",
|
||||||
|
"resize-observer-polyfill": "^1.5.1",
|
||||||
"sass-embedded": "^1.92.1",
|
"sass-embedded": "^1.92.1",
|
||||||
"typescript": "~5.8.0",
|
"typescript": "~5.8.0",
|
||||||
"vite": "^7.0.6",
|
"vite": "^7.0.6",
|
||||||
|
|
|
||||||
30
src/App.vue
30
src/App.vue
|
|
@ -1,35 +1,11 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { RouterLink, RouterView } from "vue-router";
|
import { RouterView } from "vue-router";
|
||||||
|
import HeaderNavBar from "./components/HeaderNavBar.vue";
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<v-app>
|
<v-app>
|
||||||
<v-app-bar fixed app>
|
<HeaderNavBar />
|
||||||
<v-container class="d-flex align-center pa-0">
|
|
||||||
<div class="d-flex align-center">
|
|
||||||
<RouterLink class="mx-2 text-decoration-none" to="/">
|
|
||||||
<v-btn
|
|
||||||
variant="outlined"
|
|
||||||
color="primary"
|
|
||||||
size="large"
|
|
||||||
prepend-icon="mdi-home-outline"
|
|
||||||
>
|
|
||||||
Home
|
|
||||||
</v-btn>
|
|
||||||
</RouterLink>
|
|
||||||
<RouterLink class="mx-2 text-decoration-none" to="/about">
|
|
||||||
<v-btn
|
|
||||||
variant="outlined"
|
|
||||||
color="primary"
|
|
||||||
size="large"
|
|
||||||
prepend-icon="mdi-chat-question-outline"
|
|
||||||
>
|
|
||||||
About
|
|
||||||
</v-btn>
|
|
||||||
</RouterLink>
|
|
||||||
</div>
|
|
||||||
</v-container>
|
|
||||||
</v-app-bar>
|
|
||||||
|
|
||||||
<v-main>
|
<v-main>
|
||||||
<v-container class="align-start fill-height">
|
<v-container class="align-start fill-height">
|
||||||
|
|
|
||||||
32
src/components/HeaderNavBar.vue
Normal file
32
src/components/HeaderNavBar.vue
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { RouterLink } from "vue-router";
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<v-app-bar fixed app>
|
||||||
|
<v-container class="d-flex align-center pa-0">
|
||||||
|
<div class="d-flex align-center">
|
||||||
|
<RouterLink class="mx-2 text-decoration-none" to="/" data-role="homeNavigation" v-slot="{ isActive }">
|
||||||
|
<v-btn
|
||||||
|
:variant="isActive ? 'elevated' : 'outlined'"
|
||||||
|
color="primary"
|
||||||
|
size="large"
|
||||||
|
prepend-icon="mdi-home-outline"
|
||||||
|
>
|
||||||
|
Home
|
||||||
|
</v-btn>
|
||||||
|
</RouterLink>
|
||||||
|
<RouterLink class="mx-2 text-decoration-none" to="/about" data-role="aboutNavigation" v-slot="{ isActive }">
|
||||||
|
<v-btn
|
||||||
|
:variant="isActive ? 'elevated' : 'outlined'"
|
||||||
|
color="primary"
|
||||||
|
size="large"
|
||||||
|
prepend-icon="mdi-chat-question-outline"
|
||||||
|
>
|
||||||
|
About
|
||||||
|
</v-btn>
|
||||||
|
</RouterLink>
|
||||||
|
</div>
|
||||||
|
</v-container>
|
||||||
|
</v-app-bar>
|
||||||
|
</template>
|
||||||
138
tests/components/HeardeNavBar.spec.ts
Normal file
138
tests/components/HeardeNavBar.spec.ts
Normal file
|
|
@ -0,0 +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"
|
||||||
|
|
||||||
|
global.ResizeObserver = require('resize-observer-polyfill');
|
||||||
|
|
||||||
|
// Mock routes for testing
|
||||||
|
const routes = [
|
||||||
|
{ path: "/", component: { template: "<div>Home</div>" } },
|
||||||
|
{ path: "/about", component: { template: "<div>About</div>" } }
|
||||||
|
]
|
||||||
|
|
||||||
|
const router = createRouter({
|
||||||
|
history: createWebHistory(),
|
||||||
|
routes
|
||||||
|
});
|
||||||
|
|
||||||
|
const vuetify = createVuetify({
|
||||||
|
components,
|
||||||
|
directives,
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("HeaderNavBar", () => {
|
||||||
|
let wrapper: any
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
wrapper = mount({
|
||||||
|
template: `
|
||||||
|
<v-app>
|
||||||
|
<v-layout>
|
||||||
|
<HeaderNavBar />
|
||||||
|
</v-layout>
|
||||||
|
</v-app>
|
||||||
|
`,
|
||||||
|
}, {
|
||||||
|
global: {
|
||||||
|
components: {
|
||||||
|
HeaderNavBar,
|
||||||
|
},
|
||||||
|
plugins: [router, vuetify]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("renders the component", () => {
|
||||||
|
expect(wrapper.exists()).toBe(true)
|
||||||
|
expect(wrapper.findComponent({ name: "VAppBar" }).exists()).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("renders both navigation buttons", () => {
|
||||||
|
const homeButton = wrapper.find("[data-role='homeNavigation']");
|
||||||
|
const aboutButton = wrapper.find("[data-role='aboutNavigation']");
|
||||||
|
|
||||||
|
expect(homeButton.exists()).toBe(true);
|
||||||
|
expect(aboutButton.exists()).toBe(true);
|
||||||
|
expect(homeButton.text()).toContain("Home");
|
||||||
|
expect(aboutButton.text()).toContain("About");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("has correct icons on buttons", () => {
|
||||||
|
const homeButton = wrapper.find("[data-role='homeNavigation']");
|
||||||
|
const aboutButton = wrapper.find("[data-role='aboutNavigation']");
|
||||||
|
|
||||||
|
expect(homeButton.find("i").attributes("class")).toContain("mdi-home-outline");
|
||||||
|
expect(aboutButton.find("i").attributes("class")).toContain("mdi-chat-question-outline");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("has correct RouterLink paths", () => {
|
||||||
|
const routerLinks = wrapper.findAllComponents({ name: "RouterLink" });
|
||||||
|
|
||||||
|
expect(routerLinks).toHaveLength(2);
|
||||||
|
expect(routerLinks[0].props("to")).toBe("/");
|
||||||
|
expect(routerLinks[1].props("to")).toBe("/about");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("applies correct button variants based on active route", async () => {
|
||||||
|
// Navigate to home route
|
||||||
|
await router.push("/")
|
||||||
|
await wrapper.vm.$nextTick()
|
||||||
|
|
||||||
|
// Check home button is elevated (active) and about is outlined (inactive)
|
||||||
|
const homeButton = wrapper.find("[data-role='homeNavigation']");
|
||||||
|
const aboutButton = wrapper.find("[data-role='aboutNavigation']");
|
||||||
|
|
||||||
|
expect(homeButton.find("button").attributes("class")).toContain("elevated");
|
||||||
|
expect(aboutButton.find("button").attributes("class")).toContain("outlined");
|
||||||
|
|
||||||
|
// // Navigate to about route
|
||||||
|
await router.push("/about");
|
||||||
|
await wrapper.vm.$nextTick();
|
||||||
|
|
||||||
|
// // Check about button is now elevated and home is outlined
|
||||||
|
expect(homeButton.find("button").attributes("class")).toContain("outlined");
|
||||||
|
expect(aboutButton.find("button").attributes("class")).toContain("elevated");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("has correct button styling classes", () => {
|
||||||
|
const routerLinks = wrapper.findAllComponents({ name: "RouterLink" });
|
||||||
|
|
||||||
|
routerLinks.forEach((link: any) => {
|
||||||
|
expect(link.classes()).toContain("mx-2")
|
||||||
|
expect(link.classes()).toContain("text-decoration-none")
|
||||||
|
});
|
||||||
|
|
||||||
|
const buttons = wrapper.findAllComponents({ name: "v-btn" });
|
||||||
|
buttons.forEach((button: any) => {
|
||||||
|
expect(button.attributes("class")).toContain("v-btn--size-large");
|
||||||
|
expect(button.attributes("class")).toContain("-primary");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("has proper app bar structure", () => {
|
||||||
|
const appBar = wrapper.findComponent({ name: "VAppBar" });
|
||||||
|
const container = wrapper.findComponent({ name: "VContainer" });
|
||||||
|
|
||||||
|
expect(appBar.attributes("fixed")).toBe("");
|
||||||
|
expect(appBar.attributes("app")).toBe("");
|
||||||
|
expect(container.classes()).toContain("d-flex");
|
||||||
|
expect(container.classes()).toContain("align-center");
|
||||||
|
expect(container.classes()).toContain("pa-0");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("maintains accessibility attributes", () => {
|
||||||
|
const homeButton = wrapper.find("[data-role='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)
|
||||||
|
buttons.forEach((button: any) => {
|
||||||
|
expect(button.element.tagName).toBe("BUTTON");
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
Loading…
Add table
Add a link
Reference in a new issue