Newer
Older
gnexus-ui-kit / tests / unit / gn-modal.spec.js
@Eugene Sukhodolskiy Eugene Sukhodolskiy 1 day ago 2 KB Add Vue adapter behavior contract tests
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();
	});
});