agent-orchestrator/ui-electrobun/tests/unit/keybinding-store.test.ts
Hibryda 1995f03682 test: complete test suite — 166 new tests (stores + hardening + agent)
@agor/stores (37 tests):
- theme: 6 (17 themes, 3 groups, no duplicates)
- notifications: 11 (types, rate limiter, window expiry)
- health: 20 (scoring, burn rate, context pressure, tool tracking)

Electrobun stores (90 tests):
- agent-store: 27 (seqId, dedup, double-start guard, persistence)
- workspace-store: 17 (CRUD, derived state, aggregates)
- plugin-store: 14 (commands, events, permissions, meta)
- keybinding-store: 18 (defaults, chords, conflicts, capture)

Hardening (39 tests):
- durable-sequencing: 10 (monotonic, dedup, restore)
- file-conflict: 10 (mtime, atomic write, workflows)
- backpressure: 7 (paste 64KB, buffer 50MB, line 10MB)
- retention: 7 (count, age, running protected)
- channel-acl: 9 (join/leave, rejection, isolation)

Total across all suites: 1,020+ tests
2026-03-22 05:07:40 +01:00

211 lines
8.8 KiB
TypeScript

// Tests for Electrobun keybinding-store — pure logic.
// Uses bun:test. Tests default bindings, chord serialization, conflict detection.
import { describe, it, expect, beforeEach } from 'bun:test';
// ── Replicated types ──────────────────────────────────────────────────────────
interface Keybinding {
id: string;
label: string;
category: 'Global' | 'Navigation' | 'Terminal' | 'Settings';
chord: string;
defaultChord: string;
}
// ── Default bindings (replicated from keybinding-store.svelte.ts) ───────────
const DEFAULTS: Keybinding[] = [
{ id: 'palette', label: 'Command Palette', category: 'Global', chord: 'Ctrl+K', defaultChord: 'Ctrl+K' },
{ id: 'settings', label: 'Open Settings', category: 'Global', chord: 'Ctrl+,', defaultChord: 'Ctrl+,' },
{ id: 'group1', label: 'Switch to Group 1', category: 'Navigation', chord: 'Ctrl+1', defaultChord: 'Ctrl+1' },
{ id: 'group2', label: 'Switch to Group 2', category: 'Navigation', chord: 'Ctrl+2', defaultChord: 'Ctrl+2' },
{ id: 'group3', label: 'Switch to Group 3', category: 'Navigation', chord: 'Ctrl+3', defaultChord: 'Ctrl+3' },
{ id: 'group4', label: 'Switch to Group 4', category: 'Navigation', chord: 'Ctrl+4', defaultChord: 'Ctrl+4' },
{ id: 'newTerminal', label: 'New Terminal Tab', category: 'Terminal', chord: 'Ctrl+Shift+T', defaultChord: 'Ctrl+Shift+T' },
{ id: 'closeTab', label: 'Close Terminal Tab', category: 'Terminal', chord: 'Ctrl+Shift+W', defaultChord: 'Ctrl+Shift+W' },
{ id: 'nextTab', label: 'Next Terminal Tab', category: 'Terminal', chord: 'Ctrl+]', defaultChord: 'Ctrl+]' },
{ id: 'prevTab', label: 'Previous Terminal Tab', category: 'Terminal', chord: 'Ctrl+[', defaultChord: 'Ctrl+[' },
{ id: 'search', label: 'Global Search', category: 'Global', chord: 'Ctrl+Shift+F', defaultChord: 'Ctrl+Shift+F' },
{ id: 'notifications', label: 'Notification Center', category: 'Global', chord: 'Ctrl+Shift+N', defaultChord: 'Ctrl+Shift+N' },
{ id: 'minimize', label: 'Minimize Window', category: 'Global', chord: 'Ctrl+M', defaultChord: 'Ctrl+M' },
{ id: 'toggleFiles', label: 'Toggle Files Tab', category: 'Navigation', chord: 'Ctrl+Shift+E', defaultChord: 'Ctrl+Shift+E' },
{ id: 'toggleMemory', label: 'Toggle Memory Tab', category: 'Navigation', chord: 'Ctrl+Shift+M', defaultChord: 'Ctrl+Shift+M' },
{ id: 'reload', label: 'Reload App', category: 'Settings', chord: 'Ctrl+R', defaultChord: 'Ctrl+R' },
];
// ── Chord serialization (replicated) ─────────────────────────────────────────
interface MockKeyboardEvent {
ctrlKey: boolean;
metaKey: boolean;
shiftKey: boolean;
altKey: boolean;
key: string;
}
function chordFromEvent(e: MockKeyboardEvent): string {
const parts: string[] = [];
if (e.ctrlKey || e.metaKey) parts.push('Ctrl');
if (e.shiftKey) parts.push('Shift');
if (e.altKey) parts.push('Alt');
const key = e.key === ' ' ? 'Space' : e.key;
if (!['Control', 'Shift', 'Alt', 'Meta'].includes(key)) {
parts.push(key.length === 1 ? key.toUpperCase() : key);
}
return parts.join('+');
}
// ── Store logic (replicated without runes) ──────────────────────────────────
function createKeybindingState() {
let bindings: Keybinding[] = DEFAULTS.map(b => ({ ...b }));
return {
getBindings: () => bindings,
setChord(id: string, chord: string): void {
bindings = bindings.map(b => b.id === id ? { ...b, chord } : b);
},
resetChord(id: string): void {
const def = DEFAULTS.find(b => b.id === id);
if (!def) return;
bindings = bindings.map(b => b.id === id ? { ...b, chord: def.defaultChord } : b);
},
findConflicts(chord: string, excludeId?: string): Keybinding[] {
return bindings.filter(b => b.chord === chord && b.id !== excludeId);
},
};
}
// ── Tests ───────────────────────────────────────────────────────────────────
describe('default bindings', () => {
it('has exactly 16 default bindings', () => {
expect(DEFAULTS).toHaveLength(16);
});
it('all bindings have unique ids', () => {
const ids = DEFAULTS.map(b => b.id);
expect(new Set(ids).size).toBe(ids.length);
});
it('all chords match defaultChord initially', () => {
for (const b of DEFAULTS) {
expect(b.chord).toBe(b.defaultChord);
}
});
it('covers all 4 categories', () => {
const categories = new Set(DEFAULTS.map(b => b.category));
expect(categories.has('Global')).toBe(true);
expect(categories.has('Navigation')).toBe(true);
expect(categories.has('Terminal')).toBe(true);
expect(categories.has('Settings')).toBe(true);
});
it('command palette is Ctrl+K', () => {
const palette = DEFAULTS.find(b => b.id === 'palette');
expect(palette?.chord).toBe('Ctrl+K');
});
});
describe('chordFromEvent', () => {
it('serializes Ctrl+K', () => {
expect(chordFromEvent({ ctrlKey: true, metaKey: false, shiftKey: false, altKey: false, key: 'k' })).toBe('Ctrl+K');
});
it('serializes Ctrl+Shift+F', () => {
expect(chordFromEvent({ ctrlKey: true, metaKey: false, shiftKey: true, altKey: false, key: 'f' })).toBe('Ctrl+Shift+F');
});
it('serializes Alt+1', () => {
expect(chordFromEvent({ ctrlKey: false, metaKey: false, shiftKey: false, altKey: true, key: '1' })).toBe('Alt+1');
});
it('maps space to Space', () => {
expect(chordFromEvent({ ctrlKey: true, metaKey: false, shiftKey: false, altKey: false, key: ' ' })).toBe('Ctrl+Space');
});
it('ignores pure modifier keys', () => {
expect(chordFromEvent({ ctrlKey: true, metaKey: false, shiftKey: false, altKey: false, key: 'Control' })).toBe('Ctrl');
});
it('metaKey treated as Ctrl', () => {
expect(chordFromEvent({ ctrlKey: false, metaKey: true, shiftKey: false, altKey: false, key: 'k' })).toBe('Ctrl+K');
});
it('preserves multi-char key names', () => {
expect(chordFromEvent({ ctrlKey: false, metaKey: false, shiftKey: false, altKey: false, key: 'Escape' })).toBe('Escape');
});
});
describe('setChord / resetChord', () => {
let state: ReturnType<typeof createKeybindingState>;
beforeEach(() => {
state = createKeybindingState();
});
it('setChord updates the binding', () => {
state.setChord('palette', 'Ctrl+P');
const b = state.getBindings().find(b => b.id === 'palette');
expect(b?.chord).toBe('Ctrl+P');
expect(b?.defaultChord).toBe('Ctrl+K'); // default unchanged
});
it('resetChord restores default', () => {
state.setChord('palette', 'Ctrl+P');
state.resetChord('palette');
const b = state.getBindings().find(b => b.id === 'palette');
expect(b?.chord).toBe('Ctrl+K');
});
it('resetChord ignores unknown id', () => {
const before = state.getBindings().length;
state.resetChord('nonexistent');
expect(state.getBindings().length).toBe(before);
});
});
describe('conflict detection', () => {
let state: ReturnType<typeof createKeybindingState>;
beforeEach(() => {
state = createKeybindingState();
});
it('detects conflict when two bindings share a chord', () => {
state.setChord('settings', 'Ctrl+K'); // same as palette
const conflicts = state.findConflicts('Ctrl+K', 'settings');
expect(conflicts).toHaveLength(1);
expect(conflicts[0].id).toBe('palette');
});
it('no conflict when chord is unique', () => {
state.setChord('palette', 'Ctrl+Shift+P');
const conflicts = state.findConflicts('Ctrl+Shift+P', 'palette');
expect(conflicts).toHaveLength(0);
});
it('excludes self from conflict check', () => {
const conflicts = state.findConflicts('Ctrl+K', 'palette');
expect(conflicts).toHaveLength(0);
});
it('finds multiple conflicts', () => {
state.setChord('search', 'Ctrl+K');
state.setChord('reload', 'Ctrl+K');
const conflicts = state.findConflicts('Ctrl+K', 'settings');
expect(conflicts).toHaveLength(3); // palette, search, reload
});
});
describe('capture mode', () => {
it('chordFromEvent records full chord for capture', () => {
// Simulate user pressing Ctrl+Shift+X in capture mode
const chord = chordFromEvent({
ctrlKey: true, metaKey: false, shiftKey: true, altKey: false, key: 'x',
});
expect(chord).toBe('Ctrl+Shift+X');
});
});