From e80bd15d5c61e8c87511fef551336937762cace8 Mon Sep 17 00:00:00 2001 From: yyh Date: Sun, 15 Feb 2026 19:34:30 +0800 Subject: [PATCH] fix --- .../use-document-list-query-state.spec.tsx | 28 ++++++++++++++++++- .../hooks/use-document-list-query-state.ts | 5 +++- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/web/app/components/datasets/documents/hooks/__tests__/use-document-list-query-state.spec.tsx b/web/app/components/datasets/documents/hooks/__tests__/use-document-list-query-state.spec.tsx index ed41b7c216..7b262e8d7e 100644 --- a/web/app/components/datasets/documents/hooks/__tests__/use-document-list-query-state.spec.tsx +++ b/web/app/components/datasets/documents/hooks/__tests__/use-document-list-query-state.spec.tsx @@ -235,7 +235,7 @@ describe('useDocumentListQueryState', () => { await waitFor(() => expect(onUrlUpdate).toHaveBeenCalled()) const update = onUrlUpdate.mock.calls[onUrlUpdate.mock.calls.length - 1][0] - expect(update.searchParams.get('keyword')).toBe('test query') + expect(update.searchParams.get('keyword')).toBe(encodeURIComponent('test query')) }) it('should remove keyword from URL when keyword is empty', async () => { @@ -250,6 +250,32 @@ describe('useDocumentListQueryState', () => { expect(update.searchParams.has('keyword')).toBe(false) }) + it('should remove keyword from URL when keyword contains only whitespace', async () => { + const { result, onUrlUpdate } = renderWithAdapter('?keyword=existing') + + act(() => { + result.current.updateQuery({ keyword: ' ' }) + }) + + await waitFor(() => expect(onUrlUpdate).toHaveBeenCalled()) + const update = onUrlUpdate.mock.calls[onUrlUpdate.mock.calls.length - 1][0] + expect(update.searchParams.has('keyword')).toBe(false) + expect(result.current.query.keyword).toBe('') + }) + + it('should preserve literal percent-encoded-like keyword values', async () => { + const { result, onUrlUpdate } = renderWithAdapter() + + act(() => { + result.current.updateQuery({ keyword: '%2F' }) + }) + + await waitFor(() => expect(onUrlUpdate).toHaveBeenCalled()) + const update = onUrlUpdate.mock.calls[onUrlUpdate.mock.calls.length - 1][0] + expect(update.searchParams.get('keyword')).toBe(encodeURIComponent('%2F')) + expect(result.current.query.keyword).toBe('%2F') + }) + it('should keep decoded keyword state when updating query from legacy URL', async () => { const { result, onUrlUpdate } = renderWithAdapter('?keyword=test%2520query') diff --git a/web/app/components/datasets/documents/hooks/use-document-list-query-state.ts b/web/app/components/datasets/documents/hooks/use-document-list-query-state.ts index 62b85c9911..9284bb4f3a 100644 --- a/web/app/components/datasets/documents/hooks/use-document-list-query-state.ts +++ b/web/app/components/datasets/documents/hooks/use-document-list-query-state.ts @@ -66,7 +66,8 @@ const parseAsKeyword = createParser({ return value } }, - serialize: value => value, + // Keep parse/serialize symmetric while preserving legacy URL behavior. + serialize: value => encodeURIComponent(value), }).withDefault('') export const documentListParsers = { @@ -92,6 +93,8 @@ function useDocumentListQueryState() { patch.status = sanitizeStatusValue(patch.status) if ('sort' in patch) patch.sort = sanitizeSortValue(patch.sort) + if ('keyword' in patch && typeof patch.keyword === 'string' && patch.keyword.trim() === '') + patch.keyword = '' setQuery(patch) }, [setQuery])