import { beforeEach, describe, expect, it } from "vitest";
import { mount } from "@vue/test-utils";
import { nextTick } from "vue";
import GnModal from "../../src/vue/components/GnModal.js";
import { raf } from "../setup.js";
const mountModal = (props = {}, slots = {}) =>
mount(GnModal, {
props: { open: false, title: "Test dialog", ...props },
slots,
attachTo: document.body
});
beforeEach(() => {
document.body.innerHTML = "";
});
describe("GnModal", () => {
it("renders nothing while closed", async () => {
const wrapper = mountModal();
await nextTick();
expect(document.querySelector(".modal")).toBeNull();
wrapper.unmount();
});
it("renders when mounted already open (immediate watcher)", async () => {
const wrapper = mountModal({ open: true });
await nextTick();
const dialog = document.querySelector(".modal-dialog");
expect(dialog).not.toBeNull();
expect(dialog.getAttribute("role")).toBe("dialog");
expect(dialog.getAttribute("aria-modal")).toBe("true");
expect(dialog.getAttribute("aria-labelledby")).toBe(
document.querySelector(".modal-title")?.id
);
expect(document.querySelector(".modal-title")?.textContent).toContain("Test dialog");
wrapper.unmount();
});
it("follows the a-hide -> a-show transition contract on open", async () => {
const wrapper = mountModal();
await nextTick();
await wrapper.setProps({ open: true });
const backdrop = document.querySelector(".modal");
// hidden state must be rendered first so the CSS transition can run
expect(backdrop.className).toContain("a-hide");
await nextTick();
await raf();
expect(document.querySelector(".modal").className).toContain("a-show");
wrapper.unmount();
});
it("closes on Escape and emits update:open + close", async () => {
const wrapper = mountModal({ open: true });
await nextTick();
document.dispatchEvent(new KeyboardEvent("keydown", { key: "Escape" }));
await nextTick();
expect(wrapper.emitted("update:open")).toContainEqual([false]);
expect(wrapper.emitted("close")).toBeTruthy();
wrapper.unmount();
});
it("closes on backdrop click unless closeOnBackdrop is false", async () => {
const wrapper = mountModal({ open: true });
await nextTick();
document.querySelector(".modal-backdrop").click();
expect(wrapper.emitted("update:open")).toContainEqual([false]);
wrapper.unmount();
const guarded = mountModal({ open: true, closeOnBackdrop: false });
await nextTick();
document.querySelector(".modal-backdrop").click();
expect(guarded.emitted("update:open")).toBeFalsy();
guarded.unmount();
});
it("returns focus to the trigger element on close", async () => {
const trigger = document.createElement("button");
document.body.appendChild(trigger);
trigger.focus();
const wrapper = mountModal();
await nextTick();
await wrapper.setProps({ open: true });
await nextTick();
await wrapper.setProps({ open: false });
expect(document.activeElement).toBe(trigger);
wrapper.unmount();
});
});