From 0bbf9bb34e1d8195ac57e2ccaae299a365200129 Mon Sep 17 00:00:00 2001 From: waterWang Date: Sun, 2 Aug 2026 17:36:59 +0800 Subject: [PATCH] feat: add maxLabelLength truncation to Header breadcrumb for long labels Adds maxLabelLength={28} to the Breadcrumb in Header so that the first and last visible breadcrumb labels are truncated with a middle-ellipsis when they exceed 28 characters. The full label is preserved in title and aria-label for accessibility. - Add maxLabelLength={28} to Breadcrumb in Header.tsx - Add 3 tests for maxLabelLength truncation in Header.test.tsx Closes #947 --- src/pages/Header.test.tsx | 80 +++++++++++++++++++++++++++++++++++++++ src/pages/Header.tsx | 2 +- 2 files changed, 81 insertions(+), 1 deletion(-) diff --git a/src/pages/Header.test.tsx b/src/pages/Header.test.tsx index 52cdd1e..d4dece8 100644 --- a/src/pages/Header.test.tsx +++ b/src/pages/Header.test.tsx @@ -224,4 +224,84 @@ describe("Header – GrantFox FWC26", () => { const ellipsis = container.querySelector(".breadcrumb-ellipsis"); expect(ellipsis).not.toBeNull(); }); + + // ── maxLabelLength truncation tests ───────────────────────────────────── + // + // The Header passes maxLabelLength={28} to Breadcrumb, so long first and + // last crumb labels are truncated with a middle-ellipsis. These tests + // verify that the truncation is visible and that the full label is + // preserved in `title` / `aria-label`. + + it("truncates the first crumb label when it exceeds maxLabelLength", () => { + const longItems: BreadcrumbItem[] = [ + { + label: "A Very Long Root Segment Name That Keeps Going", + href: "/very-long-root-segment", + }, + { + label: "Short", + href: "/very-long-root-segment/short", + isCurrent: true, + }, + ]; + + render(
); + + const link = screen.getByRole("link", { + name: "A Very Long Root Segment Name That Keeps Going", + }); + // Visual text should be truncated (contain …) + expect(link.textContent).toContain("\u2026"); + // Full label preserved in title and aria-label + expect(link.getAttribute("title")).toBe( + "A Very Long Root Segment Name That Keeps Going", + ); + expect(link.getAttribute("aria-label")).toBe( + "A Very Long Root Segment Name That Keeps Going", + ); + }); + + it("truncates the current-page crumb label when it exceeds maxLabelLength", () => { + const longItems: BreadcrumbItem[] = [ + { + label: "Home", + href: "/", + }, + { + label: "A Very Long Current Page Title Indeed That Should Be Truncated", + href: "/long", + isCurrent: true, + }, + ]; + + render(
); + + const current = document.querySelector( + '[aria-current="page"]', + ); + expect(current).not.toBeNull(); + // Visual text should be truncated + expect(current?.textContent).toContain("\u2026"); + // Full label preserved in title and aria-label + expect(current?.getAttribute("title")).toBe( + "A Very Long Current Page Title Indeed That Should Be Truncated", + ); + expect(current?.getAttribute("aria-label")).toBe( + "A Very Long Current Page Title Indeed That Should Be Truncated", + ); + }); + + it("does not truncate short crumb labels", () => { + const shortItems: BreadcrumbItem[] = [ + { label: "Home", href: "/" }, + { label: "Settings", href: "/settings", isCurrent: true }, + ]; + + render(
); + + const link = screen.getByRole("link", { name: "Home" }); + expect(link.textContent).toBe("Home"); + // No aria-label override when not truncated + expect(link.getAttribute("aria-label")).toBeNull(); + }); }); \ No newline at end of file diff --git a/src/pages/Header.tsx b/src/pages/Header.tsx index 2ec9a30..8ee2878 100644 --- a/src/pages/Header.tsx +++ b/src/pages/Header.tsx @@ -40,7 +40,7 @@ export default function Header({ breadcrumbItems }: HeaderProps) {
- +
);