diff --git a/src/renderer/src/components/quick-open-file-list.ts b/src/renderer/src/components/quick-open-file-list.ts index 7f605ce2e41..250f6da7b33 100644 --- a/src/renderer/src/components/quick-open-file-list.ts +++ b/src/renderer/src/components/quick-open-file-list.ts @@ -26,6 +26,8 @@ export type RuntimeFileListState = { loading: boolean loadError: string | null truncated?: boolean + /** Query that produced `files`; null means a request is still settling. */ + resolvedQuery?: string | null operationOwner?: FileExplorerOperationOwner } @@ -145,6 +147,7 @@ export function useRuntimeFileListForWorktree({ const [loading, setLoading] = useState(false) const [loadError, setLoadError] = useState(null) const [truncated, setTruncated] = useState(false) + const [resolvedQuery, setResolvedQuery] = useState(undefined) const [listedOperationOwner, setListedOperationOwner] = useState({ kind: 'unresolved' }) @@ -205,7 +208,7 @@ export function useRuntimeFileListForWorktree({ useEffect(() => { if (!enabled) { setLoading(false) - setTruncated(false) + setResolvedQuery(null) setListedOperationOwner({ kind: 'unresolved' }) return } @@ -213,9 +216,10 @@ export function useRuntimeFileListForWorktree({ if (!target.canList || !worktreeId || !worktreePath || !operationRouteAvailable) { setFiles([]) setListedOperationOwner({ kind: 'unresolved' }) - setLoadError(operationRouteAvailable ? null : getFileExplorerOwnerUnresolvedMessage()) + setLoadError(!operationRouteAvailable ? getFileExplorerOwnerUnresolvedMessage() : null) setLoading(false) setTruncated(false) + setResolvedQuery(null) return } @@ -223,6 +227,7 @@ export function useRuntimeFileListForWorktree({ const requestKeyChanged = lastRequestKeyRef.current !== requestKey if (requestKeyChanged) { setFiles([]) + setResolvedQuery(null) } lastRequestKeyRef.current = requestKey setLoadError(null) @@ -231,6 +236,7 @@ export function useRuntimeFileListForWorktree({ if (usesRuntimePathSearch && (remoteQuery.length === 0 || remoteQueryTooLarge)) { setFiles([]) setLoading(false) + setResolvedQuery(remoteQuery) setListedOperationOwner(operationOwnerRef.current) return } @@ -277,6 +283,7 @@ export function useRuntimeFileListForWorktree({ if (!cancelled) { setFiles(result.files) setTruncated(result.truncated) + setResolvedQuery(usesRuntimePathSearch ? remoteQuery : undefined) setListedOperationOwner(requestOperationOwner) } }) @@ -284,6 +291,7 @@ export function useRuntimeFileListForWorktree({ if (!cancelled) { setFiles([]) setTruncated(false) + setResolvedQuery(usesRuntimePathSearch ? remoteQuery : null) setLoadError(cleanRuntimeFileListError(error)) } }) @@ -323,6 +331,7 @@ export function useRuntimeFileListForWorktree({ loading: loading || connectionPending, loadError, truncated, + resolvedQuery, operationOwner: listedOperationOwner } } diff --git a/src/renderer/src/components/right-sidebar/use-file-explorer-name-filter.test.ts b/src/renderer/src/components/right-sidebar/use-file-explorer-name-filter.test.ts new file mode 100644 index 00000000000..72717f0cf49 --- /dev/null +++ b/src/renderer/src/components/right-sidebar/use-file-explorer-name-filter.test.ts @@ -0,0 +1,45 @@ +// @vitest-environment happy-dom + +import { act, cleanup, renderHook } from '@testing-library/react' +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { useAppStore } from '@/store' +import type { RuntimeFileListState } from '@/components/quick-open-file-list' +import { useFileExplorerNameFilter } from './use-file-explorer-name-filter' + +const useRuntimeFileListForWorktreeMock = vi.hoisted(() => vi.fn()) + +vi.mock('@/components/quick-open-file-list', () => ({ + useRuntimeFileListForWorktree: useRuntimeFileListForWorktreeMock +})) + +const emptyState: RuntimeFileListState = { + files: [], + loading: false, + loadError: null +} + +describe('useFileExplorerNameFilter', () => { + beforeEach(() => { + useRuntimeFileListForWorktreeMock.mockReset().mockReturnValue(emptyState) + useAppStore.setState({ activeWorktreeId: 'worktree-1' }) + }) + + afterEach(() => { + cleanup() + }) + + it('passes the active filename query to the runtime path search', () => { + const { result } = renderHook(() => + useFileExplorerNameFilter({ isFilesViewActive: true, activeWorktreeId: 'worktree-1' }) + ) + + act(() => result.current.setNameFilterQuery('AppDelegate.swift')) + + expect(useRuntimeFileListForWorktreeMock).toHaveBeenLastCalledWith({ + enabled: true, + worktreeId: 'worktree-1', + query: 'AppDelegate.swift' + }) + expect(result.current.nameFilterSource?.query).toBe('AppDelegate.swift') + }) +}) diff --git a/src/renderer/src/components/right-sidebar/use-file-explorer-name-filter.ts b/src/renderer/src/components/right-sidebar/use-file-explorer-name-filter.ts index 8e271008abf..883ca986abf 100644 --- a/src/renderer/src/components/right-sidebar/use-file-explorer-name-filter.ts +++ b/src/renderer/src/components/right-sidebar/use-file-explorer-name-filter.ts @@ -45,7 +45,8 @@ export function useFileExplorerNameFilter({ }, [hasNameFilter]) const nameFilterFiles = useRuntimeFileListForWorktree({ enabled: hasNameFilter && !nameFilterQueryTooLarge, - worktreeId: activeWorktreeId + worktreeId: activeWorktreeId, + query: nameFilterQuery }) const nameFilterSource = useMemo( () => @@ -55,9 +56,13 @@ export function useFileExplorerNameFilter({ operationOwner: nameFilterFiles.operationOwner, relativePaths: nameFilterQueryTooLarge ? [] - : nameFilterFiles.loading && nameFilterFiles.files.length === 0 - ? null - : nameFilterFiles.files + : nameFilterFiles.resolvedQuery === nameFilterQuery.trim() + ? nameFilterFiles.loading + ? null + : nameFilterFiles.files + : nameFilterFiles.loading + ? null + : [] } : null, [ @@ -65,6 +70,7 @@ export function useFileExplorerNameFilter({ nameFilterFiles.files, nameFilterFiles.loading, nameFilterFiles.operationOwner, + nameFilterFiles.resolvedQuery, nameFilterQuery, nameFilterQueryTooLarge ]