/** * KeybindingStore 测试 — 快捷键自定义系统 * * 测试覆盖: * - 初始化加载默认键绑定 * - 自定义键绑定 (setCustomKeybinding) * - 重置为默认 (resetKeybinding / resetAll) * - 冲突检测 (getConflicts) * - 键序列规范化 (normalizeKeySequence) * - 持久化到 settings * - 导出/导入 */ import { describe, it, expect, beforeEach, vi } from 'vitest' import { useKeybindingStore } from '../../src/stores/keybindingStore' import { KeybindingAction } from '../../src/constants/keybindings' describe('KeybindingStore', () => { beforeEach(() => { // 重置 store 到初始状态 useKeybindingStore.setState({ customKeybindings: {}, isLoaded: false, }) // 清除 mock 调用记录 vi.clearAllMocks() }) // ─── 1. 初始化与加载 ──────────────────────────────── describe('初始化与加载', () => { it('应有正确的默认状态', () => { const state = useKeybindingStore.getState() expect(state.customKeybindings).toEqual({}) expect(state.isLoaded).toBe(false) }) it('loadFromSettings 应从 opsxApi 加载自定义快捷键', async () => { const customData = { [KeybindingAction.TAB_CLOSE]: 'Ctrl+Q', [KeybindingAction.TAB_NEW]: 'Ctrl+T', } // Mock opsxApi 返回自定义快捷键 vi.mocked(window.opsxApi.settingsGet).mockResolvedValueOnce({ success: true, value: customData, }) await useKeybindingStore.getState().loadFromSettings() const state = useKeybindingStore.getState() expect(state.isLoaded).toBe(true) expect(state.customKeybindings).toEqual(customData) }) it('loadFromSettings 无保存数据时应使用空自定义', async () => { vi.mocked(window.opsxApi.settingsGet).mockResolvedValueOnce({ success: false, }) await useKeybindingStore.getState().loadFromSettings() const state = useKeybindingStore.getState() expect(state.isLoaded).toBe(true) expect(state.customKeybindings).toEqual({}) }) it('loadFromSettings API 报错时应安全降级', async () => { vi.mocked(window.opsxApi.settingsGet).mockRejectedValueOnce(new Error('API error')) await useKeybindingStore.getState().loadFromSettings() const state = useKeybindingStore.getState() expect(state.isLoaded).toBe(true) expect(state.customKeybindings).toEqual({}) }) }) // ─── 2. 获取有效快捷键 ────────────────────────────── describe('getEffectiveKeys', () => { it('无自定义时应返回默认键', () => { const keys = useKeybindingStore.getState().getEffectiveKeys(KeybindingAction.TAB_NEXT) expect(keys).toBe('Ctrl+Tab') }) it('有自定义时应返回自定义键', () => { useKeybindingStore.setState({ customKeybindings: { [KeybindingAction.TAB_NEXT]: 'Alt+Right', }, }) const keys = useKeybindingStore.getState().getEffectiveKeys(KeybindingAction.TAB_NEXT) expect(keys).toBe('Alt+Right') }) it('自定义为空字符串时应返回默认键', () => { useKeybindingStore.setState({ customKeybindings: { [KeybindingAction.TAB_NEXT]: '', }, }) const keys = useKeybindingStore.getState().getEffectiveKeys(KeybindingAction.TAB_NEXT) expect(keys).toBe('Ctrl+Tab') }) }) // ─── 3. 设置自定义快捷键 ──────────────────────────── describe('setCustomKeybinding', () => { it('应设置自定义键绑定并持久化', async () => { await useKeybindingStore.getState().setCustomKeybinding( KeybindingAction.TAB_CLOSE, 'Ctrl+Q' ) const state = useKeybindingStore.getState() expect(state.customKeybindings[KeybindingAction.TAB_CLOSE]).toBe('Ctrl+Q') // 应调用 settingsSet 持久化 expect(window.opsxApi.settingsSet).toHaveBeenCalledWith( 'keybindings', { [KeybindingAction.TAB_CLOSE]: 'Ctrl+Q' } ) }) it('多次设置应合并而非覆盖其他', async () => { await useKeybindingStore.getState().setCustomKeybinding( KeybindingAction.TAB_CLOSE, 'Ctrl+Q' ) await useKeybindingStore.getState().setCustomKeybinding( KeybindingAction.TAB_NEW, 'Ctrl+T' ) const state = useKeybindingStore.getState() expect(state.customKeybindings[KeybindingAction.TAB_CLOSE]).toBe('Ctrl+Q') expect(state.customKeybindings[KeybindingAction.TAB_NEW]).toBe('Ctrl+T') // 第二次调用应包含两个 const lastCall = vi.mocked(window.opsxApi.settingsSet).mock.calls.at(-1) expect(lastCall?.[1]).toEqual({ [KeybindingAction.TAB_CLOSE]: 'Ctrl+Q', [KeybindingAction.TAB_NEW]: 'Ctrl+T', }) }) it('设置为与默认相同的值时仍应记录', async () => { await useKeybindingStore.getState().setCustomKeybinding( KeybindingAction.TAB_CLOSE, 'Ctrl+W' // 等同默认 ) const state = useKeybindingStore.getState() expect(state.customKeybindings[KeybindingAction.TAB_CLOSE]).toBe('Ctrl+W') }) }) // ─── 4. 重置快捷键 ────────────────────────────────── describe('重置操作', () => { it('resetKeybinding 应移除单个自定义', async () => { useKeybindingStore.setState({ customKeybindings: { [KeybindingAction.TAB_CLOSE]: 'Ctrl+Q', [KeybindingAction.TAB_NEW]: 'Ctrl+T', }, }) await useKeybindingStore.getState().resetKeybinding(KeybindingAction.TAB_CLOSE) const state = useKeybindingStore.getState() expect(state.customKeybindings[KeybindingAction.TAB_CLOSE]).toBeUndefined() expect(state.customKeybindings[KeybindingAction.TAB_NEW]).toBe('Ctrl+T') }) it('resetAll 应清除所有自定义', async () => { useKeybindingStore.setState({ customKeybindings: { [KeybindingAction.TAB_CLOSE]: 'Ctrl+Q', [KeybindingAction.TAB_NEW]: 'Ctrl+T', }, }) await useKeybindingStore.getState().resetAll() const state = useKeybindingStore.getState() expect(state.customKeybindings).toEqual({}) expect(window.opsxApi.settingsSet).toHaveBeenCalledWith('keybindings', {}) }) }) // ─── 5. 冲突检测 ──────────────────────────────────── describe('冲突检测', () => { it('getConflicts 应检测到与其他操作的冲突', () => { // TAB_CLOSE 默认是 Ctrl+W,设置 TAB_NEW 也为 Ctrl+W 应产生冲突 useKeybindingStore.setState({ customKeybindings: { [KeybindingAction.TAB_NEW]: 'Ctrl+W', }, }) const conflicts = useKeybindingStore.getState().getConflicts( KeybindingAction.TAB_NEW, 'Ctrl+W' ) // 应检测到 TAB_CLOSE (默认 Ctrl+W) 和 TAB_NEW (自定义 Ctrl+W) 冲突 expect(conflicts.length).toBeGreaterThan(0) expect(conflicts.some((c) => c.id === KeybindingAction.TAB_CLOSE)).toBe(true) }) it('getConflicts 不应报告自身为冲突', () => { const conflicts = useKeybindingStore.getState().getConflicts( KeybindingAction.TAB_CLOSE, 'Ctrl+W' // 它自己的默认值 ) expect(conflicts).toEqual([]) }) it('getConflicts 应考虑自定义快捷键', () => { useKeybindingStore.setState({ customKeybindings: { [KeybindingAction.TAB_CLOSE]: 'Alt+F4', }, }) // 设置另一个操作为 Alt+F4 const conflicts = useKeybindingStore.getState().getConflicts( KeybindingAction.TAB_NEW, 'Alt+F4' ) expect(conflicts.some((c) => c.id === KeybindingAction.TAB_CLOSE)).toBe(true) }) it('无冲突时应返回空数组', () => { const conflicts = useKeybindingStore.getState().getConflicts( KeybindingAction.TAB_NEW, 'Ctrl+Alt+X' // 不冲突 ) expect(conflicts).toEqual([]) }) it('特殊的 Ctrl+1…9 范围不应产生冲突', () => { const conflicts = useKeybindingStore.getState().getConflicts( KeybindingAction.TAB_GOTO_N, 'Ctrl+1…9' ) expect(conflicts).toEqual([]) }) }) // ─── 6. 获取所有有效快捷键映射 ────────────────────── describe('getAllEffective', () => { it('应返回所有操作的有效键绑定', () => { useKeybindingStore.setState({ customKeybindings: { [KeybindingAction.TAB_CLOSE]: 'Ctrl+Q', }, }) const all = useKeybindingStore.getState().getAllEffective() expect(all[KeybindingAction.TAB_CLOSE]).toBe('Ctrl+Q') // 自定义 expect(all[KeybindingAction.TAB_NEXT]).toBe('Ctrl+Tab') // 默认 expect(all[KeybindingAction.TOOLS_COMMAND_PALETTE]).toBe('Ctrl+Shift+P') // 默认 }) }) // ─── 7. 键序列规范化 ──────────────────────────────── describe('normalizeKeySequence', () => { it('应规范化修饰键顺序', () => { const { normalizeKeySequence } = useKeybindingStore.getState() expect(normalizeKeySequence('Shift+Ctrl+Tab')).toBe('Ctrl+Shift+Tab') }) it('应处理小写键名', () => { const { normalizeKeySequence } = useKeybindingStore.getState() expect(normalizeKeySequence('ctrl+shift+p')).toBe('Ctrl+Shift+P') }) it('应处理空格和多余分隔符', () => { const { normalizeKeySequence } = useKeybindingStore.getState() expect(normalizeKeySequence(' Ctrl + Tab ')).toBe('Ctrl+Tab') }) it('应正确处理单键 (F11)', () => { const { normalizeKeySequence } = useKeybindingStore.getState() expect(normalizeKeySequence('F11')).toBe('F11') }) it('Meta 应映射为 Ctrl', () => { const { normalizeKeySequence } = useKeybindingStore.getState() expect(normalizeKeySequence('Meta+W')).toBe('Ctrl+W') }) }) })