199 lines
7.0 KiB
TypeScript
199 lines
7.0 KiB
TypeScript
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");
|
|
});
|
|
});
|