@mdxui/terminal 2.0.0

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.
Files changed (191) hide show
  1. package/README.md +571 -0
  2. package/dist/ansi-css-Sk5mWtdK.d.ts +119 -0
  3. package/dist/ansi-css-V6JIHGsM.d.ts +119 -0
  4. package/dist/ansi-css-_3eSEU9d.d.ts +119 -0
  5. package/dist/chunk-3EFDH7PK.js +5235 -0
  6. package/dist/chunk-3RG5ZIWI.js +10 -0
  7. package/dist/chunk-3X5IR6WE.js +884 -0
  8. package/dist/chunk-4FV5ZDCE.js +5236 -0
  9. package/dist/chunk-4OVMSF2J.js +243 -0
  10. package/dist/chunk-63FEETIS.js +4048 -0
  11. package/dist/chunk-B43KP7XJ.js +884 -0
  12. package/dist/chunk-BMTJXWUV.js +655 -0
  13. package/dist/chunk-C3SVH4N7.js +882 -0
  14. package/dist/chunk-EVWR7Y47.js +874 -0
  15. package/dist/chunk-F6A5VWUC.js +1285 -0
  16. package/dist/chunk-FD7KW7GE.js +882 -0
  17. package/dist/chunk-GBQ6UD6I.js +655 -0
  18. package/dist/chunk-GMDD3M6U.js +5227 -0
  19. package/dist/chunk-JBHRXOXM.js +1058 -0
  20. package/dist/chunk-JFOO3EYO.js +1182 -0
  21. package/dist/chunk-JQ5H3WXL.js +1291 -0
  22. package/dist/chunk-JQD5NASE.js +234 -0
  23. package/dist/chunk-KRHJP5R7.js +592 -0
  24. package/dist/chunk-KWF6WVJE.js +962 -0
  25. package/dist/chunk-LHYQVN3H.js +1038 -0
  26. package/dist/chunk-M3TLQLGC.js +1032 -0
  27. package/dist/chunk-MVW4Q5OP.js +240 -0
  28. package/dist/chunk-NXCZSWLU.js +1294 -0
  29. package/dist/chunk-O25TNRO6.js +607 -0
  30. package/dist/chunk-PNECDA2I.js +884 -0
  31. package/dist/chunk-QIHWRLJR.js +962 -0
  32. package/dist/chunk-QW5YMQ7K.js +882 -0
  33. package/dist/chunk-R5U7XKVJ.js +16 -0
  34. package/dist/chunk-RP2MVQLR.js +962 -0
  35. package/dist/chunk-TP6RXGXA.js +1087 -0
  36. package/dist/chunk-TQQSTITZ.js +655 -0
  37. package/dist/chunk-X24GWXQV.js +1281 -0
  38. package/dist/components/index.d.ts +802 -0
  39. package/dist/components/index.js +149 -0
  40. package/dist/data/index.d.ts +2554 -0
  41. package/dist/data/index.js +51 -0
  42. package/dist/forms/index.d.ts +1596 -0
  43. package/dist/forms/index.js +464 -0
  44. package/dist/index-CQRFZntR.d.ts +867 -0
  45. package/dist/index.d.ts +579 -0
  46. package/dist/index.js +786 -0
  47. package/dist/interactive-D0JkWosD.d.ts +217 -0
  48. package/dist/keyboard/index.d.ts +2 -0
  49. package/dist/keyboard/index.js +43 -0
  50. package/dist/renderers/index.d.ts +546 -0
  51. package/dist/renderers/index.js +2157 -0
  52. package/dist/storybook/index.d.ts +396 -0
  53. package/dist/storybook/index.js +641 -0
  54. package/dist/theme/index.d.ts +1339 -0
  55. package/dist/theme/index.js +123 -0
  56. package/dist/types-Bxu5PAgA.d.ts +710 -0
  57. package/dist/types-CIlop5Ji.d.ts +701 -0
  58. package/dist/types-Ca8p_p5X.d.ts +710 -0
  59. package/package.json +90 -0
  60. package/src/__tests__/components/data/card.test.ts +458 -0
  61. package/src/__tests__/components/data/list.test.ts +473 -0
  62. package/src/__tests__/components/data/metrics.test.ts +541 -0
  63. package/src/__tests__/components/data/table.test.ts +448 -0
  64. package/src/__tests__/components/input/field.test.ts +555 -0
  65. package/src/__tests__/components/input/form.test.ts +870 -0
  66. package/src/__tests__/components/input/search.test.ts +1238 -0
  67. package/src/__tests__/components/input/select.test.ts +658 -0
  68. package/src/__tests__/components/navigation/breadcrumb.test.ts +923 -0
  69. package/src/__tests__/components/navigation/command-palette.test.ts +1095 -0
  70. package/src/__tests__/components/navigation/sidebar.test.ts +1018 -0
  71. package/src/__tests__/components/navigation/tabs.test.ts +995 -0
  72. package/src/__tests__/components.test.tsx +1197 -0
  73. package/src/__tests__/core/compiler.test.ts +986 -0
  74. package/src/__tests__/core/parser.test.ts +785 -0
  75. package/src/__tests__/core/tier-switcher.test.ts +1103 -0
  76. package/src/__tests__/core/types.test.ts +1398 -0
  77. package/src/__tests__/data/collections.test.ts +1337 -0
  78. package/src/__tests__/data/db.test.ts +1265 -0
  79. package/src/__tests__/data/reactive.test.ts +1010 -0
  80. package/src/__tests__/data/sync.test.ts +1614 -0
  81. package/src/__tests__/errors.test.ts +660 -0
  82. package/src/__tests__/forms/integration.test.ts +444 -0
  83. package/src/__tests__/integration.test.ts +905 -0
  84. package/src/__tests__/keyboard.test.ts +1791 -0
  85. package/src/__tests__/renderer.test.ts +489 -0
  86. package/src/__tests__/renderers/ansi-css.test.ts +948 -0
  87. package/src/__tests__/renderers/ansi.test.ts +1366 -0
  88. package/src/__tests__/renderers/ascii.test.ts +1360 -0
  89. package/src/__tests__/renderers/interactive.test.ts +2353 -0
  90. package/src/__tests__/renderers/markdown.test.ts +1483 -0
  91. package/src/__tests__/renderers/text.test.ts +1369 -0
  92. package/src/__tests__/renderers/unicode.test.ts +1307 -0
  93. package/src/__tests__/theme.test.ts +639 -0
  94. package/src/__tests__/utils/assertions.ts +685 -0
  95. package/src/__tests__/utils/index.ts +115 -0
  96. package/src/__tests__/utils/test-renderer.ts +381 -0
  97. package/src/__tests__/utils/utils.test.ts +560 -0
  98. package/src/components/containers/card.ts +56 -0
  99. package/src/components/containers/dialog.ts +53 -0
  100. package/src/components/containers/index.ts +9 -0
  101. package/src/components/containers/panel.ts +59 -0
  102. package/src/components/feedback/badge.ts +40 -0
  103. package/src/components/feedback/index.ts +8 -0
  104. package/src/components/feedback/spinner.ts +23 -0
  105. package/src/components/helpers.ts +81 -0
  106. package/src/components/index.ts +153 -0
  107. package/src/components/layout/breadcrumb.ts +31 -0
  108. package/src/components/layout/index.ts +10 -0
  109. package/src/components/layout/list.ts +29 -0
  110. package/src/components/layout/sidebar.ts +79 -0
  111. package/src/components/layout/table.ts +62 -0
  112. package/src/components/primitives/box.ts +95 -0
  113. package/src/components/primitives/button.ts +54 -0
  114. package/src/components/primitives/index.ts +11 -0
  115. package/src/components/primitives/input.ts +88 -0
  116. package/src/components/primitives/select.ts +97 -0
  117. package/src/components/primitives/text.ts +60 -0
  118. package/src/components/render.ts +155 -0
  119. package/src/components/templates/app.ts +43 -0
  120. package/src/components/templates/index.ts +8 -0
  121. package/src/components/templates/site.ts +54 -0
  122. package/src/components/types.ts +777 -0
  123. package/src/core/compiler.ts +718 -0
  124. package/src/core/parser.ts +127 -0
  125. package/src/core/tier-switcher.ts +607 -0
  126. package/src/core/types.ts +672 -0
  127. package/src/data/collection.ts +316 -0
  128. package/src/data/collections.ts +50 -0
  129. package/src/data/context.tsx +174 -0
  130. package/src/data/db.ts +127 -0
  131. package/src/data/hooks.ts +532 -0
  132. package/src/data/index.ts +138 -0
  133. package/src/data/reactive.ts +1225 -0
  134. package/src/data/saas-collections.ts +375 -0
  135. package/src/data/sync.ts +1213 -0
  136. package/src/data/types.ts +660 -0
  137. package/src/forms/converters.ts +512 -0
  138. package/src/forms/index.ts +133 -0
  139. package/src/forms/schemas.ts +403 -0
  140. package/src/forms/types.ts +476 -0
  141. package/src/index.ts +542 -0
  142. package/src/keyboard/focus.ts +748 -0
  143. package/src/keyboard/index.ts +96 -0
  144. package/src/keyboard/integration.ts +371 -0
  145. package/src/keyboard/manager.ts +377 -0
  146. package/src/keyboard/presets.ts +90 -0
  147. package/src/renderers/ansi-css.ts +576 -0
  148. package/src/renderers/ansi.ts +802 -0
  149. package/src/renderers/ascii.ts +680 -0
  150. package/src/renderers/breadcrumb.ts +480 -0
  151. package/src/renderers/command-palette.ts +802 -0
  152. package/src/renderers/components/field.ts +210 -0
  153. package/src/renderers/components/form.ts +327 -0
  154. package/src/renderers/components/index.ts +21 -0
  155. package/src/renderers/components/search.ts +449 -0
  156. package/src/renderers/components/select.ts +222 -0
  157. package/src/renderers/index.ts +101 -0
  158. package/src/renderers/interactive/component-handlers.ts +622 -0
  159. package/src/renderers/interactive/cursor-manager.ts +147 -0
  160. package/src/renderers/interactive/focus-manager.ts +279 -0
  161. package/src/renderers/interactive/index.ts +661 -0
  162. package/src/renderers/interactive/input-handler.ts +164 -0
  163. package/src/renderers/interactive/keyboard-handler.ts +212 -0
  164. package/src/renderers/interactive/mouse-handler.ts +167 -0
  165. package/src/renderers/interactive/state-manager.ts +109 -0
  166. package/src/renderers/interactive/types.ts +338 -0
  167. package/src/renderers/interactive-string.ts +299 -0
  168. package/src/renderers/interactive.ts +59 -0
  169. package/src/renderers/markdown.ts +950 -0
  170. package/src/renderers/sidebar.ts +549 -0
  171. package/src/renderers/tabs.ts +682 -0
  172. package/src/renderers/text.ts +791 -0
  173. package/src/renderers/unicode.ts +917 -0
  174. package/src/renderers/utils.ts +942 -0
  175. package/src/router/adapters.ts +383 -0
  176. package/src/router/types.ts +140 -0
  177. package/src/router/utils.ts +452 -0
  178. package/src/schemas.ts +205 -0
  179. package/src/storybook/index.ts +91 -0
  180. package/src/storybook/interactive-decorator.tsx +659 -0
  181. package/src/storybook/keyboard-simulator.ts +501 -0
  182. package/src/theme/ansi-codes.ts +80 -0
  183. package/src/theme/box-drawing.ts +132 -0
  184. package/src/theme/color-convert.ts +254 -0
  185. package/src/theme/color-support.ts +321 -0
  186. package/src/theme/index.ts +134 -0
  187. package/src/theme/strip-ansi.ts +50 -0
  188. package/src/theme/tailwind-map.ts +469 -0
  189. package/src/theme/text-styles.ts +206 -0
  190. package/src/theme/theme-system.ts +568 -0
  191. package/src/types.ts +103 -0
@@ -0,0 +1,1265 @@
1
+ /**
2
+ * @mdxui/terminal TanStack DB Integration Tests
3
+ *
4
+ * TDD RED Phase: These tests define the contract for TanStack DB integration.
5
+ * All tests should FAIL initially because the implementation doesn't exist yet.
6
+ *
7
+ * The data layer provides:
8
+ * - createDB() - Database instance creation
9
+ * - createCollection() - Typed collection with Zod schema validation
10
+ * - useQuery() - Reactive data queries with filtering and sorting
11
+ * - useMutation() - Mutations with optimistic updates
12
+ */
13
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
14
+ import { z } from 'zod'
15
+ import { renderHook, act, waitFor } from '@testing-library/react'
16
+ import * as React from 'react'
17
+
18
+ // ============================================================================
19
+ // Test Schemas
20
+ // ============================================================================
21
+
22
+ const UserSchema = z.object({
23
+ id: z.string(),
24
+ name: z.string(),
25
+ email: z.string().email(),
26
+ age: z.number().optional(),
27
+ role: z.enum(['admin', 'user', 'guest']),
28
+ createdAt: z.date(),
29
+ })
30
+
31
+ const TodoSchema = z.object({
32
+ id: z.string(),
33
+ title: z.string(),
34
+ completed: z.boolean(),
35
+ priority: z.number().min(1).max(5),
36
+ userId: z.string(),
37
+ })
38
+
39
+ type User = z.infer<typeof UserSchema>
40
+ type Todo = z.infer<typeof TodoSchema>
41
+
42
+ // ============================================================================
43
+ // createDB() Tests
44
+ // ============================================================================
45
+
46
+ describe('@mdxui/terminal data layer - createDB', () => {
47
+ it('creates a database instance', async () => {
48
+ const { createDB } = await import('@mdxui/terminal')
49
+
50
+ const db = createDB()
51
+
52
+ expect(db).toBeDefined()
53
+ expect(typeof db).toBe('object')
54
+ })
55
+
56
+ it('accepts collections array configuration', async () => {
57
+ const { createDB, createCollection } = await import('@mdxui/terminal')
58
+
59
+ const usersCollection = createCollection({
60
+ name: 'users',
61
+ schema: UserSchema,
62
+ })
63
+
64
+ const todosCollection = createCollection({
65
+ name: 'todos',
66
+ schema: TodoSchema,
67
+ })
68
+
69
+ const db = createDB({
70
+ collections: [usersCollection, todosCollection],
71
+ })
72
+
73
+ expect(db).toBeDefined()
74
+ expect(db.collections).toBeDefined()
75
+ expect(db.collections.users).toBeDefined()
76
+ expect(db.collections.todos).toBeDefined()
77
+ })
78
+
79
+ it('accepts sync adapter configuration', async () => {
80
+ const { createDB } = await import('@mdxui/terminal')
81
+
82
+ const mockSyncAdapter = {
83
+ push: vi.fn(),
84
+ pull: vi.fn(),
85
+ subscribe: vi.fn(),
86
+ }
87
+
88
+ const db = createDB({
89
+ sync: mockSyncAdapter,
90
+ })
91
+
92
+ expect(db).toBeDefined()
93
+ expect(db.sync).toBe(mockSyncAdapter)
94
+ })
95
+
96
+ it('returns database with standard methods', async () => {
97
+ const { createDB } = await import('@mdxui/terminal')
98
+
99
+ const db = createDB()
100
+
101
+ expect(typeof db.close).toBe('function')
102
+ expect(typeof db.clear).toBe('function')
103
+ })
104
+ })
105
+
106
+ // ============================================================================
107
+ // createCollection() Tests
108
+ // ============================================================================
109
+
110
+ describe('@mdxui/terminal data layer - createCollection', () => {
111
+ it('creates a typed collection with name and schema', async () => {
112
+ const { createCollection } = await import('@mdxui/terminal')
113
+
114
+ const collection = createCollection({
115
+ name: 'users',
116
+ schema: UserSchema,
117
+ })
118
+
119
+ expect(collection).toBeDefined()
120
+ expect(collection.name).toBe('users')
121
+ expect(collection.schema).toBe(UserSchema)
122
+ })
123
+
124
+ it('validates data against Zod schema on insert', async () => {
125
+ const { createDB, createCollection } = await import('@mdxui/terminal')
126
+
127
+ const usersCollection = createCollection({
128
+ name: 'users',
129
+ schema: UserSchema,
130
+ })
131
+
132
+ const db = createDB({
133
+ collections: [usersCollection],
134
+ })
135
+
136
+ const validUser: User = {
137
+ id: '1',
138
+ name: 'John Doe',
139
+ email: 'john@example.com',
140
+ role: 'user',
141
+ createdAt: new Date(),
142
+ }
143
+
144
+ // Valid data should not throw
145
+ await expect(db.collections.users.insert(validUser)).resolves.not.toThrow()
146
+ })
147
+
148
+ it('rejects invalid data on insert', async () => {
149
+ const { createDB, createCollection } = await import('@mdxui/terminal')
150
+
151
+ const usersCollection = createCollection({
152
+ name: 'users',
153
+ schema: UserSchema,
154
+ })
155
+
156
+ const db = createDB({
157
+ collections: [usersCollection],
158
+ })
159
+
160
+ const invalidUser = {
161
+ id: '1',
162
+ name: 'John Doe',
163
+ email: 'not-an-email', // Invalid email
164
+ role: 'user',
165
+ createdAt: new Date(),
166
+ }
167
+
168
+ await expect(db.collections.users.insert(invalidUser)).rejects.toThrow()
169
+ })
170
+
171
+ it('returns collection with CRUD methods', async () => {
172
+ const { createCollection } = await import('@mdxui/terminal')
173
+
174
+ const collection = createCollection({
175
+ name: 'users',
176
+ schema: UserSchema,
177
+ })
178
+
179
+ expect(typeof collection.insert).toBe('function')
180
+ expect(typeof collection.update).toBe('function')
181
+ expect(typeof collection.delete).toBe('function')
182
+ expect(typeof collection.findOne).toBe('function')
183
+ expect(typeof collection.findMany).toBe('function')
184
+ })
185
+
186
+ it('supports primary key configuration', async () => {
187
+ const { createCollection } = await import('@mdxui/terminal')
188
+
189
+ const collection = createCollection({
190
+ name: 'users',
191
+ schema: UserSchema,
192
+ primaryKey: 'id',
193
+ })
194
+
195
+ expect(collection.primaryKey).toBe('id')
196
+ })
197
+
198
+ it('supports optional indexes configuration', async () => {
199
+ const { createCollection } = await import('@mdxui/terminal')
200
+
201
+ const collection = createCollection({
202
+ name: 'users',
203
+ schema: UserSchema,
204
+ indexes: ['email', 'role'],
205
+ })
206
+
207
+ expect(collection.indexes).toContain('email')
208
+ expect(collection.indexes).toContain('role')
209
+ })
210
+ })
211
+
212
+ // ============================================================================
213
+ // useQuery() Hook Tests
214
+ // ============================================================================
215
+
216
+ describe('@mdxui/terminal data layer - useQuery', () => {
217
+ // Provider wrapper for hooks
218
+ let wrapper: React.FC<{ children: React.ReactNode }>
219
+
220
+ beforeEach(async () => {
221
+ const { createDB, createCollection, DBProvider } = await import('@mdxui/terminal')
222
+
223
+ const usersCollection = createCollection({
224
+ name: 'users',
225
+ schema: UserSchema,
226
+ })
227
+
228
+ const db = createDB({
229
+ collections: [usersCollection],
230
+ })
231
+
232
+ // Seed test data
233
+ await db.collections.users.insert({
234
+ id: '1',
235
+ name: 'Alice',
236
+ email: 'alice@example.com',
237
+ age: 30,
238
+ role: 'admin',
239
+ createdAt: new Date('2024-01-01'),
240
+ })
241
+ await db.collections.users.insert({
242
+ id: '2',
243
+ name: 'Bob',
244
+ email: 'bob@example.com',
245
+ age: 25,
246
+ role: 'user',
247
+ createdAt: new Date('2024-02-01'),
248
+ })
249
+ await db.collections.users.insert({
250
+ id: '3',
251
+ name: 'Charlie',
252
+ email: 'charlie@example.com',
253
+ age: 35,
254
+ role: 'user',
255
+ createdAt: new Date('2024-03-01'),
256
+ })
257
+
258
+ wrapper = ({ children }) =>
259
+ React.createElement(DBProvider, { db }, children)
260
+ })
261
+
262
+ it('returns reactive data from collection', async () => {
263
+ const { useQuery } = await import('@mdxui/terminal')
264
+
265
+ const { result } = renderHook(
266
+ () =>
267
+ useQuery({
268
+ from: 'users',
269
+ }),
270
+ { wrapper }
271
+ )
272
+
273
+ await waitFor(() => {
274
+ expect(result.current.data).toBeDefined()
275
+ expect(result.current.data).toHaveLength(3)
276
+ })
277
+ })
278
+
279
+ it('supports from collection parameter', async () => {
280
+ const { useQuery } = await import('@mdxui/terminal')
281
+
282
+ const { result } = renderHook(
283
+ () =>
284
+ useQuery({
285
+ from: 'users',
286
+ }),
287
+ { wrapper }
288
+ )
289
+
290
+ await waitFor(() => {
291
+ expect(result.current.data).toBeDefined()
292
+ })
293
+
294
+ expect(result.current.data?.[0]).toHaveProperty('name')
295
+ expect(result.current.data?.[0]).toHaveProperty('email')
296
+ })
297
+
298
+ it('supports where filtering with equality', async () => {
299
+ const { useQuery } = await import('@mdxui/terminal')
300
+
301
+ const { result } = renderHook(
302
+ () =>
303
+ useQuery({
304
+ from: 'users',
305
+ where: { role: 'admin' },
306
+ }),
307
+ { wrapper }
308
+ )
309
+
310
+ await waitFor(() => {
311
+ expect(result.current.data).toHaveLength(1)
312
+ expect(result.current.data?.[0].name).toBe('Alice')
313
+ })
314
+ })
315
+
316
+ it('supports where filtering with comparison operators', async () => {
317
+ const { useQuery } = await import('@mdxui/terminal')
318
+
319
+ const { result } = renderHook(
320
+ () =>
321
+ useQuery({
322
+ from: 'users',
323
+ where: { age: { $gt: 28 } },
324
+ }),
325
+ { wrapper }
326
+ )
327
+
328
+ await waitFor(() => {
329
+ expect(result.current.data).toHaveLength(2)
330
+ expect(result.current.data?.every((u) => (u.age ?? 0) > 28)).toBe(true)
331
+ })
332
+ })
333
+
334
+ it('supports orderBy sorting ascending', async () => {
335
+ const { useQuery } = await import('@mdxui/terminal')
336
+
337
+ const { result } = renderHook(
338
+ () =>
339
+ useQuery({
340
+ from: 'users',
341
+ orderBy: { age: 'asc' },
342
+ }),
343
+ { wrapper }
344
+ )
345
+
346
+ await waitFor(() => {
347
+ expect(result.current.data).toHaveLength(3)
348
+ expect(result.current.data?.[0].name).toBe('Bob') // age 25
349
+ expect(result.current.data?.[1].name).toBe('Alice') // age 30
350
+ expect(result.current.data?.[2].name).toBe('Charlie') // age 35
351
+ })
352
+ })
353
+
354
+ it('supports orderBy sorting descending', async () => {
355
+ const { useQuery } = await import('@mdxui/terminal')
356
+
357
+ const { result } = renderHook(
358
+ () =>
359
+ useQuery({
360
+ from: 'users',
361
+ orderBy: { age: 'desc' },
362
+ }),
363
+ { wrapper }
364
+ )
365
+
366
+ await waitFor(() => {
367
+ expect(result.current.data).toHaveLength(3)
368
+ expect(result.current.data?.[0].name).toBe('Charlie') // age 35
369
+ expect(result.current.data?.[2].name).toBe('Bob') // age 25
370
+ })
371
+ })
372
+
373
+ it('supports multiple orderBy fields', async () => {
374
+ const { useQuery } = await import('@mdxui/terminal')
375
+
376
+ const { result } = renderHook(
377
+ () =>
378
+ useQuery({
379
+ from: 'users',
380
+ orderBy: [
381
+ { role: 'asc' },
382
+ { age: 'desc' },
383
+ ],
384
+ }),
385
+ { wrapper }
386
+ )
387
+
388
+ await waitFor(() => {
389
+ expect(result.current.data).toHaveLength(3)
390
+ // admin first, then users sorted by age desc
391
+ expect(result.current.data?.[0].role).toBe('admin')
392
+ })
393
+ })
394
+
395
+ it('updates when data changes', async () => {
396
+ const { useQuery, createDB, createCollection, DBProvider } = await import('@mdxui/terminal')
397
+
398
+ const usersCollection = createCollection({
399
+ name: 'users',
400
+ schema: UserSchema,
401
+ })
402
+
403
+ const db = createDB({
404
+ collections: [usersCollection],
405
+ })
406
+
407
+ await db.collections.users.insert({
408
+ id: '1',
409
+ name: 'Initial User',
410
+ email: 'initial@example.com',
411
+ role: 'user',
412
+ createdAt: new Date(),
413
+ })
414
+
415
+ const testWrapper: React.FC<{ children: React.ReactNode }> = ({ children }) =>
416
+ React.createElement(DBProvider, { db }, children)
417
+
418
+ const { result } = renderHook(
419
+ () =>
420
+ useQuery({
421
+ from: 'users',
422
+ }),
423
+ { wrapper: testWrapper }
424
+ )
425
+
426
+ await waitFor(() => {
427
+ expect(result.current.data).toHaveLength(1)
428
+ })
429
+
430
+ // Add new user
431
+ await act(async () => {
432
+ await db.collections.users.insert({
433
+ id: '2',
434
+ name: 'New User',
435
+ email: 'new@example.com',
436
+ role: 'admin',
437
+ createdAt: new Date(),
438
+ })
439
+ })
440
+
441
+ await waitFor(() => {
442
+ expect(result.current.data).toHaveLength(2)
443
+ })
444
+ })
445
+
446
+ it('returns loading state', async () => {
447
+ const { useQuery } = await import('@mdxui/terminal')
448
+
449
+ const { result } = renderHook(
450
+ () =>
451
+ useQuery({
452
+ from: 'users',
453
+ }),
454
+ { wrapper }
455
+ )
456
+
457
+ // Initially loading
458
+ expect(result.current.isLoading).toBe(true)
459
+
460
+ await waitFor(() => {
461
+ expect(result.current.isLoading).toBe(false)
462
+ })
463
+ })
464
+
465
+ it('returns error state on query failure', async () => {
466
+ const { useQuery } = await import('@mdxui/terminal')
467
+
468
+ const { result } = renderHook(
469
+ () =>
470
+ useQuery({
471
+ from: 'nonexistent_collection' as any,
472
+ }),
473
+ { wrapper }
474
+ )
475
+
476
+ await waitFor(() => {
477
+ expect(result.current.error).toBeDefined()
478
+ })
479
+ })
480
+
481
+ it('supports limit parameter', async () => {
482
+ const { useQuery } = await import('@mdxui/terminal')
483
+
484
+ const { result } = renderHook(
485
+ () =>
486
+ useQuery({
487
+ from: 'users',
488
+ limit: 2,
489
+ }),
490
+ { wrapper }
491
+ )
492
+
493
+ await waitFor(() => {
494
+ expect(result.current.data).toHaveLength(2)
495
+ })
496
+ })
497
+
498
+ it('supports offset parameter', async () => {
499
+ const { useQuery } = await import('@mdxui/terminal')
500
+
501
+ const { result } = renderHook(
502
+ () =>
503
+ useQuery({
504
+ from: 'users',
505
+ orderBy: { age: 'asc' },
506
+ offset: 1,
507
+ }),
508
+ { wrapper }
509
+ )
510
+
511
+ await waitFor(() => {
512
+ expect(result.current.data?.[0].name).toBe('Alice') // skipped Bob
513
+ })
514
+ })
515
+ })
516
+
517
+ // ============================================================================
518
+ // useMutation() Hook Tests
519
+ // ============================================================================
520
+
521
+ describe('@mdxui/terminal data layer - useMutation', () => {
522
+ let wrapper: React.FC<{ children: React.ReactNode }>
523
+ let db: any
524
+
525
+ beforeEach(async () => {
526
+ const { createDB, createCollection, DBProvider } = await import('@mdxui/terminal')
527
+
528
+ const usersCollection = createCollection({
529
+ name: 'users',
530
+ schema: UserSchema,
531
+ })
532
+
533
+ db = createDB({
534
+ collections: [usersCollection],
535
+ })
536
+
537
+ await db.collections.users.insert({
538
+ id: '1',
539
+ name: 'Alice',
540
+ email: 'alice@example.com',
541
+ role: 'admin',
542
+ createdAt: new Date(),
543
+ })
544
+
545
+ wrapper = ({ children }) =>
546
+ React.createElement(DBProvider, { db }, children)
547
+ })
548
+
549
+ it('returns mutation function', async () => {
550
+ const { useMutation } = await import('@mdxui/terminal')
551
+
552
+ const { result } = renderHook(
553
+ () =>
554
+ useMutation({
555
+ collection: 'users',
556
+ operation: 'insert',
557
+ }),
558
+ { wrapper }
559
+ )
560
+
561
+ expect(result.current.mutate).toBeDefined()
562
+ expect(typeof result.current.mutate).toBe('function')
563
+ })
564
+
565
+ it('executes insert mutation', async () => {
566
+ const { useMutation, useQuery } = await import('@mdxui/terminal')
567
+
568
+ const { result: mutationResult } = renderHook(
569
+ () =>
570
+ useMutation({
571
+ collection: 'users',
572
+ operation: 'insert',
573
+ }),
574
+ { wrapper }
575
+ )
576
+
577
+ const { result: queryResult } = renderHook(
578
+ () =>
579
+ useQuery({
580
+ from: 'users',
581
+ }),
582
+ { wrapper }
583
+ )
584
+
585
+ await waitFor(() => {
586
+ expect(queryResult.current.data).toHaveLength(1)
587
+ })
588
+
589
+ await act(async () => {
590
+ await mutationResult.current.mutate({
591
+ id: '2',
592
+ name: 'Bob',
593
+ email: 'bob@example.com',
594
+ role: 'user',
595
+ createdAt: new Date(),
596
+ })
597
+ })
598
+
599
+ await waitFor(() => {
600
+ expect(queryResult.current.data).toHaveLength(2)
601
+ })
602
+ })
603
+
604
+ it('executes update mutation', async () => {
605
+ const { useMutation, useQuery } = await import('@mdxui/terminal')
606
+
607
+ const { result: mutationResult } = renderHook(
608
+ () =>
609
+ useMutation({
610
+ collection: 'users',
611
+ operation: 'update',
612
+ }),
613
+ { wrapper }
614
+ )
615
+
616
+ await act(async () => {
617
+ await mutationResult.current.mutate({
618
+ where: { id: '1' },
619
+ data: { name: 'Alice Updated' },
620
+ })
621
+ })
622
+
623
+ const { result: queryResult } = renderHook(
624
+ () =>
625
+ useQuery({
626
+ from: 'users',
627
+ where: { id: '1' },
628
+ }),
629
+ { wrapper }
630
+ )
631
+
632
+ await waitFor(() => {
633
+ expect(queryResult.current.data?.[0].name).toBe('Alice Updated')
634
+ })
635
+ })
636
+
637
+ it('executes delete mutation', async () => {
638
+ const { useMutation, useQuery } = await import('@mdxui/terminal')
639
+
640
+ const { result: mutationResult } = renderHook(
641
+ () =>
642
+ useMutation({
643
+ collection: 'users',
644
+ operation: 'delete',
645
+ }),
646
+ { wrapper }
647
+ )
648
+
649
+ await act(async () => {
650
+ await mutationResult.current.mutate({ id: '1' })
651
+ })
652
+
653
+ const { result: queryResult } = renderHook(
654
+ () =>
655
+ useQuery({
656
+ from: 'users',
657
+ }),
658
+ { wrapper }
659
+ )
660
+
661
+ await waitFor(() => {
662
+ expect(queryResult.current.data).toHaveLength(0)
663
+ })
664
+ })
665
+
666
+ it('supports optimistic updates', async () => {
667
+ const { useMutation, useQuery } = await import('@mdxui/terminal')
668
+
669
+ const { result: queryResult } = renderHook(
670
+ () =>
671
+ useQuery({
672
+ from: 'users',
673
+ }),
674
+ { wrapper }
675
+ )
676
+
677
+ await waitFor(() => {
678
+ expect(queryResult.current.data).toHaveLength(1)
679
+ })
680
+
681
+ const { result: mutationResult } = renderHook(
682
+ () =>
683
+ useMutation({
684
+ collection: 'users',
685
+ operation: 'insert',
686
+ optimistic: true,
687
+ }),
688
+ { wrapper }
689
+ )
690
+
691
+ // With optimistic update, data should update immediately
692
+ act(() => {
693
+ mutationResult.current.mutate({
694
+ id: '2',
695
+ name: 'Bob',
696
+ email: 'bob@example.com',
697
+ role: 'user',
698
+ createdAt: new Date(),
699
+ })
700
+ })
701
+
702
+ // Should immediately reflect the optimistic update
703
+ expect(queryResult.current.data).toHaveLength(2)
704
+ })
705
+
706
+ it('rolls back optimistic update on error', async () => {
707
+ const { useMutation, useQuery, createDB, createCollection, DBProvider } =
708
+ await import('@mdxui/terminal')
709
+
710
+ const usersCollection = createCollection({
711
+ name: 'users',
712
+ schema: UserSchema,
713
+ })
714
+
715
+ // Create DB with a sync adapter that fails
716
+ const failingSyncAdapter = {
717
+ push: vi.fn().mockRejectedValue(new Error('Sync failed')),
718
+ pull: vi.fn(),
719
+ subscribe: vi.fn(),
720
+ }
721
+
722
+ const failingDb = createDB({
723
+ collections: [usersCollection],
724
+ sync: failingSyncAdapter,
725
+ })
726
+
727
+ await failingDb.collections.users.insert({
728
+ id: '1',
729
+ name: 'Alice',
730
+ email: 'alice@example.com',
731
+ role: 'admin',
732
+ createdAt: new Date(),
733
+ })
734
+
735
+ const failingWrapper: React.FC<{ children: React.ReactNode }> = ({ children }) =>
736
+ React.createElement(DBProvider, { db: failingDb }, children)
737
+
738
+ const { result: queryResult } = renderHook(
739
+ () =>
740
+ useQuery({
741
+ from: 'users',
742
+ }),
743
+ { wrapper: failingWrapper }
744
+ )
745
+
746
+ await waitFor(() => {
747
+ expect(queryResult.current.data).toHaveLength(1)
748
+ })
749
+
750
+ const { result: mutationResult } = renderHook(
751
+ () =>
752
+ useMutation({
753
+ collection: 'users',
754
+ operation: 'insert',
755
+ optimistic: true,
756
+ }),
757
+ { wrapper: failingWrapper }
758
+ )
759
+
760
+ await act(async () => {
761
+ try {
762
+ await mutationResult.current.mutate({
763
+ id: '2',
764
+ name: 'Bob',
765
+ email: 'bob@example.com',
766
+ role: 'user',
767
+ createdAt: new Date(),
768
+ })
769
+ } catch {
770
+ // Expected to fail
771
+ }
772
+ })
773
+
774
+ // Should roll back to original state
775
+ await waitFor(() => {
776
+ expect(queryResult.current.data).toHaveLength(1)
777
+ })
778
+ })
779
+
780
+ it('returns isPending state during mutation', async () => {
781
+ const { useMutation } = await import('@mdxui/terminal')
782
+
783
+ const { result } = renderHook(
784
+ () =>
785
+ useMutation({
786
+ collection: 'users',
787
+ operation: 'insert',
788
+ }),
789
+ { wrapper }
790
+ )
791
+
792
+ expect(result.current.isPending).toBe(false)
793
+
794
+ act(() => {
795
+ result.current.mutate({
796
+ id: '2',
797
+ name: 'Bob',
798
+ email: 'bob@example.com',
799
+ role: 'user',
800
+ createdAt: new Date(),
801
+ })
802
+ })
803
+
804
+ expect(result.current.isPending).toBe(true)
805
+
806
+ await waitFor(() => {
807
+ expect(result.current.isPending).toBe(false)
808
+ })
809
+ })
810
+
811
+ it('returns error state on mutation failure', async () => {
812
+ const { useMutation } = await import('@mdxui/terminal')
813
+
814
+ const { result } = renderHook(
815
+ () =>
816
+ useMutation({
817
+ collection: 'users',
818
+ operation: 'insert',
819
+ }),
820
+ { wrapper }
821
+ )
822
+
823
+ await act(async () => {
824
+ try {
825
+ await result.current.mutate({
826
+ id: '1', // Duplicate ID should fail
827
+ name: 'Duplicate',
828
+ email: 'dup@example.com',
829
+ role: 'user',
830
+ createdAt: new Date(),
831
+ })
832
+ } catch {
833
+ // Expected
834
+ }
835
+ })
836
+
837
+ expect(result.current.error).toBeDefined()
838
+ })
839
+ })
840
+
841
+ // ============================================================================
842
+ // Query Filtering Tests
843
+ // ============================================================================
844
+
845
+ describe('@mdxui/terminal data layer - query filtering', () => {
846
+ let wrapper: React.FC<{ children: React.ReactNode }>
847
+
848
+ beforeEach(async () => {
849
+ const { createDB, createCollection, DBProvider } = await import('@mdxui/terminal')
850
+
851
+ const todosCollection = createCollection({
852
+ name: 'todos',
853
+ schema: TodoSchema,
854
+ })
855
+
856
+ const db = createDB({
857
+ collections: [todosCollection],
858
+ })
859
+
860
+ // Seed test data
861
+ await db.collections.todos.insert({
862
+ id: '1',
863
+ title: 'Buy groceries',
864
+ completed: false,
865
+ priority: 3,
866
+ userId: 'user1',
867
+ })
868
+ await db.collections.todos.insert({
869
+ id: '2',
870
+ title: 'Write tests',
871
+ completed: true,
872
+ priority: 5,
873
+ userId: 'user1',
874
+ })
875
+ await db.collections.todos.insert({
876
+ id: '3',
877
+ title: 'Review PR',
878
+ completed: false,
879
+ priority: 4,
880
+ userId: 'user2',
881
+ })
882
+ await db.collections.todos.insert({
883
+ id: '4',
884
+ title: 'Deploy app',
885
+ completed: true,
886
+ priority: 5,
887
+ userId: 'user2',
888
+ })
889
+
890
+ wrapper = ({ children }) =>
891
+ React.createElement(DBProvider, { db }, children)
892
+ })
893
+
894
+ it('filters with $eq (equality)', async () => {
895
+ const { useQuery } = await import('@mdxui/terminal')
896
+
897
+ const { result } = renderHook(
898
+ () =>
899
+ useQuery({
900
+ from: 'todos',
901
+ where: { completed: { $eq: true } },
902
+ }),
903
+ { wrapper }
904
+ )
905
+
906
+ await waitFor(() => {
907
+ expect(result.current.data).toHaveLength(2)
908
+ expect(result.current.data?.every((t) => t.completed)).toBe(true)
909
+ })
910
+ })
911
+
912
+ it('filters with $ne (not equal)', async () => {
913
+ const { useQuery } = await import('@mdxui/terminal')
914
+
915
+ const { result } = renderHook(
916
+ () =>
917
+ useQuery({
918
+ from: 'todos',
919
+ where: { userId: { $ne: 'user1' } },
920
+ }),
921
+ { wrapper }
922
+ )
923
+
924
+ await waitFor(() => {
925
+ expect(result.current.data).toHaveLength(2)
926
+ expect(result.current.data?.every((t) => t.userId !== 'user1')).toBe(true)
927
+ })
928
+ })
929
+
930
+ it('filters with $gt (greater than)', async () => {
931
+ const { useQuery } = await import('@mdxui/terminal')
932
+
933
+ const { result } = renderHook(
934
+ () =>
935
+ useQuery({
936
+ from: 'todos',
937
+ where: { priority: { $gt: 3 } },
938
+ }),
939
+ { wrapper }
940
+ )
941
+
942
+ await waitFor(() => {
943
+ expect(result.current.data).toHaveLength(3)
944
+ expect(result.current.data?.every((t) => t.priority > 3)).toBe(true)
945
+ })
946
+ })
947
+
948
+ it('filters with $gte (greater than or equal)', async () => {
949
+ const { useQuery } = await import('@mdxui/terminal')
950
+
951
+ const { result } = renderHook(
952
+ () =>
953
+ useQuery({
954
+ from: 'todos',
955
+ where: { priority: { $gte: 4 } },
956
+ }),
957
+ { wrapper }
958
+ )
959
+
960
+ await waitFor(() => {
961
+ expect(result.current.data).toHaveLength(3)
962
+ expect(result.current.data?.every((t) => t.priority >= 4)).toBe(true)
963
+ })
964
+ })
965
+
966
+ it('filters with $lt (less than)', async () => {
967
+ const { useQuery } = await import('@mdxui/terminal')
968
+
969
+ const { result } = renderHook(
970
+ () =>
971
+ useQuery({
972
+ from: 'todos',
973
+ where: { priority: { $lt: 5 } },
974
+ }),
975
+ { wrapper }
976
+ )
977
+
978
+ await waitFor(() => {
979
+ expect(result.current.data).toHaveLength(2)
980
+ expect(result.current.data?.every((t) => t.priority < 5)).toBe(true)
981
+ })
982
+ })
983
+
984
+ it('filters with $lte (less than or equal)', async () => {
985
+ const { useQuery } = await import('@mdxui/terminal')
986
+
987
+ const { result } = renderHook(
988
+ () =>
989
+ useQuery({
990
+ from: 'todos',
991
+ where: { priority: { $lte: 4 } },
992
+ }),
993
+ { wrapper }
994
+ )
995
+
996
+ await waitFor(() => {
997
+ expect(result.current.data).toHaveLength(2)
998
+ expect(result.current.data?.every((t) => t.priority <= 4)).toBe(true)
999
+ })
1000
+ })
1001
+
1002
+ it('filters with $in (in array)', async () => {
1003
+ const { useQuery } = await import('@mdxui/terminal')
1004
+
1005
+ const { result } = renderHook(
1006
+ () =>
1007
+ useQuery({
1008
+ from: 'todos',
1009
+ where: { priority: { $in: [3, 5] } },
1010
+ }),
1011
+ { wrapper }
1012
+ )
1013
+
1014
+ await waitFor(() => {
1015
+ expect(result.current.data).toHaveLength(3)
1016
+ expect(result.current.data?.every((t) => [3, 5].includes(t.priority))).toBe(
1017
+ true
1018
+ )
1019
+ })
1020
+ })
1021
+
1022
+ it('filters with $nin (not in array)', async () => {
1023
+ const { useQuery } = await import('@mdxui/terminal')
1024
+
1025
+ const { result } = renderHook(
1026
+ () =>
1027
+ useQuery({
1028
+ from: 'todos',
1029
+ where: { priority: { $nin: [3, 5] } },
1030
+ }),
1031
+ { wrapper }
1032
+ )
1033
+
1034
+ await waitFor(() => {
1035
+ expect(result.current.data).toHaveLength(1)
1036
+ expect(result.current.data?.[0].priority).toBe(4)
1037
+ })
1038
+ })
1039
+
1040
+ it('combines multiple where conditions (AND)', async () => {
1041
+ const { useQuery } = await import('@mdxui/terminal')
1042
+
1043
+ const { result } = renderHook(
1044
+ () =>
1045
+ useQuery({
1046
+ from: 'todos',
1047
+ where: {
1048
+ completed: false,
1049
+ priority: { $gte: 3 },
1050
+ },
1051
+ }),
1052
+ { wrapper }
1053
+ )
1054
+
1055
+ await waitFor(() => {
1056
+ expect(result.current.data).toHaveLength(2)
1057
+ expect(result.current.data?.every((t) => !t.completed && t.priority >= 3)).toBe(
1058
+ true
1059
+ )
1060
+ })
1061
+ })
1062
+
1063
+ it('filters with $or operator', async () => {
1064
+ const { useQuery } = await import('@mdxui/terminal')
1065
+
1066
+ const { result } = renderHook(
1067
+ () =>
1068
+ useQuery({
1069
+ from: 'todos',
1070
+ where: {
1071
+ $or: [{ priority: 5 }, { userId: 'user1' }],
1072
+ },
1073
+ }),
1074
+ { wrapper }
1075
+ )
1076
+
1077
+ await waitFor(() => {
1078
+ expect(result.current.data).toHaveLength(3)
1079
+ })
1080
+ })
1081
+ })
1082
+
1083
+ // ============================================================================
1084
+ // Query Sorting Tests
1085
+ // ============================================================================
1086
+
1087
+ describe('@mdxui/terminal data layer - query sorting', () => {
1088
+ let wrapper: React.FC<{ children: React.ReactNode }>
1089
+
1090
+ beforeEach(async () => {
1091
+ const { createDB, createCollection, DBProvider } = await import('@mdxui/terminal')
1092
+
1093
+ const todosCollection = createCollection({
1094
+ name: 'todos',
1095
+ schema: TodoSchema,
1096
+ })
1097
+
1098
+ const db = createDB({
1099
+ collections: [todosCollection],
1100
+ })
1101
+
1102
+ await db.collections.todos.insert({
1103
+ id: '1',
1104
+ title: 'A task',
1105
+ completed: false,
1106
+ priority: 3,
1107
+ userId: 'user1',
1108
+ })
1109
+ await db.collections.todos.insert({
1110
+ id: '2',
1111
+ title: 'B task',
1112
+ completed: true,
1113
+ priority: 3,
1114
+ userId: 'user2',
1115
+ })
1116
+ await db.collections.todos.insert({
1117
+ id: '3',
1118
+ title: 'C task',
1119
+ completed: false,
1120
+ priority: 1,
1121
+ userId: 'user1',
1122
+ })
1123
+
1124
+ wrapper = ({ children }) =>
1125
+ React.createElement(DBProvider, { db }, children)
1126
+ })
1127
+
1128
+ it('sorts by single field ascending', async () => {
1129
+ const { useQuery } = await import('@mdxui/terminal')
1130
+
1131
+ const { result } = renderHook(
1132
+ () =>
1133
+ useQuery({
1134
+ from: 'todos',
1135
+ orderBy: { title: 'asc' },
1136
+ }),
1137
+ { wrapper }
1138
+ )
1139
+
1140
+ await waitFor(() => {
1141
+ expect(result.current.data?.[0].title).toBe('A task')
1142
+ expect(result.current.data?.[1].title).toBe('B task')
1143
+ expect(result.current.data?.[2].title).toBe('C task')
1144
+ })
1145
+ })
1146
+
1147
+ it('sorts by single field descending', async () => {
1148
+ const { useQuery } = await import('@mdxui/terminal')
1149
+
1150
+ const { result } = renderHook(
1151
+ () =>
1152
+ useQuery({
1153
+ from: 'todos',
1154
+ orderBy: { title: 'desc' },
1155
+ }),
1156
+ { wrapper }
1157
+ )
1158
+
1159
+ await waitFor(() => {
1160
+ expect(result.current.data?.[0].title).toBe('C task')
1161
+ expect(result.current.data?.[2].title).toBe('A task')
1162
+ })
1163
+ })
1164
+
1165
+ it('sorts by multiple fields', async () => {
1166
+ const { useQuery } = await import('@mdxui/terminal')
1167
+
1168
+ const { result } = renderHook(
1169
+ () =>
1170
+ useQuery({
1171
+ from: 'todos',
1172
+ orderBy: [
1173
+ { priority: 'asc' },
1174
+ { title: 'desc' },
1175
+ ],
1176
+ }),
1177
+ { wrapper }
1178
+ )
1179
+
1180
+ await waitFor(() => {
1181
+ // Priority 1 first
1182
+ expect(result.current.data?.[0].priority).toBe(1)
1183
+ // Then priority 3, sorted by title desc (B before A)
1184
+ expect(result.current.data?.[1].title).toBe('B task')
1185
+ expect(result.current.data?.[2].title).toBe('A task')
1186
+ })
1187
+ })
1188
+
1189
+ it('handles null/undefined values in sort', async () => {
1190
+ const { createDB, createCollection, DBProvider, useQuery } = await import('@mdxui/terminal')
1191
+
1192
+ const SchemaWithOptional = z.object({
1193
+ id: z.string(),
1194
+ name: z.string(),
1195
+ score: z.number().optional(),
1196
+ })
1197
+
1198
+ const collection = createCollection({
1199
+ name: 'items',
1200
+ schema: SchemaWithOptional,
1201
+ })
1202
+
1203
+ const db = createDB({
1204
+ collections: [collection],
1205
+ })
1206
+
1207
+ await db.collections.items.insert({ id: '1', name: 'A', score: 10 })
1208
+ await db.collections.items.insert({ id: '2', name: 'B' }) // no score
1209
+ await db.collections.items.insert({ id: '3', name: 'C', score: 5 })
1210
+
1211
+ const testWrapper: React.FC<{ children: React.ReactNode }> = ({ children }) =>
1212
+ React.createElement(DBProvider, { db }, children)
1213
+
1214
+ const { result } = renderHook(
1215
+ () =>
1216
+ useQuery({
1217
+ from: 'items',
1218
+ orderBy: { score: 'asc' },
1219
+ }),
1220
+ { wrapper: testWrapper }
1221
+ )
1222
+
1223
+ await waitFor(() => {
1224
+ // Nulls should be last or first consistently
1225
+ expect(result.current.data).toHaveLength(3)
1226
+ })
1227
+ })
1228
+ })
1229
+
1230
+ // ============================================================================
1231
+ // DBProvider Tests
1232
+ // ============================================================================
1233
+
1234
+ describe('@mdxui/terminal data layer - DBProvider', () => {
1235
+ it('exports DBProvider component', async () => {
1236
+ const { DBProvider } = await import('@mdxui/terminal')
1237
+
1238
+ expect(DBProvider).toBeDefined()
1239
+ })
1240
+
1241
+ it('provides database context to children', async () => {
1242
+ const { createDB, DBProvider, useDBContext } = await import('@mdxui/terminal')
1243
+
1244
+ const db = createDB()
1245
+
1246
+ const wrapper: React.FC<{ children: React.ReactNode }> = ({ children }) =>
1247
+ React.createElement(DBProvider, { db }, children)
1248
+
1249
+ const { result } = renderHook(() => useDBContext(), { wrapper })
1250
+
1251
+ expect(result.current).toBe(db)
1252
+ })
1253
+
1254
+ it('throws error when useQuery used outside DBProvider', async () => {
1255
+ const { useQuery } = await import('@mdxui/terminal')
1256
+
1257
+ expect(() =>
1258
+ renderHook(() =>
1259
+ useQuery({
1260
+ from: 'users',
1261
+ })
1262
+ )
1263
+ ).toThrow()
1264
+ })
1265
+ })