muya 2.5.3 → 2.5.5
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.
- package/{src/__tests__ → __tests__}/bench.test.tsx +4 -4
- package/{src/__tests__ → __tests__}/compare.test.tsx +8 -6
- package/{src/__tests__ → __tests__}/create.test.tsx +2 -2
- package/{src/utils/__tests__ → __tests__}/is.test.ts +3 -3
- package/{src/__tests__ → __tests__}/scheduler.test.tsx +1 -1
- package/{src/__tests__ → __tests__}/select.test.tsx +5 -5
- package/{src/utils/__tests__ → __tests__}/shallow.test.ts +1 -1
- package/{src/__tests__ → __tests__}/use-value-loadable.test.tsx +3 -3
- package/{src/__tests__ → __tests__}/use-value.test.tsx +8 -8
- package/build.ts +67 -0
- package/dist/cjs/index.js +1 -0
- package/dist/esm/create.js +1 -0
- package/dist/esm/debug/development-tools.js +1 -0
- package/dist/esm/index.js +1 -0
- package/dist/esm/select.js +1 -0
- package/{types → dist/types}/create-state.d.ts +1 -0
- package/dist/types/create-state.d.ts.map +1 -0
- package/{types → dist/types}/create.d.ts +1 -0
- package/dist/types/create.d.ts.map +1 -0
- package/dist/types/debug/development-tools.d.ts +13 -0
- package/dist/types/debug/development-tools.d.ts.map +1 -0
- package/{types → dist/types}/index.d.ts +3 -1
- package/dist/types/index.d.ts.map +1 -0
- package/{types → dist/types}/scheduler.d.ts +1 -0
- package/dist/types/scheduler.d.ts.map +1 -0
- package/{types → dist/types}/select.d.ts +1 -0
- package/dist/types/select.d.ts.map +1 -0
- package/{types → dist/types}/types.d.ts +1 -0
- package/dist/types/types.d.ts.map +1 -0
- package/{types → dist/types}/use-value-loadable.d.ts +1 -0
- package/dist/types/use-value-loadable.d.ts.map +1 -0
- package/{types → dist/types}/use-value.d.ts +2 -1
- package/dist/types/use-value.d.ts.map +1 -0
- package/{types → dist/types}/utils/common.d.ts +1 -0
- package/dist/types/utils/common.d.ts.map +1 -0
- package/{types → dist/types}/utils/create-emitter.d.ts +1 -0
- package/dist/types/utils/create-emitter.d.ts.map +1 -0
- package/{types → dist/types}/utils/id.d.ts +1 -0
- package/dist/types/utils/id.d.ts.map +1 -0
- package/{types → dist/types}/utils/is.d.ts +1 -0
- package/dist/types/utils/is.d.ts.map +1 -0
- package/{types → dist/types}/utils/shallow.d.ts +1 -0
- package/dist/types/utils/shallow.d.ts.map +1 -0
- package/package.json +23 -8
- package/src/create-state.d.ts.map +1 -0
- package/src/create.d.ts.map +1 -0
- package/src/create.ts +7 -2
- package/src/debug/development-tools.d.ts.map +1 -0
- package/src/debug/development-tools.ts +5 -40
- package/src/index.d.ts.map +1 -0
- package/src/index.ts +2 -1
- package/src/scheduler.d.ts.map +1 -0
- package/src/select.d.ts.map +1 -0
- package/src/select.ts +7 -2
- package/src/types.d.ts.map +1 -0
- package/src/use-value-loadable.d.ts.map +1 -0
- package/src/use-value.d.ts.map +1 -0
- package/src/use-value.ts +1 -1
- package/src/utils/common.d.ts.map +1 -0
- package/src/utils/create-emitter.d.ts.map +1 -0
- package/src/utils/id.d.ts.map +1 -0
- package/src/utils/is.d.ts.map +1 -0
- package/src/utils/shallow.d.ts.map +1 -0
- package/tsconfig.build.json +12 -0
- package/cjs/index.js +0 -1
- package/esm/__tests__/test-utils.js +0 -1
- package/esm/create.js +0 -1
- package/esm/debug/development-tools.js +0 -1
- package/esm/index.js +0 -1
- package/esm/select.js +0 -1
- package/esm/sqlite/__tests__/create-sqlite.test.js +0 -1
- package/esm/sqlite/__tests__/map-deque.test.js +0 -1
- package/esm/sqlite/__tests__/table.test.js +0 -1
- package/esm/sqlite/__tests__/tokenizer.test.js +0 -1
- package/esm/sqlite/__tests__/where.test.js +0 -1
- package/esm/sqlite/create-sqlite.js +0 -1
- package/esm/sqlite/index.js +0 -1
- package/esm/sqlite/table/backend.js +0 -1
- package/esm/sqlite/table/bun-backend.js +0 -1
- package/esm/sqlite/table/index.js +0 -1
- package/esm/sqlite/table/map-deque.js +0 -1
- package/esm/sqlite/table/table.js +0 -43
- package/esm/sqlite/table/table.types.js +0 -0
- package/esm/sqlite/table/tokenizer.js +0 -1
- package/esm/sqlite/table/where.js +0 -1
- package/esm/sqlite/use-sqlite-count.js +0 -1
- package/esm/sqlite/use-sqlite.js +0 -1
- package/esm/utils/__tests__/is.test.js +0 -1
- package/esm/utils/__tests__/shallow.test.js +0 -1
- package/src/sqlite/__tests__/create-sqlite.test.ts +0 -264
- package/src/sqlite/__tests__/map-deque.test.ts +0 -61
- package/src/sqlite/__tests__/table.test.ts +0 -351
- package/src/sqlite/__tests__/tokenizer.test.ts +0 -43
- package/src/sqlite/__tests__/use-slite-count.test.tsx +0 -96
- package/src/sqlite/__tests__/use-sqlite.more.test.tsx +0 -637
- package/src/sqlite/__tests__/use-sqlite.test.tsx +0 -1008
- package/src/sqlite/__tests__/where.test.ts +0 -234
- package/src/sqlite/create-sqlite.ts +0 -164
- package/src/sqlite/index.ts +0 -4
- package/src/sqlite/table/backend.ts +0 -21
- package/src/sqlite/table/bun-backend.ts +0 -47
- package/src/sqlite/table/index.ts +0 -6
- package/src/sqlite/table/map-deque.ts +0 -29
- package/src/sqlite/table/table.ts +0 -353
- package/src/sqlite/table/table.types.ts +0 -129
- package/src/sqlite/table/tokenizer.ts +0 -35
- package/src/sqlite/table/where.ts +0 -207
- package/src/sqlite/use-sqlite-count.ts +0 -69
- package/src/sqlite/use-sqlite.ts +0 -250
- package/types/__tests__/test-utils.d.ts +0 -25
- package/types/debug/development-tools.d.ts +0 -8
- package/types/sqlite/create-sqlite.d.ts +0 -31
- package/types/sqlite/index.d.ts +0 -4
- package/types/sqlite/table/backend.d.ts +0 -20
- package/types/sqlite/table/bun-backend.d.ts +0 -6
- package/types/sqlite/table/index.d.ts +0 -6
- package/types/sqlite/table/map-deque.d.ts +0 -5
- package/types/sqlite/table/table.d.ts +0 -21
- package/types/sqlite/table/table.types.d.ts +0 -91
- package/types/sqlite/table/tokenizer.d.ts +0 -11
- package/types/sqlite/table/where.d.ts +0 -37
- package/types/sqlite/use-sqlite-count.d.ts +0 -17
- package/types/sqlite/use-sqlite.d.ts +0 -39
- /package/{src/__tests__ → __tests__}/test-utils.ts +0 -0
- /package/{esm → dist/esm}/create-state.js +0 -0
- /package/{esm → dist/esm}/scheduler.js +0 -0
- /package/{esm → dist/esm}/types.js +0 -0
- /package/{esm → dist/esm}/use-value-loadable.js +0 -0
- /package/{esm → dist/esm}/use-value.js +0 -0
- /package/{esm → dist/esm}/utils/common.js +0 -0
- /package/{esm → dist/esm}/utils/create-emitter.js +0 -0
- /package/{esm → dist/esm}/utils/id.js +0 -0
- /package/{esm → dist/esm}/utils/is.js +0 -0
- /package/{esm → dist/esm}/utils/shallow.js +0 -0
|
@@ -1,637 +0,0 @@
|
|
|
1
|
-
import { act, renderHook } from '@testing-library/react-hooks'
|
|
2
|
-
import { createSqliteState } from '../create-sqlite'
|
|
3
|
-
import { useSqliteValue } from '../use-sqlite'
|
|
4
|
-
import { waitFor } from '@testing-library/react'
|
|
5
|
-
import { bunMemoryBackend } from '../table/bun-backend'
|
|
6
|
-
import { StrictMode, Suspense } from 'react'
|
|
7
|
-
import type { Key } from '../table'
|
|
8
|
-
|
|
9
|
-
const backend = bunMemoryBackend()
|
|
10
|
-
|
|
11
|
-
interface Person {
|
|
12
|
-
id: string
|
|
13
|
-
name: string
|
|
14
|
-
age: number
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Wrapper component to provide necessary React context for testing.
|
|
19
|
-
* @param props - The props object containing children.
|
|
20
|
-
* @param props.children - The children to render inside the wrapper.
|
|
21
|
-
* @returns The wrapped children with React context.
|
|
22
|
-
*/
|
|
23
|
-
function Wrapper({ children }: Readonly<{ children: React.ReactNode }>) {
|
|
24
|
-
return (
|
|
25
|
-
<StrictMode>
|
|
26
|
-
<Suspense fallback={<div>Loading...</div>}>{children}</Suspense>
|
|
27
|
-
</StrictMode>
|
|
28
|
-
)
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
describe('use-sqlite edge cases', () => {
|
|
32
|
-
it('should remove an item and verify it is removed', async () => {
|
|
33
|
-
const sql = createSqliteState<Person>({ backend, tableName: 'RemoveTest', key: 'id' })
|
|
34
|
-
await sql.batchSet([
|
|
35
|
-
{ id: '1', name: 'Alice', age: 30 },
|
|
36
|
-
{ id: '2', name: 'Bob', age: 25 },
|
|
37
|
-
])
|
|
38
|
-
|
|
39
|
-
const { result } = renderHook(() => useSqliteValue(sql, {}, []), { wrapper: Wrapper })
|
|
40
|
-
|
|
41
|
-
await waitFor(() => {
|
|
42
|
-
expect(result.current[0]).toEqual([
|
|
43
|
-
{ id: '1', name: 'Alice', age: 30 },
|
|
44
|
-
{ id: '2', name: 'Bob', age: 25 },
|
|
45
|
-
])
|
|
46
|
-
expect(result.current[1].keysIndex).toEqual(
|
|
47
|
-
new Map([
|
|
48
|
-
['1', 0],
|
|
49
|
-
['2', 1],
|
|
50
|
-
]),
|
|
51
|
-
)
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
act(() => {
|
|
55
|
-
sql.delete('1')
|
|
56
|
-
})
|
|
57
|
-
|
|
58
|
-
await waitFor(() => {
|
|
59
|
-
expect(result.current[0]).toEqual([{ id: '2', name: 'Bob', age: 25 }])
|
|
60
|
-
expect(result.current[1].keysIndex).toEqual(new Map([['2', 0]]))
|
|
61
|
-
})
|
|
62
|
-
|
|
63
|
-
act(() => {
|
|
64
|
-
sql.delete('2')
|
|
65
|
-
})
|
|
66
|
-
|
|
67
|
-
await waitFor(() => {
|
|
68
|
-
expect(result.current[0]).toEqual([])
|
|
69
|
-
})
|
|
70
|
-
})
|
|
71
|
-
it('should handle deleting a non-existent item gracefully', async () => {
|
|
72
|
-
const sql = createSqliteState<Person>({ backend, tableName: 'EdgeCaseTest', key: 'id' })
|
|
73
|
-
await sql.batchSet([
|
|
74
|
-
{ id: '1', name: 'Alice', age: 30 },
|
|
75
|
-
{ id: '2', name: 'Bob', age: 25 },
|
|
76
|
-
])
|
|
77
|
-
|
|
78
|
-
const { result } = renderHook(() => useSqliteValue(sql, {}, []), { wrapper: Wrapper })
|
|
79
|
-
|
|
80
|
-
await waitFor(() => {
|
|
81
|
-
expect(result.current[0]).toEqual([
|
|
82
|
-
{ id: '1', name: 'Alice', age: 30 },
|
|
83
|
-
{ id: '2', name: 'Bob', age: 25 },
|
|
84
|
-
])
|
|
85
|
-
})
|
|
86
|
-
|
|
87
|
-
act(() => {
|
|
88
|
-
sql.delete('3') // Attempt to delete a non-existent item
|
|
89
|
-
})
|
|
90
|
-
|
|
91
|
-
await waitFor(() => {
|
|
92
|
-
expect(result.current[0]).toEqual([
|
|
93
|
-
{ id: '1', name: 'Alice', age: 30 },
|
|
94
|
-
{ id: '2', name: 'Bob', age: 25 },
|
|
95
|
-
]) // State should remain unchanged
|
|
96
|
-
})
|
|
97
|
-
})
|
|
98
|
-
|
|
99
|
-
it('should handle deleting all items', async () => {
|
|
100
|
-
const sql = createSqliteState<Person>({ backend, tableName: 'DeleteAllTest', key: 'id' })
|
|
101
|
-
await sql.batchSet([
|
|
102
|
-
{ id: '1', name: 'Alice', age: 30 },
|
|
103
|
-
{ id: '2', name: 'Bob', age: 25 },
|
|
104
|
-
])
|
|
105
|
-
|
|
106
|
-
const { result } = renderHook(() => useSqliteValue(sql, {}, []), { wrapper: Wrapper })
|
|
107
|
-
|
|
108
|
-
await waitFor(() => {
|
|
109
|
-
expect(result.current[0]).toEqual([
|
|
110
|
-
{ id: '1', name: 'Alice', age: 30 },
|
|
111
|
-
{ id: '2', name: 'Bob', age: 25 },
|
|
112
|
-
])
|
|
113
|
-
})
|
|
114
|
-
|
|
115
|
-
act(() => {
|
|
116
|
-
sql.delete('1')
|
|
117
|
-
sql.delete('2')
|
|
118
|
-
})
|
|
119
|
-
|
|
120
|
-
await waitFor(() => {
|
|
121
|
-
expect(result.current[0]).toEqual([]) // All items should be removed
|
|
122
|
-
})
|
|
123
|
-
})
|
|
124
|
-
|
|
125
|
-
it('should handle concurrent operations', async () => {
|
|
126
|
-
const sql = createSqliteState<Person>({ backend, tableName: 'ConcurrentOpsTest', key: 'id' })
|
|
127
|
-
await sql.batchSet([
|
|
128
|
-
{ id: '1', name: 'Alice', age: 30 },
|
|
129
|
-
{ id: '2', name: 'Bob', age: 25 },
|
|
130
|
-
])
|
|
131
|
-
|
|
132
|
-
const { result } = renderHook(() => useSqliteValue(sql, {}, []), { wrapper: Wrapper })
|
|
133
|
-
|
|
134
|
-
await waitFor(() => {
|
|
135
|
-
expect(result.current[0]).toEqual([
|
|
136
|
-
{ id: '1', name: 'Alice', age: 30 },
|
|
137
|
-
{ id: '2', name: 'Bob', age: 25 },
|
|
138
|
-
])
|
|
139
|
-
})
|
|
140
|
-
|
|
141
|
-
act(() => {
|
|
142
|
-
sql.delete('1')
|
|
143
|
-
sql.set({ id: '3', name: 'Carol', age: 40 })
|
|
144
|
-
})
|
|
145
|
-
|
|
146
|
-
await waitFor(() => {
|
|
147
|
-
expect(result.current[0]).toEqual([
|
|
148
|
-
{ id: '2', name: 'Bob', age: 25 },
|
|
149
|
-
{ id: '3', name: 'Carol', age: 40 },
|
|
150
|
-
]) // State should reflect both operations
|
|
151
|
-
})
|
|
152
|
-
})
|
|
153
|
-
|
|
154
|
-
it('should handle repeated updates, removals, and insertions in a loop', async () => {
|
|
155
|
-
const sql = createSqliteState<Person>({ backend, tableName: 'LoopTest', key: 'id' })
|
|
156
|
-
sql.clear()
|
|
157
|
-
// Initial batch set
|
|
158
|
-
await sql.batchSet([
|
|
159
|
-
{ id: '1', name: 'Alice', age: 30 },
|
|
160
|
-
{ id: '2', name: 'Bob', age: 25 },
|
|
161
|
-
])
|
|
162
|
-
|
|
163
|
-
const { result } = renderHook(() => useSqliteValue(sql, {}, []), { wrapper: Wrapper })
|
|
164
|
-
|
|
165
|
-
await waitFor(() => {
|
|
166
|
-
expect(result.current[0]).toEqual([
|
|
167
|
-
{ id: '1', name: 'Alice', age: 30 },
|
|
168
|
-
{ id: '2', name: 'Bob', age: 25 },
|
|
169
|
-
])
|
|
170
|
-
})
|
|
171
|
-
|
|
172
|
-
// Perform updates, removals, and insertions in a loop
|
|
173
|
-
act(() => {
|
|
174
|
-
for (let index = 0; index < 10; index++) {
|
|
175
|
-
sql.set({ id: `new-${index}`, name: `Person ${index}`, age: 20 + index }) // Insert new item
|
|
176
|
-
sql.delete('1') // Remove an existing item
|
|
177
|
-
sql.set({ id: '2', name: `Updated Bob ${index}`, age: 25 + index }) // Update an existing item
|
|
178
|
-
}
|
|
179
|
-
// fetch next
|
|
180
|
-
result.current[1].nextPage()
|
|
181
|
-
})
|
|
182
|
-
|
|
183
|
-
await waitFor(() => {
|
|
184
|
-
expect(result.current[0]).toEqual([
|
|
185
|
-
{ id: '2', name: 'Updated Bob 9', age: 34 },
|
|
186
|
-
{ id: 'new-0', name: 'Person 0', age: 20 },
|
|
187
|
-
{ id: 'new-1', name: 'Person 1', age: 21 },
|
|
188
|
-
{ id: 'new-2', name: 'Person 2', age: 22 },
|
|
189
|
-
{ id: 'new-3', name: 'Person 3', age: 23 },
|
|
190
|
-
{ id: 'new-4', name: 'Person 4', age: 24 },
|
|
191
|
-
{ id: 'new-5', name: 'Person 5', age: 25 },
|
|
192
|
-
{ id: 'new-6', name: 'Person 6', age: 26 },
|
|
193
|
-
{ id: 'new-7', name: 'Person 7', age: 27 },
|
|
194
|
-
{ id: 'new-8', name: 'Person 8', age: 28 },
|
|
195
|
-
{ id: 'new-9', name: 'Person 9', age: 29 },
|
|
196
|
-
])
|
|
197
|
-
})
|
|
198
|
-
})
|
|
199
|
-
it('should handle concurrent insertions and deletions', async () => {
|
|
200
|
-
const sql = createSqliteState<Person>({ backend, tableName: 'ConcurrentInsertDeleteTest', key: 'id' })
|
|
201
|
-
await sql.batchSet([
|
|
202
|
-
{ id: '1', name: 'Alice', age: 30 },
|
|
203
|
-
{ id: '2', name: 'Bob', age: 25 },
|
|
204
|
-
])
|
|
205
|
-
|
|
206
|
-
const { result } = renderHook(() => useSqliteValue(sql, {}, []), { wrapper: Wrapper })
|
|
207
|
-
|
|
208
|
-
await waitFor(() => {
|
|
209
|
-
expect(result.current[0]).toEqual([
|
|
210
|
-
{ id: '1', name: 'Alice', age: 30 },
|
|
211
|
-
{ id: '2', name: 'Bob', age: 25 },
|
|
212
|
-
])
|
|
213
|
-
})
|
|
214
|
-
|
|
215
|
-
act(() => {
|
|
216
|
-
sql.set({ id: '3', name: 'Carol', age: 40 })
|
|
217
|
-
sql.delete('1')
|
|
218
|
-
})
|
|
219
|
-
|
|
220
|
-
await waitFor(() => {
|
|
221
|
-
expect(result.current[0]).toEqual([
|
|
222
|
-
{ id: '2', name: 'Bob', age: 25 },
|
|
223
|
-
{ id: '3', name: 'Carol', age: 40 },
|
|
224
|
-
])
|
|
225
|
-
})
|
|
226
|
-
})
|
|
227
|
-
|
|
228
|
-
it('should handle pagination with empty pages', async () => {
|
|
229
|
-
const sql = createSqliteState<Person>({ backend, tableName: 'EmptyPaginationTest', key: 'id' })
|
|
230
|
-
sql.clear()
|
|
231
|
-
|
|
232
|
-
const { result } = renderHook(() => useSqliteValue(sql, {}, []), { wrapper: Wrapper })
|
|
233
|
-
|
|
234
|
-
await waitFor(() => {
|
|
235
|
-
expect(result.current[0]).toEqual([])
|
|
236
|
-
})
|
|
237
|
-
|
|
238
|
-
act(() => {
|
|
239
|
-
result.current[1].nextPage()
|
|
240
|
-
})
|
|
241
|
-
|
|
242
|
-
await waitFor(() => {
|
|
243
|
-
expect(result.current[0]).toEqual([]) // Still empty
|
|
244
|
-
})
|
|
245
|
-
})
|
|
246
|
-
|
|
247
|
-
it('should handle duplicate key insertions gracefully', async () => {
|
|
248
|
-
const sql = createSqliteState<Person>({ backend, tableName: 'DuplicateKeyTest', key: 'id' })
|
|
249
|
-
await sql.batchSet([{ id: '1', name: 'Alice', age: 30 }])
|
|
250
|
-
|
|
251
|
-
const { result } = renderHook(() => useSqliteValue(sql, {}, []), { wrapper: Wrapper })
|
|
252
|
-
|
|
253
|
-
await waitFor(() => {
|
|
254
|
-
expect(result.current[0]).toEqual([{ id: '1', name: 'Alice', age: 30 }])
|
|
255
|
-
})
|
|
256
|
-
|
|
257
|
-
act(() => {
|
|
258
|
-
sql.set({ id: '1', name: 'Updated Alice', age: 35 })
|
|
259
|
-
})
|
|
260
|
-
|
|
261
|
-
await waitFor(() => {
|
|
262
|
-
expect(result.current[0]).toEqual([{ id: '1', name: 'Updated Alice', age: 35 }])
|
|
263
|
-
})
|
|
264
|
-
})
|
|
265
|
-
|
|
266
|
-
it('should handle reset during pagination', async () => {
|
|
267
|
-
const sql = createSqliteState<Person>({ backend, tableName: 'ResetDuringPaginationTest', key: 'id' })
|
|
268
|
-
await sql.batchSet([
|
|
269
|
-
{ id: '1', name: 'Alice', age: 30 },
|
|
270
|
-
{ id: '2', name: 'Bob', age: 25 },
|
|
271
|
-
])
|
|
272
|
-
|
|
273
|
-
const { result } = renderHook(() => useSqliteValue(sql, {}, []), { wrapper: Wrapper })
|
|
274
|
-
|
|
275
|
-
await waitFor(() => {
|
|
276
|
-
expect(result.current[0]).toEqual([
|
|
277
|
-
{ id: '1', name: 'Alice', age: 30 },
|
|
278
|
-
{ id: '2', name: 'Bob', age: 25 },
|
|
279
|
-
])
|
|
280
|
-
})
|
|
281
|
-
|
|
282
|
-
act(() => {
|
|
283
|
-
result.current[1].reset()
|
|
284
|
-
result.current[1].nextPage()
|
|
285
|
-
})
|
|
286
|
-
|
|
287
|
-
await waitFor(() => {
|
|
288
|
-
expect(result.current[0]).toEqual([
|
|
289
|
-
{ id: '1', name: 'Alice', age: 30 },
|
|
290
|
-
{ id: '2', name: 'Bob', age: 25 },
|
|
291
|
-
])
|
|
292
|
-
})
|
|
293
|
-
})
|
|
294
|
-
|
|
295
|
-
it('should handle invalid key deletion gracefully', async () => {
|
|
296
|
-
const sql = createSqliteState<Person>({ backend, tableName: 'InvalidKeyDeletionTest', key: 'id' })
|
|
297
|
-
await sql.batchSet([
|
|
298
|
-
{ id: '1', name: 'Alice', age: 30 },
|
|
299
|
-
{ id: '2', name: 'Bob', age: 25 },
|
|
300
|
-
])
|
|
301
|
-
|
|
302
|
-
const { result } = renderHook(() => useSqliteValue(sql, {}, []), { wrapper: Wrapper })
|
|
303
|
-
|
|
304
|
-
await waitFor(() => {
|
|
305
|
-
expect(result.current[0]).toEqual([
|
|
306
|
-
{ id: '1', name: 'Alice', age: 30 },
|
|
307
|
-
{ id: '2', name: 'Bob', age: 25 },
|
|
308
|
-
])
|
|
309
|
-
})
|
|
310
|
-
|
|
311
|
-
act(() => {
|
|
312
|
-
sql.delete('non-existent-key')
|
|
313
|
-
})
|
|
314
|
-
|
|
315
|
-
await waitFor(() => {
|
|
316
|
-
expect(result.current[0]).toEqual([
|
|
317
|
-
{ id: '1', name: 'Alice', age: 30 },
|
|
318
|
-
{ id: '2', name: 'Bob', age: 25 },
|
|
319
|
-
])
|
|
320
|
-
})
|
|
321
|
-
})
|
|
322
|
-
it('should update a visible document', async () => {
|
|
323
|
-
const sql = createSqliteState<Person>({ backend, tableName: 'UpdateVisibleTest', key: 'id' })
|
|
324
|
-
await sql.batchSet([
|
|
325
|
-
{ id: '1', name: 'Alice', age: 30 },
|
|
326
|
-
{ id: '2', name: 'Bob', age: 25 },
|
|
327
|
-
])
|
|
328
|
-
|
|
329
|
-
const { result } = renderHook(() => useSqliteValue(sql, {}, []), { wrapper: Wrapper })
|
|
330
|
-
|
|
331
|
-
await waitFor(() => {
|
|
332
|
-
expect(result.current[0]).toEqual([
|
|
333
|
-
{ id: '1', name: 'Alice', age: 30 },
|
|
334
|
-
{ id: '2', name: 'Bob', age: 25 },
|
|
335
|
-
])
|
|
336
|
-
})
|
|
337
|
-
|
|
338
|
-
act(() => {
|
|
339
|
-
sql.set({ id: '1', name: 'Updated Alice', age: 35 })
|
|
340
|
-
})
|
|
341
|
-
|
|
342
|
-
await waitFor(() => {
|
|
343
|
-
expect(result.current[0]).toEqual([
|
|
344
|
-
{ id: '1', name: 'Updated Alice', age: 35 },
|
|
345
|
-
{ id: '2', name: 'Bob', age: 25 },
|
|
346
|
-
])
|
|
347
|
-
})
|
|
348
|
-
})
|
|
349
|
-
|
|
350
|
-
it('should update a non-visible document', async () => {
|
|
351
|
-
const sql = createSqliteState<Person>({ backend, tableName: 'UpdateNonVisibleTest', key: 'id' })
|
|
352
|
-
await sql.batchSet([
|
|
353
|
-
{ id: '1', name: 'Alice', age: 30 },
|
|
354
|
-
{ id: '2', name: 'Bob', age: 25 },
|
|
355
|
-
{ id: '3', name: 'Carol', age: 40 },
|
|
356
|
-
])
|
|
357
|
-
|
|
358
|
-
const { result } = renderHook(() => useSqliteValue(sql, { pageSize: 2 }, []), { wrapper: Wrapper })
|
|
359
|
-
|
|
360
|
-
await waitFor(() => {
|
|
361
|
-
expect(result.current[0]).toEqual([
|
|
362
|
-
{ id: '1', name: 'Alice', age: 30 },
|
|
363
|
-
{ id: '2', name: 'Bob', age: 25 },
|
|
364
|
-
])
|
|
365
|
-
})
|
|
366
|
-
|
|
367
|
-
act(() => {
|
|
368
|
-
sql.set({ id: '3', name: 'Updated Carol', age: 45 })
|
|
369
|
-
})
|
|
370
|
-
|
|
371
|
-
await waitFor(() => {
|
|
372
|
-
expect(result.current[0]).toEqual([
|
|
373
|
-
{ id: '1', name: 'Alice', age: 30 },
|
|
374
|
-
{ id: '2', name: 'Bob', age: 25 },
|
|
375
|
-
]) // No change in visible items
|
|
376
|
-
})
|
|
377
|
-
|
|
378
|
-
act(() => {
|
|
379
|
-
result.current[1].nextPage()
|
|
380
|
-
})
|
|
381
|
-
|
|
382
|
-
await waitFor(() => {
|
|
383
|
-
expect(result.current[0]).toEqual([
|
|
384
|
-
{ id: '1', name: 'Alice', age: 30 },
|
|
385
|
-
{ id: '2', name: 'Bob', age: 25 },
|
|
386
|
-
{ id: '3', name: 'Updated Carol', age: 45 },
|
|
387
|
-
])
|
|
388
|
-
})
|
|
389
|
-
})
|
|
390
|
-
|
|
391
|
-
it('should handle updates during pagination', async () => {
|
|
392
|
-
const sql = createSqliteState<Person>({ backend, tableName: 'UpdateDuringPaginationTest', key: 'id' })
|
|
393
|
-
await sql.batchSet([
|
|
394
|
-
{ id: '1', name: 'Alice', age: 30 },
|
|
395
|
-
{ id: '2', name: 'Bob', age: 25 },
|
|
396
|
-
{ id: '3', name: 'Carol', age: 40 },
|
|
397
|
-
])
|
|
398
|
-
|
|
399
|
-
const { result } = renderHook(() => useSqliteValue(sql, { pageSize: 2 }, []), { wrapper: Wrapper })
|
|
400
|
-
|
|
401
|
-
await waitFor(() => {
|
|
402
|
-
expect(result.current[0]).toEqual([
|
|
403
|
-
{ id: '1', name: 'Alice', age: 30 },
|
|
404
|
-
{ id: '2', name: 'Bob', age: 25 },
|
|
405
|
-
])
|
|
406
|
-
})
|
|
407
|
-
|
|
408
|
-
act(() => {
|
|
409
|
-
result.current[1].nextPage()
|
|
410
|
-
})
|
|
411
|
-
|
|
412
|
-
await waitFor(() => {
|
|
413
|
-
expect(result.current[0]).toEqual([
|
|
414
|
-
{ id: '1', name: 'Alice', age: 30 },
|
|
415
|
-
{ id: '2', name: 'Bob', age: 25 },
|
|
416
|
-
{ id: '3', name: 'Carol', age: 40 },
|
|
417
|
-
])
|
|
418
|
-
})
|
|
419
|
-
|
|
420
|
-
act(() => {
|
|
421
|
-
sql.set({ id: '2', name: 'Updated Bob', age: 35 })
|
|
422
|
-
})
|
|
423
|
-
|
|
424
|
-
await waitFor(() => {
|
|
425
|
-
expect(result.current[0]).toEqual([
|
|
426
|
-
{ id: '1', name: 'Alice', age: 30 },
|
|
427
|
-
{ id: '2', name: 'Updated Bob', age: 35 },
|
|
428
|
-
{ id: '3', name: 'Carol', age: 40 },
|
|
429
|
-
])
|
|
430
|
-
})
|
|
431
|
-
})
|
|
432
|
-
|
|
433
|
-
it('should handle rapid consecutive updates without losing state', async () => {
|
|
434
|
-
const sql = createSqliteState<Person>({ backend, tableName: 'RaceState', key: 'id' })
|
|
435
|
-
|
|
436
|
-
// Insert initial document
|
|
437
|
-
await sql.set({ id: '1', name: 'Alice', age: 30 })
|
|
438
|
-
|
|
439
|
-
const { result } = renderHook(() => useSqliteValue(sql, {}, []))
|
|
440
|
-
|
|
441
|
-
await waitFor(() => {
|
|
442
|
-
expect(result.current[0]).toEqual([{ id: '1', name: 'Alice', age: 30 }])
|
|
443
|
-
})
|
|
444
|
-
|
|
445
|
-
// Now simulate fast consecutive updates to the same key
|
|
446
|
-
// These will hit the subscription handler while it's still awaiting `state.get`
|
|
447
|
-
await Promise.all([
|
|
448
|
-
sql.set({ id: '1', name: 'AliceV2', age: 31 }),
|
|
449
|
-
sql.set({ id: '1', name: 'AliceV3', age: 32 }),
|
|
450
|
-
sql.set({ id: '1', name: 'AliceV4', age: 33 }),
|
|
451
|
-
])
|
|
452
|
-
|
|
453
|
-
// Wait for hook to stabilize
|
|
454
|
-
await waitFor(() => {
|
|
455
|
-
// eslint-disable-next-line prefer-destructuring, unicorn/prevent-abbreviations
|
|
456
|
-
const docs = result.current[0]
|
|
457
|
-
expect(docs?.length).toBe(1)
|
|
458
|
-
// Expect the *latest* update to win
|
|
459
|
-
expect(docs?.[0]).toEqual({ id: '1', name: 'AliceV4', age: 33 })
|
|
460
|
-
})
|
|
461
|
-
})
|
|
462
|
-
|
|
463
|
-
it('should not overwrite newer updates with stale state.get results', async () => {
|
|
464
|
-
const sql = createSqliteState<Person>({ backend, tableName: 'RaceFailState', key: 'id' })
|
|
465
|
-
await sql.set({ id: '1', name: 'Initial', age: 20 })
|
|
466
|
-
|
|
467
|
-
// Delay the first get call artificially to simulate slow DB
|
|
468
|
-
let callCount = 0
|
|
469
|
-
const originalGet = sql.get
|
|
470
|
-
// @ts-expect-error - We mocking the get method for testing
|
|
471
|
-
sql.get = async (key: Key, selector: ((document: Person) => Person) | undefined) => {
|
|
472
|
-
callCount++
|
|
473
|
-
if (callCount === 1) {
|
|
474
|
-
// Simulate slow resolution for first update
|
|
475
|
-
await new Promise((resolve) => setTimeout(resolve, 50))
|
|
476
|
-
}
|
|
477
|
-
return originalGet(key, selector)
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
const { result } = renderHook(() => useSqliteValue(sql, {}, []))
|
|
481
|
-
|
|
482
|
-
await waitFor(() => {
|
|
483
|
-
expect(result.current[0]).toEqual([{ id: '1', name: 'Initial', age: 20 }])
|
|
484
|
-
})
|
|
485
|
-
|
|
486
|
-
// Trigger two consecutive updates
|
|
487
|
-
await sql.set({ id: '1', name: 'AliceV1', age: 21 })
|
|
488
|
-
await sql.set({ id: '1', name: 'AliceV2', age: 22 })
|
|
489
|
-
|
|
490
|
-
// Wait for hook to stabilize
|
|
491
|
-
await waitFor(() => {
|
|
492
|
-
// eslint-disable-next-line prefer-destructuring, unicorn/prevent-abbreviations
|
|
493
|
-
const docs = result.current[0]
|
|
494
|
-
expect(docs?.length).toBe(1)
|
|
495
|
-
// 🔥 Correct behavior: should end with the *latest* version (AliceV2)
|
|
496
|
-
// ❌ Buggy behavior: may still show AliceV1 because the delayed get resolves last
|
|
497
|
-
expect(docs?.[0]).toEqual({ id: '1', name: 'AliceV2', age: 22 })
|
|
498
|
-
})
|
|
499
|
-
})
|
|
500
|
-
it('should reset correctly during pagination', async () => {
|
|
501
|
-
const sql = createSqliteState<Person>({ backend, tableName: 'ResetMid', key: 'id' })
|
|
502
|
-
const people = Array.from({ length: 200 }, (_, index) => ({ id: `${index + 1}`, name: `P${index + 1}`, age: index }))
|
|
503
|
-
await sql.batchSet(people)
|
|
504
|
-
|
|
505
|
-
const { result } = renderHook(() => useSqliteValue(sql, { pageSize: 50 }, []))
|
|
506
|
-
|
|
507
|
-
await waitFor(() => {
|
|
508
|
-
expect(result.current[0]?.length).toBe(50)
|
|
509
|
-
})
|
|
510
|
-
|
|
511
|
-
// Load next page
|
|
512
|
-
await act(async () => {
|
|
513
|
-
await result.current[1].nextPage()
|
|
514
|
-
})
|
|
515
|
-
expect(result.current[0]?.length).toBe(100)
|
|
516
|
-
|
|
517
|
-
// Reset
|
|
518
|
-
await act(async () => {
|
|
519
|
-
await result.current[1].reset()
|
|
520
|
-
})
|
|
521
|
-
await waitFor(() => {
|
|
522
|
-
// Should go back to first page only
|
|
523
|
-
expect(result.current[0]?.length).toBe(50)
|
|
524
|
-
expect(result.current[0]?.[0]?.id).toBe('1')
|
|
525
|
-
})
|
|
526
|
-
})
|
|
527
|
-
it('should overwrite duplicate keys instead of duplicating items', async () => {
|
|
528
|
-
const sql = createSqliteState<Person>({ backend, tableName: 'DupTest', key: 'id' })
|
|
529
|
-
await sql.set({ id: '1', name: 'Alice', age: 30 })
|
|
530
|
-
await sql.set({ id: '1', name: 'Alice2', age: 35 }) // overwrite
|
|
531
|
-
|
|
532
|
-
const { result } = renderHook(() => useSqliteValue(sql, {}, []))
|
|
533
|
-
|
|
534
|
-
await waitFor(() => {
|
|
535
|
-
expect(result.current[0]).toEqual([{ id: '1', name: 'Alice2', age: 35 }])
|
|
536
|
-
})
|
|
537
|
-
})
|
|
538
|
-
it('should not update after unmount', async () => {
|
|
539
|
-
const sql = createSqliteState<Person>({ backend, tableName: 'UnmountTest', key: 'id' })
|
|
540
|
-
const { unmount, result } = renderHook(() => useSqliteValue(sql, {}, []))
|
|
541
|
-
|
|
542
|
-
unmount()
|
|
543
|
-
await sql.set({ id: '1', name: 'ShouldNotAppear', age: 99 })
|
|
544
|
-
|
|
545
|
-
// wait briefly to give subscription a chance
|
|
546
|
-
await new Promise((r) => setTimeout(r, 20))
|
|
547
|
-
|
|
548
|
-
expect(result.current[0]).toBeNull() // no state change after unmount
|
|
549
|
-
})
|
|
550
|
-
it('should restart iterator when sortBy changes', async () => {
|
|
551
|
-
const sql = createSqliteState<Person>({ backend, tableName: 'SortChange', key: 'id' })
|
|
552
|
-
await sql.batchSet([
|
|
553
|
-
{ id: '1', name: 'A', age: 40 },
|
|
554
|
-
{ id: '2', name: 'B', age: 20 },
|
|
555
|
-
])
|
|
556
|
-
|
|
557
|
-
const { result, rerender } = renderHook(({ sortBy }) => useSqliteValue(sql, { sortBy }, [sortBy]), {
|
|
558
|
-
initialProps: { sortBy: 'age' as keyof Person },
|
|
559
|
-
})
|
|
560
|
-
|
|
561
|
-
await waitFor(() => {
|
|
562
|
-
expect(result.current[0]?.[0]?.age).toBe(20)
|
|
563
|
-
})
|
|
564
|
-
|
|
565
|
-
act(() => {
|
|
566
|
-
rerender({ sortBy: 'name' })
|
|
567
|
-
})
|
|
568
|
-
|
|
569
|
-
await waitFor(() => {
|
|
570
|
-
expect(result.current[0]?.[0]?.name).toBe('A') // sorted by name asc
|
|
571
|
-
})
|
|
572
|
-
})
|
|
573
|
-
|
|
574
|
-
it('should return done when nextPage is called on empty table', async () => {
|
|
575
|
-
const sql = createSqliteState<Person>({ backend, tableName: 'EmptyNext', key: 'id' })
|
|
576
|
-
const { result } = renderHook(() => useSqliteValue(sql, {}, []))
|
|
577
|
-
|
|
578
|
-
// @ts-expect-error - Testing internal method
|
|
579
|
-
const isDone = await act(result.current[1].nextPage)
|
|
580
|
-
expect(isDone).toBe(true)
|
|
581
|
-
expect(result.current[0]).toEqual([])
|
|
582
|
-
})
|
|
583
|
-
|
|
584
|
-
it('should handle deletion of non-visible item gracefully', async () => {
|
|
585
|
-
const sql = createSqliteState<Person>({ backend, tableName: 'DeleteNonVisible', key: 'id' })
|
|
586
|
-
const people = Array.from({ length: 200 }, (_, index) => ({ id: `${index + 1}`, name: `P${index + 1}`, age: index }))
|
|
587
|
-
await sql.batchSet(people)
|
|
588
|
-
let renderCount = 0
|
|
589
|
-
const { result } = renderHook(() => {
|
|
590
|
-
renderCount++
|
|
591
|
-
return useSqliteValue(sql, { pageSize: 50 }, [])
|
|
592
|
-
})
|
|
593
|
-
await waitFor(() => {
|
|
594
|
-
expect(result.current[0]?.length).toBe(50)
|
|
595
|
-
})
|
|
596
|
-
const initialRenders = renderCount
|
|
597
|
-
|
|
598
|
-
// Delete item outside current page
|
|
599
|
-
await act(async () => {
|
|
600
|
-
await sql.delete('150')
|
|
601
|
-
})
|
|
602
|
-
|
|
603
|
-
await waitFor(() => {
|
|
604
|
-
expect(result.current[0]?.length).toBe(50) // unchanged page size
|
|
605
|
-
})
|
|
606
|
-
// No re-render for non-visible item deletion
|
|
607
|
-
expect(renderCount).toBe(initialRenders)
|
|
608
|
-
})
|
|
609
|
-
it('should not rerender when using select if the selected value does not change', async () => {
|
|
610
|
-
const sql = createSqliteState<Person>({ backend, tableName: 'SelectNoReRender', key: 'id' })
|
|
611
|
-
await sql.set({ id: '1', name: 'Alice', age: 30 })
|
|
612
|
-
|
|
613
|
-
let renders = 0
|
|
614
|
-
const { result } = renderHook(() => {
|
|
615
|
-
renders++
|
|
616
|
-
// Only project `name`
|
|
617
|
-
return useSqliteValue(sql, { select: (p) => p.name }, [])
|
|
618
|
-
})
|
|
619
|
-
|
|
620
|
-
await waitFor(() => {
|
|
621
|
-
expect(result.current[0]).toEqual(['Alice'])
|
|
622
|
-
})
|
|
623
|
-
const initialRenders = renders
|
|
624
|
-
|
|
625
|
-
// Update age (not part of select projection)
|
|
626
|
-
await act(async () => {
|
|
627
|
-
await sql.set({ id: '1', name: 'Alice', age: 31 })
|
|
628
|
-
})
|
|
629
|
-
|
|
630
|
-
// Wait a bit to let subscription flush
|
|
631
|
-
await new Promise((r) => setTimeout(r, 20))
|
|
632
|
-
|
|
633
|
-
// No re-render since selected value "Alice" didn't change
|
|
634
|
-
expect(result.current[0]).toEqual(['Alice'])
|
|
635
|
-
expect(renders).toBe(initialRenders)
|
|
636
|
-
})
|
|
637
|
-
})
|