diff --git a/src/app/docs/page.tsx b/src/app/docs/page.tsx index 6769a87..ab17d1d 100644 --- a/src/app/docs/page.tsx +++ b/src/app/docs/page.tsx @@ -1,5 +1,7 @@ import { PageShell } from "@/components/PageShell"; +import { CurlBlock } from "@/components/CurlBlock"; import { messages } from "@/lib/messages"; +import { resolveApiBase } from "@/lib/resolveApiBase"; export const metadata = { title: "Docs — AgentPay" }; diff --git a/src/components/__tests__/Header.test.tsx b/src/components/__tests__/Header.test.tsx index 54d7962..faee338 100644 --- a/src/components/__tests__/Header.test.tsx +++ b/src/components/__tests__/Header.test.tsx @@ -53,14 +53,13 @@ describe("Header", () => { mockPathname.mockReturnValue("/"); render(
); - // Exactly one link has aria-current="page" + // Exactly one link has aria-current="page". The mobile menu panel is only + // rendered while open, so at rest only the desktop "Home" link is active. const activeLinks = screen.getAllByRole("link").filter( (link) => link.getAttribute("aria-current") === "page" ); - // Home link is active twice (desktop + mobile) - expect(activeLinks.length).toBe(2); + expect(activeLinks.length).toBe(1); expect(activeLinks[0]).toHaveTextContent("Home"); - expect(activeLinks[1]).toHaveTextContent("Home"); }); it("marks zero links as active for an unknown route", () => { @@ -80,14 +79,15 @@ describe("Header", () => { // Open the secondary menu to expose secondary links fireEvent.click(screen.getByRole("button", { name: /more/i })); - // The active links should strictly be the desktop "Services" and the mobile "Services" + // With the mobile panel closed, the only current link is the desktop + // "Services" primary link; the opened "More" menu holds secondary links, + // none of which match /services. const activeLinks = screen.getAllByRole("link", { hidden: true }).filter( (link) => link.getAttribute("aria-current") === "page" ); - - expect(activeLinks.length).toBe(2); + + expect(activeLinks.length).toBe(1); expect(activeLinks[0]).toHaveTextContent("Services"); - expect(activeLinks[1]).toHaveTextContent("Services"); }); it("shows More button that opens secondary menu", () => { diff --git a/src/components/__tests__/Tooltip.test.tsx b/src/components/__tests__/Tooltip.test.tsx index a7b1290..72cc7ce 100644 --- a/src/components/__tests__/Tooltip.test.tsx +++ b/src/components/__tests__/Tooltip.test.tsx @@ -1,6 +1,22 @@ import { act, fireEvent, render, screen, waitFor } from "@testing-library/react"; import { Tooltip } from "../Tooltip"; +/** + * Locks down the WCAG 2.1 SC 1.4.13 (Content on Hover or Focus) contract that + * Tooltip.tsx documents: show on hover/focus, stay hoverable, link the trigger + * via aria-describedby only while visible, and dismiss on Escape without moving + * focus. + * + * DOM shape rendered by Tooltip: + * span.wrapper ← owns the hover/focus/keydown handlers + * └ span[aria-describedby?] ← trigger's direct parent + * └ {children} ← the focusable trigger + * └ span[role="tooltip"] ← only mounted while visible + * + * Event note: React 19 listens for focus/blur via focusin/focusout and for + * mouseenter/mouseleave via mouseover/mouseout, so the tests fire those native + * events. A real .focus() is used where document.activeElement matters. + */ const renderTooltip = () => render( @@ -8,103 +24,153 @@ const renderTooltip = () => ); -// DOM shape: wrapper > [ describedBy-span >