@codeleap/query 5.8.3 → 5.8.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,458 @@
1
+ import { describe, it, expect, beforeEach, spyOn, mock } from 'bun:test'
2
+ import { Mutations, createMutations } from '../lib/Mutations'
3
+ import { QueryKeys } from '../lib/QueryKeys'
4
+ import { createTestQueryClient, TestUser, TestUserFilters, createMockUsers } from './setup'
5
+ import { InfiniteData } from '@tanstack/react-query'
6
+
7
+ describe('Mutations', () => {
8
+ let queryClient: ReturnType<typeof createTestQueryClient>
9
+ let queryKeys: QueryKeys<TestUser, TestUserFilters>
10
+ let mutations: Mutations<TestUser, TestUserFilters>
11
+
12
+ beforeEach(() => {
13
+ queryClient = createTestQueryClient()
14
+ queryKeys = new QueryKeys('users', queryClient)
15
+ mutations = new Mutations(queryKeys, queryClient, 'users')
16
+ })
17
+
18
+ describe('constructor', () => {
19
+ it('should create Mutations instance with correct parameters', () => {
20
+ expect(mutations).toBeInstanceOf(Mutations)
21
+ expect(mutations['queryKeys']).toBe(queryKeys)
22
+ expect(mutations['queryClient']).toBe(queryClient)
23
+ expect(mutations['queryName']).toBe('users')
24
+ })
25
+ })
26
+
27
+ describe('addItem', () => {
28
+ const mockUsers = createMockUsers(3)
29
+
30
+ beforeEach(() => {
31
+ const mockData: InfiniteData<TestUser[], number> = {
32
+ pages: [mockUsers.slice(0, 2), mockUsers.slice(2)],
33
+ pageParams: [0, 2]
34
+ }
35
+ queryClient.setQueryData(['users', 'list'], mockData)
36
+ })
37
+
38
+ it('should add item to start by default', () => {
39
+ const newUser = createMockUsers(1)[0]
40
+
41
+ mutations.addItem(newUser)
42
+
43
+ const data = queryClient.getQueryData(['users', 'list']) as InfiniteData<TestUser[], number>
44
+ expect(data.pages[0][0]).toEqual(newUser)
45
+ expect(data.pages[0]).toHaveLength(3) // original 2 + new 1
46
+ })
47
+
48
+ it('should add item to start when position is "start"', () => {
49
+ const newUser = createMockUsers(1)[0]
50
+
51
+ mutations.addItem(newUser, 'start')
52
+
53
+ const data = queryClient.getQueryData(['users', 'list']) as InfiniteData<TestUser[], number>
54
+ expect(data.pages[0][0]).toEqual(newUser)
55
+ })
56
+
57
+ it('should add item to end when position is "end"', () => {
58
+ const newUser = createMockUsers(1)[0]
59
+
60
+ mutations.addItem(newUser, 'end')
61
+
62
+ const data = queryClient.getQueryData(['users', 'list']) as InfiniteData<TestUser[], number>
63
+ const lastPage = data.pages[data.pages.length - 1]
64
+ expect(lastPage[lastPage.length - 1]).toEqual(newUser)
65
+ })
66
+
67
+ it('should create new data when no cache exists', () => {
68
+ queryClient.clear()
69
+ const newUser = createMockUsers(1)[0]
70
+
71
+ mutations.addItem(newUser)
72
+
73
+ const data = queryClient.getQueryData(['users', 'list']) as InfiniteData<TestUser[], number>
74
+ expect(data).toEqual({
75
+ pageParams: [0],
76
+ pages: [[newUser]]
77
+ })
78
+ })
79
+
80
+ it('should add item with specific list filters', () => {
81
+ const filters = { status: 'active' as const }
82
+ const newUser = createMockUsers(1)[0]
83
+
84
+ // Set up filtered cache
85
+ queryClient.setQueryData(['users', 'list', filters], {
86
+ pages: [mockUsers.slice(0, 1)],
87
+ pageParams: [0]
88
+ })
89
+
90
+ mutations.addItem(newUser, 'start', filters)
91
+
92
+ const data = queryClient.getQueryData(['users', 'list', filters]) as InfiniteData<TestUser[], number>
93
+ expect(data.pages[0][0]).toEqual(newUser)
94
+ })
95
+
96
+ it('should handle multiple query keys (RemovedItemMap)', () => {
97
+ const newUser = createMockUsers(1)[0]
98
+ const queryKey1 = ['users', 'list']
99
+ const queryKey2 = ['users', 'list', { status: 'active' }]
100
+
101
+ // Set up multiple caches
102
+ queryClient.setQueryData(queryKey1, {
103
+ pages: [mockUsers.slice(0, 2)],
104
+ pageParams: [0]
105
+ })
106
+ queryClient.setQueryData(queryKey2, {
107
+ pages: [mockUsers.slice(0, 1)],
108
+ pageParams: [0]
109
+ })
110
+
111
+ const removedItemMap = [
112
+ [queryKey1, { pageIndex: 0, itemIndex: 1 }],
113
+ [queryKey2, { pageIndex: 0, itemIndex: 0 }]
114
+ ] as any
115
+
116
+ mutations.addItem(newUser, removedItemMap)
117
+
118
+ const data1 = queryClient.getQueryData(queryKey1) as InfiniteData<TestUser[], number>
119
+ const data2 = queryClient.getQueryData(queryKey2) as InfiniteData<TestUser[], number>
120
+
121
+ expect(data1.pages[0][1]).toEqual(newUser)
122
+ expect(data2.pages[0][0]).toEqual(newUser)
123
+ })
124
+
125
+ it('should handle item position beyond page length', () => {
126
+ const newUser = createMockUsers(1)[0]
127
+ const queryKey = ['users', 'list']
128
+
129
+ queryClient.setQueryData(queryKey, {
130
+ pages: [mockUsers.slice(0, 1)],
131
+ pageParams: [0]
132
+ })
133
+
134
+ const removedItemMap = [
135
+ [queryKey, { pageIndex: 0, itemIndex: 10 }] // Beyond page length
136
+ ] as any
137
+
138
+ mutations.addItem(newUser, removedItemMap)
139
+
140
+ const data = queryClient.getQueryData(queryKey) as InfiniteData<TestUser[], number>
141
+ expect(data.pages[0][data.pages[0].length - 1]).toEqual(newUser)
142
+ })
143
+
144
+ it('should handle page index beyond pages length', () => {
145
+ const newUser = createMockUsers(1)[0]
146
+ const queryKey = ['users', 'list']
147
+
148
+ queryClient.setQueryData(queryKey, {
149
+ pages: [mockUsers.slice(0, 1)],
150
+ pageParams: [0]
151
+ })
152
+
153
+ const removedItemMap = [
154
+ [queryKey, { pageIndex: 5, itemIndex: 0 }] // Beyond pages length
155
+ ] as any
156
+
157
+ mutations.addItem(newUser, removedItemMap)
158
+
159
+ const data = queryClient.getQueryData(queryKey) as InfiniteData<TestUser[], number>
160
+ const lastPage = data.pages[data.pages.length - 1]
161
+ expect(lastPage[lastPage.length - 1]).toEqual(newUser)
162
+ })
163
+
164
+ it('should handle empty pages array', () => {
165
+ const newUser = createMockUsers(1)[0]
166
+ const queryKey = ['users', 'list']
167
+
168
+ queryClient.setQueryData(queryKey, {
169
+ pages: [],
170
+ pageParams: []
171
+ })
172
+
173
+ const removedItemMap = [
174
+ [queryKey, { pageIndex: 0, itemIndex: 0 }]
175
+ ] as any
176
+
177
+ mutations.addItem(newUser, removedItemMap)
178
+
179
+ const data = queryClient.getQueryData(queryKey) as InfiniteData<TestUser[], number>
180
+ expect(data.pages[0]).toEqual([newUser])
181
+ })
182
+ })
183
+
184
+ describe('removeItem', () => {
185
+ const mockUsers = createMockUsers(3)
186
+
187
+ beforeEach(() => {
188
+ // Set up multiple list queries
189
+ queryClient.setQueryData(['users', 'list'], {
190
+ pages: [mockUsers.slice(0, 2), [mockUsers[2]]],
191
+ pageParams: [0, 2]
192
+ })
193
+ queryClient.setQueryData(['users', 'list', { status: 'active' }], {
194
+ pages: [[mockUsers[0]]],
195
+ pageParams: [0]
196
+ })
197
+
198
+ // Set up retrieve cache
199
+ queryClient.setQueryData(['users', 'retrieve', mockUsers[0].id], mockUsers[0])
200
+ })
201
+
202
+ it('should remove item from all list queries and return removal map', () => {
203
+ const getAllListQueriesSpy = spyOn(queryKeys, 'getAllListQueries').mockReturnValue([
204
+ { queryKey: ['users', 'list'], state: { data: queryClient.getQueryData(['users', 'list']) } },
205
+ { queryKey: ['users', 'list', { status: 'active' }], state: { data: queryClient.getQueryData(['users', 'list', { status: 'active' }]) } }
206
+ ] as any)
207
+
208
+ const removedMap = mutations.removeItem(mockUsers[0].id)
209
+
210
+ expect(removedMap).toHaveLength(2)
211
+ expect(removedMap![0]).toEqual([['users', 'list'], { pageIndex: 0, itemIndex: 0 }])
212
+ expect(removedMap![1]).toEqual([['users', 'list', { status: 'active' }], { pageIndex: 0, itemIndex: 0 }])
213
+
214
+ // Verify item was removed from caches
215
+ const data1 = queryClient.getQueryData(['users', 'list']) as InfiniteData<TestUser[], number>
216
+ const data2 = queryClient.getQueryData(['users', 'list', { status: 'active' }]) as InfiniteData<TestUser[], number>
217
+
218
+ expect(data1.pages[0]).not.toContain(mockUsers[0])
219
+ expect(data2.pages[0]).toHaveLength(0)
220
+ })
221
+
222
+ it('should remove retrieve query data', () => {
223
+ const removeRetrieveQueryDataSpy = spyOn(queryKeys, 'removeRetrieveQueryData')
224
+ const getAllListQueriesSpy = spyOn(queryKeys, 'getAllListQueries').mockReturnValue([])
225
+
226
+ mutations.removeItem(mockUsers[0].id)
227
+
228
+ expect(removeRetrieveQueryDataSpy).toHaveBeenCalledWith(mockUsers[0].id)
229
+ })
230
+
231
+ it('should return empty array when item not found in any list', () => {
232
+ const getAllListQueriesSpy = spyOn(queryKeys, 'getAllListQueries').mockReturnValue([
233
+ { queryKey: ['users', 'list'], state: { data: queryClient.getQueryData(['users', 'list']) } }
234
+ ] as any)
235
+
236
+ const removedMap = mutations.removeItem('non-existent-id')
237
+
238
+ expect(removedMap).toEqual([])
239
+ })
240
+
241
+ it('should handle empty pages after removal', () => {
242
+ const getAllListQueriesSpy = spyOn(queryKeys, 'getAllListQueries').mockReturnValue([
243
+ { queryKey: ['users', 'list'], state: { data: { pages: [[mockUsers[0]]], pageParams: [0] } } }
244
+ ] as any)
245
+
246
+ mutations.removeItem(mockUsers[0].id)
247
+
248
+ const data = queryClient.getQueryData(['users', 'list']) as InfiniteData<TestUser[], number>
249
+ expect(data.pages).toEqual([[]])
250
+ expect(data.pageParams).toEqual([0])
251
+ })
252
+
253
+ it('should filter out empty pages', () => {
254
+ const getAllListQueriesSpy = spyOn(queryKeys, 'getAllListQueries').mockReturnValue([
255
+ { queryKey: ['users', 'list'], state: { data: { pages: [[mockUsers[0]], [mockUsers[1]]], pageParams: [0, 1] } } }
256
+ ] as any)
257
+
258
+ mutations.removeItem(mockUsers[0].id)
259
+
260
+ const data = queryClient.getQueryData(['users', 'list']) as InfiniteData<TestUser[], number>
261
+ expect(data.pages).toEqual([[mockUsers[1]]])
262
+ expect(data.pageParams).toEqual([0])
263
+ })
264
+
265
+ it('should handle queries with no current data', () => {
266
+ const getAllListQueriesSpy = spyOn(queryKeys, 'getAllListQueries').mockReturnValue([
267
+ { queryKey: ['users', 'list'], state: { data: null } }
268
+ ] as any)
269
+
270
+ const removedMap = mutations.removeItem(mockUsers[0].id)
271
+
272
+ expect(removedMap).toEqual([])
273
+ })
274
+ })
275
+
276
+ describe('updateItems', () => {
277
+ const mockUsers = createMockUsers(3)
278
+
279
+ beforeEach(() => {
280
+ const getAllListQueriesSpy = spyOn(queryKeys, 'getAllListQueries').mockReturnValue([
281
+ {
282
+ queryKey: ['users', 'list'],
283
+ state: {
284
+ data: {
285
+ pages: [mockUsers.slice(0, 2), [mockUsers[2]]],
286
+ pageParams: [0, 2]
287
+ }
288
+ }
289
+ }
290
+ ] as any)
291
+ })
292
+
293
+ it('should update single item by id', () => {
294
+ const updateData = { ...mockUsers[0], name: 'Updated Name' }
295
+ const setQueryDataSpy = spyOn(queryClient, 'setQueryData')
296
+
297
+ mutations.updateItems(updateData)
298
+
299
+ expect(setQueryDataSpy).toHaveBeenCalledWith(
300
+ ['users', 'retrieve', updateData.id],
301
+ updateData
302
+ )
303
+ })
304
+
305
+ it('should update single item by tempId', () => {
306
+ const updateData = { ...mockUsers[0], name: 'Updated Name', tempId: 'temp-1' }
307
+ const setQueryDataSpy = spyOn(queryClient, 'setQueryData')
308
+
309
+ mutations.updateItems(updateData)
310
+
311
+ const expectedData = { ...updateData }
312
+ // @ts-ignore
313
+ delete expectedData.tempId
314
+
315
+ expect(setQueryDataSpy).toHaveBeenCalledWith(
316
+ ['users', 'retrieve', updateData.id],
317
+ expectedData
318
+ )
319
+ })
320
+
321
+ it('should update multiple items', () => {
322
+ const updateData = [
323
+ { ...mockUsers[0], name: 'Updated Name 1' },
324
+ { ...mockUsers[1], name: 'Updated Name 2' }
325
+ ]
326
+ const setQueryDataSpy = spyOn(queryClient, 'setQueryData')
327
+
328
+ mutations.updateItems(updateData)
329
+
330
+ expect(setQueryDataSpy).toHaveBeenCalledTimes(3) // 2 retrieve + 1 list update
331
+ })
332
+
333
+ it('should not update if data is the same (deep equal)', () => {
334
+ const setQueryDataSpy = spyOn(queryClient, 'setQueryData')
335
+
336
+ // Update with same data
337
+ mutations.updateItems(mockUsers[0])
338
+
339
+ // Should only set retrieve cache, not update list (no changes)
340
+ expect(setQueryDataSpy).toHaveBeenCalledTimes(1)
341
+ expect(setQueryDataSpy).toHaveBeenCalledWith(
342
+ ['users', 'retrieve', mockUsers[0].id],
343
+ mockUsers[0]
344
+ )
345
+ })
346
+
347
+ it('should update list cache when item data changes', () => {
348
+ const updateData = { ...mockUsers[0], name: 'Updated Name' }
349
+ const setQueryDataSpy = spyOn(queryClient, 'setQueryData')
350
+
351
+ mutations.updateItems(updateData)
352
+
353
+ // Should update both retrieve and list cache
354
+ expect(setQueryDataSpy).toHaveBeenCalledWith(
355
+ ['users', 'retrieve', updateData.id],
356
+ updateData
357
+ )
358
+ expect(setQueryDataSpy).toHaveBeenCalledWith(
359
+ ['users', 'list'],
360
+ expect.objectContaining({
361
+ pages: expect.arrayContaining([
362
+ expect.arrayContaining([updateData])
363
+ ])
364
+ })
365
+ )
366
+ })
367
+
368
+ it('should handle queries with no data', () => {
369
+ // Override the spy for this test
370
+ spyOn(queryKeys, 'getAllListQueries').mockReturnValue([
371
+ { queryKey: ['users', 'list'], state: { data: null } }
372
+ ] as any)
373
+
374
+ const updateData = { ...mockUsers[0], name: 'Updated Name' }
375
+ const setQueryDataSpy = spyOn(queryClient, 'setQueryData')
376
+
377
+ mutations.updateItems(updateData)
378
+
379
+ // Should only update retrieve cache
380
+ expect(setQueryDataSpy).toHaveBeenCalledTimes(1)
381
+ expect(setQueryDataSpy).toHaveBeenCalledWith(
382
+ ['users', 'retrieve', updateData.id],
383
+ updateData
384
+ )
385
+ })
386
+
387
+ it('should preserve page structure when updating items', () => {
388
+ const updateData = { ...mockUsers[2], name: 'Updated Name' } // Item in second page
389
+ const setQueryDataSpy = spyOn(queryClient, 'setQueryData')
390
+
391
+ mutations.updateItems(updateData)
392
+
393
+ expect(setQueryDataSpy).toHaveBeenCalledWith(
394
+ ['users', 'list'],
395
+ expect.objectContaining({
396
+ pages: [
397
+ mockUsers.slice(0, 2), // First page unchanged
398
+ [updateData] // Second page with updated item
399
+ ],
400
+ pageParams: [0, 2]
401
+ })
402
+ )
403
+ })
404
+
405
+ it('should handle items not found in list', () => {
406
+ const nonExistentItem = { id: 'non-existent', name: 'New Name' } as TestUser
407
+ const setQueryDataSpy = spyOn(queryClient, 'setQueryData')
408
+
409
+ mutations.updateItems(nonExistentItem)
410
+
411
+ // Should only update retrieve cache
412
+ expect(setQueryDataSpy).toHaveBeenCalledTimes(1)
413
+ expect(setQueryDataSpy).toHaveBeenCalledWith(
414
+ ['users', 'retrieve', nonExistentItem.id],
415
+ nonExistentItem
416
+ )
417
+ })
418
+
419
+ it('should remove tempId from cached data', () => {
420
+ const updateData = { ...mockUsers[0], name: 'Updated Name', tempId: 'temp-123' }
421
+ const setQueryDataSpy = spyOn(queryClient, 'setQueryData')
422
+
423
+ mutations.updateItems(updateData)
424
+
425
+ // Check retrieve cache call - tempId should be removed
426
+ const retrieveCall = setQueryDataSpy.mock.calls.find(call =>
427
+ Array.isArray(call[0]) && call[0].includes('retrieve')
428
+ )
429
+
430
+ expect(retrieveCall).toBeDefined()
431
+ const cachedData = retrieveCall![1] as any
432
+ expect(cachedData).not.toHaveProperty('tempId')
433
+ expect(cachedData.name).toBe('Updated Name')
434
+
435
+ // Check list cache call - tempId should be removed from items
436
+ const listCall = setQueryDataSpy.mock.calls.find(call =>
437
+ Array.isArray(call[0]) && call[0].includes('list')
438
+ )
439
+
440
+ if (listCall) {
441
+ const updatedListData = listCall[1] as any
442
+ const updatedItem = updatedListData.pages.flat().find((item: any) => item.id === updateData.id)
443
+ expect(updatedItem).not.toHaveProperty('tempId')
444
+ }
445
+ })
446
+ })
447
+
448
+ describe('createMutations factory', () => {
449
+ it('should create Mutations instance', () => {
450
+ const mutations = createMutations(queryKeys, queryClient, 'test')
451
+
452
+ expect(mutations).toBeInstanceOf(Mutations)
453
+ expect(mutations['queryKeys']).toBe(queryKeys)
454
+ expect(mutations['queryClient']).toBe(queryClient)
455
+ expect(mutations['queryName']).toBe('test')
456
+ })
457
+ })
458
+ })