* Improve mobile terminal typing and selection * Address mobile terminal review feedback * Remove dead native terminal input branch * Replay bracketed paste in terminal preamble * Stop per-key terminal resize claims * Stabilize terminal resize browser test * Use tracked terminal mode for native paste * Restore terminal toolbar focus handling * Adapt native terminal to active visibility state * Stabilize native mobile terminal interactions * Add native terminal fallback boundary * test(app): expect explicit terminal resize reclaim * fix(app): stabilize terminal buffer coordinates * fix(app): restrict terminal resize ownership * fix(terminal): gate restored input modes * fix(app): recover stalled terminal WebViews * chore(app): remove obsolete terminal harnesses * fix(app): make terminal resize claims explicit * test(app): stabilize native terminal device flows * test(app): track absolute scrollback coordinates * fix(app): isolate terminal WebView streams * fix(terminal): arbitrate size ownership across clients The daemon now distinguishes explicit ownership claims from passive geometry updates. This keeps the active client authoritative while allowing its split, viewport, and keyboard changes to resize the PTY without idle clients stealing it. * test(server): make worker resize assertion portable Query the worker's authoritative terminal snapshot instead of invoking a Unix-only shell command, so the resize regression exercises the same boundary on Windows and Unix.
129 lines
4.0 KiB
TypeScript
129 lines
4.0 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { TerminalInputModeTracker } from "./terminal-input-mode.js";
|
|
|
|
describe("TerminalInputModeTracker", () => {
|
|
it("activates from a pushed Kitty keyboard mode and builds a replay preamble", () => {
|
|
const tracker = new TerminalInputModeTracker();
|
|
|
|
expect(tracker.feed("\x1b[>7u").changed).toBe(true);
|
|
|
|
expect(tracker.supportsModifiedEnter()).toBe(true);
|
|
expect(tracker.getKittyKeyboardFlags()).toBe(7);
|
|
expect(tracker.getPreamble()).toBe("\x1b[=7;1u");
|
|
});
|
|
|
|
it("tracks split terminal output chunks", () => {
|
|
const tracker = new TerminalInputModeTracker();
|
|
|
|
tracker.feed("\x1b[>");
|
|
const result = tracker.feed("1u");
|
|
|
|
expect(result.changed).toBe(true);
|
|
expect(tracker.getKittyKeyboardFlags()).toBe(1);
|
|
});
|
|
|
|
it("restores pushed Kitty keyboard modes when the foreground program pops them", () => {
|
|
const tracker = new TerminalInputModeTracker();
|
|
|
|
tracker.feed("\x1b[>1u");
|
|
tracker.feed("\x1b[>7u");
|
|
expect(tracker.getKittyKeyboardFlags()).toBe(7);
|
|
|
|
expect(tracker.feed("\x1b[<u").changed).toBe(true);
|
|
expect(tracker.getKittyKeyboardFlags()).toBe(1);
|
|
|
|
expect(tracker.feed("\x1b[<u").changed).toBe(true);
|
|
expect(tracker.supportsModifiedEnter()).toBe(false);
|
|
});
|
|
|
|
it("answers Kitty keyboard mode queries with the current flags", () => {
|
|
const tracker = new TerminalInputModeTracker();
|
|
tracker.feed("\x1b[=3;1u");
|
|
|
|
expect(tracker.feed("\x1b[?u").responses).toEqual(["\x1b[?3u"]);
|
|
});
|
|
|
|
it("tracks ConPTY Win32 input mode and replays it after snapshots", () => {
|
|
const tracker = new TerminalInputModeTracker();
|
|
|
|
expect(tracker.feed("\x1b[?9001h").changed).toBe(true);
|
|
|
|
expect(tracker.getState()).toEqual({
|
|
kittyKeyboardFlags: 0,
|
|
win32InputMode: true,
|
|
applicationCursorKeys: false,
|
|
bracketedPaste: false,
|
|
});
|
|
expect(tracker.supportsModifiedEnter()).toBe(true);
|
|
expect(tracker.getPreamble()).toBe("\x1b[?9001h");
|
|
|
|
expect(tracker.feed("\x1b[?9001l").changed).toBe(true);
|
|
expect(tracker.supportsModifiedEnter()).toBe(false);
|
|
});
|
|
|
|
it("keeps Kitty and Win32 input modes independent", () => {
|
|
const tracker = new TerminalInputModeTracker();
|
|
|
|
tracker.feed("\x1b[>7u\x1b[?9001h");
|
|
|
|
expect(tracker.getState()).toEqual({
|
|
kittyKeyboardFlags: 7,
|
|
win32InputMode: true,
|
|
applicationCursorKeys: false,
|
|
bracketedPaste: false,
|
|
});
|
|
expect(tracker.getPreamble()).toBe("\x1b[=7;1u\x1b[?9001h");
|
|
});
|
|
|
|
it("tracks application cursor keys mode independently", () => {
|
|
const tracker = new TerminalInputModeTracker();
|
|
|
|
expect(tracker.feed("\x1b[?1h").changed).toBe(true);
|
|
expect(tracker.getState()).toEqual({
|
|
kittyKeyboardFlags: 0,
|
|
win32InputMode: false,
|
|
applicationCursorKeys: true,
|
|
bracketedPaste: false,
|
|
});
|
|
expect(tracker.getPreamble()).toBe("\x1b[?1h");
|
|
|
|
expect(tracker.feed("\x1b[?1l").changed).toBe(true);
|
|
expect(tracker.getState()).toEqual({
|
|
kittyKeyboardFlags: 0,
|
|
win32InputMode: false,
|
|
applicationCursorKeys: false,
|
|
bracketedPaste: false,
|
|
});
|
|
});
|
|
|
|
it("tracks bracketed paste mode independently", () => {
|
|
const tracker = new TerminalInputModeTracker();
|
|
|
|
expect(tracker.feed("\x1b[?2004h").changed).toBe(true);
|
|
expect(tracker.getState()).toEqual({
|
|
kittyKeyboardFlags: 0,
|
|
win32InputMode: false,
|
|
applicationCursorKeys: false,
|
|
bracketedPaste: true,
|
|
});
|
|
expect(tracker.getPreamble()).toBe("\x1b[?2004h");
|
|
|
|
expect(tracker.feed("\x1b[?2004l").changed).toBe(true);
|
|
expect(tracker.getState()).toEqual({
|
|
kittyKeyboardFlags: 0,
|
|
win32InputMode: false,
|
|
applicationCursorKeys: false,
|
|
bracketedPaste: false,
|
|
});
|
|
expect(tracker.getPreamble()).toBe("");
|
|
});
|
|
|
|
it("ignores encoded key input sequences", () => {
|
|
const tracker = new TerminalInputModeTracker();
|
|
|
|
tracker.feed("\x1b[13;2u");
|
|
|
|
expect(tracker.supportsModifiedEnter()).toBe(false);
|
|
});
|
|
});
|