@svgrid/grid 1.1.2 → 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 (141) hide show
  1. package/dist/SvGrid.svelte +3 -1
  2. package/dist/SvGridDropdown.svelte +7 -3
  3. package/dist/cdn/svgrid.js +13504 -0
  4. package/dist/cdn/svgrid.svelte-external.js +11188 -0
  5. package/package.json +12 -2
  6. package/src/SvGrid.svelte +3 -1
  7. package/src/SvGridDropdown.svelte +7 -3
  8. package/dist/SvGrid.helpers.test.d.ts +0 -1
  9. package/dist/SvGrid.helpers.test.js +0 -298
  10. package/dist/SvGridChart.test.d.ts +0 -1
  11. package/dist/SvGridChart.test.js +0 -62
  12. package/dist/a11y.contract.test.d.ts +0 -1
  13. package/dist/a11y.contract.test.js +0 -39
  14. package/dist/a11y.test.d.ts +0 -1
  15. package/dist/a11y.test.js +0 -44
  16. package/dist/aligned-grids.test.d.ts +0 -1
  17. package/dist/aligned-grids.test.js +0 -75
  18. package/dist/build-api.coverage.test.d.ts +0 -20
  19. package/dist/build-api.coverage.test.js +0 -505
  20. package/dist/cell-formatting.test.d.ts +0 -1
  21. package/dist/cell-formatting.test.js +0 -234
  22. package/dist/cell-render.test.d.ts +0 -1
  23. package/dist/cell-render.test.js +0 -338
  24. package/dist/chart-export.test.d.ts +0 -1
  25. package/dist/chart-export.test.js +0 -302
  26. package/dist/chart.coverage.test.d.ts +0 -1
  27. package/dist/chart.coverage.test.js +0 -748
  28. package/dist/chart.test.d.ts +0 -1
  29. package/dist/chart.test.js +0 -289
  30. package/dist/clipboard.test.d.ts +0 -1
  31. package/dist/clipboard.test.js +0 -700
  32. package/dist/collaboration.coverage.test.d.ts +0 -1
  33. package/dist/collaboration.coverage.test.js +0 -200
  34. package/dist/collaboration.test.d.ts +0 -1
  35. package/dist/collaboration.test.js +0 -65
  36. package/dist/column-groups.test.d.ts +0 -1
  37. package/dist/column-groups.test.js +0 -56
  38. package/dist/column-types.test.d.ts +0 -1
  39. package/dist/column-types.test.js +0 -62
  40. package/dist/columns.test.d.ts +0 -1
  41. package/dist/columns.test.js +0 -625
  42. package/dist/conditional-formatting.test.d.ts +0 -1
  43. package/dist/conditional-formatting.test.js +0 -87
  44. package/dist/core.coverage.test.d.ts +0 -1
  45. package/dist/core.coverage.test.js +0 -186
  46. package/dist/core.performance.test.d.ts +0 -1
  47. package/dist/core.performance.test.js +0 -29
  48. package/dist/createGrid.test.d.ts +0 -1
  49. package/dist/createGrid.test.js +0 -9
  50. package/dist/editing.test.d.ts +0 -1
  51. package/dist/editing.test.js +0 -732
  52. package/dist/editors/cell-editors.coverage.test.d.ts +0 -1
  53. package/dist/editors/cell-editors.coverage.test.js +0 -139
  54. package/dist/editors/cell-editors.test.d.ts +0 -1
  55. package/dist/editors/cell-editors.test.js +0 -75
  56. package/dist/facet-buckets.test.d.ts +0 -1
  57. package/dist/facet-buckets.test.js +0 -296
  58. package/dist/fill-patterns.test.d.ts +0 -1
  59. package/dist/fill-patterns.test.js +0 -81
  60. package/dist/filter-operators.test.d.ts +0 -1
  61. package/dist/filter-operators.test.js +0 -135
  62. package/dist/filtering/excel-filters.test.d.ts +0 -1
  63. package/dist/filtering/excel-filters.test.js +0 -116
  64. package/dist/filtering/locale-filter.test.d.ts +0 -13
  65. package/dist/filtering/locale-filter.test.js +0 -189
  66. package/dist/flex-render.test.d.ts +0 -1
  67. package/dist/flex-render.test.js +0 -142
  68. package/dist/group-aggregate.test.d.ts +0 -1
  69. package/dist/group-aggregate.test.js +0 -32
  70. package/dist/hyperformula-adapter.test.d.ts +0 -1
  71. package/dist/hyperformula-adapter.test.js +0 -205
  72. package/dist/keyboard-handlers.coverage.test.d.ts +0 -1
  73. package/dist/keyboard-handlers.coverage.test.js +0 -495
  74. package/dist/keyboard.test.d.ts +0 -1
  75. package/dist/keyboard.test.js +0 -27
  76. package/dist/menus.test.d.ts +0 -1
  77. package/dist/menus.test.js +0 -560
  78. package/dist/named-views.coverage.test.d.ts +0 -1
  79. package/dist/named-views.coverage.test.js +0 -180
  80. package/dist/named-views.test.d.ts +0 -1
  81. package/dist/named-views.test.js +0 -57
  82. package/dist/new-features.test.d.ts +0 -1
  83. package/dist/new-features.test.js +0 -183
  84. package/dist/render-component.test.d.ts +0 -1
  85. package/dist/render-component.test.js +0 -41
  86. package/dist/row-drag.test.d.ts +0 -1
  87. package/dist/row-drag.test.js +0 -142
  88. package/dist/row-resize.test.d.ts +0 -1
  89. package/dist/row-resize.test.js +0 -329
  90. package/dist/scroll-sync.test.d.ts +0 -1
  91. package/dist/scroll-sync.test.js +0 -290
  92. package/dist/selection.multi-range.test.d.ts +0 -1
  93. package/dist/selection.multi-range.test.js +0 -55
  94. package/dist/selection.test.d.ts +0 -1
  95. package/dist/selection.test.js +0 -647
  96. package/dist/server-data-source.coverage.test.d.ts +0 -1
  97. package/dist/server-data-source.coverage.test.js +0 -154
  98. package/dist/server-data-source.test.d.ts +0 -1
  99. package/dist/server-data-source.test.js +0 -81
  100. package/dist/sparkline.test.d.ts +0 -1
  101. package/dist/sparkline.test.js +0 -52
  102. package/dist/spreadsheet.test.d.ts +0 -1
  103. package/dist/spreadsheet.test.js +0 -446
  104. package/dist/subscribe.test.d.ts +0 -1
  105. package/dist/subscribe.test.js +0 -93
  106. package/dist/svgrid.api-extensions.test.d.ts +0 -14
  107. package/dist/svgrid.api-extensions.test.js +0 -334
  108. package/dist/svgrid.api.test.d.ts +0 -15
  109. package/dist/svgrid.api.test.js +0 -169
  110. package/dist/svgrid.behavior.test.d.ts +0 -15
  111. package/dist/svgrid.behavior.test.js +0 -625
  112. package/dist/svgrid.column-reorder.test.d.ts +0 -15
  113. package/dist/svgrid.column-reorder.test.js +0 -224
  114. package/dist/svgrid.comments-autocomplete.test.d.ts +0 -1
  115. package/dist/svgrid.comments-autocomplete.test.js +0 -96
  116. package/dist/svgrid.context-menu.test.d.ts +0 -1
  117. package/dist/svgrid.context-menu.test.js +0 -102
  118. package/dist/svgrid.features.test.d.ts +0 -1
  119. package/dist/svgrid.features.test.js +0 -129
  120. package/dist/svgrid.interaction.test.d.ts +0 -9
  121. package/dist/svgrid.interaction.test.js +0 -349
  122. package/dist/svgrid.locale-filtering.test.d.ts +0 -15
  123. package/dist/svgrid.locale-filtering.test.js +0 -261
  124. package/dist/svgrid.new-features.wrapper.test.d.ts +0 -1
  125. package/dist/svgrid.new-features.wrapper.test.js +0 -194
  126. package/dist/svgrid.row-pinning.test.d.ts +0 -14
  127. package/dist/svgrid.row-pinning.test.js +0 -250
  128. package/dist/svgrid.set-filter.test.d.ts +0 -17
  129. package/dist/svgrid.set-filter.test.js +0 -268
  130. package/dist/svgrid.wrapper.test.d.ts +0 -1
  131. package/dist/svgrid.wrapper.test.js +0 -59
  132. package/dist/test-setup.d.ts +0 -1
  133. package/dist/test-setup.js +0 -35
  134. package/dist/transaction.test.d.ts +0 -1
  135. package/dist/transaction.test.js +0 -89
  136. package/dist/virtualization/column-virtualizer.test.d.ts +0 -1
  137. package/dist/virtualization/column-virtualizer.test.js +0 -25
  138. package/dist/virtualization/scroll-scaling.test.d.ts +0 -1
  139. package/dist/virtualization/scroll-scaling.test.js +0 -127
  140. package/dist/virtualization/virtualizer.test.d.ts +0 -1
  141. 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 {};