Files
ComfyUI_frontend/src/stores/commandStore.test.ts
2026-07-01 15:39:10 +02:00

219 lines
6.6 KiB
TypeScript

import { createTestingPinia } from '@pinia/testing'
import { setActivePinia } from 'pinia'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { useCommandStore } from '@/stores/commandStore'
vi.mock('@/composables/useErrorHandling', () => ({
useErrorHandling: () => ({
wrapWithErrorHandlingAsync:
(fn: () => Promise<void>, errorHandler?: (e: unknown) => void) =>
async () => {
try {
await fn()
} catch (e) {
if (errorHandler) errorHandler(e)
else throw e
}
}
})
}))
vi.mock('@/platform/keybindings/keybindingStore', () => ({
useKeybindingStore: () => ({
getKeybindingByCommandId: () => null
})
}))
describe('commandStore', () => {
beforeEach(() => {
setActivePinia(createTestingPinia({ stubActions: false }))
})
describe('registerCommand', () => {
it('registers a command by id', () => {
const store = useCommandStore()
store.registerCommand({
id: 'test.command',
function: vi.fn()
})
expect(store.isRegistered('test.command')).toBe(true)
})
it('warns on duplicate registration and overwrites with new function', async () => {
const store = useCommandStore()
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
const originalFn = vi.fn()
const replacementFn = vi.fn()
store.registerCommand({ id: 'dup', function: originalFn })
store.registerCommand({ id: 'dup', function: replacementFn })
expect(warnSpy).toHaveBeenCalledWith('Command dup already registered')
warnSpy.mockRestore()
await store.getCommand('dup')?.function()
expect(replacementFn).toHaveBeenCalled()
expect(originalFn).not.toHaveBeenCalled()
})
})
describe('getCommand', () => {
it('returns the registered command', () => {
const store = useCommandStore()
const fn = vi.fn()
store.registerCommand({ id: 'get.test', function: fn, label: 'Test' })
const cmd = store.getCommand('get.test')
expect(cmd).toBeDefined()
expect(cmd?.label).toBe('Test')
})
it('returns undefined for unregistered command', () => {
const store = useCommandStore()
expect(store.getCommand('nonexistent')).toBeUndefined()
})
})
describe('execute', () => {
it('executes a registered command', async () => {
const store = useCommandStore()
const fn = vi.fn()
store.registerCommand({ id: 'exec.test', function: fn })
await store.execute('exec.test')
expect(fn).toHaveBeenCalled()
})
it('throws for unregistered command', async () => {
const store = useCommandStore()
await expect(store.execute('missing')).rejects.toThrow(
'Command missing not found'
)
})
it('passes metadata to the command function', async () => {
const store = useCommandStore()
const fn = vi.fn()
store.registerCommand({ id: 'meta.test', function: fn })
await store.execute('meta.test', { metadata: { source: 'keyboard' } })
expect(fn).toHaveBeenCalledWith({ source: 'keyboard' })
})
it('calls errorHandler on failure', async () => {
const store = useCommandStore()
const error = new Error('fail')
store.registerCommand({
id: 'err.test',
function: () => {
throw error
}
})
const handler = vi.fn()
await store.execute('err.test', { errorHandler: handler })
expect(handler).toHaveBeenCalledWith(error)
})
})
describe('unregisterCommand', () => {
it('removes a registered command', () => {
const store = useCommandStore()
store.registerCommand({ id: 'to.remove', function: vi.fn() })
store.unregisterCommand('to.remove')
expect(store.isRegistered('to.remove')).toBe(false)
})
it('is a no-op for an unregistered id', () => {
const store = useCommandStore()
store.registerCommand({ id: 'keep.me', function: vi.fn() })
store.unregisterCommand('nonexistent')
expect(store.isRegistered('keep.me')).toBe(true)
expect(store.commands).toHaveLength(1)
})
})
describe('isRegistered', () => {
it('returns false for unregistered command', () => {
const store = useCommandStore()
expect(store.isRegistered('nope')).toBe(false)
})
})
describe('loadExtensionCommands', () => {
it('registers commands from an extension', () => {
const store = useCommandStore()
store.loadExtensionCommands({
name: 'test-ext',
commands: [
{ id: 'ext.cmd1', function: vi.fn(), label: 'Cmd 1' },
{ id: 'ext.cmd2', function: vi.fn(), label: 'Cmd 2' }
]
})
expect(store.isRegistered('ext.cmd1')).toBe(true)
expect(store.isRegistered('ext.cmd2')).toBe(true)
expect(store.getCommand('ext.cmd1')?.source).toBe('test-ext')
expect(store.getCommand('ext.cmd2')?.source).toBe('test-ext')
})
it('skips extensions without commands', () => {
const store = useCommandStore()
store.loadExtensionCommands({ name: 'no-commands' })
expect(store.commands).toHaveLength(0)
})
})
describe('getCommand resolves dynamic properties', () => {
it('resolves label as function', () => {
const store = useCommandStore()
store.registerCommand({
id: 'label.fn',
function: vi.fn(),
label: () => 'Dynamic'
})
expect(store.getCommand('label.fn')?.label).toBe('Dynamic')
})
it('resolves tooltip as function', () => {
const store = useCommandStore()
store.registerCommand({
id: 'tip.fn',
function: vi.fn(),
tooltip: () => 'Dynamic tip'
})
expect(store.getCommand('tip.fn')?.tooltip).toBe('Dynamic tip')
})
it('uses explicit menubarLabel over label', () => {
const store = useCommandStore()
store.registerCommand({
id: 'mbl.explicit',
function: vi.fn(),
label: 'Label',
menubarLabel: 'Menu Label'
})
expect(store.getCommand('mbl.explicit')?.menubarLabel).toBe('Menu Label')
})
it('falls back menubarLabel to label', () => {
const store = useCommandStore()
store.registerCommand({
id: 'mbl.default',
function: vi.fn(),
label: 'My Label'
})
expect(store.getCommand('mbl.default')?.menubarLabel).toBe('My Label')
})
})
describe('formatKeySequence', () => {
it('returns empty string when command has no keybinding', () => {
const store = useCommandStore()
store.registerCommand({ id: 'no.kb', function: vi.fn() })
const cmd = store.getCommand('no.kb')!
expect(store.formatKeySequence(cmd)).toBe('')
})
})
})