From 98144ab4ff7e734c5fbe4485951b76a64615af0f Mon Sep 17 00:00:00 2001 From: saravanakumardb1 Date: Tue, 10 Mar 2026 09:28:14 -0700 Subject: [PATCH] test(web): add shell and navigation coverage --- docs/roadmaps/03_WEB_ROADMAP.md | 11 +++++++--- web/src/components/AppShell.test.tsx | 23 +++++++++++++++++++++ web/src/components/Sidebar.test.tsx | 30 ++++++++++++++++++++++++++++ web/src/test/setupTests.ts | 1 + web/vitest.config.ts | 15 ++++++++++++++ 5 files changed, 77 insertions(+), 3 deletions(-) create mode 100644 web/src/components/AppShell.test.tsx create mode 100644 web/src/components/Sidebar.test.tsx create mode 100644 web/src/test/setupTests.ts create mode 100644 web/vitest.config.ts diff --git a/docs/roadmaps/03_WEB_ROADMAP.md b/docs/roadmaps/03_WEB_ROADMAP.md index 1b543d5..7c7c3a5 100644 --- a/docs/roadmaps/03_WEB_ROADMAP.md +++ b/docs/roadmaps/03_WEB_ROADMAP.md @@ -58,7 +58,7 @@ Stack: Next.js 16 + React 19 + TypeScript - [ ] Performance pass - [ ] Accessibility pass - [ ] Token validation pass -- [ ] Production build passes +- [x] Production build passes - [ ] UX polish pass # High-Collision Areas @@ -108,6 +108,13 @@ Stack: Next.js 16 + React 19 + TypeScript - stronger focus-visible treatment for interactive controls - clearer active-nav semantics via `aria-current` - keyboard/accessibility guidance surfaced in navigation/settings + - Added the first web UI test harness and coverage for: + - shared `AppShell` skip-link/main landmark behavior + - shared `Sidebar` primary-nav and active-page semantics + - Verified `web/` with: + - `npm test` + - `npm run typecheck` + - `npm run build` # Open Questions @@ -139,8 +146,6 @@ Stack: Next.js 16 + React 19 + TypeScript - Extraction-backed task review flows - Backend-backed agent activity timeline, approval queue, proposal diff review, and audit filtering - Remaining dense/accessibility polish and performance hardening -- Remaining verification: - - run `npm test` # Done When diff --git a/web/src/components/AppShell.test.tsx b/web/src/components/AppShell.test.tsx new file mode 100644 index 0000000..dee4345 --- /dev/null +++ b/web/src/components/AppShell.test.tsx @@ -0,0 +1,23 @@ +import { render, screen } from "@testing-library/react"; +import { describe, expect, it, vi } from "vitest"; +import { AppShell } from "./AppShell"; + +vi.mock("@/components/Sidebar", () => ({ + Sidebar: () =>
Sidebar
, +})); + +describe("AppShell", () => { + it("renders skip link, page title, actions, and main content landmark", () => { + render( + Run}> +
Results
+
+ ); + + expect(screen.getByRole("link", { name: "Skip to main content" })).toHaveAttribute("href", "#main-content"); + expect(screen.getByRole("heading", { level: 1, name: "Search" })).toBeInTheDocument(); + expect(screen.getByRole("button", { name: "Run" })).toBeInTheDocument(); + expect(screen.getByRole("main")).toHaveAttribute("id", "main-content"); + expect(screen.getByText("Results")).toBeInTheDocument(); + }); +}); diff --git a/web/src/components/Sidebar.test.tsx b/web/src/components/Sidebar.test.tsx new file mode 100644 index 0000000..2ad1c2a --- /dev/null +++ b/web/src/components/Sidebar.test.tsx @@ -0,0 +1,30 @@ +import { render, screen } from "@testing-library/react"; +import { describe, expect, it, vi } from "vitest"; +import { Sidebar } from "./Sidebar"; + +const usePathnameMock = vi.fn(); + +vi.mock("next/navigation", () => ({ + usePathname: () => usePathnameMock(), +})); + +vi.mock("next/link", () => ({ + default: ({ href, children, ...props }: React.ComponentProps<"a"> & { href: string }) => ( + + {children} + + ), +})); + +describe("Sidebar", () => { + it("marks the active navigation item and exposes primary navigation landmarks", () => { + usePathnameMock.mockReturnValue("/search"); + + render(); + + expect(screen.getByLabelText("Primary")).toBeInTheDocument(); + expect(screen.getByRole("navigation", { name: "Primary navigation" })).toBeInTheDocument(); + expect(screen.getByRole("link", { name: "Search" })).toHaveAttribute("aria-current", "page"); + expect(screen.getByText("Keyboard flow")).toBeInTheDocument(); + }); +}); diff --git a/web/src/test/setupTests.ts b/web/src/test/setupTests.ts new file mode 100644 index 0000000..f149f27 --- /dev/null +++ b/web/src/test/setupTests.ts @@ -0,0 +1 @@ +import "@testing-library/jest-dom/vitest"; diff --git a/web/vitest.config.ts b/web/vitest.config.ts new file mode 100644 index 0000000..af1831d --- /dev/null +++ b/web/vitest.config.ts @@ -0,0 +1,15 @@ +import path from "node:path"; +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + environment: "jsdom", + setupFiles: ["./src/test/setupTests.ts"], + globals: true, + }, + resolve: { + alias: { + "@": path.resolve(__dirname, "./src"), + }, + }, +});