Debounce settings search (#2187)
This commit is contained in:
parent
5642277aaf
commit
49d14fd260
|
|
@ -211,6 +211,7 @@ function Settings(): React.JSX.Element {
|
|||
const removeRepo = useAppStore((s) => s.removeRepo)
|
||||
const settingsNavigationTarget = useAppStore((s) => s.settingsNavigationTarget)
|
||||
const clearSettingsTarget = useAppStore((s) => s.clearSettingsTarget)
|
||||
const settingsSearchInputQuery = useAppStore((s) => s.settingsSearchInputQuery)
|
||||
const settingsSearchQuery = useAppStore((s) => s.settingsSearchQuery)
|
||||
const setSettingsSearchQuery = useAppStore((s) => s.setSettingsSearchQuery)
|
||||
|
||||
|
|
@ -941,7 +942,7 @@ function Settings(): React.JSX.Element {
|
|||
generalSections={generalNavSections}
|
||||
repoSections={repoNavSections}
|
||||
hasRepos={repos.length > 0}
|
||||
searchQuery={settingsSearchQuery}
|
||||
searchQuery={settingsSearchInputQuery}
|
||||
searchInputRef={searchInputRef}
|
||||
onBack={closeSettingsPageWithPromptGuard}
|
||||
onSearchChange={setSettingsSearchQuery}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,56 @@
|
|||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import {
|
||||
createSettingsSearchState,
|
||||
SETTINGS_SEARCH_DEBOUNCE_MS,
|
||||
type SettingsSearchState
|
||||
} from './settings-search-state'
|
||||
|
||||
function createTestSearchState(): {
|
||||
getState: () => SettingsSearchState
|
||||
setState: (state: Partial<SettingsSearchState>) => void
|
||||
} {
|
||||
let state = {} as SettingsSearchState
|
||||
const setState = (updates: Partial<SettingsSearchState>): void => {
|
||||
state = { ...state, ...updates }
|
||||
}
|
||||
state = createSettingsSearchState(setState)
|
||||
return { getState: () => state, setState }
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
vi.useRealTimers()
|
||||
})
|
||||
|
||||
describe('settings search state', () => {
|
||||
it('updates the search input immediately and debounces the applied filter', () => {
|
||||
vi.useFakeTimers()
|
||||
const store = createTestSearchState()
|
||||
|
||||
store.getState().setSettingsSearchQuery('terminal')
|
||||
|
||||
expect(store.getState().settingsSearchInputQuery).toBe('terminal')
|
||||
expect(store.getState().settingsSearchQuery).toBe('')
|
||||
|
||||
vi.advanceTimersByTime(SETTINGS_SEARCH_DEBOUNCE_MS - 1)
|
||||
expect(store.getState().settingsSearchQuery).toBe('')
|
||||
|
||||
vi.advanceTimersByTime(1)
|
||||
expect(store.getState().settingsSearchQuery).toBe('terminal')
|
||||
})
|
||||
|
||||
it('clears settings search immediately and cancels pending debounce work', () => {
|
||||
vi.useFakeTimers()
|
||||
const store = createTestSearchState()
|
||||
store.setState({ settingsSearchInputQuery: 'terminal', settingsSearchQuery: 'terminal' })
|
||||
|
||||
store.getState().setSettingsSearchQuery('agents')
|
||||
vi.advanceTimersByTime(SETTINGS_SEARCH_DEBOUNCE_MS - 1)
|
||||
store.getState().setSettingsSearchQuery('')
|
||||
|
||||
expect(store.getState().settingsSearchInputQuery).toBe('')
|
||||
expect(store.getState().settingsSearchQuery).toBe('')
|
||||
|
||||
vi.advanceTimersByTime(SETTINGS_SEARCH_DEBOUNCE_MS)
|
||||
expect(store.getState().settingsSearchQuery).toBe('')
|
||||
})
|
||||
})
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
export const SETTINGS_SEARCH_DEBOUNCE_MS = 150
|
||||
|
||||
export type SettingsSearchState = {
|
||||
settingsSearchInputQuery: string
|
||||
settingsSearchQuery: string
|
||||
setSettingsSearchQuery: (q: string) => void
|
||||
}
|
||||
|
||||
export function createSettingsSearchState(
|
||||
set: (state: Partial<SettingsSearchState>) => void
|
||||
): SettingsSearchState {
|
||||
let settingsSearchDebounceTimer: ReturnType<typeof setTimeout> | null = null
|
||||
|
||||
const clearSettingsSearchDebounce = (): void => {
|
||||
if (settingsSearchDebounceTimer) {
|
||||
clearTimeout(settingsSearchDebounceTimer)
|
||||
settingsSearchDebounceTimer = null
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
settingsSearchInputQuery: '',
|
||||
settingsSearchQuery: '',
|
||||
setSettingsSearchQuery: (q) => {
|
||||
clearSettingsSearchDebounce()
|
||||
if (q.trim() === '') {
|
||||
set({ settingsSearchInputQuery: q, settingsSearchQuery: q })
|
||||
return
|
||||
}
|
||||
set({ settingsSearchInputQuery: q })
|
||||
// Why: applying settings search mounts and filters many heavy sections,
|
||||
// so keep typing responsive while waiting for the query to settle.
|
||||
settingsSearchDebounceTimer = setTimeout(() => {
|
||||
settingsSearchDebounceTimer = null
|
||||
set({ settingsSearchQuery: q })
|
||||
}, SETTINGS_SEARCH_DEBOUNCE_MS)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import { describe, expect, it, vi, beforeEach } from 'vitest'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { createTestStore, makeWorktree } from './store-test-helpers'
|
||||
import type { AppState } from '../types'
|
||||
import type { WorktreeLineage } from '../../../../shared/types'
|
||||
|
|
|
|||
|
|
@ -10,11 +10,10 @@ import {
|
|||
import { normalizeTerminalQuickCommands } from '../../../../shared/terminal-quick-commands'
|
||||
import { normalizeVisibleTaskProviders } from '../../../../shared/task-providers'
|
||||
import { normalizeOpenInApplications } from '../../../../shared/open-in-applications'
|
||||
import { createSettingsSearchState, type SettingsSearchState } from './settings-search-state'
|
||||
|
||||
export type SettingsSlice = {
|
||||
export type SettingsSlice = SettingsSearchState & {
|
||||
settings: GlobalSettings | null
|
||||
settingsSearchQuery: string
|
||||
setSettingsSearchQuery: (q: string) => void
|
||||
fetchSettings: () => Promise<void>
|
||||
updateSettings: (updates: Partial<GlobalSettings>) => Promise<void>
|
||||
switchRuntimeEnvironment: (environmentId: string | null) => Promise<boolean>
|
||||
|
|
@ -221,8 +220,7 @@ async function verifyRuntimeEnvironmentReachable(environmentId: string | null):
|
|||
|
||||
export const createSettingsSlice: StateCreator<AppState, [], [], SettingsSlice> = (set, get) => ({
|
||||
settings: null,
|
||||
settingsSearchQuery: '',
|
||||
setSettingsSearchQuery: (q) => set({ settingsSearchQuery: q }),
|
||||
...createSettingsSearchState((state) => set(state)),
|
||||
|
||||
fetchSettings: async () => {
|
||||
try {
|
||||
|
|
|
|||
Loading…
Reference in New Issue