Files
paseo/packages/app/src/keyboard/focus-scope.test.ts
T
Mohamed Boudra a2b1498c3f fix(app): broaden keyboard focus scope resolution to check multiple candidates
Check target, parentElement, and document.activeElement as focus
candidates instead of relying solely on the direct event target.
Fixes scope misdetection when the keyboard event target is a text
node or non-element.
2026-04-03 16:53:14 +07:00

81 lines
2.3 KiB
TypeScript

import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { resolveKeyboardFocusScope } from "./focus-scope";
class FakeNode {
parentElement: FakeElement | null = null;
}
class FakeElement extends FakeNode {
tagName: string;
isContentEditable = false;
private selectors: Set<string>;
constructor(input?: { tagName?: string; selectors?: string[]; isContentEditable?: boolean }) {
super();
this.tagName = (input?.tagName ?? "div").toUpperCase();
this.selectors = new Set(input?.selectors ?? []);
if (input?.isContentEditable) {
this.isContentEditable = true;
}
}
closest(selector: string): FakeElement | null {
if (this.selectors.has(selector)) {
return this;
}
return this.parentElement?.closest(selector) ?? null;
}
}
describe("resolveKeyboardFocusScope", () => {
const globalRef = globalThis as {
Element?: unknown;
Node?: unknown;
document?: { activeElement?: unknown };
};
const originalElement = globalRef.Element;
const originalNode = globalRef.Node;
const originalDocument = globalRef.document;
beforeEach(() => {
globalRef.Element = FakeElement;
globalRef.Node = FakeNode;
globalRef.document = { activeElement: null };
});
afterEach(() => {
globalRef.Element = originalElement;
globalRef.Node = originalNode;
globalRef.document = originalDocument;
});
it("resolves terminal scope from the direct keyboard event target", () => {
const target = new FakeElement({ selectors: [".xterm"] });
const scope = resolveKeyboardFocusScope({
target: target as unknown as EventTarget,
commandCenterOpen: false,
});
expect(scope).toBe("terminal");
});
it("falls back to activeElement when target is not an Element", () => {
const activeElement = new FakeElement({ selectors: [".xterm"] });
globalRef.document = { activeElement };
const scope = resolveKeyboardFocusScope({
target: null,
commandCenterOpen: false,
});
expect(scope).toBe("terminal");
});
it("detects editable scope from activeElement fallback", () => {
const activeElement = new FakeElement({ tagName: "input" });
globalRef.document = { activeElement };
const scope = resolveKeyboardFocusScope({
target: null,
commandCenterOpen: false,
});
expect(scope).toBe("editable");
});
});