diff --git a/apps/web/src/components/__tests__/dialog.test.tsx b/apps/web/src/components/__tests__/dialog.test.tsx
new file mode 100644
index 0000000..9c483f4
--- /dev/null
+++ b/apps/web/src/components/__tests__/dialog.test.tsx
@@ -0,0 +1,71 @@
+// @vitest-environment jsdom
+import { cleanup, render, screen } from "@testing-library/react";
+import { userEvent } from "@testing-library/user-event";
+import { afterEach, beforeAll, describe, expect, it, vi } from "vitest";
+import { Dialog, DialogHeader } from "../ui/dialog.js";
+
+beforeAll(() => {
+ HTMLDialogElement.prototype.showModal =
+ HTMLDialogElement.prototype.showModal ||
+ function showModal(this: HTMLDialogElement) {
+ this.setAttribute("open", "");
+ };
+ HTMLDialogElement.prototype.close =
+ HTMLDialogElement.prototype.close ||
+ function close(this: HTMLDialogElement) {
+ this.removeAttribute("open");
+ };
+});
+
+afterEach(cleanup);
+
+describe("Dialog", () => {
+ it("opens when open=true", () => {
+ render(
+ ,
+ );
+ expect(screen.getByText("Content")).toBeDefined();
+ });
+
+ it("closes when open changes from true to false", () => {
+ const { rerender } = render(
+ ,
+ );
+ const dialog = document.querySelector("dialog");
+ expect(dialog?.hasAttribute("open")).toBe(true);
+
+ rerender(
+ ,
+ );
+ expect(dialog?.hasAttribute("open")).toBe(false);
+ });
+
+ it("calls onClose on cancel event", () => {
+ const onClose = vi.fn();
+ render(
+ ,
+ );
+ const dialog = document.querySelector("dialog");
+ dialog?.dispatchEvent(new Event("cancel"));
+ expect(onClose).toHaveBeenCalledOnce();
+ });
+});
+
+describe("DialogHeader", () => {
+ it("renders title and close button", async () => {
+ const onClose = vi.fn();
+ render();
+
+ expect(screen.getByText("Test Title")).toBeDefined();
+ await userEvent.click(screen.getByRole("button"));
+ expect(onClose).toHaveBeenCalledOnce();
+ });
+});
diff --git a/apps/web/src/components/__tests__/tooltip.test.tsx b/apps/web/src/components/__tests__/tooltip.test.tsx
new file mode 100644
index 0000000..f160286
--- /dev/null
+++ b/apps/web/src/components/__tests__/tooltip.test.tsx
@@ -0,0 +1,42 @@
+// @vitest-environment jsdom
+import { cleanup, fireEvent, render, screen } from "@testing-library/react";
+import { afterEach, describe, expect, it } from "vitest";
+import { Tooltip } from "../ui/tooltip.js";
+
+afterEach(cleanup);
+
+describe("Tooltip", () => {
+ it("renders children", () => {
+ render(
+
+
+ ,
+ );
+ expect(screen.getByText("Hover me")).toBeDefined();
+ });
+
+ it("does not show tooltip initially", () => {
+ render(
+
+ Target
+ ,
+ );
+ expect(screen.queryByRole("tooltip")).toBeNull();
+ });
+
+ it("shows tooltip on pointer enter and hides on pointer leave", () => {
+ render(
+
+ Target
+ ,
+ );
+
+ const wrapper = screen.getByText("Target").closest("span");
+ fireEvent.pointerEnter(wrapper as HTMLElement);
+ expect(screen.getByRole("tooltip")).toBeDefined();
+ expect(screen.getByText("Hint text")).toBeDefined();
+
+ fireEvent.pointerLeave(wrapper as HTMLElement);
+ expect(screen.queryByRole("tooltip")).toBeNull();
+ });
+});
diff --git a/vitest.config.ts b/vitest.config.ts
index 0c38f55..ed448de 100644
--- a/vitest.config.ts
+++ b/vitest.config.ts
@@ -34,8 +34,8 @@ export default defineConfig({
branches: 55,
},
"apps/web/src/components/ui": {
- lines: 86,
- branches: 83,
+ lines: 93,
+ branches: 90,
},
},
},