// @vitest-environment jsdom import { act, fireEvent, render, screen, waitFor, within, } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { useState } from "react"; import { describe, expect, it, vi } from "vitest"; import { Button, Checkbox, DataTable, Drawer, IconButton, Menu, MenuIcon, Pagination, Popover, RadioGroup, Switch, Tabs, ToastProvider, useToast, } from "../../src/presentation/design-system/index.ts"; describe("design-system platform interactions", () => { it("keeps decorative icons out of the accessibility tree and names icon actions", () => { render( <> , ); expect(screen.getByRole("button", { name: "탐색 열기" })).toBeVisible(); expect(screen.queryByRole("img")).not.toBeInTheDocument(); }); it("supports native choices, indeterminate state, radio arrows and switches", async () => { const user = userEvent.setup(); const onRadio = vi.fn(); const onSwitch = vi.fn(); render( <> , ); const checkbox = screen.getByRole("checkbox", { name: "일부 선택" }); expect(checkbox).toBePartiallyChecked(); const normal = screen.getByRole("radio", { name: "보통" }); normal.focus(); await user.keyboard("{ArrowRight}"); expect(onRadio).toHaveBeenCalledWith("compact"); await user.click(screen.getByRole("switch", { name: "알림" })); expect(onSwitch).toHaveBeenCalledWith(true); }); it("moves through a menu, executes once, dismisses and restores focus", async () => { const user = userEvent.setup(); const inspect = vi.fn(); render( , ); const trigger = screen.getByRole("button", { name: "작업" }); trigger.focus(); await user.keyboard("{ArrowDown}"); expect(screen.getByRole("menuitem", { name: "열기" })).toHaveFocus(); await user.keyboard("{ArrowDown}{Enter}"); expect(inspect).toHaveBeenCalledOnce(); await waitFor(() => expect(trigger).toHaveFocus()); expect(screen.queryByRole("menu")).not.toBeInTheDocument(); }); it("supports manual tab activation with arrow-key roving focus", async () => { const user = userEvent.setup(); render( , ); const tokenTab = screen.getByRole("tab", { name: "토큰" }); tokenTab.focus(); await user.keyboard("{ArrowRight}"); const patternTab = screen.getByRole("tab", { name: "패턴" }); expect(patternTab).toHaveFocus(); expect(patternTab).toHaveAttribute("aria-selected", "false"); await user.keyboard("{Enter}"); expect(patternTab).toHaveAttribute("aria-selected", "true"); expect(screen.getByRole("tabpanel", { name: "패턴" })).toHaveTextContent( "패턴 내용", ); }); it("uses a modal drawer and restores focus after Escape", async () => { const user = userEvent.setup(); function Harness() { const [open, setOpen] = useState(false); return ( <> setOpen(false)} open={open} title="탐색"> 대상 ); } render(); const trigger = screen.getByRole("button", { name: "탐색 열기" }); await user.click(trigger); expect(screen.getByRole("dialog", { name: "탐색" })).toHaveAttribute("open"); await user.keyboard("{Escape}"); await waitFor(() => expect(trigger).toHaveFocus()); }); it("bounds the toast queue and collapses duplicate IDs", async () => { const user = userEvent.setup(); function Harness() { const toast = useToast(); return ( <> ); } render( , ); await user.click(screen.getByRole("button", { name: "중복" })); await user.click(screen.getByRole("button", { name: "중복" })); expect(screen.getByText("×2")).toBeVisible(); await user.click(screen.getByRole("button", { name: "다섯 개" })); const region = screen.getByRole("region", { name: "알림" }); expect(within(region).getAllByRole("article")).toHaveLength(3); expect(within(region).queryByText("알림 0")).not.toBeInTheDocument(); expect(within(region).getByText("알림 4")).toBeVisible(); }); it("pauses toast timeout while the user is interacting", () => { vi.useFakeTimers(); function Harness() { const toast = useToast(); return ( ); } render( , ); fireEvent.click(screen.getByRole("button", { name: "표시" })); const item = screen.getByRole("article"); fireEvent.mouseEnter(item); act(() => vi.advanceTimersByTime(2000)); expect(item).toBeVisible(); fireEvent.mouseLeave(item); act(() => vi.advanceTimersByTime(1000)); expect(screen.queryByText("시간 제한")).not.toBeInTheDocument(); vi.useRealTimers(); }); it("provides dismissible popover and data/navigation patterns", async () => { const user = userEvent.setup(); const onPage = vi.fn(); render( <> row.name }, ]} empty="비어 있음" rowKey={(row) => row.id} rows={[{ id: "one", name: "첫 항목" }]} /> `${page}페이지`} previousLabel="이전" /> , ); const popoverTrigger = screen.getByRole("button", { name: "설명 열기" }); await user.click(popoverTrigger); expect(screen.getByRole("dialog")).toHaveTextContent("내부 작업"); await user.keyboard("{Escape}"); expect(screen.queryByRole("dialog")).not.toBeInTheDocument(); expect(popoverTrigger).toHaveFocus(); expect(screen.getByRole("table", { name: "결과" })).toHaveTextContent( "첫 항목", ); await user.click(screen.getByRole("link", { name: "2페이지" })); expect(onPage).toHaveBeenCalledWith(2); }); });