foggy-data-viewer 1.0.1-beta.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 (34) hide show
  1. package/README.md +273 -0
  2. package/dist/favicon.svg +4 -0
  3. package/dist/index.js +1531 -0
  4. package/dist/index.umd +1 -0
  5. package/dist/style.css +1 -0
  6. package/package.json +51 -0
  7. package/src/App.vue +469 -0
  8. package/src/api/viewer.ts +163 -0
  9. package/src/components/DataTable.test.ts +533 -0
  10. package/src/components/DataTable.vue +810 -0
  11. package/src/components/DataTableWithSearch.test.ts +628 -0
  12. package/src/components/DataTableWithSearch.vue +277 -0
  13. package/src/components/DataViewer.vue +310 -0
  14. package/src/components/SearchToolbar.test.ts +521 -0
  15. package/src/components/SearchToolbar.vue +406 -0
  16. package/src/components/composables/index.ts +2 -0
  17. package/src/components/composables/useTableSelection.test.ts +248 -0
  18. package/src/components/composables/useTableSelection.ts +44 -0
  19. package/src/components/composables/useTableSummary.test.ts +341 -0
  20. package/src/components/composables/useTableSummary.ts +129 -0
  21. package/src/components/filters/BoolFilter.vue +103 -0
  22. package/src/components/filters/DateRangeFilter.vue +194 -0
  23. package/src/components/filters/NumberRangeFilter.vue +160 -0
  24. package/src/components/filters/SelectFilter.vue +464 -0
  25. package/src/components/filters/TextFilter.vue +230 -0
  26. package/src/components/filters/index.ts +5 -0
  27. package/src/examples/EnhancedTableExample.vue +136 -0
  28. package/src/index.ts +32 -0
  29. package/src/main.ts +14 -0
  30. package/src/types/index.ts +159 -0
  31. package/src/utils/README.md +140 -0
  32. package/src/utils/schemaHelper.test.ts +215 -0
  33. package/src/utils/schemaHelper.ts +44 -0
  34. package/src/vite-env.d.ts +7 -0
@@ -0,0 +1,533 @@
1
+ import { describe, it, expect, beforeEach, vi } from 'vitest'
2
+ import { mount } from '@vue/test-utils'
3
+ import { h } from 'vue'
4
+ import DataTable from './DataTable.vue'
5
+ import type { EnhancedColumnSchema } from '@/types'
6
+
7
+ describe('DataTable', () => {
8
+ const mockColumns: EnhancedColumnSchema[] = [
9
+ {
10
+ name: 'id',
11
+ type: 'INTEGER',
12
+ title: 'ID',
13
+ width: 100,
14
+ fixed: 'left'
15
+ },
16
+ {
17
+ name: 'name',
18
+ type: 'TEXT',
19
+ title: '名称',
20
+ width: 150
21
+ },
22
+ {
23
+ name: 'amount',
24
+ type: 'MONEY',
25
+ title: '金额',
26
+ width: 120
27
+ }
28
+ ]
29
+
30
+ const mockData = [
31
+ { id: 1, name: 'Test 1', amount: 100 },
32
+ { id: 2, name: 'Test 2', amount: 200 },
33
+ { id: 3, name: 'Test 3', amount: 300 }
34
+ ]
35
+
36
+ // 全局配置用于所有测试
37
+ const globalConfig = {
38
+ global: {
39
+ stubs: {
40
+ 'vxe-grid': true // 使用stub避免需要实际的vxe-table组件
41
+ }
42
+ }
43
+ }
44
+
45
+ describe('Basic Rendering', () => {
46
+ it('should render table with columns and data', () => {
47
+ const wrapper = mount(DataTable, {
48
+ props: {
49
+ columns: mockColumns,
50
+ data: mockData,
51
+ total: 3,
52
+ loading: false
53
+ },
54
+ ...globalConfig
55
+ })
56
+
57
+ expect(wrapper.exists()).toBe(true)
58
+ // 组件已成功挂载
59
+ })
60
+
61
+ it('should render with loading state', () => {
62
+ const wrapper = mount(DataTable, {
63
+ props: {
64
+ columns: mockColumns,
65
+ data: [],
66
+ total: 0,
67
+ loading: true
68
+ },
69
+ ...globalConfig
70
+ })
71
+
72
+ expect(wrapper.exists()).toBe(true)
73
+ // vxe-table 会显示 loading 状态
74
+ })
75
+
76
+ it('should render with empty data', () => {
77
+ const wrapper = mount(DataTable, {
78
+ props: {
79
+ columns: mockColumns,
80
+ data: [],
81
+ total: 0,
82
+ loading: false
83
+ },
84
+ ...globalConfig
85
+ })
86
+
87
+ expect(wrapper.exists()).toBe(true)
88
+ })
89
+ })
90
+
91
+ describe('Column Configuration', () => {
92
+ it('should apply column width', () => {
93
+ const wrapper = mount(DataTable, {
94
+ props: {
95
+ columns: mockColumns,
96
+ data: mockData,
97
+ total: 3,
98
+ loading: false
99
+ },
100
+ ...globalConfig
101
+ })
102
+
103
+ expect(wrapper.exists()).toBe(true)
104
+ // vxe-table 会应用列宽配置
105
+ })
106
+
107
+ it('should apply fixed columns', () => {
108
+ const wrapper = mount(DataTable, {
109
+ props: {
110
+ columns: mockColumns,
111
+ data: mockData,
112
+ total: 3,
113
+ loading: false
114
+ },
115
+ ...globalConfig
116
+ })
117
+
118
+ expect(wrapper.exists()).toBe(true)
119
+ // 第一列是固定列
120
+ })
121
+
122
+ it('should handle custom formatter', () => {
123
+ const formatter = (value: unknown) => `¥${value}`
124
+ const columnsWithFormatter: EnhancedColumnSchema[] = [
125
+ {
126
+ name: 'amount',
127
+ type: 'MONEY',
128
+ title: '金额',
129
+ customFormatter: formatter
130
+ }
131
+ ]
132
+
133
+ const wrapper = mount(DataTable, {
134
+ props: {
135
+ columns: columnsWithFormatter,
136
+ data: [{ amount: 100 }],
137
+ total: 1,
138
+ loading: false
139
+ },
140
+ ...globalConfig
141
+ })
142
+
143
+ expect(wrapper.exists()).toBe(true)
144
+ })
145
+
146
+ it('should handle custom render', () => {
147
+ const render = ({ value }: { value: unknown }) => h('span', { class: 'custom' }, String(value))
148
+ const columnsWithRender: EnhancedColumnSchema[] = [
149
+ {
150
+ name: 'status',
151
+ type: 'TEXT',
152
+ title: '状态',
153
+ customRender: render
154
+ }
155
+ ]
156
+
157
+ const wrapper = mount(DataTable, {
158
+ props: {
159
+ columns: columnsWithRender,
160
+ data: [{ status: 'active' }],
161
+ total: 1,
162
+ loading: false
163
+ },
164
+ ...globalConfig
165
+ })
166
+
167
+ expect(wrapper.exists()).toBe(true)
168
+ })
169
+ })
170
+
171
+ describe('Pagination', () => {
172
+ it('should render pagination with correct total', () => {
173
+ const wrapper = mount(DataTable, {
174
+ props: {
175
+ columns: mockColumns,
176
+ data: mockData,
177
+ total: 100,
178
+ loading: false,
179
+ pageSize: 10
180
+ },
181
+ ...globalConfig
182
+ })
183
+
184
+ expect(wrapper.exists()).toBe(true)
185
+ // vxe-table 会显示分页组件
186
+ })
187
+
188
+ it('should use custom page size', () => {
189
+ const wrapper = mount(DataTable, {
190
+ props: {
191
+ columns: mockColumns,
192
+ data: mockData,
193
+ total: 100,
194
+ loading: false,
195
+ pageSize: 20
196
+ },
197
+ ...globalConfig
198
+ })
199
+
200
+ expect(wrapper.exists()).toBe(true)
201
+ })
202
+
203
+ it('should emit page-change event when page changes', async () => {
204
+ const wrapper = mount(DataTable, {
205
+ props: {
206
+ columns: mockColumns,
207
+ data: mockData,
208
+ total: 100,
209
+ loading: false
210
+ },
211
+ ...globalConfig
212
+ })
213
+
214
+ // 模拟分页变化
215
+ await wrapper.vm.$emit('page-change', 2, 50)
216
+ expect(wrapper.emitted('page-change')).toBeTruthy()
217
+ expect(wrapper.emitted('page-change')?.[0]).toEqual([2, 50])
218
+ })
219
+ })
220
+
221
+ describe('Sorting', () => {
222
+ it('should emit sort-change event when sorting', async () => {
223
+ const wrapper = mount(DataTable, {
224
+ props: {
225
+ columns: mockColumns,
226
+ data: mockData,
227
+ total: 3,
228
+ loading: false
229
+ },
230
+ ...globalConfig
231
+ })
232
+
233
+ await wrapper.vm.$emit('sort-change', 'name', 'asc')
234
+ expect(wrapper.emitted('sort-change')).toBeTruthy()
235
+ expect(wrapper.emitted('sort-change')?.[0]).toEqual(['name', 'asc'])
236
+ })
237
+
238
+ it('should emit sort-change with null when clearing sort', async () => {
239
+ const wrapper = mount(DataTable, {
240
+ props: {
241
+ columns: mockColumns,
242
+ data: mockData,
243
+ total: 3,
244
+ loading: false
245
+ },
246
+ ...globalConfig
247
+ })
248
+
249
+ await wrapper.vm.$emit('sort-change', null, null)
250
+ expect(wrapper.emitted('sort-change')).toBeTruthy()
251
+ expect(wrapper.emitted('sort-change')?.[0]).toEqual([null, null])
252
+ })
253
+ })
254
+
255
+ describe('Filtering', () => {
256
+ it('should show filters when showFilters is true', () => {
257
+ const wrapper = mount(DataTable, {
258
+ props: {
259
+ columns: mockColumns,
260
+ data: mockData,
261
+ total: 3,
262
+ loading: false,
263
+ showFilters: true
264
+ },
265
+ ...globalConfig
266
+ })
267
+
268
+ expect(wrapper.exists()).toBe(true)
269
+ })
270
+
271
+ it('should hide filters when showFilters is false', () => {
272
+ const wrapper = mount(DataTable, {
273
+ props: {
274
+ columns: mockColumns,
275
+ data: mockData,
276
+ total: 3,
277
+ loading: false,
278
+ showFilters: false
279
+ },
280
+ ...globalConfig
281
+ })
282
+
283
+ expect(wrapper.exists()).toBe(true)
284
+ })
285
+
286
+ it('should emit filter-change event', async () => {
287
+ const wrapper = mount(DataTable, {
288
+ props: {
289
+ columns: mockColumns,
290
+ data: mockData,
291
+ total: 3,
292
+ loading: false
293
+ },
294
+ ...globalConfig
295
+ })
296
+
297
+ const slices = [{ field: 'name', op: '=', value: 'test' }]
298
+ await wrapper.vm.$emit('filter-change', slices)
299
+ expect(wrapper.emitted('filter-change')).toBeTruthy()
300
+ expect(wrapper.emitted('filter-change')?.[0]).toEqual([slices])
301
+ })
302
+
303
+ it('should apply initial slice', () => {
304
+ const initialSlice = [{ field: 'status', op: '=', value: 'active' }]
305
+ const wrapper = mount(DataTable, {
306
+ props: {
307
+ columns: mockColumns,
308
+ data: mockData,
309
+ total: 3,
310
+ loading: false,
311
+ initialSlice
312
+ },
313
+ ...globalConfig
314
+ })
315
+
316
+ expect(wrapper.exists()).toBe(true)
317
+ })
318
+ })
319
+
320
+ describe('Events', () => {
321
+ it('should emit row-click event', async () => {
322
+ const wrapper = mount(DataTable, {
323
+ props: {
324
+ columns: mockColumns,
325
+ data: mockData,
326
+ total: 3,
327
+ loading: false
328
+ },
329
+ ...globalConfig
330
+ })
331
+
332
+ const row = mockData[0]
333
+ await wrapper.vm.$emit('row-click', row, mockColumns[0])
334
+ expect(wrapper.emitted('row-click')).toBeTruthy()
335
+ })
336
+
337
+ it('should emit row-dblclick event', async () => {
338
+ const wrapper = mount(DataTable, {
339
+ props: {
340
+ columns: mockColumns,
341
+ data: mockData,
342
+ total: 3,
343
+ loading: false
344
+ },
345
+ ...globalConfig
346
+ })
347
+
348
+ const row = mockData[0]
349
+ await wrapper.vm.$emit('row-dblclick', row, mockColumns[0])
350
+ expect(wrapper.emitted('row-dblclick')).toBeTruthy()
351
+ })
352
+ })
353
+
354
+ describe('Summary', () => {
355
+ it('should display server summary when provided', () => {
356
+ const serverSummary = {
357
+ amount: 600,
358
+ count: 3
359
+ }
360
+
361
+ const wrapper = mount(DataTable, {
362
+ props: {
363
+ columns: mockColumns,
364
+ data: mockData,
365
+ total: 3,
366
+ loading: false,
367
+ serverSummary
368
+ },
369
+ ...globalConfig
370
+ })
371
+
372
+ expect(wrapper.exists()).toBe(true)
373
+ })
374
+
375
+ it('should handle null server summary', () => {
376
+ const wrapper = mount(DataTable, {
377
+ props: {
378
+ columns: mockColumns,
379
+ data: mockData,
380
+ total: 3,
381
+ loading: false,
382
+ serverSummary: null
383
+ },
384
+ ...globalConfig
385
+ })
386
+
387
+ expect(wrapper.exists()).toBe(true)
388
+ })
389
+ })
390
+
391
+ describe('Filter Options Loader', () => {
392
+ it('should call filter options loader when needed', async () => {
393
+ const mockLoader = vi.fn().mockResolvedValue([
394
+ { value: 'option1', label: 'Option 1' },
395
+ { value: 'option2', label: 'Option 2' }
396
+ ])
397
+
398
+ const wrapper = mount(DataTable, {
399
+ props: {
400
+ columns: mockColumns,
401
+ data: mockData,
402
+ total: 3,
403
+ loading: false,
404
+ filterOptionsLoader: mockLoader
405
+ },
406
+ ...globalConfig
407
+ })
408
+
409
+ expect(wrapper.exists()).toBe(true)
410
+ // 实际使用中会调用 mockLoader
411
+ })
412
+ })
413
+
414
+ describe('Exposed Methods', () => {
415
+ it('should have resetPagination method', () => {
416
+ const wrapper = mount(DataTable, {
417
+ props: {
418
+ columns: mockColumns,
419
+ data: mockData,
420
+ total: 100,
421
+ loading: false
422
+ },
423
+ ...globalConfig
424
+ })
425
+
426
+ expect(wrapper.vm.resetPagination).toBeDefined()
427
+ })
428
+
429
+ it('should have clearFilters method', () => {
430
+ const wrapper = mount(DataTable, {
431
+ props: {
432
+ columns: mockColumns,
433
+ data: mockData,
434
+ total: 3,
435
+ loading: false
436
+ },
437
+ ...globalConfig
438
+ })
439
+
440
+ expect(wrapper.vm.clearFilters).toBeDefined()
441
+ })
442
+
443
+ it('should have getFilters method', () => {
444
+ const wrapper = mount(DataTable, {
445
+ props: {
446
+ columns: mockColumns,
447
+ data: mockData,
448
+ total: 3,
449
+ loading: false
450
+ },
451
+ ...globalConfig
452
+ })
453
+
454
+ expect(wrapper.vm.getFilters).toBeDefined()
455
+ })
456
+
457
+ it('should have setFilter method', () => {
458
+ const wrapper = mount(DataTable, {
459
+ props: {
460
+ columns: mockColumns,
461
+ data: mockData,
462
+ total: 3,
463
+ loading: false
464
+ },
465
+ ...globalConfig
466
+ })
467
+
468
+ expect(wrapper.vm.setFilter).toBeDefined()
469
+ })
470
+
471
+ it('should have getGridInstance method', () => {
472
+ const wrapper = mount(DataTable, {
473
+ props: {
474
+ columns: mockColumns,
475
+ data: mockData,
476
+ total: 3,
477
+ loading: false
478
+ },
479
+ ...globalConfig
480
+ })
481
+
482
+ expect(wrapper.vm.getGridInstance).toBeDefined()
483
+ })
484
+ })
485
+
486
+ describe('Props Validation', () => {
487
+ it('should accept valid props', () => {
488
+ const wrapper = mount(DataTable, {
489
+ props: {
490
+ columns: mockColumns,
491
+ data: mockData,
492
+ total: 3,
493
+ loading: false,
494
+ pageSize: 50,
495
+ showFilters: true
496
+ },
497
+ ...globalConfig
498
+ })
499
+
500
+ expect(wrapper.exists()).toBe(true)
501
+ })
502
+
503
+ it('should use default pageSize when not provided', () => {
504
+ const wrapper = mount(DataTable, {
505
+ props: {
506
+ columns: mockColumns,
507
+ data: mockData,
508
+ total: 3,
509
+ loading: false
510
+ },
511
+ ...globalConfig
512
+ })
513
+
514
+ expect(wrapper.exists()).toBe(true)
515
+ // 默认 pageSize 是 50
516
+ })
517
+
518
+ it('should use default showFilters when not provided', () => {
519
+ const wrapper = mount(DataTable, {
520
+ props: {
521
+ columns: mockColumns,
522
+ data: mockData,
523
+ total: 3,
524
+ loading: false
525
+ },
526
+ ...globalConfig
527
+ })
528
+
529
+ expect(wrapper.exists()).toBe(true)
530
+ // 默认 showFilters 是 true
531
+ })
532
+ })
533
+ })