@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.
- package/dist/SvGrid.svelte +3 -1
- package/dist/SvGridDropdown.svelte +7 -3
- package/dist/cdn/svgrid.js +13504 -0
- package/dist/cdn/svgrid.svelte-external.js +11188 -0
- package/package.json +12 -2
- package/src/SvGrid.svelte +3 -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,268 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Component tests for `api.setFacetFilter()` - the imperative entry
|
|
3
|
-
* point that powers the three set-filter UX patterns (Excel-style
|
|
4
|
-
* column-menu, async value loaders, and tree-list cascades). The grid
|
|
5
|
-
* is filter-source-agnostic; this test exercises the API directly so
|
|
6
|
-
* coverage is independent of any specific UI implementation.
|
|
7
|
-
*
|
|
8
|
-
* Covers:
|
|
9
|
-
* - Single column facet filter narrows the displayed rows
|
|
10
|
-
* - Multi-value filter (OR within column) accepts every listed value
|
|
11
|
-
* - Passing null / empty clears the filter
|
|
12
|
-
* - Two columns AND across (different columns)
|
|
13
|
-
* - Combines with operator filter from setFilter()
|
|
14
|
-
* - Survives unknown values (no crash, just no matches)
|
|
15
|
-
* - Unicode / non-ASCII values match exactly (no normalization)
|
|
16
|
-
*/
|
|
17
|
-
import { describe, expect, it } from 'vitest';
|
|
18
|
-
import { mount, unmount } from 'svelte';
|
|
19
|
-
import SvGrid from './SvGrid.svelte';
|
|
20
|
-
import { columnFilteringFeature, createCoreRowModel, createFilteredRowModel, createSortedRowModel, rowSortingFeature, sortFns, tableFeatures, } from './index';
|
|
21
|
-
const features = tableFeatures({ columnFilteringFeature, rowSortingFeature });
|
|
22
|
-
const rows = [
|
|
23
|
-
{ id: 'O-1', region: 'Americas', country: 'United States', status: 'open', amount: 120 },
|
|
24
|
-
{ id: 'O-2', region: 'Americas', country: 'United States', status: 'paid', amount: 240 },
|
|
25
|
-
{ id: 'O-3', region: 'Americas', country: 'Canada', status: 'delivered', amount: 410 },
|
|
26
|
-
{ id: 'O-4', region: 'Americas', country: 'Brazil', status: 'shipped', amount: 180 },
|
|
27
|
-
{ id: 'O-5', region: 'EMEA', country: 'Germany', status: 'paid', amount: 330 },
|
|
28
|
-
{ id: 'O-6', region: 'EMEA', country: 'Germany', status: 'returned', amount: 205 },
|
|
29
|
-
{ id: 'O-7', region: 'EMEA', country: 'France', status: 'open', amount: 155 },
|
|
30
|
-
{ id: 'O-8', region: 'EMEA', country: 'France', status: 'shipped', amount: 420 },
|
|
31
|
-
{ id: 'O-9', region: 'APAC', country: 'Japan', status: 'paid', amount: 890 },
|
|
32
|
-
{ id: 'O-10', region: 'APAC', country: 'Japan', status: 'returned', amount: 240 },
|
|
33
|
-
{ id: 'O-11', region: 'APAC', country: 'Australia', status: 'open', amount: 175 },
|
|
34
|
-
{ id: 'O-12', region: 'APAC', country: 'Singapore', status: 'delivered', amount: 1200 },
|
|
35
|
-
];
|
|
36
|
-
const cols = [
|
|
37
|
-
{ field: 'id', header: 'ID', width: 90 },
|
|
38
|
-
{ field: 'region', header: 'Region', width: 110 },
|
|
39
|
-
{ field: 'country', header: 'Country', width: 160 },
|
|
40
|
-
{ field: 'status', header: 'Status', width: 110 },
|
|
41
|
-
{ field: 'amount', header: 'Amount', width: 100, align: 'right' },
|
|
42
|
-
];
|
|
43
|
-
function mountGrid() {
|
|
44
|
-
return new Promise((res, rej) => {
|
|
45
|
-
const target = document.createElement('div');
|
|
46
|
-
document.body.appendChild(target);
|
|
47
|
-
let api = null;
|
|
48
|
-
const app = mount(SvGrid, {
|
|
49
|
-
target,
|
|
50
|
-
props: {
|
|
51
|
-
data: rows,
|
|
52
|
-
columns: cols,
|
|
53
|
-
features,
|
|
54
|
-
_rowModels: {
|
|
55
|
-
coreRowModel: createCoreRowModel(),
|
|
56
|
-
filteredRowModel: createFilteredRowModel(),
|
|
57
|
-
sortedRowModel: createSortedRowModel(sortFns),
|
|
58
|
-
},
|
|
59
|
-
containerHeight: 360,
|
|
60
|
-
virtualization: false,
|
|
61
|
-
columnVirtualization: false,
|
|
62
|
-
showPagination: false,
|
|
63
|
-
showColumnFilters: false,
|
|
64
|
-
showGlobalFilter: false,
|
|
65
|
-
showRowSelection: false,
|
|
66
|
-
onApiReady(received) {
|
|
67
|
-
api = received;
|
|
68
|
-
res({ api, destroy: () => { unmount(app); target.remove(); } });
|
|
69
|
-
},
|
|
70
|
-
},
|
|
71
|
-
});
|
|
72
|
-
queueMicrotask(() => { if (!api)
|
|
73
|
-
rej(new Error('onApiReady never fired')); });
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
const tick = () => new Promise((r) => queueMicrotask(r));
|
|
77
|
-
describe('SvGridApi.setFacetFilter - single column', () => {
|
|
78
|
-
it('narrows to one allowed value', async () => {
|
|
79
|
-
const { api, destroy } = await mountGrid();
|
|
80
|
-
try {
|
|
81
|
-
api.setFacetFilter('region', ['EMEA']);
|
|
82
|
-
await tick();
|
|
83
|
-
const ids = api.getDisplayedRows().map((r) => r.id);
|
|
84
|
-
expect(ids).toEqual(['O-5', 'O-6', 'O-7', 'O-8']);
|
|
85
|
-
}
|
|
86
|
-
finally {
|
|
87
|
-
destroy();
|
|
88
|
-
}
|
|
89
|
-
});
|
|
90
|
-
it('accepts multiple values (OR within column)', async () => {
|
|
91
|
-
const { api, destroy } = await mountGrid();
|
|
92
|
-
try {
|
|
93
|
-
api.setFacetFilter('region', ['EMEA', 'APAC']);
|
|
94
|
-
await tick();
|
|
95
|
-
const regions = new Set(api.getDisplayedRows().map((r) => r.region));
|
|
96
|
-
expect(regions).toEqual(new Set(['EMEA', 'APAC']));
|
|
97
|
-
expect(api.getDisplayedRows().length).toBe(8);
|
|
98
|
-
}
|
|
99
|
-
finally {
|
|
100
|
-
destroy();
|
|
101
|
-
}
|
|
102
|
-
});
|
|
103
|
-
it('empty allowed array filters everything out', async () => {
|
|
104
|
-
// An empty SET is treated as a clear (matches the implementation -
|
|
105
|
-
// see `setFacetFilter`'s null branch). Verify the documented
|
|
106
|
-
// behavior: empty array clears.
|
|
107
|
-
const { api, destroy } = await mountGrid();
|
|
108
|
-
try {
|
|
109
|
-
api.setFacetFilter('region', []);
|
|
110
|
-
await tick();
|
|
111
|
-
expect(api.getDisplayedRows().length).toBe(rows.length);
|
|
112
|
-
}
|
|
113
|
-
finally {
|
|
114
|
-
destroy();
|
|
115
|
-
}
|
|
116
|
-
});
|
|
117
|
-
it('null clears the filter', async () => {
|
|
118
|
-
const { api, destroy } = await mountGrid();
|
|
119
|
-
try {
|
|
120
|
-
api.setFacetFilter('region', ['EMEA']);
|
|
121
|
-
await tick();
|
|
122
|
-
expect(api.getDisplayedRows().length).toBe(4);
|
|
123
|
-
api.setFacetFilter('region', null);
|
|
124
|
-
await tick();
|
|
125
|
-
expect(api.getDisplayedRows().length).toBe(rows.length);
|
|
126
|
-
}
|
|
127
|
-
finally {
|
|
128
|
-
destroy();
|
|
129
|
-
}
|
|
130
|
-
});
|
|
131
|
-
});
|
|
132
|
-
describe('SvGridApi.setFacetFilter - multi-column AND', () => {
|
|
133
|
-
it('region + status both narrow', async () => {
|
|
134
|
-
const { api, destroy } = await mountGrid();
|
|
135
|
-
try {
|
|
136
|
-
api.setFacetFilter('region', ['EMEA']);
|
|
137
|
-
api.setFacetFilter('status', ['paid', 'returned']);
|
|
138
|
-
await tick();
|
|
139
|
-
const ids = api.getDisplayedRows().map((r) => r.id);
|
|
140
|
-
expect(ids).toEqual(['O-5', 'O-6']); // EMEA rows whose status is paid or returned
|
|
141
|
-
}
|
|
142
|
-
finally {
|
|
143
|
-
destroy();
|
|
144
|
-
}
|
|
145
|
-
});
|
|
146
|
-
it('region + country + status all compose', async () => {
|
|
147
|
-
const { api, destroy } = await mountGrid();
|
|
148
|
-
try {
|
|
149
|
-
api.setFacetFilter('region', ['APAC']);
|
|
150
|
-
api.setFacetFilter('country', ['Japan', 'Singapore']);
|
|
151
|
-
api.setFacetFilter('status', ['delivered']);
|
|
152
|
-
await tick();
|
|
153
|
-
const ids = api.getDisplayedRows().map((r) => r.id);
|
|
154
|
-
expect(ids).toEqual(['O-12']); // APAC + Singapore + delivered
|
|
155
|
-
}
|
|
156
|
-
finally {
|
|
157
|
-
destroy();
|
|
158
|
-
}
|
|
159
|
-
});
|
|
160
|
-
});
|
|
161
|
-
describe('SvGridApi.setFacetFilter - composition with operator filters', () => {
|
|
162
|
-
it('facet narrows; setFilter further narrows on the same column id', async () => {
|
|
163
|
-
const { api, destroy } = await mountGrid();
|
|
164
|
-
try {
|
|
165
|
-
api.setFacetFilter('region', ['Americas', 'EMEA']);
|
|
166
|
-
api.setFilter('country', { operator: 'startsWith', value: 'g' }); // → Germany
|
|
167
|
-
await tick();
|
|
168
|
-
const ids = api.getDisplayedRows().map((r) => r.id);
|
|
169
|
-
expect(ids).toEqual(['O-5', 'O-6']);
|
|
170
|
-
}
|
|
171
|
-
finally {
|
|
172
|
-
destroy();
|
|
173
|
-
}
|
|
174
|
-
});
|
|
175
|
-
it('clearing the facet but keeping setFilter only honors setFilter', async () => {
|
|
176
|
-
const { api, destroy } = await mountGrid();
|
|
177
|
-
try {
|
|
178
|
-
api.setFacetFilter('region', ['EMEA']);
|
|
179
|
-
api.setFilter('country', { operator: 'contains', value: 'fran' });
|
|
180
|
-
await tick();
|
|
181
|
-
expect(api.getDisplayedRows().map((r) => r.id)).toEqual(['O-7', 'O-8']);
|
|
182
|
-
api.setFacetFilter('region', null);
|
|
183
|
-
await tick();
|
|
184
|
-
expect(api.getDisplayedRows().map((r) => r.id)).toEqual(['O-7', 'O-8']);
|
|
185
|
-
}
|
|
186
|
-
finally {
|
|
187
|
-
destroy();
|
|
188
|
-
}
|
|
189
|
-
});
|
|
190
|
-
});
|
|
191
|
-
describe('SvGridApi.setFacetFilter - edge cases', () => {
|
|
192
|
-
it('unknown allowed values silently match nothing', async () => {
|
|
193
|
-
const { api, destroy } = await mountGrid();
|
|
194
|
-
try {
|
|
195
|
-
api.setFacetFilter('status', ['SHIPPED_IN_TRANSIT', 'PENDING_REVIEW']);
|
|
196
|
-
await tick();
|
|
197
|
-
expect(api.getDisplayedRows().length).toBe(0);
|
|
198
|
-
}
|
|
199
|
-
finally {
|
|
200
|
-
destroy();
|
|
201
|
-
}
|
|
202
|
-
});
|
|
203
|
-
it('mixed known + unknown values: known values still match', async () => {
|
|
204
|
-
const { api, destroy } = await mountGrid();
|
|
205
|
-
try {
|
|
206
|
-
api.setFacetFilter('status', ['shipped', 'UNKNOWN_VALUE']);
|
|
207
|
-
await tick();
|
|
208
|
-
const ids = api.getDisplayedRows().map((r) => r.id);
|
|
209
|
-
expect(ids).toEqual(['O-4', 'O-8']);
|
|
210
|
-
}
|
|
211
|
-
finally {
|
|
212
|
-
destroy();
|
|
213
|
-
}
|
|
214
|
-
});
|
|
215
|
-
});
|
|
216
|
-
describe('SvGridApi.setFacetFilter - simulates the tree-list pattern', () => {
|
|
217
|
-
// Demo 111's tree picker resolves a hierarchical selection to a flat
|
|
218
|
-
// list of leaf values, then calls setFacetFilter. These tests
|
|
219
|
-
// exercise that contract.
|
|
220
|
-
it('simulates a tree drop: select Americas → all American countries', async () => {
|
|
221
|
-
const { api, destroy } = await mountGrid();
|
|
222
|
-
try {
|
|
223
|
-
// Leaf descendants of the "Americas" tree node:
|
|
224
|
-
const americansCountries = ['United States', 'Canada', 'Brazil'];
|
|
225
|
-
api.setFacetFilter('country', americansCountries);
|
|
226
|
-
await tick();
|
|
227
|
-
const regions = new Set(api.getDisplayedRows().map((r) => r.region));
|
|
228
|
-
expect(regions).toEqual(new Set(['Americas']));
|
|
229
|
-
}
|
|
230
|
-
finally {
|
|
231
|
-
destroy();
|
|
232
|
-
}
|
|
233
|
-
});
|
|
234
|
-
it('simulates a partial-tree selection: only Japan + Singapore', async () => {
|
|
235
|
-
const { api, destroy } = await mountGrid();
|
|
236
|
-
try {
|
|
237
|
-
api.setFacetFilter('country', ['Japan', 'Singapore']);
|
|
238
|
-
await tick();
|
|
239
|
-
const ids = api.getDisplayedRows().map((r) => r.id);
|
|
240
|
-
expect(ids).toEqual(['O-9', 'O-10', 'O-12']);
|
|
241
|
-
}
|
|
242
|
-
finally {
|
|
243
|
-
destroy();
|
|
244
|
-
}
|
|
245
|
-
});
|
|
246
|
-
});
|
|
247
|
-
describe('SvGridApi.setFacetFilter - simulates the async loader pattern', () => {
|
|
248
|
-
// Demo 111's async card emulates a server returning the distinct
|
|
249
|
-
// emails. The grid is told the final selection via setFacetFilter -
|
|
250
|
-
// there's no special "loading" code path inside the grid, the
|
|
251
|
-
// consumer's UI handles that. Verify the contract works regardless of
|
|
252
|
-
// whether the values were enumerated client-side or server-side.
|
|
253
|
-
it('async-loaded values: select user-resolved subset', async () => {
|
|
254
|
-
const { api, destroy } = await mountGrid();
|
|
255
|
-
try {
|
|
256
|
-
// Pretend the user clicked these from a server-loaded value list.
|
|
257
|
-
const userSelected = ['Germany', 'France'];
|
|
258
|
-
api.setFacetFilter('country', userSelected);
|
|
259
|
-
await tick();
|
|
260
|
-
const regions = new Set(api.getDisplayedRows().map((r) => r.region));
|
|
261
|
-
expect(regions).toEqual(new Set(['EMEA']));
|
|
262
|
-
expect(api.getDisplayedRows().length).toBe(4);
|
|
263
|
-
}
|
|
264
|
-
finally {
|
|
265
|
-
destroy();
|
|
266
|
-
}
|
|
267
|
-
});
|
|
268
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import { readFileSync } from 'node:fs';
|
|
2
|
-
import { resolve } from 'node:path';
|
|
3
|
-
import { describe, expect, it } from 'vitest';
|
|
4
|
-
import SvGrid from './SvGrid.svelte';
|
|
5
|
-
describe('SvGrid wrapper', () => {
|
|
6
|
-
it('exports a production wrapper component', () => {
|
|
7
|
-
expect(SvGrid).toBeDefined();
|
|
8
|
-
});
|
|
9
|
-
it('contains full-wrapper controls and aria-activedescendant wiring', () => {
|
|
10
|
-
// The wrapper spans SvGrid.svelte plus its extracted sibling modules
|
|
11
|
-
// (types + pure helpers); read them together so these "contains X"
|
|
12
|
-
// assertions hold wherever the code physically lives.
|
|
13
|
-
const source = [
|
|
14
|
-
'./SvGrid.svelte',
|
|
15
|
-
'./SvGrid.controller.svelte.ts',
|
|
16
|
-
'./GridMenus.svelte',
|
|
17
|
-
'./GridFooter.svelte',
|
|
18
|
-
'./SvGrid.types.ts',
|
|
19
|
-
'./SvGrid.helpers.ts',
|
|
20
|
-
'./filter-operators.ts',
|
|
21
|
-
'./facet-buckets.ts',
|
|
22
|
-
'./cell-values.ts',
|
|
23
|
-
'./clipboard.ts',
|
|
24
|
-
'./build-api.ts',
|
|
25
|
-
'./columns.ts',
|
|
26
|
-
'./selection.ts',
|
|
27
|
-
'./editing.ts',
|
|
28
|
-
'./cell-render.ts',
|
|
29
|
-
'./menus.ts',
|
|
30
|
-
'./summaries.ts',
|
|
31
|
-
'./keyboard-handlers.ts',
|
|
32
|
-
'./scroll-sync.ts',
|
|
33
|
-
'./features.ts',
|
|
34
|
-
]
|
|
35
|
-
.map((p) => readFileSync(resolve(__dirname, p), 'utf-8'))
|
|
36
|
-
.join('\n');
|
|
37
|
-
expect(source).toContain('Filter all rows');
|
|
38
|
-
expect(source).toContain('Previous');
|
|
39
|
-
expect(source).toContain('role="status"');
|
|
40
|
-
expect(source).toContain('role="alert"');
|
|
41
|
-
expect(source).toContain('activeDescendantId');
|
|
42
|
-
expect(source).toContain('getGridRootA11yProps');
|
|
43
|
-
expect(source).toContain('data-svgrid-header-col');
|
|
44
|
-
expect(source).toContain('data-svgrid-row');
|
|
45
|
-
expect(source).toContain('createSvelteVirtualizer');
|
|
46
|
-
expect(source).toContain('props.virtualization ?? true');
|
|
47
|
-
expect(source).toContain('virtualizer.getVirtualItems()');
|
|
48
|
-
expect(source).toContain('createColumnVirtualizer');
|
|
49
|
-
expect(source).toContain('data-selected-range');
|
|
50
|
-
expect(source).toContain('enableInlineEditing');
|
|
51
|
-
expect(source).toContain('showFilterMenu');
|
|
52
|
-
expect(source).toContain('<tfoot');
|
|
53
|
-
expect(source).toContain('editorType === "number"');
|
|
54
|
-
expect(source).toContain('editorType === "date"');
|
|
55
|
-
expect(source).toContain('editorType === "checkbox"');
|
|
56
|
-
expect(source).toContain('applyExcelFilter');
|
|
57
|
-
expect(source).toContain('showFilterRow');
|
|
58
|
-
});
|
|
59
|
-
});
|
package/dist/test-setup.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/dist/test-setup.js
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
// Vitest setup. jsdom doesn't ship ResizeObserver, IntersectionObserver,
|
|
2
|
-
// or the layout APIs SvGrid touches inside its mount effects. Provide
|
|
3
|
-
// minimal no-op stubs so component mounting completes without crashing.
|
|
4
|
-
//
|
|
5
|
-
// These assign to globals/prototypes the jsdom env doesn't fully type, so
|
|
6
|
-
// the targets are cast to `any`. We deliberately avoid expect-error
|
|
7
|
-
// suppression directives: when a stub happens to line up with the lib types,
|
|
8
|
-
// such a directive becomes "unused" and svelte-check fails the build (an
|
|
9
|
-
// unused expect-error directive is itself an error).
|
|
10
|
-
if (typeof globalThis.ResizeObserver === 'undefined') {
|
|
11
|
-
;
|
|
12
|
-
globalThis.ResizeObserver = class {
|
|
13
|
-
observe() { }
|
|
14
|
-
unobserve() { }
|
|
15
|
-
disconnect() { }
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
|
-
if (typeof globalThis.IntersectionObserver === 'undefined') {
|
|
19
|
-
;
|
|
20
|
-
globalThis.IntersectionObserver = class {
|
|
21
|
-
observe() { }
|
|
22
|
-
unobserve() { }
|
|
23
|
-
disconnect() { }
|
|
24
|
-
takeRecords() {
|
|
25
|
-
return [];
|
|
26
|
-
}
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
// jsdom's HTMLElement.scrollIntoView is a no-op; some grid code calls it
|
|
30
|
-
// during the first effect. Make sure the method exists on every element.
|
|
31
|
-
if (typeof Element !== 'undefined' && !Element.prototype.scrollIntoView) {
|
|
32
|
-
;
|
|
33
|
-
Element.prototype.scrollIntoView = function () { };
|
|
34
|
-
}
|
|
35
|
-
export {};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/dist/transaction.test.js
DELETED
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import { mount, unmount } from 'svelte';
|
|
3
|
-
import SvGrid from './SvGrid.svelte';
|
|
4
|
-
import { createCoreRowModel, createFilteredRowModel, createSortedRowModel, sortFns, tableFeatures, } from './index';
|
|
5
|
-
const features = tableFeatures({});
|
|
6
|
-
const seed = [
|
|
7
|
-
{ id: 'a', symbol: 'AAPL', price: 100 },
|
|
8
|
-
{ id: 'b', symbol: 'MSFT', price: 200 },
|
|
9
|
-
{ id: 'c', symbol: 'NVDA', price: 300 },
|
|
10
|
-
];
|
|
11
|
-
const columns = [
|
|
12
|
-
{ field: 'symbol', header: 'Symbol' },
|
|
13
|
-
{ field: 'price', header: 'Price' },
|
|
14
|
-
];
|
|
15
|
-
function mountGrid() {
|
|
16
|
-
return new Promise((resolve, reject) => {
|
|
17
|
-
const target = document.createElement('div');
|
|
18
|
-
document.body.appendChild(target);
|
|
19
|
-
let captured = null;
|
|
20
|
-
const app = mount(SvGrid, {
|
|
21
|
-
target,
|
|
22
|
-
props: {
|
|
23
|
-
data: seed,
|
|
24
|
-
columns,
|
|
25
|
-
features,
|
|
26
|
-
getRowId: (o) => o.id,
|
|
27
|
-
_rowModels: {
|
|
28
|
-
coreRowModel: createCoreRowModel(),
|
|
29
|
-
filteredRowModel: createFilteredRowModel(),
|
|
30
|
-
sortedRowModel: createSortedRowModel(sortFns),
|
|
31
|
-
},
|
|
32
|
-
containerHeight: 400,
|
|
33
|
-
virtualization: false,
|
|
34
|
-
onApiReady(api) {
|
|
35
|
-
captured = api;
|
|
36
|
-
resolve({ api, destroy: () => { unmount(app); target.remove(); } });
|
|
37
|
-
},
|
|
38
|
-
},
|
|
39
|
-
});
|
|
40
|
-
queueMicrotask(() => { if (!captured)
|
|
41
|
-
reject(new Error('onApiReady never fired')); });
|
|
42
|
-
});
|
|
43
|
-
}
|
|
44
|
-
const tick = () => Promise.resolve();
|
|
45
|
-
describe('api.applyTransaction', () => {
|
|
46
|
-
it('adds, updates (by id), and removes (by id) in one batch', async () => {
|
|
47
|
-
const { api, destroy } = await mountGrid();
|
|
48
|
-
try {
|
|
49
|
-
const result = api.applyTransaction({
|
|
50
|
-
add: [{ id: 'd', symbol: 'AMZN', price: 400 }],
|
|
51
|
-
update: [{ id: 'b', symbol: 'MSFT', price: 222 }],
|
|
52
|
-
remove: ['a'],
|
|
53
|
-
});
|
|
54
|
-
await tick();
|
|
55
|
-
expect(result).toEqual({ added: 1, updated: 1, removed: 1 });
|
|
56
|
-
const data = api.getData();
|
|
57
|
-
expect(data.map((o) => o.id)).toEqual(['b', 'c', 'd']);
|
|
58
|
-
expect(data.find((o) => o.id === 'b')?.price).toBe(222);
|
|
59
|
-
}
|
|
60
|
-
finally {
|
|
61
|
-
destroy();
|
|
62
|
-
}
|
|
63
|
-
});
|
|
64
|
-
it('removes by row reference too', async () => {
|
|
65
|
-
const { api, destroy } = await mountGrid();
|
|
66
|
-
try {
|
|
67
|
-
const ref = api.getData()[2]; // 'c'
|
|
68
|
-
const result = api.applyTransaction({ remove: [ref] });
|
|
69
|
-
await tick();
|
|
70
|
-
expect(result.removed).toBe(1);
|
|
71
|
-
expect(api.getData().map((o) => o.id)).toEqual(['a', 'b']);
|
|
72
|
-
}
|
|
73
|
-
finally {
|
|
74
|
-
destroy();
|
|
75
|
-
}
|
|
76
|
-
});
|
|
77
|
-
it('ignores unknown ids without throwing', async () => {
|
|
78
|
-
const { api, destroy } = await mountGrid();
|
|
79
|
-
try {
|
|
80
|
-
const result = api.applyTransaction({ update: [{ id: 'zzz', symbol: 'X', price: 1 }], remove: ['nope'] });
|
|
81
|
-
await tick();
|
|
82
|
-
expect(result).toEqual({ added: 0, updated: 0, removed: 0 });
|
|
83
|
-
expect(api.getData()).toHaveLength(3);
|
|
84
|
-
}
|
|
85
|
-
finally {
|
|
86
|
-
destroy();
|
|
87
|
-
}
|
|
88
|
-
});
|
|
89
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import { createColumnVirtualizer } from './column-virtualizer';
|
|
3
|
-
describe('column virtualizer', () => {
|
|
4
|
-
it('computes horizontal window', () => {
|
|
5
|
-
const v = createColumnVirtualizer({
|
|
6
|
-
count: 1000,
|
|
7
|
-
viewportWidth: 700,
|
|
8
|
-
overscan: 3,
|
|
9
|
-
estimateSize: () => 140,
|
|
10
|
-
});
|
|
11
|
-
const items = v.getVirtualItems();
|
|
12
|
-
expect(items[0]?.index).toBe(0);
|
|
13
|
-
expect(items.length).toBeGreaterThan(0);
|
|
14
|
-
});
|
|
15
|
-
it('updates indices on horizontal scroll', () => {
|
|
16
|
-
const v = createColumnVirtualizer({
|
|
17
|
-
count: 1000,
|
|
18
|
-
viewportWidth: 700,
|
|
19
|
-
estimateSize: () => 140,
|
|
20
|
-
});
|
|
21
|
-
v.setHorizontalOffset(2800);
|
|
22
|
-
const state = v.getState();
|
|
23
|
-
expect(state.startIndex).toBeGreaterThan(0);
|
|
24
|
-
});
|
|
25
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { createRowScrollScaling, resolveMaxDomHeight } from './scroll-scaling';
|
|
3
|
-
const VIEWPORT = 600;
|
|
4
|
-
const FALLBACK = 8000000;
|
|
5
|
-
describe('createRowScrollScaling - inert (content fits under the cap)', () => {
|
|
6
|
-
// 1M rows * 18px = 18M, under a 33.5M (Chrome) cap -> no scaling.
|
|
7
|
-
const s = createRowScrollScaling(18000000, 33554400, VIEWPORT);
|
|
8
|
-
it('is inactive and exposes the true height as the DOM height', () => {
|
|
9
|
-
expect(s.active).toBe(false);
|
|
10
|
-
expect(s.domTotal).toBe(18000000);
|
|
11
|
-
});
|
|
12
|
-
it('both maps are the identity', () => {
|
|
13
|
-
for (const v of [0, 1, 1234, 9000000, 17999400]) {
|
|
14
|
-
expect(s.domToLogical(v)).toBe(v);
|
|
15
|
-
expect(s.logicalToDom(v)).toBe(v);
|
|
16
|
-
}
|
|
17
|
-
});
|
|
18
|
-
});
|
|
19
|
-
describe('createRowScrollScaling - active (content exceeds the cap)', () => {
|
|
20
|
-
// 1M rows * 32px = 32M, over Firefox's ~17.9M cap -> scaling engages.
|
|
21
|
-
const trueTotal = 32000000;
|
|
22
|
-
const maxDom = 17895697;
|
|
23
|
-
const s = createRowScrollScaling(trueTotal, maxDom, VIEWPORT);
|
|
24
|
-
const domMax = s.domTotal - VIEWPORT;
|
|
25
|
-
const logicalMax = trueTotal - VIEWPORT;
|
|
26
|
-
it('caps the DOM height at the browser limit', () => {
|
|
27
|
-
expect(s.active).toBe(true);
|
|
28
|
-
expect(s.domTotal).toBe(maxDom);
|
|
29
|
-
expect(s.domTotal).toBeLessThanOrEqual(maxDom);
|
|
30
|
-
});
|
|
31
|
-
it('maps the endpoints exactly so the first and last rows are reachable', () => {
|
|
32
|
-
expect(s.domToLogical(0)).toBe(0);
|
|
33
|
-
// The bottom of the DOM scroll range reaches the bottom of the logical range.
|
|
34
|
-
expect(s.domToLogical(domMax)).toBeCloseTo(logicalMax, 3);
|
|
35
|
-
expect(s.logicalToDom(0)).toBe(0);
|
|
36
|
-
expect(s.logicalToDom(logicalMax)).toBeCloseTo(domMax, 3);
|
|
37
|
-
});
|
|
38
|
-
it('clamps out-of-range inputs instead of overshooting', () => {
|
|
39
|
-
expect(s.domToLogical(-100)).toBe(0);
|
|
40
|
-
expect(s.domToLogical(domMax + 10000)).toBeCloseTo(logicalMax, 3);
|
|
41
|
-
expect(s.logicalToDom(-100)).toBe(0);
|
|
42
|
-
expect(s.logicalToDom(logicalMax + 10000)).toBeCloseTo(domMax, 3);
|
|
43
|
-
});
|
|
44
|
-
it('logicalToDom is the inverse of domToLogical across the range', () => {
|
|
45
|
-
for (let i = 0; i <= 10; i += 1) {
|
|
46
|
-
const domTop = (domMax * i) / 10;
|
|
47
|
-
const roundTrip = s.logicalToDom(s.domToLogical(domTop));
|
|
48
|
-
expect(roundTrip).toBeCloseTo(domTop, 2);
|
|
49
|
-
}
|
|
50
|
-
});
|
|
51
|
-
it('both maps are monotonic non-decreasing', () => {
|
|
52
|
-
let prevL = -1;
|
|
53
|
-
let prevD = -1;
|
|
54
|
-
for (let i = 0; i <= 20; i += 1) {
|
|
55
|
-
const domTop = (domMax * i) / 20;
|
|
56
|
-
const logical = s.domToLogical(domTop);
|
|
57
|
-
expect(logical).toBeGreaterThanOrEqual(prevL);
|
|
58
|
-
prevL = logical;
|
|
59
|
-
const logIn = (logicalMax * i) / 20;
|
|
60
|
-
const dom = s.logicalToDom(logIn);
|
|
61
|
-
expect(dom).toBeGreaterThanOrEqual(prevD);
|
|
62
|
-
prevD = dom;
|
|
63
|
-
}
|
|
64
|
-
});
|
|
65
|
-
});
|
|
66
|
-
describe('resolveMaxDomHeight - picks the smaller, real scrollable cap', () => {
|
|
67
|
-
it('keeps the desktop cap when both signals agree', () => {
|
|
68
|
-
// Chrome ~33.5M; offsetHeight and scrollHeight match. 0.5% safety shave.
|
|
69
|
-
const cap = resolveMaxDomHeight(33554400, 33554400, FALLBACK);
|
|
70
|
-
expect(cap).toBeLessThan(33554400);
|
|
71
|
-
expect(cap).toBeGreaterThan(33000000);
|
|
72
|
-
});
|
|
73
|
-
it('uses the SCROLL cap when a phone over-reports offsetHeight', () => {
|
|
74
|
-
// High-DPR mobile: layout says 24M, but the container only scrolls ~6M.
|
|
75
|
-
// Trusting offsetHeight is exactly what stranded the last rows.
|
|
76
|
-
const cap = resolveMaxDomHeight(24000000, 6000000, FALLBACK);
|
|
77
|
-
expect(cap).toBeLessThanOrEqual(6000000);
|
|
78
|
-
expect(cap).toBeGreaterThan(5900000);
|
|
79
|
-
});
|
|
80
|
-
it('shaves a safety margin so the spacer never sits at the exact edge', () => {
|
|
81
|
-
const cap = resolveMaxDomHeight(10000000, 10000000, FALLBACK);
|
|
82
|
-
expect(cap).toBeLessThan(10000000);
|
|
83
|
-
});
|
|
84
|
-
it('falls back when both readings are junk (jsdom returns 0)', () => {
|
|
85
|
-
expect(resolveMaxDomHeight(0, 0, FALLBACK)).toBe(FALLBACK);
|
|
86
|
-
});
|
|
87
|
-
it('falls back when the browser did not clamp (returned ~1e9)', () => {
|
|
88
|
-
expect(resolveMaxDomHeight(1000000000, 1000000000, FALLBACK)).toBe(FALLBACK);
|
|
89
|
-
});
|
|
90
|
-
it('ignores a junk signal but trusts the valid one', () => {
|
|
91
|
-
// offsetHeight unclamped/garbage, scrollHeight is the real cap.
|
|
92
|
-
const cap = resolveMaxDomHeight(1000000000, 5000000, FALLBACK);
|
|
93
|
-
expect(cap).toBeLessThanOrEqual(5000000);
|
|
94
|
-
expect(cap).toBeGreaterThan(4900000);
|
|
95
|
-
});
|
|
96
|
-
it('feeds straight into the scaler so the last row stays reachable', () => {
|
|
97
|
-
const maxDom = resolveMaxDomHeight(24000000, 6000000, FALLBACK);
|
|
98
|
-
const trueTotal = 500000 * 32; // 16M px of rows, over the phone cap
|
|
99
|
-
const s = createRowScrollScaling(trueTotal, maxDom, VIEWPORT);
|
|
100
|
-
expect(s.active).toBe(true);
|
|
101
|
-
expect(s.domTotal).toBeLessThanOrEqual(maxDom);
|
|
102
|
-
const domMax = s.domTotal - VIEWPORT;
|
|
103
|
-
expect(s.domToLogical(domMax)).toBeCloseTo(trueTotal - VIEWPORT, 0);
|
|
104
|
-
});
|
|
105
|
-
});
|
|
106
|
-
describe('createRowScrollScaling - edge cases', () => {
|
|
107
|
-
it('handles an empty grid without dividing by zero', () => {
|
|
108
|
-
const s = createRowScrollScaling(0, 17895697, VIEWPORT);
|
|
109
|
-
expect(s.active).toBe(false);
|
|
110
|
-
expect(s.domTotal).toBe(0);
|
|
111
|
-
expect(s.domToLogical(0)).toBe(0);
|
|
112
|
-
expect(s.logicalToDom(0)).toBe(0);
|
|
113
|
-
});
|
|
114
|
-
it('handles content shorter than the viewport', () => {
|
|
115
|
-
const s = createRowScrollScaling(200, 17895697, VIEWPORT);
|
|
116
|
-
expect(s.active).toBe(false);
|
|
117
|
-
expect(s.domToLogical(0)).toBe(0);
|
|
118
|
-
});
|
|
119
|
-
it('extreme scale (100M rows) keeps the last row reachable', () => {
|
|
120
|
-
const trueTotal = 100000000 * 32; // 3.2 billion px
|
|
121
|
-
const s = createRowScrollScaling(trueTotal, 33554400, VIEWPORT);
|
|
122
|
-
expect(s.active).toBe(true);
|
|
123
|
-
expect(s.domTotal).toBe(33554400);
|
|
124
|
-
const domMax = s.domTotal - VIEWPORT;
|
|
125
|
-
expect(s.domToLogical(domMax)).toBeCloseTo(trueTotal - VIEWPORT, 0);
|
|
126
|
-
});
|
|
127
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import { createVirtualizer } from './virtualizer';
|
|
3
|
-
describe('virtualizer', () => {
|
|
4
|
-
it('computes bounded virtual range with overscan', () => {
|
|
5
|
-
const v = createVirtualizer({
|
|
6
|
-
count: 100000,
|
|
7
|
-
estimateSize: 36,
|
|
8
|
-
overscan: 8,
|
|
9
|
-
viewportHeight: 360,
|
|
10
|
-
scrollOffset: 0,
|
|
11
|
-
});
|
|
12
|
-
const items = v.getVirtualItems();
|
|
13
|
-
expect(items.length).toBeGreaterThan(0);
|
|
14
|
-
expect(items[0]?.index).toBe(0);
|
|
15
|
-
expect(items[items.length - 1]?.index).toBe(18);
|
|
16
|
-
});
|
|
17
|
-
it('updates range when scroll offset changes', () => {
|
|
18
|
-
const v = createVirtualizer({
|
|
19
|
-
count: 100000,
|
|
20
|
-
estimateSize: 40,
|
|
21
|
-
overscan: 5,
|
|
22
|
-
viewportHeight: 400,
|
|
23
|
-
scrollOffset: 0,
|
|
24
|
-
});
|
|
25
|
-
v.setScrollOffset(4000);
|
|
26
|
-
const state = v.getState();
|
|
27
|
-
expect(state.startIndex).toBeLessThanOrEqual(100);
|
|
28
|
-
expect(state.endIndex).toBeGreaterThanOrEqual(110);
|
|
29
|
-
});
|
|
30
|
-
it('scrollToIndex clamps target offset', () => {
|
|
31
|
-
const v = createVirtualizer({
|
|
32
|
-
count: 100,
|
|
33
|
-
estimateSize: 30,
|
|
34
|
-
overscan: 2,
|
|
35
|
-
viewportHeight: 300,
|
|
36
|
-
scrollOffset: 0,
|
|
37
|
-
});
|
|
38
|
-
v.scrollToIndex(9999);
|
|
39
|
-
expect(v.getState().scrollOffset).toBe(2700);
|
|
40
|
-
});
|
|
41
|
-
});
|