import { describe, expect, it, vi } from "vitest";
import { cx, eventValue, iconNode, normalizeVariant, trapFocus } from "../../src/vue/utils.js";
describe("cx", () => {
it("joins strings and skips falsy values", () => {
expect(cx("a", false, null, undefined, "", "b")).toBe("a b");
});
it("flattens arrays", () => {
expect(cx("a", ["b", "c"])).toBe("a b c");
});
it("picks enabled keys from objects", () => {
expect(cx({ a: true, b: false, c: 1 })).toBe("a c");
});
});
describe("normalizeVariant", () => {
it("keeps known variants", () => {
expect(normalizeVariant("success")).toBe("success");
expect(normalizeVariant("error")).toBe("error");
});
it("falls back on unknown variants", () => {
expect(normalizeVariant("nope")).toBe("primary");
expect(normalizeVariant("nope", "info")).toBe("info");
});
});
describe("iconNode", () => {
it("renders an <i> with the ph base class", () => {
const node = iconNode("ph-house");
expect(node.type).toBe("i");
expect(node.props.class).toBe("ph ph-house");
expect(node.props["aria-hidden"]).toBe("true");
});
it("adds the ph- prefix when missing and warns in development", () => {
const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
const node = iconNode("house");
expect(node.props.class).toBe("ph ph-house");
expect(warn).toHaveBeenCalledWith(expect.stringContaining('missing the required "ph-" prefix'));
warn.mockRestore();
});
it("returns null for empty icons", () => {
expect(iconNode("")).toBeNull();
expect(iconNode(undefined)).toBeNull();
});
it("appends extra classes", () => {
const node = iconNode("ph-bold ph-spinner", "spin");
expect(node.props.class).toBe("ph ph-bold ph-spinner spin");
});
});
describe("eventValue", () => {
it("reads checked for checkboxes", () => {
expect(eventValue({ target: { type: "checkbox", checked: true } })).toBe(true);
});
it("reads value for other inputs", () => {
expect(eventValue({ target: { type: "text", value: "abc" } })).toBe("abc");
});
});
describe("trapFocus", () => {
const setupRoot = () => {
const root = document.createElement("div");
root.innerHTML = '<button id="a">a</button><button id="b">b</button><button id="c">c</button>';
document.body.appendChild(root);
root.querySelectorAll("button").forEach(button => {
// jsdom has no layout, so offsetParent is always null; pretend visible
Object.defineProperty(button, "offsetParent", { get: () => root });
});
return root;
};
const tabEvent = (shiftKey = false) => {
const event = new KeyboardEvent("keydown", { key: "Tab", shiftKey, cancelable: true, bubbles: true });
document.body.dispatchEvent(event);
return event;
};
it("wraps forward from the last focusable to the first", () => {
const root = setupRoot();
const buttons = root.querySelectorAll("button");
buttons[2].focus();
const event = tabEvent();
trapFocus(event, root);
expect(event.defaultPrevented).toBe(true);
expect(document.activeElement).toBe(buttons[0]);
root.remove();
});
it("wraps backward from the first focusable to the last", () => {
const root = setupRoot();
const buttons = root.querySelectorAll("button");
buttons[0].focus();
const event = tabEvent(true);
trapFocus(event, root);
expect(event.defaultPrevented).toBe(true);
expect(document.activeElement).toBe(buttons[2]);
root.remove();
});
it("ignores non-Tab keys", () => {
const root = setupRoot();
const event = new KeyboardEvent("keydown", { key: "Escape", cancelable: true });
trapFocus(event, root);
expect(event.defaultPrevented).toBe(false);
root.remove();
});
it("refocuses the root when nothing is focusable", () => {
const root = document.createElement("div");
root.tabIndex = -1;
document.body.appendChild(root);
root.focus();
const event = tabEvent();
trapFocus(event, root);
expect(event.defaultPrevented).toBe(true);
expect(document.activeElement).toBe(root);
root.remove();
});
});