Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.

slash-command-modes.test.tsx 7.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. import '@testing-library/jest-dom'
  2. import { slashCommandRegistry } from '../../app/components/goto-anything/actions/commands/registry'
  3. import type { SlashCommandHandler } from '../../app/components/goto-anything/actions/commands/types'
  4. // Mock the registry
  5. jest.mock('../../app/components/goto-anything/actions/commands/registry')
  6. describe('Slash Command Dual-Mode System', () => {
  7. const mockDirectCommand: SlashCommandHandler = {
  8. name: 'docs',
  9. description: 'Open documentation',
  10. mode: 'direct',
  11. execute: jest.fn(),
  12. search: jest.fn().mockResolvedValue([
  13. {
  14. id: 'docs',
  15. title: 'Documentation',
  16. description: 'Open documentation',
  17. type: 'command' as const,
  18. data: { command: 'navigation.docs', args: {} },
  19. },
  20. ]),
  21. register: jest.fn(),
  22. unregister: jest.fn(),
  23. }
  24. const mockSubmenuCommand: SlashCommandHandler = {
  25. name: 'theme',
  26. description: 'Change theme',
  27. mode: 'submenu',
  28. search: jest.fn().mockResolvedValue([
  29. {
  30. id: 'theme-light',
  31. title: 'Light Theme',
  32. description: 'Switch to light theme',
  33. type: 'command' as const,
  34. data: { command: 'theme.set', args: { theme: 'light' } },
  35. },
  36. {
  37. id: 'theme-dark',
  38. title: 'Dark Theme',
  39. description: 'Switch to dark theme',
  40. type: 'command' as const,
  41. data: { command: 'theme.set', args: { theme: 'dark' } },
  42. },
  43. ]),
  44. register: jest.fn(),
  45. unregister: jest.fn(),
  46. }
  47. beforeEach(() => {
  48. jest.clearAllMocks()
  49. ;(slashCommandRegistry as any).findCommand = jest.fn((name: string) => {
  50. if (name === 'docs') return mockDirectCommand
  51. if (name === 'theme') return mockSubmenuCommand
  52. return null
  53. })
  54. ;(slashCommandRegistry as any).getAllCommands = jest.fn(() => [
  55. mockDirectCommand,
  56. mockSubmenuCommand,
  57. ])
  58. })
  59. describe('Direct Mode Commands', () => {
  60. it('should execute immediately when selected', () => {
  61. const mockSetShow = jest.fn()
  62. const mockSetSearchQuery = jest.fn()
  63. // Simulate command selection
  64. const handler = slashCommandRegistry.findCommand('docs')
  65. expect(handler?.mode).toBe('direct')
  66. if (handler?.mode === 'direct' && handler.execute) {
  67. handler.execute()
  68. mockSetShow(false)
  69. mockSetSearchQuery('')
  70. }
  71. expect(mockDirectCommand.execute).toHaveBeenCalled()
  72. expect(mockSetShow).toHaveBeenCalledWith(false)
  73. expect(mockSetSearchQuery).toHaveBeenCalledWith('')
  74. })
  75. it('should not enter submenu for direct mode commands', () => {
  76. const handler = slashCommandRegistry.findCommand('docs')
  77. expect(handler?.mode).toBe('direct')
  78. expect(handler?.execute).toBeDefined()
  79. })
  80. it('should close modal after execution', () => {
  81. const mockModalClose = jest.fn()
  82. const handler = slashCommandRegistry.findCommand('docs')
  83. if (handler?.mode === 'direct' && handler.execute) {
  84. handler.execute()
  85. mockModalClose()
  86. }
  87. expect(mockModalClose).toHaveBeenCalled()
  88. })
  89. })
  90. describe('Submenu Mode Commands', () => {
  91. it('should show options instead of executing immediately', async () => {
  92. const handler = slashCommandRegistry.findCommand('theme')
  93. expect(handler?.mode).toBe('submenu')
  94. const results = await handler?.search('', 'en')
  95. expect(results).toHaveLength(2)
  96. expect(results?.[0].title).toBe('Light Theme')
  97. expect(results?.[1].title).toBe('Dark Theme')
  98. })
  99. it('should not have execute function for submenu mode', () => {
  100. const handler = slashCommandRegistry.findCommand('theme')
  101. expect(handler?.mode).toBe('submenu')
  102. expect(handler?.execute).toBeUndefined()
  103. })
  104. it('should keep modal open for selection', () => {
  105. const mockModalClose = jest.fn()
  106. const handler = slashCommandRegistry.findCommand('theme')
  107. // For submenu mode, modal should not close immediately
  108. expect(handler?.mode).toBe('submenu')
  109. expect(mockModalClose).not.toHaveBeenCalled()
  110. })
  111. })
  112. describe('Mode Detection and Routing', () => {
  113. it('should correctly identify direct mode commands', () => {
  114. const commands = slashCommandRegistry.getAllCommands()
  115. const directCommands = commands.filter(cmd => cmd.mode === 'direct')
  116. const submenuCommands = commands.filter(cmd => cmd.mode === 'submenu')
  117. expect(directCommands).toContainEqual(expect.objectContaining({ name: 'docs' }))
  118. expect(submenuCommands).toContainEqual(expect.objectContaining({ name: 'theme' }))
  119. })
  120. it('should handle missing mode property gracefully', () => {
  121. const commandWithoutMode: SlashCommandHandler = {
  122. name: 'test',
  123. description: 'Test command',
  124. search: jest.fn(),
  125. register: jest.fn(),
  126. unregister: jest.fn(),
  127. }
  128. ;(slashCommandRegistry as any).findCommand = jest.fn(() => commandWithoutMode)
  129. const handler = slashCommandRegistry.findCommand('test')
  130. // Default behavior should be submenu when mode is not specified
  131. expect(handler?.mode).toBeUndefined()
  132. expect(handler?.execute).toBeUndefined()
  133. })
  134. })
  135. describe('Enter Key Handling', () => {
  136. // Helper function to simulate key handler behavior
  137. const createKeyHandler = () => {
  138. return (commandKey: string) => {
  139. if (commandKey.startsWith('/')) {
  140. const commandName = commandKey.substring(1)
  141. const handler = slashCommandRegistry.findCommand(commandName)
  142. if (handler?.mode === 'direct' && handler.execute) {
  143. handler.execute()
  144. return true // Indicates handled
  145. }
  146. }
  147. return false
  148. }
  149. }
  150. it('should trigger direct execution on Enter for direct mode', () => {
  151. const keyHandler = createKeyHandler()
  152. const handled = keyHandler('/docs')
  153. expect(handled).toBe(true)
  154. expect(mockDirectCommand.execute).toHaveBeenCalled()
  155. })
  156. it('should not trigger direct execution for submenu mode', () => {
  157. const keyHandler = createKeyHandler()
  158. const handled = keyHandler('/theme')
  159. expect(handled).toBe(false)
  160. expect(mockSubmenuCommand.search).not.toHaveBeenCalled()
  161. })
  162. })
  163. describe('Command Registration', () => {
  164. it('should register both direct and submenu commands', () => {
  165. mockDirectCommand.register?.({})
  166. mockSubmenuCommand.register?.({ setTheme: jest.fn() })
  167. expect(mockDirectCommand.register).toHaveBeenCalled()
  168. expect(mockSubmenuCommand.register).toHaveBeenCalled()
  169. })
  170. it('should handle unregistration for both command types', () => {
  171. // Test unregister for direct command
  172. mockDirectCommand.unregister?.()
  173. expect(mockDirectCommand.unregister).toHaveBeenCalled()
  174. // Test unregister for submenu command
  175. mockSubmenuCommand.unregister?.()
  176. expect(mockSubmenuCommand.unregister).toHaveBeenCalled()
  177. // Verify both were called independently
  178. expect(mockDirectCommand.unregister).toHaveBeenCalledTimes(1)
  179. expect(mockSubmenuCommand.unregister).toHaveBeenCalledTimes(1)
  180. })
  181. })
  182. })