chore: initialize from frontend template 4dc033c
This commit is contained in:
@@ -0,0 +1,269 @@
|
||||
// @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(
|
||||
<>
|
||||
<MenuIcon />
|
||||
<IconButton accessibleName="탐색 열기">
|
||||
<MenuIcon />
|
||||
</IconButton>
|
||||
</>,
|
||||
);
|
||||
|
||||
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(
|
||||
<>
|
||||
<Checkbox indeterminate label="일부 선택" />
|
||||
<RadioGroup
|
||||
label="밀도"
|
||||
name="density"
|
||||
onChange={onRadio}
|
||||
options={[
|
||||
{ value: "normal", label: "보통" },
|
||||
{ value: "compact", label: "조밀" },
|
||||
]}
|
||||
value="normal"
|
||||
/>
|
||||
<Switch
|
||||
checked={false}
|
||||
label="알림"
|
||||
onChange={onSwitch}
|
||||
/>
|
||||
</>,
|
||||
);
|
||||
|
||||
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(
|
||||
<Menu
|
||||
items={[
|
||||
{ id: "open", label: "열기", onSelect: vi.fn() },
|
||||
{ id: "inspect", label: "검사", onSelect: inspect },
|
||||
]}
|
||||
triggerLabel="작업"
|
||||
/>,
|
||||
);
|
||||
|
||||
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(
|
||||
<Tabs
|
||||
activation="manual"
|
||||
label="계층"
|
||||
tabs={[
|
||||
{ id: "tokens", label: "토큰", panel: "토큰 내용" },
|
||||
{ id: "patterns", label: "패턴", panel: "패턴 내용" },
|
||||
]}
|
||||
/>,
|
||||
);
|
||||
|
||||
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 (
|
||||
<>
|
||||
<Button onClick={() => setOpen(true)}>탐색 열기</Button>
|
||||
<Drawer onClose={() => setOpen(false)} open={open} title="탐색">
|
||||
<a href="/target">대상</a>
|
||||
</Drawer>
|
||||
</>
|
||||
);
|
||||
}
|
||||
render(<Harness />);
|
||||
|
||||
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 (
|
||||
<>
|
||||
<Button
|
||||
onClick={() =>
|
||||
toast.push({ id: "same", title: "저장됨", durationMs: 60_000 })
|
||||
}
|
||||
>
|
||||
중복
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
for (let index = 0; index < 5; index += 1) {
|
||||
toast.push({
|
||||
id: `toast-${index}`,
|
||||
title: `알림 ${index}`,
|
||||
durationMs: 60_000,
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
다섯 개
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
}
|
||||
render(
|
||||
<ToastProvider limit={3}>
|
||||
<Harness />
|
||||
</ToastProvider>,
|
||||
);
|
||||
|
||||
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 (
|
||||
<Button
|
||||
onClick={() =>
|
||||
toast.push({ id: "timed", title: "시간 제한", durationMs: 1000 })
|
||||
}
|
||||
>
|
||||
표시
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
render(
|
||||
<ToastProvider>
|
||||
<Harness />
|
||||
</ToastProvider>,
|
||||
);
|
||||
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(
|
||||
<>
|
||||
<Popover triggerLabel="설명 열기">
|
||||
<Button>내부 작업</Button>
|
||||
</Popover>
|
||||
<DataTable
|
||||
caption="결과"
|
||||
columns={[
|
||||
{ id: "name", header: "이름", cell: (row) => row.name },
|
||||
]}
|
||||
empty="비어 있음"
|
||||
rowKey={(row) => row.id}
|
||||
rows={[{ id: "one", name: "첫 항목" }]}
|
||||
/>
|
||||
<Pagination
|
||||
label="페이지"
|
||||
nextLabel="다음"
|
||||
onChange={onPage}
|
||||
page={1}
|
||||
pageCount={2}
|
||||
pageLabel={(page) => `${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);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user