|
| 1 | +import { |
| 2 | + buildPickerIndex, |
| 3 | + rankOptions, |
| 4 | + type Searchable, |
| 5 | +} from '@ui/tui/primitives/picker-filter'; |
| 6 | + |
| 7 | +const OPTIONS: Searchable[] = [ |
| 8 | + { label: 'GitHub issues', hint: 'Issues opened on your repos' }, |
| 9 | + { label: 'GitHub discussions', hint: 'Q&A threads' }, |
| 10 | + { label: 'Error tracking', hint: 'Exceptions captured by PostHog' }, |
| 11 | + { label: 'Session replay', hint: 'Recorded sessions' }, |
| 12 | + { label: 'Linear issues', hint: 'Tickets from Linear' }, |
| 13 | + { label: 'Stripe', hint: 'Payments and subscriptions' }, |
| 14 | + { label: 'Zendesk tickets', hint: 'Support conversations' }, |
| 15 | + { label: 'Snowflake', description: 'Warehouse source' }, |
| 16 | +]; |
| 17 | + |
| 18 | +function rank(query: string): string[] | null { |
| 19 | + const ranked = rankOptions(buildPickerIndex(OPTIONS), OPTIONS, query); |
| 20 | + return ranked?.map((option) => option.label) ?? null; |
| 21 | +} |
| 22 | + |
| 23 | +describe('rankOptions', () => { |
| 24 | + it('returns null for an empty or whitespace-only query, meaning "unfiltered"', () => { |
| 25 | + expect(rank('')).toBeNull(); |
| 26 | + expect(rank(' ')).toBeNull(); |
| 27 | + }); |
| 28 | + |
| 29 | + it('ANDs terms, so a query spanning label words narrows to one option', () => { |
| 30 | + expect(rank('git iss')).toEqual(['GitHub issues']); |
| 31 | + }); |
| 32 | + |
| 33 | + it('matches case-insensitively', () => { |
| 34 | + expect(rank('GITHUB')).toEqual(['GitHub issues', 'GitHub discussions']); |
| 35 | + }); |
| 36 | + |
| 37 | + it('searches hint and description, not just the label', () => { |
| 38 | + expect(rank('exceptions')).toEqual(['Error tracking']); |
| 39 | + expect(rank('warehouse')).toEqual(['Snowflake']); |
| 40 | + }); |
| 41 | + |
| 42 | + it('keeps the list order for literal matches rather than reordering by score', () => { |
| 43 | + expect(rank('issues')).toEqual(['GitHub issues', 'Linear issues']); |
| 44 | + }); |
| 45 | + |
| 46 | + it('falls back to fuzzy matching when nothing matches literally', () => { |
| 47 | + // Dropped vowels: no substring match anywhere, so Fuse takes over and the |
| 48 | + // intended option has to come back first. |
| 49 | + expect(rank('gthb')?.[0]).toBe('GitHub discussions'); |
| 50 | + expect(rank('zndsk')?.[0]).toBe('Zendesk tickets'); |
| 51 | + }); |
| 52 | + |
| 53 | + it('does not use the fuzzy pass when a literal match exists', () => { |
| 54 | + // "replay" fuzzy-matches half the list at this threshold; the literal pass |
| 55 | + // must win outright so the common case stays tight. |
| 56 | + expect(rank('replay')).toEqual(['Session replay']); |
| 57 | + }); |
| 58 | + |
| 59 | + it('returns an empty list when even the fuzzy pass finds nothing', () => { |
| 60 | + expect(rank('qqqqqq')).toEqual([]); |
| 61 | + }); |
| 62 | +}); |
0 commit comments