import { beforeEach, describe, expect, it } from "vitest";
import { mount } from "@vue/test-utils";
import { nextTick } from "vue";
import GnCombobox from "../../src/vue/components/GnCombobox.js";
const mountCombobox = (props = {}) =>
mount(GnCombobox, {
props: { options: ["Alpha", "Beta", "Gamma"], ...props },
attachTo: document.body
});
beforeEach(() => {
document.body.innerHTML = "";
});
describe("GnCombobox", () => {
it("owns combobox/listbox ARIA attributes", async () => {
const wrapper = mountCombobox({ label: "Pick one" });
const input = wrapper.find("input");
const listbox = document.querySelector('[role="listbox"]');
expect(input.attributes("role")).toBe("combobox");
expect(input.attributes("aria-autocomplete")).toBe("list");
expect(input.attributes("aria-expanded")).toBe("false");
expect(input.attributes("aria-controls")).toBe(listbox.id);
expect(listbox.getAttribute("role")).toBe("listbox");
});
it("opens on focus and closes on Escape", async () => {
const wrapper = mountCombobox();
const input = wrapper.find("input");
await input.trigger("focus");
expect(input.attributes("aria-expanded")).toBe("true");
await input.trigger("keydown", { key: "Escape" });
expect(input.attributes("aria-expanded")).toBe("false");
});
it("filters options by the current value", async () => {
const wrapper = mountCombobox({ modelValue: "al" });
await nextTick();
const options = document.querySelectorAll('[role="option"]');
expect(options).toHaveLength(1);
expect(options[0].textContent).toBe("Alpha");
});
it("navigates with arrow keys and selects with Enter", async () => {
const wrapper = mountCombobox();
const input = wrapper.find("input");
await input.trigger("focus");
await input.trigger("keydown", { key: "ArrowDown" });
await nextTick();
const focusedOption = document.querySelector(".option.focus");
expect(focusedOption).not.toBeNull();
expect(focusedOption.textContent).toBe("Alpha");
expect(input.attributes("aria-activedescendant")).toBe(focusedOption.id);
await input.trigger("keydown", { key: "Enter" });
expect(wrapper.emitted("update:modelValue")[0]).toEqual(["Alpha"]);
expect(wrapper.emitted("select")[0][0]).toEqual({ value: "Alpha", label: "Alpha" });
expect(input.attributes("aria-expanded")).toBe("false");
});
it("wraps around when moving past the last option", async () => {
const wrapper = mountCombobox();
const input = wrapper.find("input");
await input.trigger("focus");
await input.trigger("keydown", { key: "ArrowDown" });
await input.trigger("keydown", { key: "ArrowDown" });
await input.trigger("keydown", { key: "ArrowDown" });
await nextTick();
expect(document.querySelector(".option.focus").textContent).toBe("Gamma");
await input.trigger("keydown", { key: "ArrowDown" });
await nextTick();
expect(document.querySelector(".option.focus").textContent).toBe("Alpha");
});
it("marks the selected option with aria-selected", async () => {
const wrapper = mountCombobox({ modelValue: "Beta" });
await nextTick();
const selected = [...document.querySelectorAll('[role="option"]')]
.find(node => node.getAttribute("aria-selected") === "true");
expect(selected?.textContent).toBe("Beta");
});
});