| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235 |
- import type { ActionItem } from '../../app/components/goto-anything/actions/types'
-
- // Mock the entire actions module to avoid import issues
- jest.mock('../../app/components/goto-anything/actions', () => ({
- matchAction: jest.fn(),
- }))
-
- jest.mock('../../app/components/goto-anything/actions/commands/registry')
-
- // Import after mocking to get mocked version
- import { matchAction } from '../../app/components/goto-anything/actions'
- import { slashCommandRegistry } from '../../app/components/goto-anything/actions/commands/registry'
-
- // Implement the actual matchAction logic for testing
- const actualMatchAction = (query: string, actions: Record<string, ActionItem>) => {
- const result = Object.values(actions).find((action) => {
- // Special handling for slash commands
- if (action.key === '/') {
- // Get all registered commands from the registry
- const allCommands = slashCommandRegistry.getAllCommands()
-
- // Check if query matches any registered command
- return allCommands.some((cmd) => {
- const cmdPattern = `/${cmd.name}`
-
- // For direct mode commands, don't match (keep in command selector)
- if (cmd.mode === 'direct')
- return false
-
- // For submenu mode commands, match when complete command is entered
- return query === cmdPattern || query.startsWith(`${cmdPattern} `)
- })
- }
-
- const reg = new RegExp(`^(${action.key}|${action.shortcut})(?:\\s|$)`)
- return reg.test(query)
- })
- return result
- }
-
- // Replace mock with actual implementation
- ;(matchAction as jest.Mock).mockImplementation(actualMatchAction)
-
- describe('matchAction Logic', () => {
- const mockActions: Record<string, ActionItem> = {
- app: {
- key: '@app',
- shortcut: '@a',
- title: 'Search Applications',
- description: 'Search apps',
- search: jest.fn(),
- },
- knowledge: {
- key: '@knowledge',
- shortcut: '@kb',
- title: 'Search Knowledge',
- description: 'Search knowledge bases',
- search: jest.fn(),
- },
- slash: {
- key: '/',
- shortcut: '/',
- title: 'Commands',
- description: 'Execute commands',
- search: jest.fn(),
- },
- }
-
- beforeEach(() => {
- jest.clearAllMocks()
- ;(slashCommandRegistry.getAllCommands as jest.Mock).mockReturnValue([
- { name: 'docs', mode: 'direct' },
- { name: 'community', mode: 'direct' },
- { name: 'feedback', mode: 'direct' },
- { name: 'account', mode: 'direct' },
- { name: 'theme', mode: 'submenu' },
- { name: 'language', mode: 'submenu' },
- ])
- })
-
- describe('@ Actions Matching', () => {
- it('should match @app with key', () => {
- const result = matchAction('@app', mockActions)
- expect(result).toBe(mockActions.app)
- })
-
- it('should match @app with shortcut', () => {
- const result = matchAction('@a', mockActions)
- expect(result).toBe(mockActions.app)
- })
-
- it('should match @knowledge with key', () => {
- const result = matchAction('@knowledge', mockActions)
- expect(result).toBe(mockActions.knowledge)
- })
-
- it('should match @knowledge with shortcut @kb', () => {
- const result = matchAction('@kb', mockActions)
- expect(result).toBe(mockActions.knowledge)
- })
-
- it('should match with text after action', () => {
- const result = matchAction('@app search term', mockActions)
- expect(result).toBe(mockActions.app)
- })
-
- it('should not match partial @ actions', () => {
- const result = matchAction('@ap', mockActions)
- expect(result).toBeUndefined()
- })
- })
-
- describe('Slash Commands Matching', () => {
- describe('Direct Mode Commands', () => {
- it('should not match direct mode commands', () => {
- const result = matchAction('/docs', mockActions)
- expect(result).toBeUndefined()
- })
-
- it('should not match direct mode with arguments', () => {
- const result = matchAction('/docs something', mockActions)
- expect(result).toBeUndefined()
- })
-
- it('should not match any direct mode command', () => {
- expect(matchAction('/community', mockActions)).toBeUndefined()
- expect(matchAction('/feedback', mockActions)).toBeUndefined()
- expect(matchAction('/account', mockActions)).toBeUndefined()
- })
- })
-
- describe('Submenu Mode Commands', () => {
- it('should match submenu mode commands exactly', () => {
- const result = matchAction('/theme', mockActions)
- expect(result).toBe(mockActions.slash)
- })
-
- it('should match submenu mode with arguments', () => {
- const result = matchAction('/theme dark', mockActions)
- expect(result).toBe(mockActions.slash)
- })
-
- it('should match all submenu commands', () => {
- expect(matchAction('/language', mockActions)).toBe(mockActions.slash)
- expect(matchAction('/language en', mockActions)).toBe(mockActions.slash)
- })
- })
-
- describe('Slash Without Command', () => {
- it('should not match single slash', () => {
- const result = matchAction('/', mockActions)
- expect(result).toBeUndefined()
- })
-
- it('should not match unregistered commands', () => {
- const result = matchAction('/unknown', mockActions)
- expect(result).toBeUndefined()
- })
- })
- })
-
- describe('Edge Cases', () => {
- it('should handle empty query', () => {
- const result = matchAction('', mockActions)
- expect(result).toBeUndefined()
- })
-
- it('should handle whitespace only', () => {
- const result = matchAction(' ', mockActions)
- expect(result).toBeUndefined()
- })
-
- it('should handle regular text without actions', () => {
- const result = matchAction('search something', mockActions)
- expect(result).toBeUndefined()
- })
-
- it('should handle special characters', () => {
- const result = matchAction('#tag', mockActions)
- expect(result).toBeUndefined()
- })
-
- it('should handle multiple @ or /', () => {
- expect(matchAction('@@app', mockActions)).toBeUndefined()
- expect(matchAction('//theme', mockActions)).toBeUndefined()
- })
- })
-
- describe('Mode-based Filtering', () => {
- it('should filter direct mode commands from matching', () => {
- ;(slashCommandRegistry.getAllCommands as jest.Mock).mockReturnValue([
- { name: 'test', mode: 'direct' },
- ])
-
- const result = matchAction('/test', mockActions)
- expect(result).toBeUndefined()
- })
-
- it('should allow submenu mode commands to match', () => {
- ;(slashCommandRegistry.getAllCommands as jest.Mock).mockReturnValue([
- { name: 'test', mode: 'submenu' },
- ])
-
- const result = matchAction('/test', mockActions)
- expect(result).toBe(mockActions.slash)
- })
-
- it('should treat undefined mode as submenu', () => {
- ;(slashCommandRegistry.getAllCommands as jest.Mock).mockReturnValue([
- { name: 'test' }, // No mode specified
- ])
-
- const result = matchAction('/test', mockActions)
- expect(result).toBe(mockActions.slash)
- })
- })
-
- describe('Registry Integration', () => {
- it('should call getAllCommands when matching slash', () => {
- matchAction('/theme', mockActions)
- expect(slashCommandRegistry.getAllCommands).toHaveBeenCalled()
- })
-
- it('should not call getAllCommands for @ actions', () => {
- matchAction('@app', mockActions)
- expect(slashCommandRegistry.getAllCommands).not.toHaveBeenCalled()
- })
-
- it('should handle empty command list', () => {
- ;(slashCommandRegistry.getAllCommands as jest.Mock).mockReturnValue([])
- const result = matchAction('/anything', mockActions)
- expect(result).toBeUndefined()
- })
- })
- })
|