You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

match-action.test.ts 7.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. import type { ActionItem } from '../../app/components/goto-anything/actions/types'
  2. // Mock the entire actions module to avoid import issues
  3. jest.mock('../../app/components/goto-anything/actions', () => ({
  4. matchAction: jest.fn(),
  5. }))
  6. jest.mock('../../app/components/goto-anything/actions/commands/registry')
  7. // Import after mocking to get mocked version
  8. import { matchAction } from '../../app/components/goto-anything/actions'
  9. import { slashCommandRegistry } from '../../app/components/goto-anything/actions/commands/registry'
  10. // Implement the actual matchAction logic for testing
  11. const actualMatchAction = (query: string, actions: Record<string, ActionItem>) => {
  12. const result = Object.values(actions).find((action) => {
  13. // Special handling for slash commands
  14. if (action.key === '/') {
  15. // Get all registered commands from the registry
  16. const allCommands = slashCommandRegistry.getAllCommands()
  17. // Check if query matches any registered command
  18. return allCommands.some((cmd) => {
  19. const cmdPattern = `/${cmd.name}`
  20. // For direct mode commands, don't match (keep in command selector)
  21. if (cmd.mode === 'direct')
  22. return false
  23. // For submenu mode commands, match when complete command is entered
  24. return query === cmdPattern || query.startsWith(`${cmdPattern} `)
  25. })
  26. }
  27. const reg = new RegExp(`^(${action.key}|${action.shortcut})(?:\\s|$)`)
  28. return reg.test(query)
  29. })
  30. return result
  31. }
  32. // Replace mock with actual implementation
  33. ;(matchAction as jest.Mock).mockImplementation(actualMatchAction)
  34. describe('matchAction Logic', () => {
  35. const mockActions: Record<string, ActionItem> = {
  36. app: {
  37. key: '@app',
  38. shortcut: '@a',
  39. title: 'Search Applications',
  40. description: 'Search apps',
  41. search: jest.fn(),
  42. },
  43. knowledge: {
  44. key: '@knowledge',
  45. shortcut: '@kb',
  46. title: 'Search Knowledge',
  47. description: 'Search knowledge bases',
  48. search: jest.fn(),
  49. },
  50. slash: {
  51. key: '/',
  52. shortcut: '/',
  53. title: 'Commands',
  54. description: 'Execute commands',
  55. search: jest.fn(),
  56. },
  57. }
  58. beforeEach(() => {
  59. jest.clearAllMocks()
  60. ;(slashCommandRegistry.getAllCommands as jest.Mock).mockReturnValue([
  61. { name: 'docs', mode: 'direct' },
  62. { name: 'community', mode: 'direct' },
  63. { name: 'feedback', mode: 'direct' },
  64. { name: 'account', mode: 'direct' },
  65. { name: 'theme', mode: 'submenu' },
  66. { name: 'language', mode: 'submenu' },
  67. ])
  68. })
  69. describe('@ Actions Matching', () => {
  70. it('should match @app with key', () => {
  71. const result = matchAction('@app', mockActions)
  72. expect(result).toBe(mockActions.app)
  73. })
  74. it('should match @app with shortcut', () => {
  75. const result = matchAction('@a', mockActions)
  76. expect(result).toBe(mockActions.app)
  77. })
  78. it('should match @knowledge with key', () => {
  79. const result = matchAction('@knowledge', mockActions)
  80. expect(result).toBe(mockActions.knowledge)
  81. })
  82. it('should match @knowledge with shortcut @kb', () => {
  83. const result = matchAction('@kb', mockActions)
  84. expect(result).toBe(mockActions.knowledge)
  85. })
  86. it('should match with text after action', () => {
  87. const result = matchAction('@app search term', mockActions)
  88. expect(result).toBe(mockActions.app)
  89. })
  90. it('should not match partial @ actions', () => {
  91. const result = matchAction('@ap', mockActions)
  92. expect(result).toBeUndefined()
  93. })
  94. })
  95. describe('Slash Commands Matching', () => {
  96. describe('Direct Mode Commands', () => {
  97. it('should not match direct mode commands', () => {
  98. const result = matchAction('/docs', mockActions)
  99. expect(result).toBeUndefined()
  100. })
  101. it('should not match direct mode with arguments', () => {
  102. const result = matchAction('/docs something', mockActions)
  103. expect(result).toBeUndefined()
  104. })
  105. it('should not match any direct mode command', () => {
  106. expect(matchAction('/community', mockActions)).toBeUndefined()
  107. expect(matchAction('/feedback', mockActions)).toBeUndefined()
  108. expect(matchAction('/account', mockActions)).toBeUndefined()
  109. })
  110. })
  111. describe('Submenu Mode Commands', () => {
  112. it('should match submenu mode commands exactly', () => {
  113. const result = matchAction('/theme', mockActions)
  114. expect(result).toBe(mockActions.slash)
  115. })
  116. it('should match submenu mode with arguments', () => {
  117. const result = matchAction('/theme dark', mockActions)
  118. expect(result).toBe(mockActions.slash)
  119. })
  120. it('should match all submenu commands', () => {
  121. expect(matchAction('/language', mockActions)).toBe(mockActions.slash)
  122. expect(matchAction('/language en', mockActions)).toBe(mockActions.slash)
  123. })
  124. })
  125. describe('Slash Without Command', () => {
  126. it('should not match single slash', () => {
  127. const result = matchAction('/', mockActions)
  128. expect(result).toBeUndefined()
  129. })
  130. it('should not match unregistered commands', () => {
  131. const result = matchAction('/unknown', mockActions)
  132. expect(result).toBeUndefined()
  133. })
  134. })
  135. })
  136. describe('Edge Cases', () => {
  137. it('should handle empty query', () => {
  138. const result = matchAction('', mockActions)
  139. expect(result).toBeUndefined()
  140. })
  141. it('should handle whitespace only', () => {
  142. const result = matchAction(' ', mockActions)
  143. expect(result).toBeUndefined()
  144. })
  145. it('should handle regular text without actions', () => {
  146. const result = matchAction('search something', mockActions)
  147. expect(result).toBeUndefined()
  148. })
  149. it('should handle special characters', () => {
  150. const result = matchAction('#tag', mockActions)
  151. expect(result).toBeUndefined()
  152. })
  153. it('should handle multiple @ or /', () => {
  154. expect(matchAction('@@app', mockActions)).toBeUndefined()
  155. expect(matchAction('//theme', mockActions)).toBeUndefined()
  156. })
  157. })
  158. describe('Mode-based Filtering', () => {
  159. it('should filter direct mode commands from matching', () => {
  160. ;(slashCommandRegistry.getAllCommands as jest.Mock).mockReturnValue([
  161. { name: 'test', mode: 'direct' },
  162. ])
  163. const result = matchAction('/test', mockActions)
  164. expect(result).toBeUndefined()
  165. })
  166. it('should allow submenu mode commands to match', () => {
  167. ;(slashCommandRegistry.getAllCommands as jest.Mock).mockReturnValue([
  168. { name: 'test', mode: 'submenu' },
  169. ])
  170. const result = matchAction('/test', mockActions)
  171. expect(result).toBe(mockActions.slash)
  172. })
  173. it('should treat undefined mode as submenu', () => {
  174. ;(slashCommandRegistry.getAllCommands as jest.Mock).mockReturnValue([
  175. { name: 'test' }, // No mode specified
  176. ])
  177. const result = matchAction('/test', mockActions)
  178. expect(result).toBe(mockActions.slash)
  179. })
  180. })
  181. describe('Registry Integration', () => {
  182. it('should call getAllCommands when matching slash', () => {
  183. matchAction('/theme', mockActions)
  184. expect(slashCommandRegistry.getAllCommands).toHaveBeenCalled()
  185. })
  186. it('should not call getAllCommands for @ actions', () => {
  187. matchAction('@app', mockActions)
  188. expect(slashCommandRegistry.getAllCommands).not.toHaveBeenCalled()
  189. })
  190. it('should handle empty command list', () => {
  191. ;(slashCommandRegistry.getAllCommands as jest.Mock).mockReturnValue([])
  192. const result = matchAction('/anything', mockActions)
  193. expect(result).toBeUndefined()
  194. })
  195. })
  196. })