// @vitest-environment happy-dom import { act } from "react"; import { createRoot, type Root } from "react-dom/client"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { AnalyticsDashboard } from "./analytics-dashboard"; import type { AnalyticsItem, PublicAnalytics } from "@/lib/analytics/model"; // Layout measurements belong to browser verification; the table exposes the same counts. vi.mock("@/components/ui/chart", () => ({ ChartContainer: () => null, ChartTooltip: () => null, ChartTooltipContent: () => null, })); const item = (key: string, guideCount: number, stygianCount: number): AnalyticsItem => ({ key, name: key, imageUrl: null, rarity: null, guideCount, stygianCount, contributions: [{ guideId: "g0", teamSignature: "guide-team", count: guideCount }, { guideId: "s0", teamSignature: "stygian-team", count: stygianCount }], }); const entries = [item("Shared", 2, 5), item("Stygian only", 0, 3)]; const data: PublicAnalytics = { guides: [ ...Array.from({ length: 4 }, (_, index) => ({ id: `g${index}`, source: "guide" as const, name: `Guide ${index}`, character: null })), ...Array.from({ length: 2 }, (_, index) => ({ id: `s${index}`, source: "stygian" as const, name: `Stygian ${index}`, character: null })), ], teamGroups: [{ guideId: "g0", signature: "guide-team", count: 1 }, { guideId: "s0", signature: "stygian-team", count: 1 }], guideCount: 4, stygianScheduleCount: 2, guideTeamCount: 6, stygianTeamCount: 3, team: { character: entries, weapon: entries, artifact: entries }, recommendation: { weapon: entries, artifact: entries }, }; let root: Root; let container: HTMLDivElement; beforeEach(() => { vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true); container = document.createElement("div"); document.body.append(container); root = createRoot(container); }); afterEach(async () => { await act(async () => root.unmount()); container.remove(); vi.unstubAllGlobals(); }); describe("public analytics dashboard", () => { it("starts with Stygian enabled and updates all three team rankings and totals on toggle", async () => { await act(async () => root.render()); const toggle = container.querySelector('[role="switch"][aria-labelledby="include-stygian-label"]')!; expect(toggle.getAttribute("aria-checked")).toBe("true"); expect(container.querySelector(`#${toggle.getAttribute("aria-labelledby")}`)?.textContent).toBe("รวมข้อมูล Stygian"); expect(container.querySelectorAll("details[data-ranking]")).toHaveLength(3); expect(container.querySelectorAll("tbody tr")).toHaveLength(6); expect(container.querySelector('[aria-label="ภาพรวมข้อมูล"]')?.textContent).toContain("ทีมทั้งหมด2"); expect(Array.from(container.querySelectorAll("tbody tr:first-child td:last-child"), (cell) => cell.textContent)).toEqual(Array(3).fill("7")); const details = container.querySelector("details[data-ranking]")!; details.open = true; await act(async () => toggle.click()); expect(toggle.getAttribute("aria-checked")).toBe("false"); expect(details.open).toBe(true); expect(container.querySelectorAll("tbody tr")).toHaveLength(3); expect(container.textContent).not.toContain("Stygian only"); expect(container.querySelector('[aria-label="ภาพรวมข้อมูล"]')?.textContent).toContain("ทีมทั้งหมด1"); expect(container.querySelector('[aria-label="ภาพรวมข้อมูล"]')?.textContent).toContain("เวอร์ชัน Stygian0"); expect(Array.from(container.querySelectorAll("tbody tr:first-child td:last-child"), (cell) => cell.textContent)).toEqual(Array(3).fill("2")); await act(async () => root.render()); expect(toggle.getAttribute("aria-checked")).toBe("false"); expect(container.querySelector('[aria-label="ภาพรวมข้อมูล"]')?.textContent).toContain("ทีมทั้งหมด2"); }); it("shows empty states for team rankings without entries", async () => { await act(async () => root.render()); expect(container.querySelectorAll("details[data-ranking]")).toHaveLength(0); expect(container.querySelectorAll('[data-slot="empty-title"]')).toHaveLength(3); }); it("lets each guide be excluded and restored and supports repeated-team filtering", async () => { await act(async () => root.render()); const guide = container.querySelector('[aria-label="นับไกด์ Guide 0"]')!; await act(async () => guide.click()); expect(guide.getAttribute("aria-pressed")).toBe("false"); expect(container.querySelector('[aria-label="ภาพรวมข้อมูล"]')?.textContent).toContain("ไกด์ตัวละคร3"); expect(container.querySelector('[aria-label="ภาพรวมข้อมูล"]')?.textContent).toContain("ทีมทั้งหมด1"); const reset = Array.from(container.querySelectorAll("button")).find((button) => button.textContent === "รวมทุกไกด์")!; await act(async () => reset.click()); expect(guide.getAttribute("aria-pressed")).toBe("true"); const deduplicate = container.querySelector('[role="switch"][aria-labelledby="ignore-repeated-teams-label"]')!; expect(deduplicate.getAttribute("aria-checked")).toBe("true"); await act(async () => deduplicate.click()); expect(deduplicate.getAttribute("aria-checked")).toBe("false"); expect(container.querySelector('[aria-label="ภาพรวมข้อมูล"]')?.textContent).toContain("ทีมทั้งหมด2"); }); });