fix(search): keep "nothing selected" when records arrive after ArrowUp
ArrowUp above the first option clears the selection so Enter opens the results page. When record results arrived after that (the options changed and the stale flag flipped), the first option was preselected again, so Enter opened a hit instead. An explicit "nothing selected" choice now holds until the term changes, like any other choice. Caught by NAV-025 in WebKit acceptance, where the records answer after the keypress. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
6c1e3a67b1
commit
5b4ef76b01
2 files changed
+23
-4
No files matched your search
@@ -293,6 +293,24 @@ describe('GlobalSearch', () => {
|
||||
expect(searchRecordsMock).toHaveBeenCalledWith('pe', expect.objectContaining({ tableNames: ['person', 'pet'], limitPerTable: 6, offset: 0, sort: 'relevance', signal: expect.any(AbortSignal) }))
|
||||
})
|
||||
|
||||
it('keeps "nothing selected" after ArrowUp when records arrive later, so Enter opens the results page', async () => {
|
||||
// records answer well after the page matches are listed and the user pressed ArrowUp
|
||||
searchRecordsMock.mockImplementation(async () => {
|
||||
await new Promise((resolve) => setTimeout(resolve, 400))
|
||||
return { results: [{ tableName: 'pet', tableLabel: 'Pet', recordId: '4', recordLabel: 'Petunia' }] }
|
||||
})
|
||||
const user = userEvent.setup()
|
||||
renderSearch(people)
|
||||
await user.type(recordInput(), 'pet')
|
||||
await vi.waitFor(() => expect(selected()).toHaveTextContent('Pet'))
|
||||
await user.keyboard('{ArrowUp}')
|
||||
expect(selected()).toBeUndefined()
|
||||
await vi.waitFor(() => option(/Petunia/), { timeout: 3000 })
|
||||
expect(selected()).toBeUndefined()
|
||||
await user.keyboard('{Enter}')
|
||||
expect(pushMock).toHaveBeenCalledWith('/app/search?q=pet')
|
||||
})
|
||||
|
||||
it('promotes an exact record match to top hits and opens it with Enter', async () => {
|
||||
searchRecordsMock.mockResolvedValue({ results: [{ tableName: 'person', recordId: '7', recordLabel: 'Zed Zulu' }] })
|
||||
const user = userEvent.setup()
|
||||
|
||||
@@ -122,13 +122,14 @@ export function useQuickSearch({ open, term, setTerm: setTermProp, navTargets, s
|
||||
userMovedRef.current = false
|
||||
}, [term])
|
||||
|
||||
// keep the active option while it is listed and the user chose it; else the first (with a
|
||||
// term). While the records belong to the previous term, preselect only a current entry
|
||||
// (a page or "search everything"), so Enter never opens a stale record.
|
||||
// keep the user's choice while it is listed, including "nothing selected" (ArrowUp above the
|
||||
// first option, so Enter opens the results page even when records arrive afterwards); else
|
||||
// the first (with a term). While the records belong to the previous term, preselect only a
|
||||
// current entry (a page or "search everything"), so Enter never opens a stale record.
|
||||
const stale = recordSearch.isStale
|
||||
useEffect(() => {
|
||||
setActiveIdState((current) => {
|
||||
if (current && userMovedRef.current && options.some((option) => option.id === current)) return current
|
||||
if (userMovedRef.current && (current === null || options.some((option) => option.id === current))) return current
|
||||
userMovedRef.current = false
|
||||
if (!hasTerm) return null
|
||||
// never preselect a completion (Enter opens a result), nor a stale record
|
||||
|
||||
Reference in new issue
Block a user