Skip to content

Commit 417fbfd

Browse files
committed
feat: implement zustand store for background jobs management
1 parent ff919be commit 417fbfd

File tree

4 files changed

+415
-61
lines changed

4 files changed

+415
-61
lines changed

src/hooks/useBackgroundJobs.test.tsx

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
11
import { act, renderHook } from '@testing-library/react'
22
import { beforeEach, describe, expect, it } from 'vitest'
33
import { useBackgroundJobs } from './useBackgroundJobs'
4+
import {
5+
BACKGROUND_JOBS_STORAGE_KEY,
6+
useBackgroundJobsStore,
7+
} from './useBackgroundJobsStore'
48
import type { BackgroundJob } from '../lib/schemas'
59

610
describe('useBackgroundJobs', () => {
7-
const STORAGE_KEY = 'background-jobs'
8-
911
beforeEach(() => {
10-
localStorage.clear()
12+
const { result } = renderHook(() => useBackgroundJobs())
13+
act(() => {
14+
result.current.clearAllJobs()
15+
})
1116
})
1217

1318
const createMockJob = (
@@ -21,6 +26,19 @@ describe('useBackgroundJobs', () => {
2126
})
2227

2328
describe('initialization and persistence', () => {
29+
it('should reset jobs using resetJobs()', () => {
30+
const { result } = renderHook(() => useBackgroundJobs())
31+
const job = createMockJob({ id: 'reset-job' })
32+
act(() => {
33+
result.current.addJob(job)
34+
})
35+
expect(result.current.jobs).toHaveLength(1)
36+
act(() => {
37+
result.current.clearAllJobs()
38+
})
39+
expect(result.current.jobs).toHaveLength(0)
40+
expect(result.current.jobsMap).toEqual({})
41+
})
2442
it('should start with empty jobs when no data in localStorage', () => {
2543
const { result } = renderHook(() => useBackgroundJobs())
2644

@@ -33,7 +51,8 @@ describe('useBackgroundJobs', () => {
3351
const job2 = createMockJob({ id: 'job2', status: 'completed' })
3452
const storedData = { job1, job2 }
3553

36-
localStorage.setItem(STORAGE_KEY, JSON.stringify(storedData))
54+
// Initialize store with existing data using proper API
55+
useBackgroundJobsStore.getState().initializeJobs(storedData)
3756

3857
const { result } = renderHook(() => useBackgroundJobs())
3958

@@ -229,8 +248,10 @@ describe('useBackgroundJobs', () => {
229248
expect(finalJob?.response).toBe('Job completed successfully')
230249
expect(finalJob?.completedAt).toBe('2025-01-01T01:00:00Z')
231250

232-
const storedData = JSON.parse(localStorage.getItem(STORAGE_KEY) || '{}')
233-
expect(storedData[job.id]).toEqual(finalJob)
251+
const storedData = JSON.parse(
252+
localStorage.getItem(BACKGROUND_JOBS_STORAGE_KEY) || '{}',
253+
)
254+
expect(storedData.state.jobs[job.id]).toEqual(finalJob)
234255
})
235256

236257
it('should handle concurrent job operations', () => {
@@ -252,7 +273,11 @@ describe('useBackgroundJobs', () => {
252273
})
253274

254275
expect(result.current.jobs).toHaveLength(0)
255-
expect(localStorage.getItem(STORAGE_KEY)).toBeNull()
276+
// Zustand persist middleware always creates localStorage entry, so check it has empty jobs
277+
const storedData = JSON.parse(
278+
localStorage.getItem(BACKGROUND_JOBS_STORAGE_KEY) || '{}',
279+
)
280+
expect(storedData.state?.jobs).toEqual({})
256281

257282
act(() => {
258283
result.current.addJob(runningJob)
@@ -305,7 +330,11 @@ describe('useBackgroundJobs', () => {
305330
const firstSessionResult = renderHook(() => useBackgroundJobs())
306331

307332
expect(firstSessionResult.result.current.jobs).toHaveLength(0)
308-
expect(localStorage.getItem(STORAGE_KEY)).toBeNull()
333+
// Zustand persist middleware always creates localStorage entry, so check it has empty jobs
334+
const initialStoredData = JSON.parse(
335+
localStorage.getItem(BACKGROUND_JOBS_STORAGE_KEY) || '{}',
336+
)
337+
expect(initialStoredData.state?.jobs).toEqual({})
309338

310339
act(() => {
311340
firstSessionResult.result.current.addJob(job1)

src/hooks/useBackgroundJobs.ts

Lines changed: 10 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
1-
import { useCallback, useMemo } from 'react'
2-
import { useLocalStorage } from './useLocalStorage'
1+
import { useMemo } from 'react'
2+
import { useBackgroundJobsStore } from './useBackgroundJobsStore'
33
import type { BackgroundJob } from '../lib/schemas'
44

5-
const STORAGE_KEY = 'background-jobs'
6-
75
interface UseBackgroundJobsReturn {
86
jobs: Array<BackgroundJob>
97
jobsMap: Record<string, BackgroundJob>
@@ -15,58 +13,17 @@ interface UseBackgroundJobsReturn {
1513
}
1614

1715
export const useBackgroundJobs = (): UseBackgroundJobsReturn => {
18-
const [jobsMap, setJobsMap] = useLocalStorage<Record<string, BackgroundJob>>(
19-
STORAGE_KEY,
20-
{},
21-
)
16+
const jobsMap = useBackgroundJobsStore((state) => state.jobs)
17+
const addJob = useBackgroundJobsStore((state) => state.addJob)
18+
const removeJob = useBackgroundJobsStore((state) => state.removeJob)
19+
const updateJob = useBackgroundJobsStore((state) => state.updateJob)
20+
const clearAllJobs = useBackgroundJobsStore((state) => state.clearAllJobs)
2221

2322
const jobs = useMemo(() => Object.values(jobsMap).filter(Boolean), [jobsMap])
2423

25-
const addJob = useCallback(
26-
(job: BackgroundJob) => {
27-
setJobsMap((prev) => ({
28-
...prev,
29-
[job.id]: job,
30-
}))
31-
},
32-
[setJobsMap],
33-
)
34-
35-
const removeJob = useCallback(
36-
(id: string) => {
37-
setJobsMap((prev) => {
38-
const { [id]: _removed, ...rest } = prev
39-
return rest
40-
})
41-
},
42-
[setJobsMap],
43-
)
44-
45-
const updateJob = useCallback(
46-
(id: string, updates: Partial<BackgroundJob>) => {
47-
setJobsMap((prev) => {
48-
if (!(id in prev)) {
49-
return prev
50-
}
51-
return {
52-
...prev,
53-
[id]: { ...prev[id], ...updates },
54-
}
55-
})
56-
},
57-
[setJobsMap],
58-
)
59-
60-
const getJobById = useCallback(
61-
(id: string) => {
62-
return jobsMap[id]
63-
},
64-
[jobsMap],
65-
)
66-
67-
const clearAllJobs = useCallback(() => {
68-
setJobsMap({})
69-
}, [setJobsMap])
24+
const getJobById = (id: string) => {
25+
return jobsMap[id]
26+
}
7027

7128
return {
7229
jobs,

0 commit comments

Comments
 (0)