109 lines
3.7 KiB
TypeScript
109 lines
3.7 KiB
TypeScript
// @vitest-environment jsdom
|
|
|
|
import { render, screen, waitFor } from "@testing-library/react";
|
|
import userEvent from "@testing-library/user-event";
|
|
import { useState } from "react";
|
|
import { describe, expect, it, vi } from "vitest";
|
|
|
|
import { Alert } from "../../src/presentation/components/ui/alert.ts";
|
|
import { Badge } from "../../src/presentation/components/ui/badge.ts";
|
|
import { Button } from "../../src/presentation/components/ui/button.ts";
|
|
import { Card } from "../../src/presentation/components/ui/card.ts";
|
|
import { Dialog } from "../../src/presentation/components/ui/dialog.ts";
|
|
import { TextField } from "../../src/presentation/components/ui/text-field.ts";
|
|
|
|
describe("domain-neutral UI primitives", () => {
|
|
it("connects field help and validation errors to the input", () => {
|
|
render(
|
|
<TextField
|
|
label="이름"
|
|
description="표시할 이름입니다."
|
|
error="이름을 입력해 주세요."
|
|
required
|
|
/>,
|
|
);
|
|
|
|
const field = screen.getByRole("textbox", { name: "이름" });
|
|
expect(field).toBeRequired();
|
|
expect(field).toHaveAccessibleDescription(
|
|
"표시할 이름입니다. 이름을 입력해 주세요.",
|
|
);
|
|
expect(field).toHaveAttribute("aria-invalid", "true");
|
|
});
|
|
|
|
it("exposes semantic variants without changing native button behavior", async () => {
|
|
const user = userEvent.setup();
|
|
const onClick = vi.fn();
|
|
render(
|
|
<>
|
|
<Button variant="danger" onClick={onClick}>
|
|
제거
|
|
</Button>
|
|
<Button disabled>사용 불가</Button>
|
|
</>,
|
|
);
|
|
|
|
await user.click(screen.getByRole("button", { name: "제거" }));
|
|
expect(onClick).toHaveBeenCalledOnce();
|
|
expect(screen.getByRole("button", { name: "제거" })).toHaveClass(
|
|
"ui-button--danger",
|
|
);
|
|
expect(screen.getByRole("button", { name: "사용 불가" })).toBeDisabled();
|
|
});
|
|
|
|
it("labels cards, alerts, and badges with visible content", async () => {
|
|
const user = userEvent.setup();
|
|
const dismiss = vi.fn();
|
|
render(
|
|
<Card title="상태 카드" footer={<Badge variant="success">준비됨</Badge>}>
|
|
<Alert title="저장됨" variant="success" onDismiss={dismiss}>
|
|
안전하게 반영했습니다.
|
|
</Alert>
|
|
</Card>,
|
|
);
|
|
|
|
expect(screen.getByRole("article", { name: "상태 카드" })).toBeVisible();
|
|
expect(screen.getByRole("status")).toHaveTextContent(
|
|
"저장됨안전하게 반영했습니다.",
|
|
);
|
|
expect(screen.getByText("준비됨")).toHaveClass("ui-badge--success");
|
|
await user.click(screen.getByRole("button", { name: "저장됨 알림 닫기" }));
|
|
expect(dismiss).toHaveBeenCalledOnce();
|
|
});
|
|
|
|
it("closes a modal and restores focus to its trigger", async () => {
|
|
const user = userEvent.setup();
|
|
|
|
function DialogHarness() {
|
|
const [open, setOpen] = useState(false);
|
|
return (
|
|
<>
|
|
<Button onClick={() => setOpen(true)}>모달 열기</Button>
|
|
<Dialog
|
|
open={open}
|
|
onClose={() => setOpen(false)}
|
|
title="연동 확인"
|
|
actions={<Button onClick={() => setOpen(false)}>확인</Button>}
|
|
>
|
|
안전한 설명
|
|
</Dialog>
|
|
</>
|
|
);
|
|
}
|
|
|
|
render(<DialogHarness />);
|
|
const trigger = screen.getByRole("button", { name: "모달 열기" });
|
|
await user.click(trigger);
|
|
|
|
expect(screen.getByRole("dialog", { name: "연동 확인" })).toHaveAttribute(
|
|
"open",
|
|
);
|
|
await user.click(screen.getByRole("button", { name: "확인" }));
|
|
|
|
await waitFor(() => expect(trigger).toHaveFocus());
|
|
expect(screen.getByRole("dialog", { hidden: true })).not.toHaveAttribute(
|
|
"open",
|
|
);
|
|
});
|
|
});
|