Files

102 lines
3.1 KiB
TypeScript

import { afterEach, describe, expect, it, vi } from "vitest";
import {
captureQuickChatLauncherFocus,
registerQuickChatCloseHandler,
requestQuickChatClose,
restoreQuickChatLauncherFocus,
} from "./quick-chat-focus";
const SILENT_FOCUS_ATTRIBUTE = "data-quick-chat-silent-focus";
afterEach(() => {
vi.unstubAllGlobals();
document.body.replaceChildren();
});
describe("quick chat launcher focus", () => {
it("routes an external close request through the registered modal lifecycle", () => {
const close = vi.fn();
const unregister = registerQuickChatCloseHandler(close);
expect(requestQuickChatClose()).toBe(true);
expect(close).toHaveBeenCalledTimes(1);
unregister();
expect(requestQuickChatClose()).toBe(false);
});
// @covers AC-UI-QUICK-TERMINAL-001.9
it("restores focus through the animation frame after closing", () => {
vi.stubGlobal("requestAnimationFrame", (callback: FrameRequestCallback) => {
callback(0);
return 0;
});
const launcher = document.createElement("button");
document.body.append(launcher);
launcher.focus();
captureQuickChatLauncherFocus();
launcher.blur();
restoreQuickChatLauncherFocus();
expect(document.activeElement).toBe(launcher);
expect(launcher.getAttribute(SILENT_FOCUS_ATTRIBUTE)).toBe("true");
launcher.blur();
expect(launcher.getAttribute(SILENT_FOCUS_ATTRIBUTE)).toBeNull();
});
it("restores focus from a non-launcher origin without a silent marker", () => {
vi.stubGlobal("requestAnimationFrame", (callback: FrameRequestCallback) => {
callback(0);
return 0;
});
const origin = document.createElement("input");
document.body.append(origin);
origin.focus();
captureQuickChatLauncherFocus({ silent: false });
origin.blur();
restoreQuickChatLauncherFocus();
expect(document.activeElement).toBe(origin);
expect(origin.getAttribute(SILENT_FOCUS_ATTRIBUTE)).toBeNull();
});
it("does not focus a launcher that was removed while the dialog was open", () => {
vi.stubGlobal("requestAnimationFrame", (callback: FrameRequestCallback) => {
callback(0);
return 0;
});
const launcher = document.createElement("button");
document.body.append(launcher);
launcher.focus();
captureQuickChatLauncherFocus();
launcher.remove();
restoreQuickChatLauncherFocus();
expect(document.activeElement).not.toBe(launcher);
expect(launcher.getAttribute(SILENT_FOCUS_ATTRIBUTE)).toBeNull();
});
it("returns to the persistent opener when a menu launcher unmounts", () => {
vi.stubGlobal("requestAnimationFrame", (callback: FrameRequestCallback) => {
callback(0);
return 0;
});
const menuButton = document.createElement("button");
const menuAction = document.createElement("button");
document.body.append(menuButton, menuAction);
menuAction.focus();
captureQuickChatLauncherFocus({ returnFocusRef: { current: menuButton } });
menuAction.remove();
restoreQuickChatLauncherFocus();
expect(document.activeElement).toBe(menuButton);
expect(menuButton.getAttribute(SILENT_FOCUS_ATTRIBUTE)).toBe("true");
});
});