@furystack/shades-common-components 12.0.0 → 12.1.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 (49) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/README.md +26 -0
  3. package/esm/components/cache-view.d.ts +46 -0
  4. package/esm/components/cache-view.d.ts.map +1 -0
  5. package/esm/components/cache-view.js +65 -0
  6. package/esm/components/cache-view.js.map +1 -0
  7. package/esm/components/cache-view.spec.d.ts +2 -0
  8. package/esm/components/cache-view.spec.d.ts.map +1 -0
  9. package/esm/components/cache-view.spec.js +183 -0
  10. package/esm/components/cache-view.spec.js.map +1 -0
  11. package/esm/components/command-palette/command-palette-input.spec.js +148 -148
  12. package/esm/components/command-palette/command-palette-input.spec.js.map +1 -1
  13. package/esm/components/command-palette/command-palette-suggestion-list.spec.js +258 -258
  14. package/esm/components/command-palette/command-palette-suggestion-list.spec.js.map +1 -1
  15. package/esm/components/context-menu/context-menu-manager.spec.js +211 -217
  16. package/esm/components/context-menu/context-menu-manager.spec.js.map +1 -1
  17. package/esm/components/data-grid/body.spec.js +173 -173
  18. package/esm/components/data-grid/body.spec.js.map +1 -1
  19. package/esm/components/data-grid/data-grid.spec.js +39 -130
  20. package/esm/components/data-grid/data-grid.spec.js.map +1 -1
  21. package/esm/components/index.d.ts +1 -0
  22. package/esm/components/index.d.ts.map +1 -1
  23. package/esm/components/index.js +1 -0
  24. package/esm/components/index.js.map +1 -1
  25. package/esm/components/skeleton.d.ts.map +1 -1
  26. package/esm/components/skeleton.js +2 -11
  27. package/esm/components/skeleton.js.map +1 -1
  28. package/esm/components/skeleton.spec.js +6 -55
  29. package/esm/components/skeleton.spec.js.map +1 -1
  30. package/esm/services/click-away-service.spec.js +14 -12
  31. package/esm/services/click-away-service.spec.js.map +1 -1
  32. package/esm/services/list-service.spec.js +170 -141
  33. package/esm/services/list-service.spec.js.map +1 -1
  34. package/esm/services/tree-service.spec.js +190 -159
  35. package/esm/services/tree-service.spec.js.map +1 -1
  36. package/package.json +8 -8
  37. package/src/components/cache-view.spec.tsx +210 -0
  38. package/src/components/cache-view.tsx +103 -0
  39. package/src/components/command-palette/command-palette-input.spec.tsx +183 -194
  40. package/src/components/command-palette/command-palette-suggestion-list.spec.tsx +303 -321
  41. package/src/components/context-menu/context-menu-manager.spec.ts +213 -258
  42. package/src/components/data-grid/body.spec.tsx +266 -276
  43. package/src/components/data-grid/data-grid.spec.tsx +137 -232
  44. package/src/components/index.ts +1 -0
  45. package/src/components/skeleton.spec.tsx +6 -73
  46. package/src/components/skeleton.tsx +2 -11
  47. package/src/services/click-away-service.spec.ts +14 -16
  48. package/src/services/list-service.spec.ts +170 -172
  49. package/src/services/tree-service.spec.ts +191 -207
@@ -1,3 +1,4 @@
1
+ import type { FindOptions } from '@furystack/core'
1
2
  import { Injector } from '@furystack/inject'
2
3
  import { createComponent, initializeShadeRoot } from '@furystack/shades'
3
4
  import { ObservableValue, sleepAsync, usingAsync } from '@furystack/utils'
@@ -29,12 +30,30 @@ describe('DataGrid', () => {
29
30
  return service
30
31
  }
31
32
 
33
+ const withTestGrid = async (
34
+ fn: (ctx: {
35
+ injector: Injector
36
+ service: CollectionService<TestEntry>
37
+ findOptions: ObservableValue<FindOptions<TestEntry, Array<keyof TestEntry>>>
38
+ }) => Promise<void>,
39
+ opts?: { createService?: () => CollectionService<TestEntry> },
40
+ ) => {
41
+ await usingAsync(new Injector(), async (injector) => {
42
+ await usingAsync(opts?.createService?.() ?? createTestService(), async (service) => {
43
+ await usingAsync(
44
+ new ObservableValue<FindOptions<TestEntry, Array<keyof TestEntry>>>({}),
45
+ async (findOptions) => {
46
+ await fn({ injector, service, findOptions })
47
+ },
48
+ )
49
+ })
50
+ })
51
+ }
52
+
32
53
  describe('rendering', () => {
33
54
  it('should render with columns', async () => {
34
- await usingAsync(new Injector(), async (injector) => {
55
+ await withTestGrid(async ({ injector, service, findOptions }) => {
35
56
  const rootElement = document.getElementById('root') as HTMLDivElement
36
- const service = createTestService()
37
- const findOptions = new ObservableValue<any>({})
38
57
 
39
58
  initializeShadeRoot({
40
59
  injector,
@@ -58,17 +77,12 @@ describe('DataGrid', () => {
58
77
 
59
78
  const headers = grid?.querySelectorAll('th')
60
79
  expect(headers?.length).toBe(2)
61
-
62
- service[Symbol.dispose]()
63
- findOptions[Symbol.dispose]()
64
80
  })
65
81
  })
66
82
 
67
83
  it('should render table structure', async () => {
68
- await usingAsync(new Injector(), async (injector) => {
84
+ await withTestGrid(async ({ injector, service, findOptions }) => {
69
85
  const rootElement = document.getElementById('root') as HTMLDivElement
70
- const service = createTestService()
71
- const findOptions = new ObservableValue<any>({})
72
86
 
73
87
  initializeShadeRoot({
74
88
  injector,
@@ -95,17 +109,12 @@ describe('DataGrid', () => {
95
109
  expect(table).not.toBeNull()
96
110
  expect(thead).not.toBeNull()
97
111
  expect(tbody).not.toBeNull()
98
-
99
- service[Symbol.dispose]()
100
- findOptions[Symbol.dispose]()
101
112
  })
102
113
  })
103
114
 
104
115
  it('should render custom header components when provided', async () => {
105
- await usingAsync(new Injector(), async (injector) => {
116
+ await withTestGrid(async ({ injector, service, findOptions }) => {
106
117
  const rootElement = document.getElementById('root') as HTMLDivElement
107
- const service = createTestService()
108
- const findOptions = new ObservableValue<any>({})
109
118
 
110
119
  initializeShadeRoot({
111
120
  injector,
@@ -130,17 +139,12 @@ describe('DataGrid', () => {
130
139
  const customHeader = grid?.querySelector('[data-testid="custom-header-id"]')
131
140
  expect(customHeader).not.toBeNull()
132
141
  expect(customHeader?.textContent).toBe('Custom ID Header')
133
-
134
- service[Symbol.dispose]()
135
- findOptions[Symbol.dispose]()
136
142
  })
137
143
  })
138
144
 
139
145
  it('should render default header components from headerComponents.default', async () => {
140
- await usingAsync(new Injector(), async (injector) => {
146
+ await withTestGrid(async ({ injector, service, findOptions }) => {
141
147
  const rootElement = document.getElementById('root') as HTMLDivElement
142
- const service = createTestService()
143
- const findOptions = new ObservableValue<any>({})
144
148
 
145
149
  initializeShadeRoot({
146
150
  injector,
@@ -167,17 +171,12 @@ describe('DataGrid', () => {
167
171
 
168
172
  expect(defaultHeaderId?.textContent).toBe('Default: id')
169
173
  expect(defaultHeaderName?.textContent).toBe('Default: name')
170
-
171
- service[Symbol.dispose]()
172
- findOptions[Symbol.dispose]()
173
174
  })
174
175
  })
175
176
 
176
177
  it('should render DataGridHeader when no custom header is provided', async () => {
177
- await usingAsync(new Injector(), async (injector) => {
178
+ await withTestGrid(async ({ injector, service, findOptions }) => {
178
179
  const rootElement = document.getElementById('root') as HTMLDivElement
179
- const service = createTestService()
180
- const findOptions = new ObservableValue<any>({})
181
180
 
182
181
  initializeShadeRoot({
183
182
  injector,
@@ -199,19 +198,14 @@ describe('DataGrid', () => {
199
198
  const grid = document.querySelector('shade-data-grid')
200
199
  const defaultHeaders = grid?.querySelectorAll('data-grid-header')
201
200
  expect(defaultHeaders?.length).toBe(2)
202
-
203
- service[Symbol.dispose]()
204
- findOptions[Symbol.dispose]()
205
201
  })
206
202
  })
207
203
  })
208
204
 
209
205
  describe('focus management', () => {
210
206
  it('should set focus on click', async () => {
211
- await usingAsync(new Injector(), async (injector) => {
207
+ await withTestGrid(async ({ injector, service, findOptions }) => {
212
208
  const rootElement = document.getElementById('root') as HTMLDivElement
213
- const service = createTestService()
214
- const findOptions = new ObservableValue<any>({})
215
209
 
216
210
  expect(service.hasFocus.getValue()).toBe(false)
217
211
 
@@ -238,17 +232,12 @@ describe('DataGrid', () => {
238
232
  wrapper?.click()
239
233
 
240
234
  expect(service.hasFocus.getValue()).toBe(true)
241
-
242
- service[Symbol.dispose]()
243
- findOptions[Symbol.dispose]()
244
235
  })
245
236
  })
246
237
 
247
238
  it('should lose focus on click outside', async () => {
248
- await usingAsync(new Injector(), async (injector) => {
239
+ await withTestGrid(async ({ injector, service, findOptions }) => {
249
240
  const rootElement = document.getElementById('root') as HTMLDivElement
250
- const service = createTestService()
251
- const findOptions = new ObservableValue<any>({})
252
241
 
253
242
  initializeShadeRoot({
254
243
  injector,
@@ -280,19 +269,14 @@ describe('DataGrid', () => {
280
269
  outside?.dispatchEvent(new MouseEvent('click', { bubbles: true }))
281
270
 
282
271
  expect(service.hasFocus.getValue()).toBe(false)
283
-
284
- service[Symbol.dispose]()
285
- findOptions[Symbol.dispose]()
286
272
  })
287
273
  })
288
274
  })
289
275
 
290
276
  describe('keyboard navigation', () => {
291
277
  it('should handle ArrowDown to move focus to next entry', async () => {
292
- await usingAsync(new Injector(), async (injector) => {
278
+ await withTestGrid(async ({ injector, service, findOptions }) => {
293
279
  const rootElement = document.getElementById('root') as HTMLDivElement
294
- const service = createTestService()
295
- const findOptions = new ObservableValue<any>({})
296
280
 
297
281
  service.hasFocus.setValue(true)
298
282
  service.focusedEntry.setValue(service.data.getValue().entries[0])
@@ -318,17 +302,12 @@ describe('DataGrid', () => {
318
302
  window.dispatchEvent(keydownEvent)
319
303
 
320
304
  expect(service.focusedEntry.getValue()).toEqual({ id: 2, name: 'Second' })
321
-
322
- service[Symbol.dispose]()
323
- findOptions[Symbol.dispose]()
324
305
  })
325
306
  })
326
307
 
327
308
  it('should handle ArrowUp to move focus to previous entry', async () => {
328
- await usingAsync(new Injector(), async (injector) => {
309
+ await withTestGrid(async ({ injector, service, findOptions }) => {
329
310
  const rootElement = document.getElementById('root') as HTMLDivElement
330
- const service = createTestService()
331
- const findOptions = new ObservableValue<any>({})
332
311
 
333
312
  service.hasFocus.setValue(true)
334
313
  service.focusedEntry.setValue(service.data.getValue().entries[1])
@@ -354,17 +333,12 @@ describe('DataGrid', () => {
354
333
  window.dispatchEvent(keydownEvent)
355
334
 
356
335
  expect(service.focusedEntry.getValue()).toEqual({ id: 1, name: 'First' })
357
-
358
- service[Symbol.dispose]()
359
- findOptions[Symbol.dispose]()
360
336
  })
361
337
  })
362
338
 
363
339
  it('should handle Home to move focus to first entry', async () => {
364
- await usingAsync(new Injector(), async (injector) => {
340
+ await withTestGrid(async ({ injector, service, findOptions }) => {
365
341
  const rootElement = document.getElementById('root') as HTMLDivElement
366
- const service = createTestService()
367
- const findOptions = new ObservableValue<any>({})
368
342
 
369
343
  service.hasFocus.setValue(true)
370
344
  service.focusedEntry.setValue(service.data.getValue().entries[2])
@@ -390,17 +364,12 @@ describe('DataGrid', () => {
390
364
  window.dispatchEvent(keydownEvent)
391
365
 
392
366
  expect(service.focusedEntry.getValue()).toEqual({ id: 1, name: 'First' })
393
-
394
- service[Symbol.dispose]()
395
- findOptions[Symbol.dispose]()
396
367
  })
397
368
  })
398
369
 
399
370
  it('should handle End to move focus to last entry', async () => {
400
- await usingAsync(new Injector(), async (injector) => {
371
+ await withTestGrid(async ({ injector, service, findOptions }) => {
401
372
  const rootElement = document.getElementById('root') as HTMLDivElement
402
- const service = createTestService()
403
- const findOptions = new ObservableValue<any>({})
404
373
 
405
374
  service.hasFocus.setValue(true)
406
375
  service.focusedEntry.setValue(service.data.getValue().entries[0])
@@ -426,17 +395,12 @@ describe('DataGrid', () => {
426
395
  window.dispatchEvent(keydownEvent)
427
396
 
428
397
  expect(service.focusedEntry.getValue()).toEqual({ id: 3, name: 'Third' })
429
-
430
- service[Symbol.dispose]()
431
- findOptions[Symbol.dispose]()
432
398
  })
433
399
  })
434
400
 
435
401
  it('should handle Tab to toggle focus', async () => {
436
- await usingAsync(new Injector(), async (injector) => {
402
+ await withTestGrid(async ({ injector, service, findOptions }) => {
437
403
  const rootElement = document.getElementById('root') as HTMLDivElement
438
- const service = createTestService()
439
- const findOptions = new ObservableValue<any>({})
440
404
 
441
405
  service.hasFocus.setValue(true)
442
406
 
@@ -461,17 +425,12 @@ describe('DataGrid', () => {
461
425
  window.dispatchEvent(keydownEvent)
462
426
 
463
427
  expect(service.hasFocus.getValue()).toBe(false)
464
-
465
- service[Symbol.dispose]()
466
- findOptions[Symbol.dispose]()
467
428
  })
468
429
  })
469
430
 
470
431
  it('should handle Escape to clear selection and search', async () => {
471
- await usingAsync(new Injector(), async (injector) => {
432
+ await withTestGrid(async ({ injector, service, findOptions }) => {
472
433
  const rootElement = document.getElementById('root') as HTMLDivElement
473
- const service = createTestService()
474
- const findOptions = new ObservableValue<any>({})
475
434
 
476
435
  const { entries } = service.data.getValue()
477
436
  service.hasFocus.setValue(true)
@@ -500,17 +459,12 @@ describe('DataGrid', () => {
500
459
 
501
460
  expect(service.selection.getValue()).toEqual([])
502
461
  expect(service.searchTerm.getValue()).toBe('')
503
-
504
- service[Symbol.dispose]()
505
- findOptions[Symbol.dispose]()
506
462
  })
507
463
  })
508
464
 
509
465
  it('should handle Space to toggle selection of focused entry', async () => {
510
- await usingAsync(new Injector(), async (injector) => {
466
+ await withTestGrid(async ({ injector, service, findOptions }) => {
511
467
  const rootElement = document.getElementById('root') as HTMLDivElement
512
- const service = createTestService()
513
- const findOptions = new ObservableValue<any>({})
514
468
 
515
469
  const { entries } = service.data.getValue()
516
470
  service.hasFocus.setValue(true)
@@ -540,17 +494,12 @@ describe('DataGrid', () => {
540
494
 
541
495
  window.dispatchEvent(new KeyboardEvent('keydown', { key: ' ', bubbles: true }))
542
496
  expect(service.selection.getValue()).not.toContain(entries[0])
543
-
544
- service[Symbol.dispose]()
545
- findOptions[Symbol.dispose]()
546
497
  })
547
498
  })
548
499
 
549
500
  it('should handle + to select all entries', async () => {
550
- await usingAsync(new Injector(), async (injector) => {
501
+ await withTestGrid(async ({ injector, service, findOptions }) => {
551
502
  const rootElement = document.getElementById('root') as HTMLDivElement
552
- const service = createTestService()
553
- const findOptions = new ObservableValue<any>({})
554
503
 
555
504
  service.hasFocus.setValue(true)
556
505
 
@@ -575,17 +524,12 @@ describe('DataGrid', () => {
575
524
  window.dispatchEvent(keydownEvent)
576
525
 
577
526
  expect(service.selection.getValue().length).toBe(3)
578
-
579
- service[Symbol.dispose]()
580
- findOptions[Symbol.dispose]()
581
527
  })
582
528
  })
583
529
 
584
530
  it('should handle - to deselect all entries', async () => {
585
- await usingAsync(new Injector(), async (injector) => {
531
+ await withTestGrid(async ({ injector, service, findOptions }) => {
586
532
  const rootElement = document.getElementById('root') as HTMLDivElement
587
- const service = createTestService()
588
- const findOptions = new ObservableValue<any>({})
589
533
 
590
534
  const { entries } = service.data.getValue()
591
535
  service.hasFocus.setValue(true)
@@ -612,17 +556,12 @@ describe('DataGrid', () => {
612
556
  window.dispatchEvent(keydownEvent)
613
557
 
614
558
  expect(service.selection.getValue().length).toBe(0)
615
-
616
- service[Symbol.dispose]()
617
- findOptions[Symbol.dispose]()
618
559
  })
619
560
  })
620
561
 
621
562
  it('should handle * to invert selection', async () => {
622
- await usingAsync(new Injector(), async (injector) => {
563
+ await withTestGrid(async ({ injector, service, findOptions }) => {
623
564
  const rootElement = document.getElementById('root') as HTMLDivElement
624
- const service = createTestService()
625
- const findOptions = new ObservableValue<any>({})
626
565
 
627
566
  const { entries } = service.data.getValue()
628
567
  service.hasFocus.setValue(true)
@@ -652,17 +591,12 @@ describe('DataGrid', () => {
652
591
  expect(selection).not.toContain(entries[0])
653
592
  expect(selection).toContain(entries[1])
654
593
  expect(selection).toContain(entries[2])
655
-
656
- service[Symbol.dispose]()
657
- findOptions[Symbol.dispose]()
658
594
  })
659
595
  })
660
596
 
661
597
  it('should not handle keyboard when not focused', async () => {
662
- await usingAsync(new Injector(), async (injector) => {
598
+ await withTestGrid(async ({ injector, service, findOptions }) => {
663
599
  const rootElement = document.getElementById('root') as HTMLDivElement
664
- const service = createTestService()
665
- const findOptions = new ObservableValue<any>({})
666
600
 
667
601
  service.hasFocus.setValue(false)
668
602
  service.focusedEntry.setValue(service.data.getValue().entries[0])
@@ -688,17 +622,12 @@ describe('DataGrid', () => {
688
622
  window.dispatchEvent(keydownEvent)
689
623
 
690
624
  expect(service.focusedEntry.getValue()).toEqual({ id: 1, name: 'First' })
691
-
692
- service[Symbol.dispose]()
693
- findOptions[Symbol.dispose]()
694
625
  })
695
626
  })
696
627
 
697
628
  it('should handle Insert to toggle selection and move to next', async () => {
698
- await usingAsync(new Injector(), async (injector) => {
629
+ await withTestGrid(async ({ injector, service, findOptions }) => {
699
630
  const rootElement = document.getElementById('root') as HTMLDivElement
700
- const service = createTestService()
701
- const findOptions = new ObservableValue<any>({})
702
631
 
703
632
  const { entries } = service.data.getValue()
704
633
  service.hasFocus.setValue(true)
@@ -726,19 +655,14 @@ describe('DataGrid', () => {
726
655
 
727
656
  expect(service.selection.getValue()).toContain(entries[0])
728
657
  expect(service.focusedEntry.getValue()).toEqual(entries[1])
729
-
730
- service[Symbol.dispose]()
731
- findOptions[Symbol.dispose]()
732
658
  })
733
659
  })
734
660
  })
735
661
 
736
662
  describe('styles', () => {
737
663
  it('should apply wrapper styles when provided', async () => {
738
- await usingAsync(new Injector(), async (injector) => {
664
+ await withTestGrid(async ({ injector, service, findOptions }) => {
739
665
  const rootElement = document.getElementById('root') as HTMLDivElement
740
- const service = createTestService()
741
- const findOptions = new ObservableValue<any>({})
742
666
 
743
667
  initializeShadeRoot({
744
668
  injector,
@@ -761,17 +685,12 @@ describe('DataGrid', () => {
761
685
 
762
686
  const grid = document.querySelector('shade-data-grid') as HTMLElement
763
687
  expect(grid?.style.backgroundColor).toBe('red')
764
-
765
- service[Symbol.dispose]()
766
- findOptions[Symbol.dispose]()
767
688
  })
768
689
  })
769
690
 
770
691
  it('should apply header styles when provided', async () => {
771
- await usingAsync(new Injector(), async (injector) => {
692
+ await withTestGrid(async ({ injector, service, findOptions }) => {
772
693
  const rootElement = document.getElementById('root') as HTMLDivElement
773
- const service = createTestService()
774
- const findOptions = new ObservableValue<any>({})
775
694
 
776
695
  initializeShadeRoot({
777
696
  injector,
@@ -795,138 +714,127 @@ describe('DataGrid', () => {
795
714
  const grid = document.querySelector('shade-data-grid')
796
715
  const headers = grid?.querySelectorAll('th') as NodeListOf<HTMLElement>
797
716
  expect(headers?.[0]?.style.color).toBe('blue')
798
-
799
- service[Symbol.dispose]()
800
- findOptions[Symbol.dispose]()
801
717
  })
802
718
  })
803
719
  })
804
720
 
805
721
  describe('empty and loading states', () => {
806
722
  it('should show empty component when no data', async () => {
807
- await usingAsync(new Injector(), async (injector) => {
808
- const rootElement = document.getElementById('root') as HTMLDivElement
809
- const service = new CollectionService<TestEntry>()
810
- const findOptions = new ObservableValue<any>({})
811
-
812
- initializeShadeRoot({
813
- injector,
814
- rootElement,
815
- jsxElement: (
816
- <DataGrid<TestEntry, 'id' | 'name'>
817
- columns={['id', 'name']}
818
- collectionService={service}
819
- findOptions={findOptions}
820
- styles={{}}
821
- headerComponents={{}}
822
- rowComponents={{}}
823
- emptyComponent={<div data-testid="empty-state">No data available</div>}
824
- />
825
- ),
826
- })
827
-
828
- await sleepAsync(50)
829
-
830
- const grid = document.querySelector('shade-data-grid')
831
- const emptyState = grid?.querySelector('[data-testid="empty-state"]')
832
- expect(emptyState).not.toBeNull()
833
- expect(emptyState?.textContent).toBe('No data available')
834
-
835
- service[Symbol.dispose]()
836
- findOptions[Symbol.dispose]()
837
- })
723
+ await withTestGrid(
724
+ async ({ injector, service, findOptions }) => {
725
+ const rootElement = document.getElementById('root') as HTMLDivElement
726
+
727
+ initializeShadeRoot({
728
+ injector,
729
+ rootElement,
730
+ jsxElement: (
731
+ <DataGrid<TestEntry, 'id' | 'name'>
732
+ columns={['id', 'name']}
733
+ collectionService={service}
734
+ findOptions={findOptions}
735
+ styles={{}}
736
+ headerComponents={{}}
737
+ rowComponents={{}}
738
+ emptyComponent={<div data-testid="empty-state">No data available</div>}
739
+ />
740
+ ),
741
+ })
742
+
743
+ await sleepAsync(50)
744
+
745
+ const grid = document.querySelector('shade-data-grid')
746
+ const emptyState = grid?.querySelector('[data-testid="empty-state"]')
747
+ expect(emptyState).not.toBeNull()
748
+ expect(emptyState?.textContent).toBe('No data available')
749
+ },
750
+ { createService: () => new CollectionService<TestEntry>() },
751
+ )
838
752
  })
839
753
  })
840
754
 
841
755
  describe('row interactions', () => {
842
756
  it('should pass row click to collectionService', async () => {
843
- await usingAsync(new Injector(), async (injector) => {
844
- const rootElement = document.getElementById('root') as HTMLDivElement
845
- const onRowClick = vi.fn()
846
- const service = new CollectionService<TestEntry>({ onRowClick })
847
- const findOptions = new ObservableValue<any>({})
848
-
849
- service.data.setValue({
850
- count: 1,
851
- entries: [{ id: 1, name: 'Test' }],
852
- })
853
-
854
- initializeShadeRoot({
855
- injector,
856
- rootElement,
857
- jsxElement: (
858
- <DataGrid<TestEntry, 'id' | 'name'>
859
- columns={['id', 'name']}
860
- collectionService={service}
861
- findOptions={findOptions}
862
- styles={{}}
863
- headerComponents={{}}
864
- rowComponents={{}}
865
- />
866
- ),
867
- })
868
-
869
- await sleepAsync(50)
757
+ const onRowClick = vi.fn()
758
+ await withTestGrid(
759
+ async ({ injector, service, findOptions }) => {
760
+ const rootElement = document.getElementById('root') as HTMLDivElement
761
+
762
+ service.data.setValue({
763
+ count: 1,
764
+ entries: [{ id: 1, name: 'Test' }],
765
+ })
766
+
767
+ initializeShadeRoot({
768
+ injector,
769
+ rootElement,
770
+ jsxElement: (
771
+ <DataGrid<TestEntry, 'id' | 'name'>
772
+ columns={['id', 'name']}
773
+ collectionService={service}
774
+ findOptions={findOptions}
775
+ styles={{}}
776
+ headerComponents={{}}
777
+ rowComponents={{}}
778
+ />
779
+ ),
780
+ })
870
781
 
871
- const grid = document.querySelector('shade-data-grid')
872
- const cell = grid?.querySelector('td') as HTMLTableCellElement
873
- cell?.click()
782
+ await sleepAsync(50)
874
783
 
875
- expect(onRowClick).toHaveBeenCalledWith({ id: 1, name: 'Test' })
784
+ const grid = document.querySelector('shade-data-grid')
785
+ const cell = grid?.querySelector('td') as HTMLTableCellElement
786
+ cell?.click()
876
787
 
877
- service[Symbol.dispose]()
878
- findOptions[Symbol.dispose]()
879
- })
788
+ expect(onRowClick).toHaveBeenCalledWith({ id: 1, name: 'Test' })
789
+ },
790
+ { createService: () => new CollectionService<TestEntry>({ onRowClick }) },
791
+ )
880
792
  })
881
793
 
882
794
  it('should pass row double click to collectionService', async () => {
883
- await usingAsync(new Injector(), async (injector) => {
884
- const rootElement = document.getElementById('root') as HTMLDivElement
885
- const onRowDoubleClick = vi.fn()
886
- const service = new CollectionService<TestEntry>({ onRowDoubleClick })
887
- const findOptions = new ObservableValue<any>({})
888
-
889
- service.data.setValue({
890
- count: 1,
891
- entries: [{ id: 1, name: 'Test' }],
892
- })
893
-
894
- initializeShadeRoot({
895
- injector,
896
- rootElement,
897
- jsxElement: (
898
- <DataGrid<TestEntry, 'id' | 'name'>
899
- columns={['id', 'name']}
900
- collectionService={service}
901
- findOptions={findOptions}
902
- styles={{}}
903
- headerComponents={{}}
904
- rowComponents={{}}
905
- />
906
- ),
907
- })
908
-
909
- await sleepAsync(50)
795
+ const onRowDoubleClick = vi.fn()
796
+ await withTestGrid(
797
+ async ({ injector, service, findOptions }) => {
798
+ const rootElement = document.getElementById('root') as HTMLDivElement
799
+
800
+ service.data.setValue({
801
+ count: 1,
802
+ entries: [{ id: 1, name: 'Test' }],
803
+ })
804
+
805
+ initializeShadeRoot({
806
+ injector,
807
+ rootElement,
808
+ jsxElement: (
809
+ <DataGrid<TestEntry, 'id' | 'name'>
810
+ columns={['id', 'name']}
811
+ collectionService={service}
812
+ findOptions={findOptions}
813
+ styles={{}}
814
+ headerComponents={{}}
815
+ rowComponents={{}}
816
+ />
817
+ ),
818
+ })
910
819
 
911
- const grid = document.querySelector('shade-data-grid')
912
- const cell = grid?.querySelector('td') as HTMLTableCellElement
913
- const dblClickEvent = new MouseEvent('dblclick', { bubbles: true })
914
- cell?.dispatchEvent(dblClickEvent)
820
+ await sleepAsync(50)
915
821
 
916
- expect(onRowDoubleClick).toHaveBeenCalledWith({ id: 1, name: 'Test' })
822
+ const grid = document.querySelector('shade-data-grid')
823
+ const cell = grid?.querySelector('td') as HTMLTableCellElement
824
+ const dblClickEvent = new MouseEvent('dblclick', { bubbles: true })
825
+ cell?.dispatchEvent(dblClickEvent)
917
826
 
918
- service[Symbol.dispose]()
919
- findOptions[Symbol.dispose]()
920
- })
827
+ expect(onRowDoubleClick).toHaveBeenCalledWith({ id: 1, name: 'Test' })
828
+ },
829
+ { createService: () => new CollectionService<TestEntry>({ onRowDoubleClick }) },
830
+ )
921
831
  })
922
832
  })
923
833
 
924
834
  describe('keyboard listener cleanup', () => {
925
835
  it('should remove keyboard listener when component is disconnected', async () => {
926
- await usingAsync(new Injector(), async (injector) => {
836
+ await withTestGrid(async ({ injector, service, findOptions }) => {
927
837
  const rootElement = document.getElementById('root') as HTMLDivElement
928
- const service = createTestService()
929
- const findOptions = new ObservableValue<any>({})
930
838
 
931
839
  service.hasFocus.setValue(true)
932
840
  service.focusedEntry.setValue(service.data.getValue().entries[0])
@@ -955,9 +863,6 @@ describe('DataGrid', () => {
955
863
 
956
864
  window.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown', bubbles: true }))
957
865
  expect(service.focusedEntry.getValue()).toEqual({ id: 1, name: 'First' })
958
-
959
- service[Symbol.dispose]()
960
- findOptions[Symbol.dispose]()
961
866
  })
962
867
  })
963
868
  })
@@ -8,6 +8,7 @@ export * from './badge.js'
8
8
  export * from './breadcrumb.js'
9
9
  export * from './button-group.js'
10
10
  export * from './button.js'
11
+ export * from './cache-view.js'
11
12
  export * from './card.js'
12
13
  export * from './carousel.js'
13
14
  export * from './chip.js'