/**
* 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();
});
});