@svgrid/grid 1.1.2 → 1.2.3
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.
- package/dist/SvGrid.css +294 -16
- package/dist/SvGrid.svelte +4 -1
- package/dist/SvGridDropdown.svelte +7 -3
- package/dist/cdn/svgrid.js +13506 -0
- package/dist/cdn/svgrid.svelte-external.js +11190 -0
- package/package.json +12 -2
- package/src/SvGrid.css +294 -16
- package/src/SvGrid.svelte +4 -1
- package/src/SvGridDropdown.svelte +7 -3
- package/dist/SvGrid.helpers.test.d.ts +0 -1
- package/dist/SvGrid.helpers.test.js +0 -298
- package/dist/SvGridChart.test.d.ts +0 -1
- package/dist/SvGridChart.test.js +0 -62
- package/dist/a11y.contract.test.d.ts +0 -1
- package/dist/a11y.contract.test.js +0 -39
- package/dist/a11y.test.d.ts +0 -1
- package/dist/a11y.test.js +0 -44
- package/dist/aligned-grids.test.d.ts +0 -1
- package/dist/aligned-grids.test.js +0 -75
- package/dist/build-api.coverage.test.d.ts +0 -20
- package/dist/build-api.coverage.test.js +0 -505
- package/dist/cell-formatting.test.d.ts +0 -1
- package/dist/cell-formatting.test.js +0 -234
- package/dist/cell-render.test.d.ts +0 -1
- package/dist/cell-render.test.js +0 -338
- package/dist/chart-export.test.d.ts +0 -1
- package/dist/chart-export.test.js +0 -302
- package/dist/chart.coverage.test.d.ts +0 -1
- package/dist/chart.coverage.test.js +0 -748
- package/dist/chart.test.d.ts +0 -1
- package/dist/chart.test.js +0 -289
- package/dist/clipboard.test.d.ts +0 -1
- package/dist/clipboard.test.js +0 -700
- package/dist/collaboration.coverage.test.d.ts +0 -1
- package/dist/collaboration.coverage.test.js +0 -200
- package/dist/collaboration.test.d.ts +0 -1
- package/dist/collaboration.test.js +0 -65
- package/dist/column-groups.test.d.ts +0 -1
- package/dist/column-groups.test.js +0 -56
- package/dist/column-types.test.d.ts +0 -1
- package/dist/column-types.test.js +0 -62
- package/dist/columns.test.d.ts +0 -1
- package/dist/columns.test.js +0 -625
- package/dist/conditional-formatting.test.d.ts +0 -1
- package/dist/conditional-formatting.test.js +0 -87
- package/dist/core.coverage.test.d.ts +0 -1
- package/dist/core.coverage.test.js +0 -186
- package/dist/core.performance.test.d.ts +0 -1
- package/dist/core.performance.test.js +0 -29
- package/dist/createGrid.test.d.ts +0 -1
- package/dist/createGrid.test.js +0 -9
- package/dist/editing.test.d.ts +0 -1
- package/dist/editing.test.js +0 -732
- package/dist/editors/cell-editors.coverage.test.d.ts +0 -1
- package/dist/editors/cell-editors.coverage.test.js +0 -139
- package/dist/editors/cell-editors.test.d.ts +0 -1
- package/dist/editors/cell-editors.test.js +0 -75
- package/dist/facet-buckets.test.d.ts +0 -1
- package/dist/facet-buckets.test.js +0 -296
- package/dist/fill-patterns.test.d.ts +0 -1
- package/dist/fill-patterns.test.js +0 -81
- package/dist/filter-operators.test.d.ts +0 -1
- package/dist/filter-operators.test.js +0 -135
- package/dist/filtering/excel-filters.test.d.ts +0 -1
- package/dist/filtering/excel-filters.test.js +0 -116
- package/dist/filtering/locale-filter.test.d.ts +0 -13
- package/dist/filtering/locale-filter.test.js +0 -189
- package/dist/flex-render.test.d.ts +0 -1
- package/dist/flex-render.test.js +0 -142
- package/dist/group-aggregate.test.d.ts +0 -1
- package/dist/group-aggregate.test.js +0 -32
- package/dist/hyperformula-adapter.test.d.ts +0 -1
- package/dist/hyperformula-adapter.test.js +0 -205
- package/dist/keyboard-handlers.coverage.test.d.ts +0 -1
- package/dist/keyboard-handlers.coverage.test.js +0 -495
- package/dist/keyboard.test.d.ts +0 -1
- package/dist/keyboard.test.js +0 -27
- package/dist/menus.test.d.ts +0 -1
- package/dist/menus.test.js +0 -560
- package/dist/named-views.coverage.test.d.ts +0 -1
- package/dist/named-views.coverage.test.js +0 -180
- package/dist/named-views.test.d.ts +0 -1
- package/dist/named-views.test.js +0 -57
- package/dist/new-features.test.d.ts +0 -1
- package/dist/new-features.test.js +0 -183
- package/dist/render-component.test.d.ts +0 -1
- package/dist/render-component.test.js +0 -41
- package/dist/row-drag.test.d.ts +0 -1
- package/dist/row-drag.test.js +0 -142
- package/dist/row-resize.test.d.ts +0 -1
- package/dist/row-resize.test.js +0 -329
- package/dist/scroll-sync.test.d.ts +0 -1
- package/dist/scroll-sync.test.js +0 -290
- package/dist/selection.multi-range.test.d.ts +0 -1
- package/dist/selection.multi-range.test.js +0 -55
- package/dist/selection.test.d.ts +0 -1
- package/dist/selection.test.js +0 -647
- package/dist/server-data-source.coverage.test.d.ts +0 -1
- package/dist/server-data-source.coverage.test.js +0 -154
- package/dist/server-data-source.test.d.ts +0 -1
- package/dist/server-data-source.test.js +0 -81
- package/dist/sparkline.test.d.ts +0 -1
- package/dist/sparkline.test.js +0 -52
- package/dist/spreadsheet.test.d.ts +0 -1
- package/dist/spreadsheet.test.js +0 -446
- package/dist/subscribe.test.d.ts +0 -1
- package/dist/subscribe.test.js +0 -93
- package/dist/svgrid.api-extensions.test.d.ts +0 -14
- package/dist/svgrid.api-extensions.test.js +0 -334
- package/dist/svgrid.api.test.d.ts +0 -15
- package/dist/svgrid.api.test.js +0 -169
- package/dist/svgrid.behavior.test.d.ts +0 -15
- package/dist/svgrid.behavior.test.js +0 -625
- package/dist/svgrid.column-reorder.test.d.ts +0 -15
- package/dist/svgrid.column-reorder.test.js +0 -224
- package/dist/svgrid.comments-autocomplete.test.d.ts +0 -1
- package/dist/svgrid.comments-autocomplete.test.js +0 -96
- package/dist/svgrid.context-menu.test.d.ts +0 -1
- package/dist/svgrid.context-menu.test.js +0 -102
- package/dist/svgrid.features.test.d.ts +0 -1
- package/dist/svgrid.features.test.js +0 -129
- package/dist/svgrid.interaction.test.d.ts +0 -9
- package/dist/svgrid.interaction.test.js +0 -349
- package/dist/svgrid.locale-filtering.test.d.ts +0 -15
- package/dist/svgrid.locale-filtering.test.js +0 -261
- package/dist/svgrid.new-features.wrapper.test.d.ts +0 -1
- package/dist/svgrid.new-features.wrapper.test.js +0 -194
- package/dist/svgrid.row-pinning.test.d.ts +0 -14
- package/dist/svgrid.row-pinning.test.js +0 -250
- package/dist/svgrid.set-filter.test.d.ts +0 -17
- package/dist/svgrid.set-filter.test.js +0 -268
- package/dist/svgrid.wrapper.test.d.ts +0 -1
- package/dist/svgrid.wrapper.test.js +0 -59
- package/dist/test-setup.d.ts +0 -1
- package/dist/test-setup.js +0 -35
- package/dist/transaction.test.d.ts +0 -1
- package/dist/transaction.test.js +0 -89
- package/dist/virtualization/column-virtualizer.test.d.ts +0 -1
- package/dist/virtualization/column-virtualizer.test.js +0 -25
- package/dist/virtualization/scroll-scaling.test.d.ts +0 -1
- package/dist/virtualization/scroll-scaling.test.js +0 -127
- package/dist/virtualization/virtualizer.test.d.ts +0 -1
- 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 {};
|
package/dist/named-views.test.js
DELETED
|
@@ -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
|
-
});
|
package/dist/row-drag.test.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/dist/row-drag.test.js
DELETED
|
@@ -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 {};
|