/** * Sidebar 單元測試 * * 重點: * 1. isNavActive() 純函式行為(edge cases:根路徑嚴格比對、子路徑 startsWith) * 2. 當前路徑的 NavItem 帶 aria-current="page" * 3. 非當前路徑的 NavItem 沒有 aria-current * 4. 導航連結數量符合 pages.md 總覽(6 項) * * 需要 mock next/navigation 的 usePathname,否則 jsdom 環境會拋錯。 */ import { render, screen } from "@testing-library/react"; import { describe, expect, it, vi } from "vitest"; import { LocaleProvider } from "@/lib/i18n/context"; import { Sidebar, isNavActive } from "./sidebar"; // mock usePathname — 每個測試自行控制回傳值 vi.mock("next/navigation", () => ({ usePathname: vi.fn(), })); // 取得可控制的 mock 引用 import { usePathname } from "next/navigation"; const usePathnameMock = vi.mocked(usePathname); describe("isNavActive", () => { it("根路徑 '/' 嚴格比對(不應被任何子路徑啟用)", () => { expect(isNavActive("/", "/")).toBe(true); expect(isNavActive("/", "/devices")).toBe(false); expect(isNavActive("/", "/devices/pair")).toBe(false); }); it("非根路徑:完全相等或子路徑都 active", () => { expect(isNavActive("/devices", "/devices")).toBe(true); expect(isNavActive("/devices", "/devices/pair")).toBe(true); expect(isNavActive("/devices", "/devices/123")).toBe(true); }); it("prefix 相同但非子路徑不應 active(避免 /device 啟用 /devices)", () => { // '/dev' 不應啟用 '/devices' 項目 expect(isNavActive("/devices", "/dev")).toBe(false); // '/devicesxyz' 也不是 /devices 的子路徑(沒有 / 接續) expect(isNavActive("/devices", "/devicesxyz")).toBe(false); }); it("不相關的路徑應為 false", () => { expect(isNavActive("/models", "/devices")).toBe(false); expect(isNavActive("/settings", "/")).toBe(false); }); }); describe("", () => { it("在 /devices/pair 時,Devices 項目被標記為 aria-current=page", () => { usePathnameMock.mockReturnValue("/devices/pair"); render( , ); const devicesLink = screen.getByRole("link", { name: /裝置/ }); expect(devicesLink).toHaveAttribute("aria-current", "page"); // Dashboard 不該 active const dashboardLink = screen.getByRole("link", { name: /儀表板/ }); expect(dashboardLink).not.toHaveAttribute("aria-current"); }); it("在根路徑 '/' 時僅 Dashboard active", () => { usePathnameMock.mockReturnValue("/"); render( , ); const dashboardLink = screen.getByRole("link", { name: /儀表板/ }); expect(dashboardLink).toHaveAttribute("aria-current", "page"); // 任一非根項目都不 active expect(screen.getByRole("link", { name: /裝置/ })).not.toHaveAttribute( "aria-current", ); }); it("包含 pages.md 總覽規定的 6 個主導航項目", () => { usePathnameMock.mockReturnValue("/"); render( , ); // nav 內部的 link 數 = 6(不含品牌 logo link) const nav = screen.getByRole("navigation"); const links = nav.querySelectorAll("a"); expect(links).toHaveLength(6); // 抽樣:確保 clusters 已納入(雲端版新增) expect(screen.getByRole("link", { name: /叢集/ })).toBeInTheDocument(); }); });