import { createRawSnippet, type Snippet } from "svelte";
import { describe, expect, it } from "vitest";
import { Dialog, dialog as dialogAppearance } from "./fixtures/dialog-tooltip-consumer";
import { classes, classesOf, render } from "./mount";
function text(content: string): Snippet {
return createRawSnippet(() => ({ render: () => `${content}` }));
}
describe("the Base Dialog", () => {
it("replaces the default close control with a custom header while retaining its accessible title", () => {
const root = render(Dialog, { title: "Contact information", triggerLabel: "Open contact", header: text("Custom contact toolbar"), children: text("Contact details") });
root.querySelector("[data-dialog-trigger]")!.click();
const dialog = root.querySelector("dialog")!;
const title = dialog.querySelector("[data-dialog-title]")!;
expect(title.className).toContain("sr-only");
expect(dialog.getAttribute("aria-labelledby")).toBe(title.id);
expect(dialog.querySelector("[data-dialog-header]")?.textContent).toBe("Custom contact toolbar");
expect(dialog.querySelector("[data-dialog-close]")).toBeNull();
expect(dialog.querySelector("[data-dialog-body]")?.className).toContain("overflow-y-auto");
});
it("labels the modal from its visible title and description", () => {
const root = render(Dialog, {
triggerLabel: "Edit deployment",
title: "Edit deployment",
description: "Changes take effect immediately.",
children: text("Dialog body"),
});
const trigger = root.querySelector("[data-dialog-trigger]")!;
trigger.click();
const dialog = root.querySelector("dialog")!;
const title = root.querySelector("[data-dialog-title]")!;
const description = root.querySelector("[data-dialog-description]")!;
expect(dialog.open).toBe(true);
expect(dialog.getAttribute("aria-modal")).toBe("true");
expect(dialog.getAttribute("aria-labelledby")).toBe(title.id);
expect(dialog.getAttribute("aria-describedby")).toBe(description.id);
expect(title.textContent).toBe("Edit deployment");
});
it("dismisses from the keyboard with Escape", () => {
const root = render(Dialog, {
triggerLabel: "Open settings",
title: "Settings",
});
root.querySelector("[data-dialog-trigger]")!.click();
const dialog = root.querySelector("dialog")!;
dialog.dispatchEvent(new KeyboardEvent("keydown", { key: "Escape", bubbles: true }));
expect(dialog.open).toBe(false);
});
it("moves focus into the modal and restores it after dismissal", () => {
const root = render(Dialog, {
triggerLabel: "Open members",
title: "Members",
children: createRawSnippet(() => ({
render: () => '',
})),
});
const trigger = root.querySelector("[data-dialog-trigger]")!;
trigger.focus();
trigger.click();
const dialog = root.querySelector("dialog")!;
expect(document.activeElement).toBe(root.querySelector("[data-first-member]"));
dialog.dispatchEvent(new KeyboardEvent("keydown", { key: "Escape", bubbles: true }));
expect(document.activeElement).toBe(trigger);
});
it("contains Tab navigation within the modal", () => {
const root = render(Dialog, {
triggerLabel: "Open actions",
title: "Actions",
children: createRawSnippet(() => ({
render: () =>
'',
})),
});
root.querySelector("[data-dialog-trigger]")!.click();
const dialog = root.querySelector("dialog")!;
const first = root.querySelector("[data-dialog-close]")!;
const last = root.querySelector("[data-last]")!;
last.focus();
dialog.dispatchEvent(new KeyboardEvent("keydown", { key: "Tab", bubbles: true }));
expect(document.activeElement).toBe(first);
first.focus();
dialog.dispatchEvent(
new KeyboardEvent("keydown", { key: "Tab", shiftKey: true, bubbles: true }),
);
expect(document.activeElement).toBe(last);
});
it("provides a labeled close control", () => {
const root = render(Dialog, { triggerLabel: "Open help", title: "Help" });
root.querySelector("[data-dialog-trigger]")!.click();
const dialog = root.querySelector("dialog")!;
const close = root.querySelector("[data-dialog-close]")!;
expect(close.getAttribute("aria-label")).toBe("Close Help");
close.click();
expect(dialog.open).toBe(false);
});
it("wears its exported token appearance and honors reduced motion", () => {
const root = render(Dialog, {
triggerLabel: "Open details",
title: "Details",
class: "max-w-xl",
});
const element = root.querySelector("dialog")!;
expect(classes(element)).toEqual(classesOf(dialogAppearance({ class: "max-w-xl" })));
expect(classes(element).has("motion-reduce:transition-none")).toBe(true);
});
});