import { mount } from "@vue/test-utils"; import { describe, expect, it } from "vitest"; import ContactDestination from "@/components/ContactDestination.vue"; import InfrastructureDiagram from "@/components/InfrastructureDiagram.vue"; import ProjectCaseStudy from "@/components/ProjectCaseStudy.vue"; import ToolkitGrid from "@/components/ToolkitGrid.vue"; import type { ContactContent, InfrastructureCaseStudy, PublicProject, ToolkitGroup, } from "@/content/types"; const infrastructure: InfrastructureCaseStudy = { id: "safe-infrastructure", title: "Secure self-hosted platform and lifecycle", summary: "A compact infrastructure case study.", bands: [ { id: "ingress-tls", title: "Ingress and TLS", summary: "Managed encrypted ingress.", }, { id: "identity-access", title: "Identity and access", summary: "Protected service access.", }, { id: "service-lifecycle", title: "Service lifecycle", summary: "Managed container lifecycle.", }, { id: "operations", title: "Operations", summary: "Routine maintenance and investigation.", }, ], featuredOperation: { title: "Synthetic stateful service migration", context: "A representative migration preserved application state.", actions: ["Prepared versions.", "Created recovery checkpoints.", "Validated the new service."], outcome: "The synthetic migration retained expected data.", }, }; const privateProject: PublicProject = { id: "private-work", title: "Private self-hosted medication tracker", visibility: "private", maturity: "active", summary: "A private application.", problem: "A recurring workflow needs reliable tracking.", contribution: "Designed and maintained the application.", technicalDecisions: ["Used a synchronised client and server design."], outcome: "The work remains private.", assetIds: [], }; const publicProject: PublicProject = { ...privateProject, id: "public-work", title: "Footy Tracker", visibility: "public", url: "https://github.com/example/footy-tracker", }; describe("semantic section components", () => { it("gives the infrastructure graphic an accessible name, description, caption, and visible text equivalent", () => { const wrapper = mount(InfrastructureDiagram, { props: { infrastructure } }); const flow = wrapper.get('[data-testid="infrastructure-flow"]'); expect(flow.attributes("role")).toBe("img"); expect(flow.attributes("aria-labelledby")).toBe( "safe-infrastructure-diagram-title", ); expect(flow.attributes("aria-describedby")).toBe( "safe-infrastructure-diagram-description", ); expect(wrapper.get("figcaption").text()).toContain( "Secure self-hosted platform and lifecycle", ); expect( wrapper.get('[data-testid="infrastructure-text-equivalent"]').text(), ).toContain("Ingress and TLS"); expect( wrapper.get('[data-testid="infrastructure-text-equivalent"]').text(), ).toContain("Identity and access"); }); it("renders the real infrastructure route with explicit trust boundaries and operational support", () => { const wrapper = mount(InfrastructureDiagram, { props: { infrastructure } }); const flow = wrapper.get('[data-testid="infrastructure-flow"]'); expect( flow .findAll("[data-infrastructure-node]") .map((node) => node.attributes("data-infrastructure-node")), ).toEqual([ "visitor", "nginx-tls", "authentication", "containerized-services", "operations", ]); expect( flow.findAll("[data-trust-boundary]").map((boundary) => boundary.text()), ).toEqual(["Public edge", "Access boundary", "Service boundary"]); expect( wrapper.get('[aria-label="Infrastructure diagram legend"]').text(), ).toContain("Trust boundary"); expect( wrapper.get('[aria-label="Infrastructure diagram legend"]').text(), ).toContain("Request flow"); expect( wrapper.get('[data-testid="infrastructure-text-equivalent"]').text(), ).toContain("Boundary labels mark changes in trust or access rules"); expect(flow.text()).toContain("fail2ban guard"); }); it("does not turn private project content into a link container", () => { const wrapper = mount(ProjectCaseStudy, { props: { project: privateProject, priority: "primary" }, }); expect(wrapper.get("article").findAll("a")).toHaveLength(0); expect(wrapper.get("h3").text()).toBe( "Private self-hosted medication tracker", ); expect(wrapper.get("article").attributes("data-project-priority")).toBe( "primary", ); }); it("uses a safe external link only for a public project with an approved URL", () => { const wrapper = mount(ProjectCaseStudy, { props: { project: publicProject, priority: "supporting" }, }); const link = wrapper.get("article a"); expect(link.attributes("href")).toBe( "https://github.com/example/footy-tracker", ); expect(link.attributes("target")).toBe("_blank"); expect(link.attributes("rel")).toBe("noopener noreferrer"); }); it("announces every toolkit technology once while hiding the decorative icon placeholder", () => { const groups: readonly ToolkitGroup[] = [ { title: "Systems & delivery", technologies: [{ name: "Linux" }], }, ]; const wrapper = mount(ToolkitGrid, { props: { groups } }); const tile = wrapper.get('[data-testid="toolkit-technology"]'); expect(tile.text()).toBe("Linux"); expect(tile.get('[aria-hidden="true"]').attributes("aria-hidden")).toBe( "true", ); expect(tile.findAll('[aria-label="Linux"]').length).toBe(0); }); it("renders a toolkit concept with a decorative vector symbol", () => { const wrapper = mount(ToolkitGrid, { props: { groups: [{ title: "Systems", technologies: [{ name: "DNS" }] }] }, }); const tile = wrapper.get('[data-testid="toolkit-technology"]'); expect(tile.text()).toBe("DNS"); expect(tile.find("svg").exists()).toBe(true); expect(tile.get("svg").attributes('aria-hidden')).toBe('true'); }); it("keeps email behind an interaction and renders safe social links", () => { const contact: ContactContent = { introduction: "Open to System Administrator and Full-Stack Developer opportunities.", email: { localPart: "hello", domain: "example.test" }, cvMessage: "For a current CV or role-specific information, contact me directly.", social: [{ service: "GitHub", url: "https://github.com/example" }], }; const wrapper = mount(ContactDestination, { props: { contact, }, }); expect(wrapper.find('a[href^="mailto:"]').exists()).toBe(false); expect(wrapper.get('summary').text()).toBe('Email Me'); expect(wrapper.text()).toContain(contact.introduction); const social = wrapper.get('a[href="https://github.com/example"]'); expect(social.text()).toBe("GitHub"); expect(social.attributes("rel")).toBe("me noopener noreferrer"); }); });