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
701 lines
22 KiB
TypeScript
701 lines
22 KiB
TypeScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
|
|
import {
|
|
executeHotkeyActionImpl,
|
|
getLogHostVisualSnapshot,
|
|
handleEscapeKeyDownImpl,
|
|
handleGlobalHotkeyKeyDownImpl,
|
|
markForwardedNativeShortcutEvent,
|
|
} from './app/AppHandlers.ts';
|
|
import { matchesKeyBinding } from '../domain/models.ts';
|
|
import { DEFAULT_KEY_BINDINGS } from '../domain/models/keyBindings.ts';
|
|
|
|
class FakeInputHTMLElement {
|
|
tagName = 'INPUT';
|
|
isContentEditable = false;
|
|
|
|
closest(): FakeInputHTMLElement | null {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
class FakeHTMLElement {
|
|
tagName = 'TEXTAREA';
|
|
isContentEditable = false;
|
|
classList = {
|
|
contains: (className: string) => className === 'xterm-helper-textarea',
|
|
};
|
|
|
|
closest(selector: string): FakeHTMLElement | null {
|
|
return selector.includes('xterm') ? this : null;
|
|
}
|
|
|
|
hasAttribute(name: string): boolean {
|
|
return name === 'data-session-id';
|
|
}
|
|
}
|
|
|
|
class FakeMonacoHTMLElement extends FakeHTMLElement {
|
|
tagName = 'TEXTAREA';
|
|
|
|
closest(selector: string): FakeMonacoHTMLElement | null {
|
|
return selector.includes('monaco') ? this : null;
|
|
}
|
|
}
|
|
|
|
const previousHTMLElement = globalThis.HTMLElement;
|
|
globalThis.HTMLElement = FakeHTMLElement as unknown as typeof HTMLElement;
|
|
|
|
test.after(() => {
|
|
globalThis.HTMLElement = previousHTMLElement;
|
|
});
|
|
|
|
test('global hotkey handler lets terminal font size shortcuts reach xterm', () => {
|
|
const target = new FakeHTMLElement();
|
|
const handledActions: string[] = [];
|
|
let prevented = false;
|
|
let stopped = false;
|
|
const event = {
|
|
key: '=',
|
|
code: 'Equal',
|
|
ctrlKey: true,
|
|
metaKey: false,
|
|
altKey: false,
|
|
shiftKey: false,
|
|
target,
|
|
composedPath: () => [target],
|
|
preventDefault: () => {
|
|
prevented = true;
|
|
},
|
|
stopPropagation: () => {
|
|
stopped = true;
|
|
},
|
|
} as unknown as KeyboardEvent;
|
|
|
|
handleGlobalHotkeyKeyDownImpl(
|
|
() => ({
|
|
HOTKEY_DEBUG: false,
|
|
closeTabKeyStr: 'Ctrl + W',
|
|
executeHotkeyAction: (action: string) => {
|
|
handledActions.push(action);
|
|
},
|
|
hotkeyScheme: 'pc',
|
|
keyBindings: DEFAULT_KEY_BINDINGS,
|
|
matchesKeyBinding,
|
|
}),
|
|
event,
|
|
);
|
|
|
|
assert.deepEqual(handledActions, []);
|
|
assert.equal(prevented, false);
|
|
assert.equal(stopped, false);
|
|
});
|
|
|
|
test('global hotkey handler routes quick switch through focused search inputs', () => {
|
|
const target = new FakeInputHTMLElement();
|
|
const handledActions: string[] = [];
|
|
const event = {
|
|
key: 'j',
|
|
code: 'KeyJ',
|
|
ctrlKey: true,
|
|
metaKey: false,
|
|
altKey: false,
|
|
shiftKey: false,
|
|
target,
|
|
composedPath: () => [target],
|
|
preventDefault: () => {},
|
|
stopPropagation: () => {},
|
|
} as unknown as KeyboardEvent;
|
|
|
|
handleGlobalHotkeyKeyDownImpl(
|
|
() => ({
|
|
HOTKEY_DEBUG: false,
|
|
closeTabKeyStr: 'Ctrl + W',
|
|
executeHotkeyAction: (action: string) => {
|
|
handledActions.push(action);
|
|
},
|
|
hotkeyScheme: 'pc',
|
|
keyBindings: DEFAULT_KEY_BINDINGS,
|
|
matchesKeyBinding,
|
|
}),
|
|
event,
|
|
);
|
|
|
|
assert.deepEqual(handledActions, ['quickSwitch']);
|
|
});
|
|
|
|
test('global hotkey handler magnifies panes from focused form inputs', () => {
|
|
const target = new FakeInputHTMLElement();
|
|
const handledActions: string[] = [];
|
|
const event = {
|
|
key: 'm',
|
|
code: 'KeyM',
|
|
ctrlKey: false,
|
|
metaKey: false,
|
|
altKey: true,
|
|
shiftKey: false,
|
|
target,
|
|
composedPath: () => [target],
|
|
preventDefault: () => {},
|
|
stopPropagation: () => {},
|
|
} as unknown as KeyboardEvent;
|
|
|
|
handleGlobalHotkeyKeyDownImpl(
|
|
() => ({
|
|
HOTKEY_DEBUG: false,
|
|
closeTabKeyStr: 'Ctrl + W',
|
|
executeHotkeyAction: (action: string) => {
|
|
handledActions.push(action);
|
|
},
|
|
hotkeyScheme: 'pc',
|
|
keyBindings: DEFAULT_KEY_BINDINGS,
|
|
matchesKeyBinding,
|
|
}),
|
|
event,
|
|
);
|
|
|
|
assert.deepEqual(handledActions, ['togglePaneZoom']);
|
|
});
|
|
|
|
test('forwarded native shortcut can run a reassigned global action from Monaco', () => {
|
|
const target = new FakeMonacoHTMLElement();
|
|
const handledActions: string[] = [];
|
|
let prevented = false;
|
|
const event = markForwardedNativeShortcutEvent({
|
|
key: 'w',
|
|
code: 'KeyW',
|
|
ctrlKey: false,
|
|
metaKey: true,
|
|
altKey: false,
|
|
shiftKey: false,
|
|
target,
|
|
composedPath: () => [target],
|
|
preventDefault: () => {
|
|
prevented = true;
|
|
},
|
|
stopPropagation: () => {},
|
|
} as unknown as KeyboardEvent);
|
|
const keyBindings = DEFAULT_KEY_BINDINGS.map((binding) => {
|
|
if (binding.action === 'closeTab') return { ...binding, mac: 'Disabled' };
|
|
if (binding.action === 'newTab') return { ...binding, mac: '⌘ + W' };
|
|
return binding;
|
|
});
|
|
|
|
handleGlobalHotkeyKeyDownImpl(
|
|
() => ({
|
|
HOTKEY_DEBUG: false,
|
|
closeTabKeyStr: 'Disabled',
|
|
executeHotkeyAction: (action: string) => {
|
|
handledActions.push(action);
|
|
},
|
|
hotkeyScheme: 'mac',
|
|
keyBindings,
|
|
matchesKeyBinding,
|
|
}),
|
|
event,
|
|
);
|
|
|
|
assert.deepEqual(handledActions, ['newTab']);
|
|
assert.equal(prevented, true);
|
|
});
|
|
|
|
test('quick switch hotkey toggles the quick switcher open state', () => {
|
|
let isQuickSwitcherOpen = false;
|
|
const setIsQuickSwitcherOpen = (next: boolean) => {
|
|
isQuickSwitcherOpen = next;
|
|
};
|
|
const noop = () => {};
|
|
const baseCtx = {
|
|
IS_DEV: false,
|
|
MOVE_FOCUS_DEBOUNCE_MS: 0,
|
|
activeTabStore: { getActiveTabId: () => 'vault' },
|
|
addConnectionLogRef: { current: noop },
|
|
closeSession: noop,
|
|
closeTabInFlightRef: { current: false },
|
|
closeWorkspace: noop,
|
|
collectSessionIds: () => [],
|
|
confirmIfBusyLocalTerminal: async () => true,
|
|
createLocalTerminalWithCurrentShell: noop,
|
|
editorTabs: [],
|
|
fromEditorTabId: () => null,
|
|
handleOpenSettingsRef: { current: noop },
|
|
handleRequestCloseEditorTabRef: { current: noop },
|
|
isEditorTabId: () => false,
|
|
isQuickSwitcherOpen,
|
|
lastMoveFocusTimeRef: { current: 0 },
|
|
moveFocusInWorkspace: noop,
|
|
orderedTabs: [],
|
|
resolveCloseIntent: () => ({ kind: 'noop' }),
|
|
resolveSnippetsShortcutIntent: () => ({ kind: 'noop' }),
|
|
sessions: [],
|
|
setActiveTabId: noop,
|
|
setAddToWorkspaceDialog: noop,
|
|
setIsQuickSwitcherOpen,
|
|
setNavigateToSection: noop,
|
|
settings: { showSftpTab: true, shellOnlyTabNumberShortcuts: false },
|
|
splitSessionWithCurrentShell: noop,
|
|
systemInfoRef: { current: { username: 'user', hostname: 'host' } },
|
|
toEditorTabId: (id: string) => `editor:${id}`,
|
|
toggleBroadcast: noop,
|
|
toggleScriptsSidePanelRef: { current: noop },
|
|
toggleSidePanelRef: { current: noop },
|
|
workspaces: [],
|
|
};
|
|
|
|
const event = {
|
|
key: 'j',
|
|
code: 'KeyJ',
|
|
ctrlKey: true,
|
|
metaKey: false,
|
|
altKey: false,
|
|
shiftKey: false,
|
|
} as KeyboardEvent;
|
|
|
|
executeHotkeyActionImpl(() => baseCtx, 'quickSwitch', event);
|
|
assert.equal(isQuickSwitcherOpen, true);
|
|
|
|
executeHotkeyActionImpl(() => ({ ...baseCtx, isQuickSwitcherOpen: true }), 'quickSwitch', event);
|
|
assert.equal(isQuickSwitcherOpen, false);
|
|
});
|
|
|
|
test('pane zoom hotkey delegates to the active in-app magnification surface', () => {
|
|
let toggles = 0;
|
|
const noop = () => {};
|
|
const controller = {
|
|
getState: () => 'focusable' as const,
|
|
focus: () => false,
|
|
restore: () => false,
|
|
toggle: () => {
|
|
toggles += 1;
|
|
return true;
|
|
},
|
|
};
|
|
|
|
executeHotkeyActionImpl(() => ({
|
|
IS_DEV: false,
|
|
MOVE_FOCUS_DEBOUNCE_MS: 0,
|
|
activeTabStore: { getActiveTabId: () => 'workspace-1' },
|
|
addConnectionLogRef: { current: noop },
|
|
closeSession: noop,
|
|
closeTabInFlightRef: { current: false },
|
|
closeWorkspace: noop,
|
|
collectSessionIds: () => [],
|
|
confirmIfBusyLocalTerminal: async () => true,
|
|
createLocalTerminalWithCurrentShell: noop,
|
|
editorTabs: [],
|
|
fromEditorTabId: () => null,
|
|
handleOpenSettingsRef: { current: noop },
|
|
handleRequestCloseEditorTabRef: { current: noop },
|
|
isEditorTabId: () => false,
|
|
isQuickSwitcherOpen: false,
|
|
lastMoveFocusTimeRef: { current: 0 },
|
|
moveFocusInWorkspace: noop,
|
|
orderedTabs: [],
|
|
resolveCloseIntent: () => ({ kind: 'noop' }),
|
|
resolveSnippetsShortcutIntent: () => ({ kind: 'noop' }),
|
|
sessions: [],
|
|
setActiveTabId: noop,
|
|
setAddToWorkspaceDialog: noop,
|
|
setIsQuickSwitcherOpen: noop,
|
|
setNavigateToSection: noop,
|
|
settings: { showSftpTab: true, shellOnlyTabNumberShortcuts: false },
|
|
sftpPaneMagnificationRef: { current: null },
|
|
splitSessionWithCurrentShell: noop,
|
|
systemInfoRef: { current: { username: 'user', hostname: 'host' } },
|
|
terminalPaneMagnificationRef: { current: controller },
|
|
toEditorTabId: (id: string) => `editor:${id}`,
|
|
toggleBroadcast: noop,
|
|
toggleScriptsSidePanelRef: { current: noop },
|
|
toggleSidePanelRef: { current: noop },
|
|
toggleWorkspaceViewMode: noop,
|
|
workspaces: [],
|
|
}), 'togglePaneZoom', {} as KeyboardEvent);
|
|
|
|
assert.equal(toggles, 1);
|
|
});
|
|
|
|
test('broadcast hotkey toggles global mode for an active orphan tab', () => {
|
|
let globalToggles = 0;
|
|
let workspaceToggles = 0;
|
|
|
|
executeHotkeyActionImpl(() => ({
|
|
activeTabStore: { getActiveTabId: () => 'orphan-1' },
|
|
editorTabs: [],
|
|
orderedTabs: ['orphan-1', 'orphan-2'],
|
|
settings: { showSftpTab: true, shellOnlyTabNumberShortcuts: false },
|
|
toEditorTabId: (id: string) => id,
|
|
sessions: [
|
|
{ id: 'orphan-1' },
|
|
{ id: 'orphan-2' },
|
|
],
|
|
workspaces: [],
|
|
canUseGlobalBroadcast: true,
|
|
toggleBroadcast: () => { workspaceToggles += 1; },
|
|
toggleGlobalBroadcast: () => { globalToggles += 1; },
|
|
}), 'broadcast', {} as KeyboardEvent);
|
|
|
|
assert.equal(globalToggles, 1);
|
|
assert.equal(workspaceToggles, 0);
|
|
});
|
|
|
|
test('move-focus shortcut cannot send input behind a magnified pane', () => {
|
|
let moveCalls = 0;
|
|
executeHotkeyActionImpl(() => ({
|
|
IS_DEV: false,
|
|
MOVE_FOCUS_DEBOUNCE_MS: 0,
|
|
activeTabStore: { getActiveTabId: () => 'workspace-1' },
|
|
editorTabs: [],
|
|
lastMoveFocusTimeRef: { current: 0 },
|
|
moveFocusInWorkspace: () => {
|
|
moveCalls += 1;
|
|
return true;
|
|
},
|
|
orderedTabs: [],
|
|
settings: { showSftpTab: true, shellOnlyTabNumberShortcuts: false },
|
|
sftpPaneMagnificationRef: { current: null },
|
|
terminalPaneMagnificationRef: {
|
|
current: {
|
|
getState: () => 'focused' as const,
|
|
focus: () => false,
|
|
restore: () => true,
|
|
toggle: () => true,
|
|
},
|
|
},
|
|
toEditorTabId: (id: string) => `editor:${id}`,
|
|
workspaces: [{ id: 'workspace-1', title: 'Workspace' }],
|
|
}), 'moveFocus', {
|
|
key: 'ArrowRight',
|
|
} as KeyboardEvent);
|
|
|
|
assert.equal(moveCalls, 0);
|
|
});
|
|
|
|
test('Escape restores magnification after transient dialogs are closed', () => {
|
|
let restores = 0;
|
|
let prevented = false;
|
|
let stopped = false;
|
|
const event = {
|
|
key: 'Escape',
|
|
defaultPrevented: false,
|
|
preventDefault: () => { prevented = true; },
|
|
stopPropagation: () => { stopped = true; },
|
|
} as unknown as KeyboardEvent;
|
|
|
|
handleEscapeKeyDownImpl(() => ({
|
|
isQuickSwitcherOpen: false,
|
|
setIsQuickSwitcherOpen: () => {},
|
|
sftpPaneMagnificationRef: { current: null },
|
|
terminalPaneMagnificationRef: {
|
|
current: {
|
|
getState: () => 'focused',
|
|
focus: () => false,
|
|
restore: () => {
|
|
restores += 1;
|
|
return true;
|
|
},
|
|
toggle: () => false,
|
|
},
|
|
},
|
|
}), event);
|
|
|
|
assert.equal(restores, 1);
|
|
assert.equal(prevented, true);
|
|
assert.equal(stopped, true);
|
|
});
|
|
|
|
test('consumed Escape does not restore magnification', () => {
|
|
let restores = 0;
|
|
handleEscapeKeyDownImpl(() => ({
|
|
isQuickSwitcherOpen: false,
|
|
setIsQuickSwitcherOpen: () => {},
|
|
terminalPaneMagnificationRef: {
|
|
current: {
|
|
getState: () => 'focused',
|
|
focus: () => false,
|
|
restore: () => {
|
|
restores += 1;
|
|
return true;
|
|
},
|
|
toggle: () => false,
|
|
},
|
|
},
|
|
}), { key: 'Escape', defaultPrevented: true } as KeyboardEvent);
|
|
|
|
assert.equal(restores, 0);
|
|
});
|
|
|
|
test('close tab hotkey routes native plugin view tabs through their owner', () => {
|
|
let closedTabId = '';
|
|
const pluginTabId = 'plugin-view:com.example.view:com.example.view.panel';
|
|
const noop = () => {};
|
|
|
|
executeHotkeyActionImpl(() => ({
|
|
IS_DEV: false,
|
|
MOVE_FOCUS_DEBOUNCE_MS: 0,
|
|
activeTabStore: { getActiveTabId: () => pluginTabId },
|
|
addConnectionLogRef: { current: noop },
|
|
closePluginViewTab: (tabId: string) => { closedTabId = tabId; },
|
|
closeSession: noop,
|
|
closeTabInFlightRef: { current: false },
|
|
closeWorkspace: noop,
|
|
collectSessionIds: () => [],
|
|
confirmIfBusyLocalTerminal: async () => true,
|
|
createLocalTerminalWithCurrentShell: noop,
|
|
editorTabs: [],
|
|
fromEditorTabId: () => null,
|
|
handleOpenSettingsRef: { current: noop },
|
|
handleRequestCloseEditorTabRef: { current: noop },
|
|
isEditorTabId: () => false,
|
|
isPluginViewTabId: (tabId: string) => tabId.startsWith('plugin-view:'),
|
|
isQuickSwitcherOpen: false,
|
|
lastMoveFocusTimeRef: { current: 0 },
|
|
moveFocusInWorkspace: noop,
|
|
orderedTabs: [pluginTabId],
|
|
resolveCloseIntent: () => ({ kind: 'noop' }),
|
|
resolveSnippetsShortcutIntent: () => ({ kind: 'noop' }),
|
|
sessions: [],
|
|
setActiveTabId: noop,
|
|
setAddToWorkspaceDialog: noop,
|
|
setIsQuickSwitcherOpen: noop,
|
|
setNavigateToSection: noop,
|
|
settings: { showSftpTab: true, shellOnlyTabNumberShortcuts: false },
|
|
splitSessionWithCurrentShell: noop,
|
|
systemInfoRef: { current: { username: 'user', hostname: 'host' } },
|
|
toEditorTabId: (id: string) => `editor:${id}`,
|
|
toggleBroadcast: noop,
|
|
toggleScriptsSidePanelRef: { current: noop },
|
|
toggleSidePanelRef: { current: noop },
|
|
toggleWorkspaceViewMode: noop,
|
|
workspaces: [],
|
|
}), 'closeTab', { key: 'w', metaKey: true } as KeyboardEvent);
|
|
|
|
assert.equal(closedTabId, pluginTabId);
|
|
});
|
|
|
|
test('next, previous, and number shortcuts include native plugin view tabs', () => {
|
|
const pluginTabId = 'plugin-view:com.example.view:com.example.view.panel';
|
|
let activeTabId = 'session-1';
|
|
const selected: string[] = [];
|
|
const noop = () => {};
|
|
const context = {
|
|
IS_DEV: false,
|
|
MOVE_FOCUS_DEBOUNCE_MS: 0,
|
|
activeTabStore: { getActiveTabId: () => activeTabId },
|
|
addConnectionLogRef: { current: noop },
|
|
closePluginViewTab: noop,
|
|
closeSession: noop,
|
|
closeTabInFlightRef: { current: false },
|
|
closeWorkspace: noop,
|
|
collectSessionIds: () => [],
|
|
confirmIfBusyLocalTerminal: async () => true,
|
|
createLocalTerminalWithCurrentShell: noop,
|
|
editorTabs: [],
|
|
fromEditorTabId: () => null,
|
|
handleOpenSettingsRef: { current: noop },
|
|
handleRequestCloseEditorTabRef: { current: noop },
|
|
isEditorTabId: () => false,
|
|
isPluginViewTabId: (tabId: string) => tabId.startsWith('plugin-view:'),
|
|
isQuickSwitcherOpen: false,
|
|
lastMoveFocusTimeRef: { current: 0 },
|
|
moveFocusInWorkspace: noop,
|
|
orderedTabs: ['session-1', pluginTabId, 'session-2'],
|
|
resolveCloseIntent: () => ({ kind: 'noop' }),
|
|
resolveSnippetsShortcutIntent: () => ({ kind: 'noop' }),
|
|
sessions: [],
|
|
setActiveTabId: (id: string) => { activeTabId = id; selected.push(id); },
|
|
setAddToWorkspaceDialog: noop,
|
|
setIsQuickSwitcherOpen: noop,
|
|
setNavigateToSection: noop,
|
|
settings: { showSftpTab: false, shellOnlyTabNumberShortcuts: false },
|
|
splitSessionWithCurrentShell: noop,
|
|
systemInfoRef: { current: { username: 'user', hostname: 'host' } },
|
|
toEditorTabId: (id: string) => `editor:${id}`,
|
|
toggleBroadcast: noop,
|
|
toggleScriptsSidePanelRef: { current: noop },
|
|
toggleSidePanelRef: { current: noop },
|
|
toggleWorkspaceViewMode: noop,
|
|
workspaces: [],
|
|
};
|
|
|
|
executeHotkeyActionImpl(() => context, 'nextTab', { key: 'Tab', ctrlKey: true } as KeyboardEvent);
|
|
assert.equal(activeTabId, pluginTabId);
|
|
executeHotkeyActionImpl(() => context, 'prevTab', { key: 'Tab', ctrlKey: true, shiftKey: true } as KeyboardEvent);
|
|
assert.equal(activeTabId, 'session-1');
|
|
executeHotkeyActionImpl(() => context, 'switchToTab', { key: '3', metaKey: true } as KeyboardEvent);
|
|
assert.equal(activeTabId, pluginTabId);
|
|
assert.deepEqual(selected, [pluginTabId, 'session-1', pluginTabId]);
|
|
});
|
|
|
|
test('switchToTab uses physical Digit code when Shift remaps e.key', () => {
|
|
let activeTabId = 'session-1';
|
|
const noop = () => {};
|
|
const context = {
|
|
IS_DEV: false,
|
|
MOVE_FOCUS_DEBOUNCE_MS: 0,
|
|
activeTabStore: { getActiveTabId: () => activeTabId },
|
|
addConnectionLogRef: { current: noop },
|
|
closeSession: noop,
|
|
closeTabInFlightRef: { current: false },
|
|
closeWorkspace: noop,
|
|
collectSessionIds: () => [],
|
|
confirmIfBusyLocalTerminal: async () => true,
|
|
createLocalTerminalWithCurrentShell: noop,
|
|
editorTabs: [],
|
|
fromEditorTabId: () => null,
|
|
handleOpenSettingsRef: { current: noop },
|
|
handleRequestCloseEditorTabRef: { current: noop },
|
|
isEditorTabId: () => false,
|
|
isQuickSwitcherOpen: false,
|
|
lastMoveFocusTimeRef: { current: 0 },
|
|
moveFocusInWorkspace: noop,
|
|
orderedTabs: ['session-1', 'session-2', 'session-3'],
|
|
resolveCloseIntent: () => ({ kind: 'noop' }),
|
|
resolveSnippetsShortcutIntent: () => ({ kind: 'noop' }),
|
|
sessions: [],
|
|
setActiveTabId: (id: string) => { activeTabId = id; },
|
|
setAddToWorkspaceDialog: noop,
|
|
setIsQuickSwitcherOpen: noop,
|
|
setNavigateToSection: noop,
|
|
settings: { showSftpTab: false, shellOnlyTabNumberShortcuts: false },
|
|
splitSessionWithCurrentShell: noop,
|
|
systemInfoRef: { current: { username: 'user', hostname: 'host' } },
|
|
toEditorTabId: (id: string) => `editor:${id}`,
|
|
toggleBroadcast: noop,
|
|
toggleScriptsSidePanelRef: { current: noop },
|
|
toggleSidePanelRef: { current: noop },
|
|
toggleWorkspaceViewMode: noop,
|
|
workspaces: [],
|
|
};
|
|
|
|
executeHotkeyActionImpl(
|
|
() => context,
|
|
'switchToTab',
|
|
{ key: '@', code: 'Digit3', ctrlKey: true, shiftKey: true } as KeyboardEvent,
|
|
);
|
|
assert.equal(activeTabId, 'session-2');
|
|
});
|
|
|
|
test('next tab includes pinned tabs when shell-only shortcut mode is disabled', () => {
|
|
let activeTabId = '';
|
|
const noop = () => {};
|
|
|
|
executeHotkeyActionImpl(
|
|
() => ({
|
|
IS_DEV: false,
|
|
MOVE_FOCUS_DEBOUNCE_MS: 0,
|
|
activeTabStore: { getActiveTabId: () => 'vault' },
|
|
addConnectionLogRef: { current: noop },
|
|
closeSession: noop,
|
|
closeTabInFlightRef: { current: false },
|
|
closeWorkspace: noop,
|
|
collectSessionIds: () => [],
|
|
confirmIfBusyLocalTerminal: async () => true,
|
|
createLocalTerminalWithCurrentShell: noop,
|
|
editorTabs: [{ id: 'editor-1' }],
|
|
fromEditorTabId: () => null,
|
|
handleOpenSettingsRef: { current: noop },
|
|
handleRequestCloseEditorTabRef: { current: noop },
|
|
isEditorTabId: () => false,
|
|
isQuickSwitcherOpen: false,
|
|
lastMoveFocusTimeRef: { current: 0 },
|
|
moveFocusInWorkspace: noop,
|
|
orderedTabs: ['session-1'],
|
|
resolveCloseIntent: () => ({ kind: 'noop' }),
|
|
resolveSnippetsShortcutIntent: () => ({ kind: 'noop' }),
|
|
sessions: [],
|
|
setActiveTabId: (id: string) => { activeTabId = id; },
|
|
setAddToWorkspaceDialog: noop,
|
|
setIsQuickSwitcherOpen: noop,
|
|
setNavigateToSection: noop,
|
|
settings: { showSftpTab: true, shellOnlyTabNumberShortcuts: false },
|
|
splitSessionWithCurrentShell: noop,
|
|
systemInfoRef: { current: { username: 'user', hostname: 'host' } },
|
|
toEditorTabId: (id: string) => `editor:${id}`,
|
|
toggleBroadcast: noop,
|
|
toggleScriptsSidePanelRef: { current: noop },
|
|
toggleSidePanelRef: { current: noop },
|
|
toggleWorkspaceViewMode: noop,
|
|
workspaces: [],
|
|
}),
|
|
'nextTab',
|
|
{ key: 'Tab', ctrlKey: true } as KeyboardEvent,
|
|
);
|
|
|
|
assert.equal(activeTabId, 'sftp');
|
|
});
|
|
|
|
test('next tab skips pinned tabs when shell-only shortcut mode is enabled', () => {
|
|
let activeTabId = '';
|
|
const noop = () => {};
|
|
|
|
executeHotkeyActionImpl(
|
|
() => ({
|
|
IS_DEV: false,
|
|
MOVE_FOCUS_DEBOUNCE_MS: 0,
|
|
activeTabStore: { getActiveTabId: () => 'vault' },
|
|
addConnectionLogRef: { current: noop },
|
|
closeSession: noop,
|
|
closeTabInFlightRef: { current: false },
|
|
closeWorkspace: noop,
|
|
collectSessionIds: () => [],
|
|
confirmIfBusyLocalTerminal: async () => true,
|
|
createLocalTerminalWithCurrentShell: noop,
|
|
editorTabs: [{ id: 'editor-1' }],
|
|
fromEditorTabId: () => null,
|
|
handleOpenSettingsRef: { current: noop },
|
|
handleRequestCloseEditorTabRef: { current: noop },
|
|
isEditorTabId: () => false,
|
|
isQuickSwitcherOpen: false,
|
|
lastMoveFocusTimeRef: { current: 0 },
|
|
moveFocusInWorkspace: noop,
|
|
orderedTabs: ['session-1'],
|
|
resolveCloseIntent: () => ({ kind: 'noop' }),
|
|
resolveSnippetsShortcutIntent: () => ({ kind: 'noop' }),
|
|
sessions: [],
|
|
setActiveTabId: (id: string) => { activeTabId = id; },
|
|
setAddToWorkspaceDialog: noop,
|
|
setIsQuickSwitcherOpen: noop,
|
|
setNavigateToSection: noop,
|
|
settings: { showSftpTab: true, shellOnlyTabNumberShortcuts: true },
|
|
splitSessionWithCurrentShell: noop,
|
|
systemInfoRef: { current: { username: 'user', hostname: 'host' } },
|
|
toEditorTabId: (id: string) => `editor:${id}`,
|
|
toggleBroadcast: noop,
|
|
toggleScriptsSidePanelRef: { current: noop },
|
|
toggleSidePanelRef: { current: noop },
|
|
toggleWorkspaceViewMode: noop,
|
|
workspaces: [],
|
|
}),
|
|
'nextTab',
|
|
{ key: 'Tab', ctrlKey: true } as KeyboardEvent,
|
|
);
|
|
|
|
assert.equal(activeTabId, 'session-1');
|
|
});
|
|
|
|
test('connection log host snapshot includes custom host icon fields', () => {
|
|
assert.deepEqual(
|
|
getLogHostVisualSnapshot({
|
|
id: 'host-1',
|
|
label: 'Database',
|
|
hostname: 'db.example.com',
|
|
username: 'root',
|
|
tags: [],
|
|
os: 'linux',
|
|
distro: 'ubuntu',
|
|
iconMode: 'custom',
|
|
iconId: 'database',
|
|
iconColor: 'blue',
|
|
}),
|
|
{
|
|
hostOs: 'linux',
|
|
hostDistro: 'ubuntu',
|
|
hostIconMode: 'custom',
|
|
hostIconId: 'database',
|
|
hostIconColorMode: 'manual',
|
|
hostIconColor: 'blue',
|
|
},
|
|
);
|
|
});
|