Files
page-builder/tests/unit/DashboardPage.spec.tsx
jaybe f71207aeb5
CI / test (push) Failing after 11m12s
CI / e2e (push) Has been cancelled
CI / pr_and_merge (push) Has been cancelled
i18n 적용
2025-12-10 15:56:51 +09:00

142 lines
4.1 KiB
TypeScript

import { describe, it, expect, afterEach, vi } from "vitest";
import { render, screen, cleanup, waitFor } from "@testing-library/react";
import DashboardPage from "@/app/dashboard/page";
export const pushMock = vi.fn();
vi.mock("next/navigation", () => {
return {
__esModule: true,
useRouter: () => ({ push: pushMock }),
};
});
afterEach(() => {
cleanup();
vi.unstubAllGlobals();
vi.clearAllMocks();
});
describe("DashboardPage - GNB와 테마", () => {
it("GNB에서 현재 페이지인 '대시보드' 탭에 aria-current=\"page\"가 설정되어야 한다", async () => {
const overview = {
summaryStats: {
totalProjects: 0,
totalSubmissions: 0,
todaySubmissions: 0,
last7DaysSubmissions: 0,
},
projectStats: [],
dailySubmissions: [],
};
const fetchMock = vi.fn().mockResolvedValue(
new Response(JSON.stringify(overview), {
status: 200,
headers: { "Content-Type": "application/json" },
}),
);
vi.stubGlobal("fetch", fetchMock);
render(<DashboardPage />);
const dashboardLink = await screen.findByRole("link", { name: "Dashboard" });
const projectsLink = await screen.findByRole("link", { name: "Projects" });
const submissionsLink = await screen.findByRole("link", { name: "All submissions" });
expect(dashboardLink.getAttribute("href")).toBe("/dashboard");
expect(dashboardLink.getAttribute("aria-current")).toBe("page");
expect(projectsLink.getAttribute("aria-current")).toBeNull();
expect(submissionsLink.getAttribute("aria-current")).toBeNull();
});
it("헤더에 테마 전환 버튼이 있고 클릭 시 html 요소의 dark 클래스가 토글되어야 한다", async () => {
const overview = {
summaryStats: {
totalProjects: 0,
totalSubmissions: 0,
todaySubmissions: 0,
last7DaysSubmissions: 0,
},
projectStats: [],
dailySubmissions: [],
};
const fetchMock = vi.fn().mockResolvedValue(
new Response(JSON.stringify(overview), {
status: 200,
headers: { "Content-Type": "application/json" },
}),
);
vi.stubGlobal("fetch", fetchMock);
render(<DashboardPage />);
const html = document.documentElement;
html.classList.remove("dark");
const themeToggleButton = await screen.findByRole("button", { name: "Toggle theme" });
expect(html.classList.contains("dark")).toBe(false);
themeToggleButton.click();
expect(html.classList.contains("dark")).toBe(true);
themeToggleButton.click();
expect(html.classList.contains("dark")).toBe(false);
});
it("대시보드 요약 카드와 프로젝트 카드가 라이트/다크 테마에서 읽기 쉬운 텍스트 색상 클래스를 사용해야 한다", async () => {
const overview = {
summaryStats: {
totalProjects: 1,
totalSubmissions: 10,
todaySubmissions: 2,
last7DaysSubmissions: 5,
},
projectStats: [
{
projectId: "p1",
title: "Test project 1",
slug: "test-project-1",
status: "DRAFT",
totalSubmissions: 3,
lastSubmissionAt: "2025-01-01T00:00:00.000Z",
},
],
dailySubmissions: [],
};
const fetchMock = vi.fn().mockResolvedValue(
new Response(JSON.stringify(overview), {
status: 200,
headers: { "Content-Type": "application/json" },
}),
);
vi.stubGlobal("fetch", fetchMock as any);
const { container } = render(<DashboardPage />);
// 요약 카드가 렌더링될 때까지 기다린다.
await screen.findByTestId("dashboard-summary-total-projects");
const summaryCard = container.querySelector(
'[data-testid="dashboard-summary-total-projects"]',
) as HTMLElement | null;
expect(summaryCard).not.toBeNull();
const summaryLabel = summaryCard!.querySelector("span") as HTMLElement | null;
expect(summaryLabel).not.toBeNull();
const summaryLabelClass = summaryLabel!.className;
expect(summaryLabelClass).toContain("text-slate-500");
expect(summaryLabelClass).toContain("dark:text-slate-400");
});
});