@svgrid/grid 1.1.1 → 1.2.1

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 (188) hide show
  1. package/dist/FlexRender.svelte +96 -96
  2. package/dist/SvGrid.controller.svelte.js +17 -6
  3. package/dist/SvGrid.css +2012 -2012
  4. package/dist/SvGrid.svelte +2571 -2346
  5. package/dist/SvGridDropdown.svelte +7 -3
  6. package/dist/build-api.js +2 -2
  7. package/dist/cdn/svgrid.js +13504 -0
  8. package/dist/cdn/svgrid.svelte-external.js +11188 -0
  9. package/dist/cell-values.d.ts +1 -1
  10. package/dist/cell-values.js +7 -7
  11. package/dist/column-types.js +1 -1
  12. package/dist/core.d.ts +1 -1
  13. package/dist/core.js +2 -2
  14. package/dist/summaries.js +4 -4
  15. package/package.json +12 -2
  16. package/src/FlexRender.svelte +96 -96
  17. package/src/SvGrid.controller.svelte.ts +2363 -2352
  18. package/src/SvGrid.css +2012 -2012
  19. package/src/SvGrid.svelte +2571 -2346
  20. package/src/SvGrid.types.ts +537 -537
  21. package/src/SvGridDropdown.svelte +7 -3
  22. package/src/a11y.contract.test.ts +49 -49
  23. package/src/a11y.test.ts +59 -59
  24. package/src/a11y.ts +59 -59
  25. package/src/build-api.ts +683 -683
  26. package/src/cell-formatting.ts +169 -169
  27. package/src/cell-values.ts +4 -4
  28. package/src/column-types.ts +1 -1
  29. package/src/core.performance.test.ts +30 -30
  30. package/src/core.ts +1077 -1077
  31. package/src/createGrid.svelte.ts +42 -42
  32. package/src/createGrid.test.ts +10 -10
  33. package/src/createGridState.svelte.ts +17 -17
  34. package/src/editing.ts +669 -669
  35. package/src/flex-render.ts +3 -3
  36. package/src/index.ts +208 -208
  37. package/src/keyboard.test.ts +59 -59
  38. package/src/keyboard.ts +97 -97
  39. package/src/merge-objects.ts +48 -48
  40. package/src/render-component.ts +28 -28
  41. package/src/spreadsheet.test.ts +489 -489
  42. package/src/spreadsheet.ts +304 -304
  43. package/src/static-functions.ts +11 -11
  44. package/src/subscribe.ts +38 -38
  45. package/src/summaries.ts +4 -4
  46. package/src/svgrid-wrapper.types.ts +412 -412
  47. package/src/svgrid.features.test.ts +157 -157
  48. package/src/svgrid.wrapper.test.ts +40 -40
  49. package/src/virtualization/column-virtualizer.test.ts +27 -27
  50. package/src/virtualization/column-virtualizer.ts +30 -30
  51. package/src/virtualization/svelte-virtualizer.svelte.ts +26 -26
  52. package/src/virtualization/types.ts +30 -30
  53. package/src/virtualization/virtualizer.test.ts +47 -47
  54. package/src/virtualization/virtualizer.ts +296 -296
  55. package/dist/SvGrid.helpers.test.d.ts +0 -1
  56. package/dist/SvGrid.helpers.test.js +0 -298
  57. package/dist/SvGridChart.test.d.ts +0 -1
  58. package/dist/SvGridChart.test.js +0 -62
  59. package/dist/a11y.contract.test.d.ts +0 -1
  60. package/dist/a11y.contract.test.js +0 -39
  61. package/dist/a11y.test.d.ts +0 -1
  62. package/dist/a11y.test.js +0 -44
  63. package/dist/aligned-grids.test.d.ts +0 -1
  64. package/dist/aligned-grids.test.js +0 -75
  65. package/dist/build-api.coverage.test.d.ts +0 -20
  66. package/dist/build-api.coverage.test.js +0 -505
  67. package/dist/cell-formatting.test.d.ts +0 -1
  68. package/dist/cell-formatting.test.js +0 -234
  69. package/dist/cell-render.test.d.ts +0 -1
  70. package/dist/cell-render.test.js +0 -338
  71. package/dist/chart-export.test.d.ts +0 -1
  72. package/dist/chart-export.test.js +0 -302
  73. package/dist/chart.coverage.test.d.ts +0 -1
  74. package/dist/chart.coverage.test.js +0 -748
  75. package/dist/chart.test.d.ts +0 -1
  76. package/dist/chart.test.js +0 -289
  77. package/dist/clipboard.test.d.ts +0 -1
  78. package/dist/clipboard.test.js +0 -700
  79. package/dist/collaboration.coverage.test.d.ts +0 -1
  80. package/dist/collaboration.coverage.test.js +0 -200
  81. package/dist/collaboration.test.d.ts +0 -1
  82. package/dist/collaboration.test.js +0 -65
  83. package/dist/column-groups.test.d.ts +0 -1
  84. package/dist/column-groups.test.js +0 -56
  85. package/dist/column-types.test.d.ts +0 -1
  86. package/dist/column-types.test.js +0 -62
  87. package/dist/columns.test.d.ts +0 -1
  88. package/dist/columns.test.js +0 -625
  89. package/dist/conditional-formatting.test.d.ts +0 -1
  90. package/dist/conditional-formatting.test.js +0 -87
  91. package/dist/core.coverage.test.d.ts +0 -1
  92. package/dist/core.coverage.test.js +0 -186
  93. package/dist/core.performance.test.d.ts +0 -1
  94. package/dist/core.performance.test.js +0 -29
  95. package/dist/createGrid.test.d.ts +0 -1
  96. package/dist/createGrid.test.js +0 -9
  97. package/dist/editing.test.d.ts +0 -1
  98. package/dist/editing.test.js +0 -732
  99. package/dist/editors/cell-editors.coverage.test.d.ts +0 -1
  100. package/dist/editors/cell-editors.coverage.test.js +0 -139
  101. package/dist/editors/cell-editors.test.d.ts +0 -1
  102. package/dist/editors/cell-editors.test.js +0 -75
  103. package/dist/facet-buckets.test.d.ts +0 -1
  104. package/dist/facet-buckets.test.js +0 -296
  105. package/dist/fill-patterns.test.d.ts +0 -1
  106. package/dist/fill-patterns.test.js +0 -81
  107. package/dist/filter-operators.test.d.ts +0 -1
  108. package/dist/filter-operators.test.js +0 -135
  109. package/dist/filtering/excel-filters.test.d.ts +0 -1
  110. package/dist/filtering/excel-filters.test.js +0 -116
  111. package/dist/filtering/locale-filter.test.d.ts +0 -13
  112. package/dist/filtering/locale-filter.test.js +0 -189
  113. package/dist/flex-render.test.d.ts +0 -1
  114. package/dist/flex-render.test.js +0 -142
  115. package/dist/group-aggregate.test.d.ts +0 -1
  116. package/dist/group-aggregate.test.js +0 -32
  117. package/dist/hyperformula-adapter.test.d.ts +0 -1
  118. package/dist/hyperformula-adapter.test.js +0 -205
  119. package/dist/keyboard-handlers.coverage.test.d.ts +0 -1
  120. package/dist/keyboard-handlers.coverage.test.js +0 -495
  121. package/dist/keyboard.test.d.ts +0 -1
  122. package/dist/keyboard.test.js +0 -27
  123. package/dist/menus.test.d.ts +0 -1
  124. package/dist/menus.test.js +0 -560
  125. package/dist/named-views.coverage.test.d.ts +0 -1
  126. package/dist/named-views.coverage.test.js +0 -180
  127. package/dist/named-views.test.d.ts +0 -1
  128. package/dist/named-views.test.js +0 -57
  129. package/dist/new-features.test.d.ts +0 -1
  130. package/dist/new-features.test.js +0 -183
  131. package/dist/render-component.test.d.ts +0 -1
  132. package/dist/render-component.test.js +0 -41
  133. package/dist/row-drag.test.d.ts +0 -1
  134. package/dist/row-drag.test.js +0 -142
  135. package/dist/row-resize.test.d.ts +0 -1
  136. package/dist/row-resize.test.js +0 -329
  137. package/dist/scroll-sync.test.d.ts +0 -1
  138. package/dist/scroll-sync.test.js +0 -290
  139. package/dist/selection.multi-range.test.d.ts +0 -1
  140. package/dist/selection.multi-range.test.js +0 -55
  141. package/dist/selection.test.d.ts +0 -1
  142. package/dist/selection.test.js +0 -647
  143. package/dist/server-data-source.coverage.test.d.ts +0 -1
  144. package/dist/server-data-source.coverage.test.js +0 -154
  145. package/dist/server-data-source.test.d.ts +0 -1
  146. package/dist/server-data-source.test.js +0 -81
  147. package/dist/sparkline.test.d.ts +0 -1
  148. package/dist/sparkline.test.js +0 -52
  149. package/dist/spreadsheet.test.d.ts +0 -1
  150. package/dist/spreadsheet.test.js +0 -446
  151. package/dist/subscribe.test.d.ts +0 -1
  152. package/dist/subscribe.test.js +0 -93
  153. package/dist/svgrid.api-extensions.test.d.ts +0 -14
  154. package/dist/svgrid.api-extensions.test.js +0 -334
  155. package/dist/svgrid.api.test.d.ts +0 -15
  156. package/dist/svgrid.api.test.js +0 -169
  157. package/dist/svgrid.behavior.test.d.ts +0 -15
  158. package/dist/svgrid.behavior.test.js +0 -625
  159. package/dist/svgrid.column-reorder.test.d.ts +0 -15
  160. package/dist/svgrid.column-reorder.test.js +0 -224
  161. package/dist/svgrid.comments-autocomplete.test.d.ts +0 -1
  162. package/dist/svgrid.comments-autocomplete.test.js +0 -96
  163. package/dist/svgrid.context-menu.test.d.ts +0 -1
  164. package/dist/svgrid.context-menu.test.js +0 -102
  165. package/dist/svgrid.features.test.d.ts +0 -1
  166. package/dist/svgrid.features.test.js +0 -129
  167. package/dist/svgrid.interaction.test.d.ts +0 -9
  168. package/dist/svgrid.interaction.test.js +0 -349
  169. package/dist/svgrid.locale-filtering.test.d.ts +0 -15
  170. package/dist/svgrid.locale-filtering.test.js +0 -261
  171. package/dist/svgrid.new-features.wrapper.test.d.ts +0 -1
  172. package/dist/svgrid.new-features.wrapper.test.js +0 -194
  173. package/dist/svgrid.row-pinning.test.d.ts +0 -14
  174. package/dist/svgrid.row-pinning.test.js +0 -250
  175. package/dist/svgrid.set-filter.test.d.ts +0 -17
  176. package/dist/svgrid.set-filter.test.js +0 -268
  177. package/dist/svgrid.wrapper.test.d.ts +0 -1
  178. package/dist/svgrid.wrapper.test.js +0 -59
  179. package/dist/test-setup.d.ts +0 -1
  180. package/dist/test-setup.js +0 -35
  181. package/dist/transaction.test.d.ts +0 -1
  182. package/dist/transaction.test.js +0 -89
  183. package/dist/virtualization/column-virtualizer.test.d.ts +0 -1
  184. package/dist/virtualization/column-virtualizer.test.js +0 -25
  185. package/dist/virtualization/scroll-scaling.test.d.ts +0 -1
  186. package/dist/virtualization/scroll-scaling.test.js +0 -127
  187. package/dist/virtualization/virtualizer.test.d.ts +0 -1
  188. package/dist/virtualization/virtualizer.test.js +0 -41
@@ -1,180 +0,0 @@
1
- import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
2
- import { attachAutoSavedView, createNamedViews, localStorageViews, memoryViews, } from './named-views';
3
- function fakeHost(initial = {}) {
4
- let state = { sorting: [], columnFilters: [], ...initial };
5
- return {
6
- getState: () => state,
7
- setState: vi.fn((s) => {
8
- state = { ...state, ...s };
9
- }),
10
- set: (s) => {
11
- state = s;
12
- },
13
- };
14
- }
15
- describe('createNamedViews - list ordering and edge cases', () => {
16
- it('lists views sorted by createdAt ascending', () => {
17
- const store = memoryViews([
18
- { name: 'late', state: {}, createdAt: 200 },
19
- { name: 'early', state: {}, createdAt: 100 },
20
- ]);
21
- const views = createNamedViews(fakeHost(), { storage: store });
22
- expect(views.list().map((v) => v.name)).toEqual(['early', 'late']);
23
- });
24
- it('defaults to memory storage when none is passed', () => {
25
- const views = createNamedViews(fakeHost({ sorting: [{ id: 'a', desc: true }] }));
26
- views.save('only');
27
- expect(views.has('only')).toBe(true);
28
- expect(views.list()).toHaveLength(1);
29
- });
30
- it('rename returns false when the source name is unknown', () => {
31
- const views = createNamedViews(fakeHost(), { storage: memoryViews() });
32
- views.save('a');
33
- expect(views.rename('missing', 'b')).toBe(false);
34
- expect(views.list().map((v) => v.name)).toEqual(['a']);
35
- });
36
- it('load returns false and does not call setState for unknown name', () => {
37
- const host = fakeHost();
38
- const views = createNamedViews(host, { storage: memoryViews() });
39
- host.setState.mockClear();
40
- expect(views.load('nope')).toBe(false);
41
- expect(host.setState).not.toHaveBeenCalled();
42
- });
43
- it('save captures the current host state snapshot', () => {
44
- const host = fakeHost({ sorting: [{ id: 'z', desc: false }] });
45
- const views = createNamedViews(host, { storage: memoryViews() });
46
- const v = views.save('snap');
47
- expect(v.state).toMatchObject({ sorting: [{ id: 'z', desc: false }] });
48
- expect(typeof v.createdAt).toBe('number');
49
- });
50
- });
51
- describe('localStorageViews', () => {
52
- beforeEach(() => localStorage.clear());
53
- afterEach(() => localStorage.clear());
54
- it('reads [] when the key is absent', () => {
55
- const store = localStorageViews('svgrid:absent');
56
- expect(store.read()).toEqual([]);
57
- });
58
- it('round-trips views through localStorage', () => {
59
- const store = localStorageViews('svgrid:rt');
60
- const views = [{ name: 'x', state: {}, createdAt: 1 }];
61
- store.write(views);
62
- expect(store.read()).toEqual(views);
63
- // raw JSON is actually present
64
- expect(JSON.parse(localStorage.getItem('svgrid:rt'))).toEqual(views);
65
- });
66
- it('returns [] for corrupt JSON', () => {
67
- localStorage.setItem('svgrid:bad', '{not valid json');
68
- expect(localStorageViews('svgrid:bad').read()).toEqual([]);
69
- });
70
- it('returns [] when the stored value is not an array', () => {
71
- localStorage.setItem('svgrid:obj', JSON.stringify({ foo: 'bar' }));
72
- expect(localStorageViews('svgrid:obj').read()).toEqual([]);
73
- });
74
- it('swallows write errors (quota / private mode)', () => {
75
- const store = localStorageViews('svgrid:quota');
76
- const spy = vi.spyOn(Storage.prototype, 'setItem').mockImplementation(() => {
77
- throw new DOMException('quota', 'QuotaExceededError');
78
- });
79
- expect(() => store.write([{ name: 'a', state: {}, createdAt: 1 }])).not.toThrow();
80
- spy.mockRestore();
81
- });
82
- it('works end-to-end as a NamedViews adapter', () => {
83
- const views = createNamedViews(fakeHost({ sorting: [{ id: 'p', desc: true }] }), {
84
- storage: localStorageViews('svgrid:adapter'),
85
- });
86
- views.save('persisted');
87
- const reopened = createNamedViews(fakeHost(), {
88
- storage: localStorageViews('svgrid:adapter'),
89
- });
90
- expect(reopened.has('persisted')).toBe(true);
91
- expect(reopened.load('persisted')).toBe(true);
92
- });
93
- });
94
- describe('attachAutoSavedView', () => {
95
- beforeEach(() => vi.useFakeTimers());
96
- afterEach(() => vi.useRealTimers());
97
- it('restores an existing autosave slot on attach', () => {
98
- const host = fakeHost();
99
- const store = memoryViews([
100
- { name: '__autosave', state: { sorting: [{ id: 'restored', desc: true }] }, createdAt: 1 },
101
- ]);
102
- const views = createNamedViews(host, { storage: store });
103
- const off = attachAutoSavedView(host, views);
104
- expect(host.setState).toHaveBeenCalledWith(expect.objectContaining({ sorting: [{ id: 'restored', desc: true }] }));
105
- off();
106
- });
107
- it('skips restore when skipRestore is set', () => {
108
- const host = fakeHost();
109
- const store = memoryViews([{ name: '__autosave', state: { sorting: [] }, createdAt: 1 }]);
110
- const views = createNamedViews(host, { storage: store });
111
- const off = attachAutoSavedView(host, views, { skipRestore: true });
112
- expect(host.setState).not.toHaveBeenCalled();
113
- off();
114
- });
115
- it('saves when the state snapshot changes after an interval', () => {
116
- const host = fakeHost({ sorting: [] });
117
- const views = createNamedViews(host, { storage: memoryViews() });
118
- const off = attachAutoSavedView(host, views, { intervalMs: 500 });
119
- expect(views.has('__autosave')).toBe(false);
120
- // mutate the host state, then let the poller tick
121
- host.set({ sorting: [{ id: 'new', desc: false }], columnFilters: [] });
122
- vi.advanceTimersByTime(500);
123
- expect(views.has('__autosave')).toBe(true);
124
- off();
125
- });
126
- it('does not save when the snapshot is unchanged', () => {
127
- const host = fakeHost({ sorting: [] });
128
- const views = createNamedViews(host, { storage: memoryViews() });
129
- const saveSpy = vi.spyOn(views, 'save');
130
- const off = attachAutoSavedView(host, views, { intervalMs: 300 });
131
- vi.advanceTimersByTime(900); // several ticks, no state change
132
- expect(saveSpy).not.toHaveBeenCalled();
133
- off();
134
- });
135
- it('uses a custom slot name', () => {
136
- const host = fakeHost({ sorting: [] });
137
- const views = createNamedViews(host, { storage: memoryViews() });
138
- const off = attachAutoSavedView(host, views, { name: 'mySlot', intervalMs: 200 });
139
- host.set({ sorting: [{ id: 'q', desc: true }], columnFilters: [] });
140
- vi.advanceTimersByTime(200);
141
- expect(views.has('mySlot')).toBe(true);
142
- off();
143
- });
144
- it('clamps the interval to a minimum of 100ms', () => {
145
- const host = fakeHost({ sorting: [] });
146
- const views = createNamedViews(host, { storage: memoryViews() });
147
- const setIntervalSpy = vi.spyOn(globalThis, 'setInterval');
148
- const off = attachAutoSavedView(host, views, { intervalMs: 5 });
149
- expect(setIntervalSpy).toHaveBeenCalledWith(expect.any(Function), 100);
150
- off();
151
- setIntervalSpy.mockRestore();
152
- });
153
- it('recovers when getState throws inside the poll loop', () => {
154
- let throwing = false;
155
- const host = {
156
- getState: () => {
157
- if (throwing)
158
- throw new Error('boom');
159
- return { sorting: [], columnFilters: [] };
160
- },
161
- setState: vi.fn(),
162
- };
163
- const views = createNamedViews(host, { storage: memoryViews() });
164
- const saveSpy = vi.spyOn(views, 'save');
165
- const off = attachAutoSavedView(host, views, { intervalMs: 200 });
166
- throwing = true;
167
- expect(() => vi.advanceTimersByTime(400)).not.toThrow();
168
- expect(saveSpy).not.toHaveBeenCalled();
169
- off();
170
- });
171
- it('detach stops the polling interval', () => {
172
- const host = fakeHost({ sorting: [] });
173
- const views = createNamedViews(host, { storage: memoryViews() });
174
- const clearSpy = vi.spyOn(globalThis, 'clearInterval');
175
- const off = attachAutoSavedView(host, views, { intervalMs: 200 });
176
- off();
177
- expect(clearSpy).toHaveBeenCalled();
178
- clearSpy.mockRestore();
179
- });
180
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,57 +0,0 @@
1
- import { describe, expect, it, vi } from 'vitest';
2
- import { createNamedViews, memoryViews } from './named-views';
3
- function fakeHost(initial = {}) {
4
- let state = { sorting: [], columnFilters: [], ...initial };
5
- return {
6
- getState: () => state,
7
- setState: vi.fn((s) => {
8
- state = { ...state, ...s };
9
- }),
10
- _peek: () => state,
11
- };
12
- }
13
- describe('createNamedViews', () => {
14
- it('saves the current state and lists it', () => {
15
- const host = fakeHost({ sorting: [{ id: 'name', desc: true }] });
16
- const views = createNamedViews(host, { storage: memoryViews() });
17
- views.save('By name');
18
- expect(views.list().map((v) => v.name)).toEqual(['By name']);
19
- expect(views.has('By name')).toBe(true);
20
- });
21
- it('load applies the saved state via setState', () => {
22
- const host = fakeHost({ sorting: [{ id: 'age', desc: false }] });
23
- const views = createNamedViews(host, { storage: memoryViews() });
24
- views.save('snap');
25
- host.setState({ sorting: [] }); // mutate away
26
- host.setState.mockClear();
27
- expect(views.load('snap')).toBe(true);
28
- expect(host.setState).toHaveBeenCalledWith(expect.objectContaining({ sorting: [{ id: 'age', desc: false }] }));
29
- expect(views.load('missing')).toBe(false);
30
- });
31
- it('save overwrites a duplicate name but keeps its createdAt', () => {
32
- const host = fakeHost();
33
- const views = createNamedViews(host, { storage: memoryViews() });
34
- const a = views.save('v');
35
- const b = views.save('v');
36
- expect(views.list()).toHaveLength(1);
37
- expect(b.createdAt).toBe(a.createdAt);
38
- });
39
- it('remove and rename', () => {
40
- const host = fakeHost();
41
- const views = createNamedViews(host, { storage: memoryViews() });
42
- views.save('a');
43
- views.save('b');
44
- expect(views.rename('a', 'c')).toBe(true);
45
- expect(views.rename('c', 'b')).toBe(false); // target exists
46
- expect(views.remove('c')).toBe(true);
47
- expect(views.remove('c')).toBe(false);
48
- expect(views.list().map((v) => v.name)).toEqual(['b']);
49
- });
50
- it('persists through a storage adapter', () => {
51
- const store = memoryViews();
52
- createNamedViews(fakeHost({ sorting: [{ id: 'x', desc: true }] }), { storage: store }).save('keep');
53
- // A fresh manager over the same storage sees it.
54
- const reopened = createNamedViews(fakeHost(), { storage: store });
55
- expect(reopened.has('keep')).toBe(true);
56
- });
57
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,183 +0,0 @@
1
- /**
2
- * Tests for the headless-engine features shipped alongside the wrapper
3
- * additions (`getRowId`, the `between` filter operator). Wrapper-level
4
- * additions are covered by the source-level smoke tests in
5
- * `svgrid.new-features.wrapper.test.ts` - jsdom + Svelte 5 mount is
6
- * brittle for tests that only need to confirm props are read.
7
- */
8
- import { describe, expect, it } from 'vitest';
9
- import { columnFilteringFeature, createCoreRowModel, createSvGrid, rowSortingFeature, tableFeatures, } from './index';
10
- import { applyExcelFilter } from './filtering/excel-filters';
11
- const data = [
12
- { id: 'ada', name: 'Ada Lovelace', age: 36 },
13
- { id: 'linus', name: 'Linus Torvalds', age: 54 },
14
- { id: 'grace', name: 'Grace Hopper', age: 85 },
15
- ];
16
- const columns = [
17
- { field: 'id', header: 'ID' },
18
- { field: 'name', header: 'Name' },
19
- { field: 'age', header: 'Age' },
20
- ];
21
- const features = tableFeatures({ rowSortingFeature });
22
- describe('createSvGrid - getRowId', () => {
23
- it('falls back to the array index as id when no getRowId is provided', () => {
24
- const grid = createSvGrid({
25
- _features: features,
26
- _rowModels: { coreRowModel: createCoreRowModel() },
27
- columns,
28
- data,
29
- });
30
- const rows = grid.getRowModel().rows;
31
- expect(rows.map((r) => r.id)).toEqual(['0', '1', '2']);
32
- });
33
- it('uses the result of getRowId(row, index) as the row id', () => {
34
- const grid = createSvGrid({
35
- _features: features,
36
- _rowModels: { coreRowModel: createCoreRowModel() },
37
- columns,
38
- data,
39
- getRowId: (row) => row.id,
40
- });
41
- const rows = grid.getRowModel().rows;
42
- expect(rows.map((r) => r.id)).toEqual(['ada', 'linus', 'grace']);
43
- });
44
- it('passes the row index to getRowId', () => {
45
- const seen = [];
46
- createSvGrid({
47
- _features: features,
48
- _rowModels: { coreRowModel: createCoreRowModel() },
49
- columns,
50
- data,
51
- getRowId: (row, index) => {
52
- seen.push({ id: row.id, index });
53
- return row.id;
54
- },
55
- }).getRowModel().rows;
56
- expect(seen).toEqual([
57
- { id: 'ada', index: 0 },
58
- { id: 'linus', index: 1 },
59
- { id: 'grace', index: 2 },
60
- ]);
61
- });
62
- it('preserves the same id across `getRowModel()` invocations (stable identity)', () => {
63
- const grid = createSvGrid({
64
- _features: features,
65
- _rowModels: { coreRowModel: createCoreRowModel() },
66
- columns,
67
- data,
68
- getRowId: (row) => `r:${row.id}`,
69
- });
70
- const first = grid.getRowModel().rows.map((r) => r.id);
71
- const second = grid.getRowModel().rows.map((r) => r.id);
72
- expect(first).toEqual(second);
73
- expect(first).toEqual(['r:ada', 'r:linus', 'r:grace']);
74
- });
75
- });
76
- describe('applyExcelFilter - between operator', () => {
77
- it('returns true when the value is inside the inclusive range', () => {
78
- expect(applyExcelFilter(50, { id: 'age', operator: 'between', value: 18, valueTo: 65 })).toBe(true);
79
- });
80
- it('is inclusive at the lower bound', () => {
81
- expect(applyExcelFilter(18, { id: 'age', operator: 'between', value: 18, valueTo: 65 })).toBe(true);
82
- });
83
- it('is inclusive at the upper bound', () => {
84
- expect(applyExcelFilter(65, { id: 'age', operator: 'between', value: 18, valueTo: 65 })).toBe(true);
85
- });
86
- it('returns false when the value is below the range', () => {
87
- expect(applyExcelFilter(17, { id: 'age', operator: 'between', value: 18, valueTo: 65 })).toBe(false);
88
- });
89
- it('returns false when the value is above the range', () => {
90
- expect(applyExcelFilter(66, { id: 'age', operator: 'between', value: 18, valueTo: 65 })).toBe(false);
91
- });
92
- it('parses string endpoints as numbers (the wrapper hands strings through)', () => {
93
- expect(applyExcelFilter('30', { id: 'age', operator: 'between', value: '18', valueTo: '65' })).toBe(true);
94
- });
95
- it('works with ISO date strings via lexicographic compare', () => {
96
- expect(applyExcelFilter('2026-06-15', {
97
- id: 'd', operator: 'between', value: '2026-01-01', valueTo: '2026-12-31',
98
- })).toBe(true);
99
- expect(applyExcelFilter('2025-12-31', {
100
- id: 'd', operator: 'between', value: '2026-01-01', valueTo: '2026-12-31',
101
- })).toBe(false);
102
- expect(applyExcelFilter('2027-01-01', {
103
- id: 'd', operator: 'between', value: '2026-01-01', valueTo: '2026-12-31',
104
- })).toBe(false);
105
- });
106
- });
107
- describe('ColumnDef.sortable + ColumnDef.filterable', () => {
108
- const features = tableFeatures({
109
- rowSortingFeature,
110
- columnFilteringFeature,
111
- });
112
- function columnsWith(opts) {
113
- return [
114
- { field: 'id', header: 'ID' },
115
- { field: 'name', header: 'Name', sortable: opts.nameSortable, filterable: opts.nameFilterable },
116
- { field: 'age', header: 'Age', sortable: opts.ageSortable },
117
- ];
118
- }
119
- it('getCanSort() returns true by default when rowSortingFeature is on', () => {
120
- const grid = createSvGrid({
121
- _features: features,
122
- _rowModels: { coreRowModel: createCoreRowModel() },
123
- data,
124
- columns: columnsWith({}),
125
- });
126
- const name = grid.getAllColumns().find((c) => c.id === 'name');
127
- expect(name?.getCanSort()).toBe(true);
128
- });
129
- it('getCanSort() returns false when columnDef.sortable === false', () => {
130
- const grid = createSvGrid({
131
- _features: features,
132
- _rowModels: { coreRowModel: createCoreRowModel() },
133
- data,
134
- columns: columnsWith({ nameSortable: false }),
135
- });
136
- const name = grid.getAllColumns().find((c) => c.id === 'name');
137
- const age = grid.getAllColumns().find((c) => c.id === 'age');
138
- expect(name?.getCanSort()).toBe(false);
139
- expect(age?.getCanSort()).toBe(true);
140
- });
141
- it('getCanSort() still returns false even with sortable: true if the feature is OFF', () => {
142
- const noSortFeatures = tableFeatures({ columnFilteringFeature });
143
- const grid = createSvGrid({
144
- _features: noSortFeatures,
145
- _rowModels: { coreRowModel: createCoreRowModel() },
146
- data,
147
- columns: [
148
- { field: 'name', header: 'Name', sortable: true },
149
- ],
150
- });
151
- const name = grid.getAllColumns().find((c) => c.id === 'name');
152
- expect(name?.getCanSort()).toBe(false);
153
- });
154
- it('getCanFilter() returns false when columnDef.filterable === false', () => {
155
- const grid = createSvGrid({
156
- _features: features,
157
- _rowModels: { coreRowModel: createCoreRowModel() },
158
- data,
159
- columns: columnsWith({ nameFilterable: false }),
160
- });
161
- const name = grid.getAllColumns().find((c) => c.id === 'name');
162
- const age = grid.getAllColumns().find((c) => c.id === 'age');
163
- expect(name?.getCanFilter()).toBe(false);
164
- expect(age?.getCanFilter()).toBe(true);
165
- });
166
- it('sortable + filterable are independent', () => {
167
- const grid = createSvGrid({
168
- _features: features,
169
- _rowModels: { coreRowModel: createCoreRowModel() },
170
- data,
171
- columns: [
172
- { field: 'name', header: 'Name', sortable: false, filterable: true },
173
- { field: 'age', header: 'Age', sortable: true, filterable: false },
174
- ],
175
- });
176
- const name = grid.getAllColumns().find((c) => c.id === 'name');
177
- const age = grid.getAllColumns().find((c) => c.id === 'age');
178
- expect(name?.getCanSort()).toBe(false);
179
- expect(name?.getCanFilter()).toBe(true);
180
- expect(age?.getCanSort()).toBe(true);
181
- expect(age?.getCanFilter()).toBe(false);
182
- });
183
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,41 +0,0 @@
1
- /**
2
- * Unit tests for the renderComponent / renderSnippet factories.
3
- */
4
- import { describe, expect, it } from 'vitest';
5
- import { RenderComponentConfig, RenderSnippetConfig, renderComponent, renderSnippet, } from './render-component';
6
- describe('renderComponent', () => {
7
- it('returns a RenderComponentConfig instance', () => {
8
- const FakeComponent = (() => null);
9
- const config = renderComponent(FakeComponent);
10
- expect(config).toBeInstanceOf(RenderComponentConfig);
11
- expect(config.component).toBe(FakeComponent);
12
- expect(config.props).toBeUndefined();
13
- });
14
- it('captures the props payload as-is', () => {
15
- const FakeComponent = (() => null);
16
- const config = renderComponent(FakeComponent, { id: 1, label: 'Hello' });
17
- expect(config.props).toEqual({ id: 1, label: 'Hello' });
18
- });
19
- it('reuses the constructor instance fields', () => {
20
- const FakeComponent = (() => null);
21
- const a = renderComponent(FakeComponent, { x: 1 });
22
- const b = renderComponent(FakeComponent, { x: 1 });
23
- // Different instances - the factory does not memoize.
24
- expect(a).not.toBe(b);
25
- expect(a.component).toBe(b.component);
26
- });
27
- });
28
- describe('renderSnippet', () => {
29
- it('returns a RenderSnippetConfig instance with the captured snippet + params', () => {
30
- const fakeSnippet = (() => null);
31
- const config = renderSnippet(fakeSnippet, { value: 42 });
32
- expect(config).toBeInstanceOf(RenderSnippetConfig);
33
- expect(config.snippet).toBe(fakeSnippet);
34
- expect(config.params).toEqual({ value: 42 });
35
- });
36
- it('allows an undefined params payload', () => {
37
- const fakeSnippet = (() => null);
38
- const config = renderSnippet(fakeSnippet);
39
- expect(config.params).toBeUndefined();
40
- });
41
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,142 +0,0 @@
1
- /**
2
- * Unit tests for the managed row-drag handlers produced by createRowDrag(ctx).
3
- *
4
- * createRowDrag is a factory closing over a mutable `ctx` handle and a
5
- * module-level singleton "bus" that carries the dragged row between grids.
6
- * Because the bus is shared, we can create TWO fake ctxs (two grids) and drive
7
- * a genuine grid-to-grid move without mounting anything.
8
- */
9
- import { describe, expect, it, vi } from 'vitest';
10
- import { createRowDrag, rowDropZone } from './row-drag';
11
- function makeCtx(rows, props = {}) {
12
- return {
13
- props,
14
- internalData: rows,
15
- get allRows() {
16
- // allRows tracks internalData order; each Row wraps `.original`.
17
- return this.internalData.map((r) => ({ original: r }));
18
- },
19
- rowDragActive: false,
20
- rowDropIndex: null,
21
- rowDropSide: null,
22
- };
23
- }
24
- function fakeEvent(clientY = 0, rectTop = 0, rectHeight = 40) {
25
- return {
26
- clientY,
27
- preventDefault: vi.fn(),
28
- stopPropagation: vi.fn(),
29
- dataTransfer: { setData: vi.fn(), effectAllowed: '', dropEffect: '' },
30
- currentTarget: {
31
- getBoundingClientRect: () => ({ top: rectTop, height: rectHeight, bottom: rectTop + rectHeight, left: 0 }),
32
- },
33
- };
34
- }
35
- const P = { rowDragManaged: true };
36
- describe('createRowDrag - same-grid reorder', () => {
37
- it('moves a dragged row after the drop target', () => {
38
- const rows = [
39
- { id: 1, title: 'a' },
40
- { id: 2, title: 'b' },
41
- { id: 3, title: 'c' },
42
- { id: 4, title: 'd' },
43
- ];
44
- const ctx = makeCtx(rows, { ...P });
45
- const h = createRowDrag(ctx);
46
- h.onRowDragStart(fakeEvent(), 0); // drag "a"
47
- ctx.rowDropSide = 'after';
48
- h.onRowDrop(fakeEvent(), 2); // drop after "c"
49
- expect(ctx.internalData.map((r) => r.title)).toEqual(['b', 'c', 'a', 'd']);
50
- });
51
- it('drops before the target when the side is "before"', () => {
52
- const rows = [
53
- { id: 1, title: 'a' },
54
- { id: 2, title: 'b' },
55
- { id: 3, title: 'c' },
56
- ];
57
- const ctx = makeCtx(rows, { ...P });
58
- const h = createRowDrag(ctx);
59
- h.onRowDragStart(fakeEvent(), 2); // drag "c"
60
- ctx.rowDropSide = 'before';
61
- h.onRowDrop(fakeEvent(), 0); // before "a"
62
- expect(ctx.internalData.map((r) => r.title)).toEqual(['c', 'a', 'b']);
63
- });
64
- });
65
- describe('createRowDrag - grid-to-grid', () => {
66
- it('moves a row from source into target when groups match', () => {
67
- const a = makeCtx([{ id: 1, title: 'a1' }, { id: 2, title: 'a2' }], { ...P, rowDragGroup: 'g' });
68
- const b = makeCtx([{ id: 9, title: 'b1' }], { ...P, rowDragGroup: 'g', onRowDragEnd: vi.fn() });
69
- const ha = createRowDrag(a);
70
- const hb = createRowDrag(b);
71
- ha.onRowDragStart(fakeEvent(), 1); // drag "a2" out of grid A
72
- b.rowDropSide = 'before';
73
- hb.onRowDrop(fakeEvent(), 0); // drop onto grid B before "b1"
74
- expect(a.internalData.map((r) => r.title)).toEqual(['a1']);
75
- expect(b.internalData.map((r) => r.title)).toEqual(['a2', 'b1']);
76
- expect(b.props.onRowDragEnd).toHaveBeenCalledWith(expect.objectContaining({ sameGrid: false, toIndex: 0, row: expect.objectContaining({ title: 'a2' }) }));
77
- });
78
- it('does NOT accept a drop when groups differ', () => {
79
- const a = makeCtx([{ id: 1, title: 'a1' }, { id: 2, title: 'a2' }], { ...P, rowDragGroup: 'g' });
80
- const b = makeCtx([{ id: 9, title: 'b1' }], { ...P, rowDragGroup: 'other' });
81
- const ha = createRowDrag(a);
82
- const hb = createRowDrag(b);
83
- ha.onRowDragStart(fakeEvent(), 0);
84
- const ev = fakeEvent();
85
- hb.onRowDragOver(ev, 0);
86
- expect(ev.preventDefault).not.toHaveBeenCalled(); // over rejected
87
- hb.onRowDrop(fakeEvent(), 0);
88
- expect(b.internalData.map((r) => r.title)).toEqual(['b1']); // unchanged
89
- expect(a.internalData.length).toBe(2); // source untouched
90
- });
91
- it('appends to the target on an empty-space (container) drop', () => {
92
- const a = makeCtx([{ id: 1, title: 'a1' }, { id: 2, title: 'a2' }], { ...P, rowDragGroup: 'g' });
93
- const b = makeCtx([{ id: 9, title: 'b1' }], { ...P, rowDragGroup: 'g' });
94
- const ha = createRowDrag(a);
95
- const hb = createRowDrag(b);
96
- ha.onRowDragStart(fakeEvent(), 0); // drag "a1"
97
- hb.onRowsContainerDrop(fakeEvent());
98
- expect(b.internalData.map((r) => r.title)).toEqual(['b1', 'a1']);
99
- expect(a.internalData.map((r) => r.title)).toEqual(['a2']);
100
- });
101
- });
102
- describe('createRowDrag - disabled', () => {
103
- it('is inert when rowDragManaged is off', () => {
104
- const ctx = makeCtx([{ id: 1, title: 'a' }, { id: 2, title: 'b' }], {});
105
- const h = createRowDrag(ctx);
106
- h.onRowDragStart(fakeEvent(), 0);
107
- ctx.rowDropSide = 'after';
108
- h.onRowDrop(fakeEvent(), 1);
109
- expect(ctx.internalData.map((r) => r.title)).toEqual(['a', 'b']);
110
- });
111
- });
112
- describe('rowDropZone (external drop target)', () => {
113
- function dropOn(el) {
114
- const ev = new Event('drop', { bubbles: true, cancelable: true });
115
- el.dispatchEvent(ev);
116
- }
117
- it('removes the dragged row from its source grid and calls onDrop', () => {
118
- const a = makeCtx([{ id: 1, title: 'a1' }, { id: 2, title: 'a2' }], { ...P, rowDragGroup: 'g' });
119
- const ha = createRowDrag(a);
120
- ha.onRowDragStart(fakeEvent(), 0); // start dragging "a1"
121
- const el = document.createElement('div');
122
- const dropped = [];
123
- const action = rowDropZone(el, { group: 'g', onDrop: (e) => dropped.push(e) });
124
- dropOn(el);
125
- expect(dropped.length).toBe(1);
126
- expect(dropped[0].row.title).toBe('a1');
127
- expect(a.internalData.map((r) => r.title)).toEqual(['a2']); // removed from source
128
- action.destroy();
129
- });
130
- it('ignores drops when the group does not match', () => {
131
- const a = makeCtx([{ id: 1, title: 'a1' }], { ...P, rowDragGroup: 'g' });
132
- const ha = createRowDrag(a);
133
- ha.onRowDragStart(fakeEvent(), 0);
134
- const el = document.createElement('div');
135
- const dropped = [];
136
- const action = rowDropZone(el, { group: 'other', onDrop: (e) => dropped.push(e) });
137
- dropOn(el);
138
- expect(dropped.length).toBe(0);
139
- expect(a.internalData.length).toBe(1); // untouched
140
- action.destroy();
141
- });
142
- });
@@ -1 +0,0 @@
1
- export {};