Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
75 lines
2.8 KiB
TypeScript
75 lines
2.8 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import test from "node:test";
|
|
|
|
import {
|
|
installTerminalKeyboardFocusTracking,
|
|
isTerminalKeyboardTarget,
|
|
} from "./terminalKeyboardFocus";
|
|
|
|
const targetWithClosest = (matches: boolean) => ({
|
|
closest: (selector: string) => {
|
|
assert.match(selector, /\.xterm/);
|
|
return matches ? {} : null;
|
|
},
|
|
}) as unknown as EventTarget;
|
|
|
|
test("terminal keyboard target detection recognizes xterm descendants", () => {
|
|
assert.equal(isTerminalKeyboardTarget(targetWithClosest(true)), true);
|
|
assert.equal(isTerminalKeyboardTarget(targetWithClosest(false)), false);
|
|
assert.equal(isTerminalKeyboardTarget(null), false);
|
|
});
|
|
|
|
test("terminal keyboard focus tracking publishes deduplicated focus transitions and cleans up", async () => {
|
|
const listeners = new Map<string, (event?: Event) => void>();
|
|
const removed: string[] = [];
|
|
const body = targetWithClosest(false);
|
|
const terminal = targetWithClosest(true);
|
|
const documentRef = {
|
|
activeElement: body as unknown as Element,
|
|
addEventListener: (type: string, listener: (event?: Event) => void) => {
|
|
listeners.set(type, listener);
|
|
},
|
|
removeEventListener: (type: string) => {
|
|
removed.push(type);
|
|
},
|
|
} as unknown as Document;
|
|
const windowRef = {
|
|
addEventListener: (type: string, listener: (event?: Event) => void) => {
|
|
listeners.set(`window:${type}`, listener);
|
|
},
|
|
removeEventListener: (type: string) => {
|
|
removed.push(`window:${type}`);
|
|
},
|
|
} as unknown as Window;
|
|
const changes: boolean[] = [];
|
|
const cleanup = installTerminalKeyboardFocusTracking(documentRef, windowRef, (focused) => {
|
|
if (changes.at(-1) !== focused) changes.push(focused);
|
|
});
|
|
|
|
assert.deepEqual(changes, [false]);
|
|
|
|
listeners.get("pointerdown")?.({ target: terminal } as unknown as Event);
|
|
listeners.get("focusin")?.({ target: terminal } as unknown as Event);
|
|
assert.deepEqual(changes, [false, true]);
|
|
|
|
(documentRef as unknown as { activeElement: Element }).activeElement = terminal as unknown as Element;
|
|
listeners.get("pointerdown")?.({ target: body } as unknown as Event);
|
|
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
assert.deepEqual(changes, [false, true]);
|
|
|
|
(documentRef as unknown as { activeElement: Element }).activeElement = body as unknown as Element;
|
|
listeners.get("focusout")?.();
|
|
await Promise.resolve();
|
|
assert.deepEqual(changes, [false, true, false]);
|
|
|
|
listeners.get("window:blur")?.();
|
|
assert.deepEqual(changes, [false, true, false]);
|
|
|
|
(documentRef as unknown as { activeElement: Element }).activeElement = terminal as unknown as Element;
|
|
listeners.get("window:focus")?.();
|
|
assert.deepEqual(changes, [false, true, false, true]);
|
|
|
|
cleanup();
|
|
assert.deepEqual(removed.sort(), ["focusin", "focusout", "pointerdown", "window:blur", "window:focus"]);
|
|
});
|