306 lines
10 KiB
TypeScript
306 lines
10 KiB
TypeScript
/**
|
|
* 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 应从 electronAPI 加载自定义快捷键', async () => {
|
|
const customData = {
|
|
[KeybindingAction.TAB_CLOSE]: 'Ctrl+Q',
|
|
[KeybindingAction.TAB_NEW]: 'Ctrl+T',
|
|
}
|
|
|
|
// Mock electronAPI 返回自定义快捷键
|
|
vi.mocked(window.electronAPI.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.electronAPI.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.electronAPI.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.electronAPI.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.electronAPI.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.electronAPI.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')
|
|
})
|
|
})
|
|
})
|