Skip to content

Commit ae0b109

Browse files
committed
fix(search): keep cancellation available while editing the draft
1 parent 672462e commit ae0b109

2 files changed

Lines changed: 59 additions & 32 deletions

File tree

‎apps/sim/app/o/[organizationId]/search/search.tsx‎

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,6 @@ function SearchField({ userId, initialValue, onSubmit }: SearchFieldProps) {
5959
setDraft(draftKey, payload)
6060
setDraft(latestDraftKey, payload)
6161
}
62-
const pending = isSearching && value.trim() === initialValue.trim()
6362
const submit = (text = value) => {
6463
if (!text.trim() || (isSearching && text.trim() === initialValue.trim())) return
6564
const { clearDraft } = useMothershipDraftsStore.getState()
@@ -98,13 +97,13 @@ function SearchField({ userId, initialValue, onSubmit }: SearchFieldProps) {
9897
submitControl={
9998
<ComposerActionButton
10099
type='button'
101-
onClick={() => (pending ? void queryClient.cancelQueries({ queryKey }) : submit())}
102-
disabled={!canSubmit}
103-
aria-label={pending ? 'Stop search' : 'Search'}
104-
aria-busy={pending}
105-
active={canSubmit}
100+
onClick={() => (isSearching ? void queryClient.cancelQueries({ queryKey }) : submit())}
101+
disabled={!canSubmit && !isSearching}
102+
aria-label={isSearching ? 'Stop search' : 'Search'}
103+
aria-busy={isSearching}
104+
active={canSubmit || isSearching}
106105
>
107-
{pending ? (
106+
{isSearching ? (
108107
<StopFilled className='block size-[14px] fill-white dark:fill-black' />
109108
) : (
110109
<ArrowUp className='block size-[16px] text-white dark:text-black' />

‎apps/sim/app/workspace/[workspaceId]/home/components/knowledge-search-results/search-transitions.test.tsx‎

Lines changed: 53 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -272,31 +272,59 @@ describe('search refinement with the real query cache and URL state', () => {
272272
})
273273

274274
describe('live search submission feedback', () => {
275-
it('cancels a pending search without accepting its late response and allows a fresh submission', async () => {
276-
const shape = resolveDeploymentShape()
277-
seedDeploymentShape({ ...shape, features: { ...shape.features, liveEnterpriseSearch: true } })
278-
await render({ organizationPage: true, params: '?q=launch' })
279-
await act(async () => {
280-
await vi.advanceTimersByTimeAsync(1)
281-
})
282-
expect(requests).toHaveLength(1)
283-
await act(async () => {
284-
const stop = container.querySelector<HTMLButtonElement>('button[aria-label="Stop search"]')
285-
if (!stop) throw new Error('No stop control for the pending search')
286-
stop.click()
287-
await vi.advanceTimersByTimeAsync(1)
288-
})
289-
expect(requests[0].signal.aborted).toBe(true)
290-
await complete(0, { title: 'Cancelled result' })
291-
expect(container.querySelector('[data-source-link]')).toBeNull()
292-
await act(async () => {
293-
container.querySelector<HTMLButtonElement>('button[aria-label="Search"]')!.click()
294-
await vi.advanceTimersByTimeAsync(1)
295-
})
296-
expect(requests).toHaveLength(2)
297-
await complete(1, { title: 'Fresh result' })
298-
expect(container.querySelector('[data-source-link]')?.textContent).toBe('Fresh result')
299-
})
275+
it.each(['launch', 'edited draft', ''])(
276+
'cancels with draft %j, ignores late results, and allows a fresh submission',
277+
async (draft) => {
278+
const shape = resolveDeploymentShape()
279+
seedDeploymentShape({ ...shape, features: { ...shape.features, liveEnterpriseSearch: true } })
280+
await render({ organizationPage: true, params: '?q=launch' })
281+
await act(async () => {
282+
await vi.advanceTimersByTimeAsync(1)
283+
})
284+
expect(requests).toHaveLength(1)
285+
await act(async () => {
286+
const input = container.querySelector('textarea')!
287+
Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, 'value')!.set!.call(
288+
input,
289+
draft
290+
)
291+
input.dispatchEvent(new Event('input', { bubbles: true }))
292+
})
293+
await act(async () => {
294+
const stop = container.querySelector<HTMLButtonElement>('button[aria-label="Stop search"]')
295+
if (!stop) throw new Error('No stop control for the pending search')
296+
stop.click()
297+
await vi.advanceTimersByTimeAsync(1)
298+
})
299+
expect(requests[0].signal.aborted).toBe(true)
300+
expect(container.querySelector('textarea')!.value).toBe(draft)
301+
await complete(0, { title: 'Cancelled result' })
302+
expect(
303+
client
304+
.getQueriesData({ queryKey: knowledgeKeys.searches() })
305+
.every(([, data]) => data === undefined)
306+
).toBe(true)
307+
await act(async () => {
308+
const input = container.querySelector('textarea')!
309+
Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, 'value')!.set!.call(
310+
input,
311+
'launch'
312+
)
313+
input.dispatchEvent(new Event('input', { bubbles: true }))
314+
})
315+
await act(async () => {
316+
container.querySelector<HTMLButtonElement>('button[aria-label="Search"]')!.click()
317+
await vi.advanceTimersByTimeAsync(1)
318+
})
319+
expect(requests).toHaveLength(2)
320+
await complete(1, { title: 'Fresh result' })
321+
expect(
322+
client
323+
.getQueriesData<WorkspaceKnowledgeSearchData>({ queryKey: knowledgeKeys.searches() })
324+
.flatMap(([, data]) => data?.results.map((result) => result.documentId) ?? [])
325+
).toEqual(['Fresh result'])
326+
}
327+
)
300328

301329
it('acknowledges the submitted query before exposing refinement controls', async () => {
302330
const shape = resolveDeploymentShape()

0 commit comments

Comments
 (0)