feat: Phase 2+3 frontend coverage — responsive + accessibility tests + CI threshold
- Viewer accessibility: 5 tests (aria-labels, aria-expanded, label association, alert role) - Viewer responsive: 3 tests (chat-collapsed, CSS grid, now-playing aria-live) - Admin accessibility: 3 tests (nav aria-label, aria-current, focusability) - Admin responsive: 2 tests (tab visibility, essential tabs) - Coverage: vitest v8 provider, 40% stmt/lines, 30% branches/funcs - CI: test:coverage step between lint and build - Total: 44 frontend tests (31 + 13 new) - Updated TODO.md (Phase 2+3 done), DONE.md, MEMORY.md
This commit is contained in:
1 parent
9178b7b295
commit
d823763dc3
8 files changed
+168
-6
No files matched your search
@@ -9,6 +9,7 @@
|
||||
"preview": "vite preview",
|
||||
"lint": "tsc --noEmit",
|
||||
"test": "vitest run",
|
||||
"test:coverage": "vitest run --coverage",
|
||||
"test:watch": "vitest"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -271,3 +271,58 @@ file '/app/media/demo2.mp4'
|
||||
expect(list.innerHTML).toContain("demo1.mp4");
|
||||
});
|
||||
});
|
||||
|
||||
describe("admin accessibility", () => {
|
||||
beforeEach(() => {
|
||||
document.body.innerHTML = `
|
||||
<nav class="admin-nav" aria-label="Admin navigation">
|
||||
<button data-tab="dashboard" aria-current="page">Dashboard</button>
|
||||
<button data-tab="playlist">Playlist</button>
|
||||
<button data-tab="users">Users</button>
|
||||
<button data-tab="moderation">Moderation</button>
|
||||
</nav>
|
||||
<div id="tab-content"></div>
|
||||
`;
|
||||
});
|
||||
|
||||
it("nav has aria-label for screen readers", () => {
|
||||
const nav = document.querySelector(".admin-nav");
|
||||
expect(nav?.getAttribute("aria-label")).toBe("Admin navigation");
|
||||
});
|
||||
|
||||
it("active tab has aria-current attribute", () => {
|
||||
const active = document.querySelector("[aria-current]");
|
||||
expect(active).toBeTruthy();
|
||||
expect(active?.getAttribute("aria-current")).toBe("page");
|
||||
});
|
||||
|
||||
it("all nav buttons are focusable", () => {
|
||||
const buttons = document.querySelectorAll(".admin-nav button");
|
||||
buttons.forEach((btn) => {
|
||||
expect(btn.getAttribute("tabindex")).toBeNull();
|
||||
expect(btn.getAttribute("disabled")).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("admin responsive layout", () => {
|
||||
beforeEach(() => {
|
||||
document.body.innerHTML = `
|
||||
<nav class="admin-nav"><button data-tab="dashboard">Dashboard</button></nav>
|
||||
<div id="tab-content"><div id="overview-content">Overview data</div></div>
|
||||
`;
|
||||
});
|
||||
|
||||
it("tab content is present and visible", () => {
|
||||
const tabContent = document.getElementById("tab-content");
|
||||
expect(tabContent).toBeTruthy();
|
||||
const style = window.getComputedStyle(tabContent!);
|
||||
expect(style.display).not.toBe("none");
|
||||
});
|
||||
|
||||
it("admin nav contains essential tabs", () => {
|
||||
const buttons = document.querySelectorAll(".admin-nav button");
|
||||
const tabs = Array.from(buttons).map((b) => b.textContent?.trim());
|
||||
expect(tabs).toContain("Dashboard");
|
||||
});
|
||||
});
|
||||
@@ -371,3 +371,68 @@ describe("player error recovery", () => {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("accessibility", () => {
|
||||
it("player video has controls and aria attributes", () => {
|
||||
const video = document.getElementById("player") as HTMLVideoElement;
|
||||
expect(video.controls).toBe(true);
|
||||
expect(video.hasAttribute("autoplay")).toBe(true);
|
||||
expect(video.hasAttribute("playsinline")).toBe(true);
|
||||
});
|
||||
|
||||
it("chat panel has aria-labels for live region", () => {
|
||||
const messages = document.getElementById("messages");
|
||||
expect(messages?.getAttribute("aria-live")).toBe("polite");
|
||||
|
||||
const chatPanel = document.getElementById("chat-panel");
|
||||
expect(chatPanel?.getAttribute("aria-label")).toBe("Chat panel");
|
||||
});
|
||||
|
||||
it("chat toggle exposes aria-expanded state", () => {
|
||||
const toggle = document.getElementById("chat-toggle")!;
|
||||
expect(toggle.getAttribute("aria-expanded")).toBe("true");
|
||||
|
||||
window.toggleChatPanel();
|
||||
expect(toggle.getAttribute("aria-expanded")).toBe("false");
|
||||
});
|
||||
|
||||
it("message input has associated label", () => {
|
||||
const input = document.getElementById("message-input") as HTMLInputElement;
|
||||
expect(input.getAttribute("type")).toBe("text");
|
||||
expect(input.placeholder).toBe("Type a message...");
|
||||
const label = document.querySelector("label[for='message-input']");
|
||||
expect(label).toBeTruthy();
|
||||
expect(label?.textContent).toBe("Message input");
|
||||
});
|
||||
|
||||
it("reconnecting overlay is an aria-live assertive region", () => {
|
||||
const overlay = document.getElementById("reconnecting-overlay");
|
||||
expect(overlay?.getAttribute("role")).toBe("alert");
|
||||
expect(overlay?.getAttribute("aria-live")).toBe("assertive");
|
||||
});
|
||||
});
|
||||
|
||||
describe("responsive layout", () => {
|
||||
it("chat-collapsed class hides sidebar at narrow viewport", () => {
|
||||
const shell = document.querySelector(".viewer-shell")!;
|
||||
expect(shell.classList.contains("chat-collapsed")).toBe(false);
|
||||
|
||||
window.toggleChatPanel();
|
||||
expect(shell.classList.contains("chat-collapsed")).toBe(true);
|
||||
|
||||
window.toggleChatPanel();
|
||||
expect(shell.classList.contains("chat-collapsed")).toBe(false);
|
||||
});
|
||||
|
||||
it("viewer-shell uses CSS grid for content layout", () => {
|
||||
const content = document.querySelector(".viewer-content");
|
||||
expect(content).toBeTruthy();
|
||||
const style = window.getComputedStyle(content!);
|
||||
expect(style.display).toBe("grid");
|
||||
});
|
||||
|
||||
it("now-playing overlay is accessible at small sizes", () => {
|
||||
const np = document.getElementById("now-playing");
|
||||
expect(np?.getAttribute("aria-live")).toBe("polite");
|
||||
});
|
||||
});
|
||||
@@ -4,5 +4,17 @@ export default defineConfig({
|
||||
test: {
|
||||
environment: "jsdom",
|
||||
include: ["src/**/*.test.ts"],
|
||||
coverage: {
|
||||
provider: "v8",
|
||||
reporter: ["text", "text-summary"],
|
||||
include: ["src/**/*.ts"],
|
||||
exclude: ["src/**/*.test.ts"],
|
||||
thresholds: {
|
||||
statements: 40,
|
||||
branches: 30,
|
||||
functions: 30,
|
||||
lines: 40,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
Reference in new issue
Block a user