dify
This commit is contained in:
212
dify/web/__tests__/goto-anything/slash-command-modes.test.tsx
Normal file
212
dify/web/__tests__/goto-anything/slash-command-modes.test.tsx
Normal file
@@ -0,0 +1,212 @@
|
||||
import '@testing-library/jest-dom'
|
||||
import { slashCommandRegistry } from '../../app/components/goto-anything/actions/commands/registry'
|
||||
import type { SlashCommandHandler } from '../../app/components/goto-anything/actions/commands/types'
|
||||
|
||||
// Mock the registry
|
||||
jest.mock('../../app/components/goto-anything/actions/commands/registry')
|
||||
|
||||
describe('Slash Command Dual-Mode System', () => {
|
||||
const mockDirectCommand: SlashCommandHandler = {
|
||||
name: 'docs',
|
||||
description: 'Open documentation',
|
||||
mode: 'direct',
|
||||
execute: jest.fn(),
|
||||
search: jest.fn().mockResolvedValue([
|
||||
{
|
||||
id: 'docs',
|
||||
title: 'Documentation',
|
||||
description: 'Open documentation',
|
||||
type: 'command' as const,
|
||||
data: { command: 'navigation.docs', args: {} },
|
||||
},
|
||||
]),
|
||||
register: jest.fn(),
|
||||
unregister: jest.fn(),
|
||||
}
|
||||
|
||||
const mockSubmenuCommand: SlashCommandHandler = {
|
||||
name: 'theme',
|
||||
description: 'Change theme',
|
||||
mode: 'submenu',
|
||||
search: jest.fn().mockResolvedValue([
|
||||
{
|
||||
id: 'theme-light',
|
||||
title: 'Light Theme',
|
||||
description: 'Switch to light theme',
|
||||
type: 'command' as const,
|
||||
data: { command: 'theme.set', args: { theme: 'light' } },
|
||||
},
|
||||
{
|
||||
id: 'theme-dark',
|
||||
title: 'Dark Theme',
|
||||
description: 'Switch to dark theme',
|
||||
type: 'command' as const,
|
||||
data: { command: 'theme.set', args: { theme: 'dark' } },
|
||||
},
|
||||
]),
|
||||
register: jest.fn(),
|
||||
unregister: jest.fn(),
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
;(slashCommandRegistry as any).findCommand = jest.fn((name: string) => {
|
||||
if (name === 'docs') return mockDirectCommand
|
||||
if (name === 'theme') return mockSubmenuCommand
|
||||
return null
|
||||
})
|
||||
;(slashCommandRegistry as any).getAllCommands = jest.fn(() => [
|
||||
mockDirectCommand,
|
||||
mockSubmenuCommand,
|
||||
])
|
||||
})
|
||||
|
||||
describe('Direct Mode Commands', () => {
|
||||
it('should execute immediately when selected', () => {
|
||||
const mockSetShow = jest.fn()
|
||||
const mockSetSearchQuery = jest.fn()
|
||||
|
||||
// Simulate command selection
|
||||
const handler = slashCommandRegistry.findCommand('docs')
|
||||
expect(handler?.mode).toBe('direct')
|
||||
|
||||
if (handler?.mode === 'direct' && handler.execute) {
|
||||
handler.execute()
|
||||
mockSetShow(false)
|
||||
mockSetSearchQuery('')
|
||||
}
|
||||
|
||||
expect(mockDirectCommand.execute).toHaveBeenCalled()
|
||||
expect(mockSetShow).toHaveBeenCalledWith(false)
|
||||
expect(mockSetSearchQuery).toHaveBeenCalledWith('')
|
||||
})
|
||||
|
||||
it('should not enter submenu for direct mode commands', () => {
|
||||
const handler = slashCommandRegistry.findCommand('docs')
|
||||
expect(handler?.mode).toBe('direct')
|
||||
expect(handler?.execute).toBeDefined()
|
||||
})
|
||||
|
||||
it('should close modal after execution', () => {
|
||||
const mockModalClose = jest.fn()
|
||||
|
||||
const handler = slashCommandRegistry.findCommand('docs')
|
||||
if (handler?.mode === 'direct' && handler.execute) {
|
||||
handler.execute()
|
||||
mockModalClose()
|
||||
}
|
||||
|
||||
expect(mockModalClose).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('Submenu Mode Commands', () => {
|
||||
it('should show options instead of executing immediately', async () => {
|
||||
const handler = slashCommandRegistry.findCommand('theme')
|
||||
expect(handler?.mode).toBe('submenu')
|
||||
|
||||
const results = await handler?.search('', 'en')
|
||||
expect(results).toHaveLength(2)
|
||||
expect(results?.[0].title).toBe('Light Theme')
|
||||
expect(results?.[1].title).toBe('Dark Theme')
|
||||
})
|
||||
|
||||
it('should not have execute function for submenu mode', () => {
|
||||
const handler = slashCommandRegistry.findCommand('theme')
|
||||
expect(handler?.mode).toBe('submenu')
|
||||
expect(handler?.execute).toBeUndefined()
|
||||
})
|
||||
|
||||
it('should keep modal open for selection', () => {
|
||||
const mockModalClose = jest.fn()
|
||||
|
||||
const handler = slashCommandRegistry.findCommand('theme')
|
||||
// For submenu mode, modal should not close immediately
|
||||
expect(handler?.mode).toBe('submenu')
|
||||
expect(mockModalClose).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('Mode Detection and Routing', () => {
|
||||
it('should correctly identify direct mode commands', () => {
|
||||
const commands = slashCommandRegistry.getAllCommands()
|
||||
const directCommands = commands.filter(cmd => cmd.mode === 'direct')
|
||||
const submenuCommands = commands.filter(cmd => cmd.mode === 'submenu')
|
||||
|
||||
expect(directCommands).toContainEqual(expect.objectContaining({ name: 'docs' }))
|
||||
expect(submenuCommands).toContainEqual(expect.objectContaining({ name: 'theme' }))
|
||||
})
|
||||
|
||||
it('should handle missing mode property gracefully', () => {
|
||||
const commandWithoutMode: SlashCommandHandler = {
|
||||
name: 'test',
|
||||
description: 'Test command',
|
||||
search: jest.fn(),
|
||||
register: jest.fn(),
|
||||
unregister: jest.fn(),
|
||||
}
|
||||
|
||||
;(slashCommandRegistry as any).findCommand = jest.fn(() => commandWithoutMode)
|
||||
|
||||
const handler = slashCommandRegistry.findCommand('test')
|
||||
// Default behavior should be submenu when mode is not specified
|
||||
expect(handler?.mode).toBeUndefined()
|
||||
expect(handler?.execute).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe('Enter Key Handling', () => {
|
||||
// Helper function to simulate key handler behavior
|
||||
const createKeyHandler = () => {
|
||||
return (commandKey: string) => {
|
||||
if (commandKey.startsWith('/')) {
|
||||
const commandName = commandKey.substring(1)
|
||||
const handler = slashCommandRegistry.findCommand(commandName)
|
||||
if (handler?.mode === 'direct' && handler.execute) {
|
||||
handler.execute()
|
||||
return true // Indicates handled
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
it('should trigger direct execution on Enter for direct mode', () => {
|
||||
const keyHandler = createKeyHandler()
|
||||
const handled = keyHandler('/docs')
|
||||
expect(handled).toBe(true)
|
||||
expect(mockDirectCommand.execute).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should not trigger direct execution for submenu mode', () => {
|
||||
const keyHandler = createKeyHandler()
|
||||
const handled = keyHandler('/theme')
|
||||
expect(handled).toBe(false)
|
||||
expect(mockSubmenuCommand.search).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('Command Registration', () => {
|
||||
it('should register both direct and submenu commands', () => {
|
||||
mockDirectCommand.register?.({})
|
||||
mockSubmenuCommand.register?.({ setTheme: jest.fn() })
|
||||
|
||||
expect(mockDirectCommand.register).toHaveBeenCalled()
|
||||
expect(mockSubmenuCommand.register).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should handle unregistration for both command types', () => {
|
||||
// Test unregister for direct command
|
||||
mockDirectCommand.unregister?.()
|
||||
expect(mockDirectCommand.unregister).toHaveBeenCalled()
|
||||
|
||||
// Test unregister for submenu command
|
||||
mockSubmenuCommand.unregister?.()
|
||||
expect(mockSubmenuCommand.unregister).toHaveBeenCalled()
|
||||
|
||||
// Verify both were called independently
|
||||
expect(mockDirectCommand.unregister).toHaveBeenCalledTimes(1)
|
||||
expect(mockSubmenuCommand.unregister).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user