@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
package/dist/columns.test.js
DELETED
|
@@ -1,625 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Unit tests for the column handlers produced by createColumns(ctx).
|
|
3
|
-
*
|
|
4
|
-
* createColumns is a factory: it closes over a mutable `ctx` handle (the
|
|
5
|
-
* controller's reactive state in production) and returns imperative event
|
|
6
|
-
* handlers. We drive it here with a plain fake ctx object so we can assert on
|
|
7
|
-
* pinning / ordering / sizing / visibility logic without mounting the grid.
|
|
8
|
-
*/
|
|
9
|
-
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
10
|
-
import { createColumns } from './columns';
|
|
11
|
-
/** A minimally-populated ctx covering every field the handlers read/write. */
|
|
12
|
-
function makeCtx(overrides = {}) {
|
|
13
|
-
const allColumns = [
|
|
14
|
-
{ id: 'a', columnDef: { width: 120 } },
|
|
15
|
-
{ id: 'b', columnDef: {} },
|
|
16
|
-
{ id: 'c', columnDef: { width: 200 } },
|
|
17
|
-
{ id: 'd', columnDef: {} },
|
|
18
|
-
];
|
|
19
|
-
const ctx = {
|
|
20
|
-
allColumns,
|
|
21
|
-
toolPanelColumns: allColumns.map((c) => ({ id: c.id })),
|
|
22
|
-
props: {},
|
|
23
|
-
userColumnOrder: undefined,
|
|
24
|
-
pinnedOffsets: { left: {}, right: {} },
|
|
25
|
-
columnPinning: { left: [], right: [] },
|
|
26
|
-
hiddenColumns: {},
|
|
27
|
-
columnWidths: {},
|
|
28
|
-
fittedColumnWidths: {},
|
|
29
|
-
MIN_COLUMN_WIDTH: 40,
|
|
30
|
-
closeMenus: vi.fn(),
|
|
31
|
-
// drag state
|
|
32
|
-
colDragId: null,
|
|
33
|
-
colDropOnId: null,
|
|
34
|
-
colDropSide: null,
|
|
35
|
-
// resize state
|
|
36
|
-
resizingColumnId: null,
|
|
37
|
-
resizeStartX: 0,
|
|
38
|
-
resizeStartWidth: 0,
|
|
39
|
-
resizePendingWidth: 0,
|
|
40
|
-
resizeRaf: null,
|
|
41
|
-
measureCanvas: null,
|
|
42
|
-
gridRootEl: null,
|
|
43
|
-
grid: {
|
|
44
|
-
getAllColumns: () => allColumns,
|
|
45
|
-
getState: () => ({ grouping: [] }),
|
|
46
|
-
setGrouping: vi.fn(),
|
|
47
|
-
},
|
|
48
|
-
...overrides,
|
|
49
|
-
};
|
|
50
|
-
return ctx;
|
|
51
|
-
}
|
|
52
|
-
describe('cellPinStyle', () => {
|
|
53
|
-
it('returns a left sticky style when the column has a left offset', () => {
|
|
54
|
-
const ctx = makeCtx({ pinnedOffsets: { left: { a: 0, b: 120 }, right: {} } });
|
|
55
|
-
const h = createColumns(ctx);
|
|
56
|
-
expect(h.cellPinStyle('a')).toBe('position: sticky; left: 0px; z-index: 30;');
|
|
57
|
-
expect(h.cellPinStyle('b')).toBe('position: sticky; left: 120px; z-index: 30;');
|
|
58
|
-
});
|
|
59
|
-
it('returns a right sticky style when the column has a right offset', () => {
|
|
60
|
-
const ctx = makeCtx({ pinnedOffsets: { left: {}, right: { d: 0 } } });
|
|
61
|
-
const h = createColumns(ctx);
|
|
62
|
-
expect(h.cellPinStyle('d')).toBe('position: sticky; right: 0px; z-index: 30;');
|
|
63
|
-
});
|
|
64
|
-
it('returns empty string for an unpinned column', () => {
|
|
65
|
-
const h = createColumns(makeCtx());
|
|
66
|
-
expect(h.cellPinStyle('a')).toBe('');
|
|
67
|
-
});
|
|
68
|
-
it('prefers the left offset when a column appears in both maps', () => {
|
|
69
|
-
const ctx = makeCtx({ pinnedOffsets: { left: { a: 5 }, right: { a: 9 } } });
|
|
70
|
-
const h = createColumns(ctx);
|
|
71
|
-
expect(h.cellPinStyle('a')).toBe('position: sticky; left: 5px; z-index: 30;');
|
|
72
|
-
});
|
|
73
|
-
});
|
|
74
|
-
describe('isColumnPinned', () => {
|
|
75
|
-
it('reports left / right / null', () => {
|
|
76
|
-
const ctx = makeCtx({ columnPinning: { left: ['a'], right: ['d'] } });
|
|
77
|
-
const h = createColumns(ctx);
|
|
78
|
-
expect(h.isColumnPinned('a')).toBe('left');
|
|
79
|
-
expect(h.isColumnPinned('d')).toBe('right');
|
|
80
|
-
expect(h.isColumnPinned('b')).toBeNull();
|
|
81
|
-
});
|
|
82
|
-
});
|
|
83
|
-
describe('getCurrentColumnOrder / emitColumnOrder / setColumnOrderInternal', () => {
|
|
84
|
-
it('reads the order from allColumns ids', () => {
|
|
85
|
-
const h = createColumns(makeCtx());
|
|
86
|
-
expect(h.getCurrentColumnOrder()).toEqual(['a', 'b', 'c', 'd']);
|
|
87
|
-
});
|
|
88
|
-
it('emitColumnOrder invokes the callback with the current order', () => {
|
|
89
|
-
const onColumnOrderChange = vi.fn();
|
|
90
|
-
const h = createColumns(makeCtx({ props: { onColumnOrderChange } }));
|
|
91
|
-
h.emitColumnOrder();
|
|
92
|
-
expect(onColumnOrderChange).toHaveBeenCalledWith(['a', 'b', 'c', 'd']);
|
|
93
|
-
});
|
|
94
|
-
it('emitColumnOrder is a no-op when no callback is provided', () => {
|
|
95
|
-
const h = createColumns(makeCtx());
|
|
96
|
-
expect(() => h.emitColumnOrder()).not.toThrow();
|
|
97
|
-
});
|
|
98
|
-
it('setColumnOrderInternal writes userColumnOrder and emits', () => {
|
|
99
|
-
const onColumnOrderChange = vi.fn();
|
|
100
|
-
const ctx = makeCtx({ props: { onColumnOrderChange } });
|
|
101
|
-
const h = createColumns(ctx);
|
|
102
|
-
h.setColumnOrderInternal(['d', 'c', 'b', 'a']);
|
|
103
|
-
expect(ctx.userColumnOrder).toEqual(['d', 'c', 'b', 'a']);
|
|
104
|
-
expect(onColumnOrderChange).toHaveBeenCalledWith(['a', 'b', 'c', 'd']);
|
|
105
|
-
});
|
|
106
|
-
});
|
|
107
|
-
describe('applyColumnDrop', () => {
|
|
108
|
-
let ctx;
|
|
109
|
-
let h;
|
|
110
|
-
beforeEach(() => {
|
|
111
|
-
ctx = makeCtx();
|
|
112
|
-
h = createColumns(ctx);
|
|
113
|
-
});
|
|
114
|
-
it('does nothing when dragId === targetId', () => {
|
|
115
|
-
h.applyColumnDrop('a', 'a', 'before');
|
|
116
|
-
expect(ctx.userColumnOrder).toBeUndefined();
|
|
117
|
-
});
|
|
118
|
-
it('drops before the target', () => {
|
|
119
|
-
h.applyColumnDrop('a', 'c', 'before');
|
|
120
|
-
expect(ctx.userColumnOrder).toEqual(['b', 'a', 'c', 'd']);
|
|
121
|
-
});
|
|
122
|
-
it('drops after the target', () => {
|
|
123
|
-
h.applyColumnDrop('a', 'c', 'after');
|
|
124
|
-
expect(ctx.userColumnOrder).toEqual(['b', 'c', 'a', 'd']);
|
|
125
|
-
});
|
|
126
|
-
it('moving the last column to the front', () => {
|
|
127
|
-
h.applyColumnDrop('d', 'a', 'before');
|
|
128
|
-
expect(ctx.userColumnOrder).toEqual(['d', 'a', 'b', 'c']);
|
|
129
|
-
});
|
|
130
|
-
it('does nothing when the target id is unknown', () => {
|
|
131
|
-
h.applyColumnDrop('a', 'zzz', 'before');
|
|
132
|
-
expect(ctx.userColumnOrder).toBeUndefined();
|
|
133
|
-
});
|
|
134
|
-
});
|
|
135
|
-
describe('column header drag handlers', () => {
|
|
136
|
-
function dragEvent() {
|
|
137
|
-
return {
|
|
138
|
-
preventDefault: vi.fn(),
|
|
139
|
-
dataTransfer: { setData: vi.fn(), effectAllowed: '', dropEffect: '' },
|
|
140
|
-
clientX: 0,
|
|
141
|
-
currentTarget: {
|
|
142
|
-
getBoundingClientRect: () => ({ left: 0, width: 100 }),
|
|
143
|
-
},
|
|
144
|
-
};
|
|
145
|
-
}
|
|
146
|
-
it('dragStart is gated off unless enableColumnReorder', () => {
|
|
147
|
-
const ctx = makeCtx();
|
|
148
|
-
const h = createColumns(ctx);
|
|
149
|
-
const e = dragEvent();
|
|
150
|
-
h.onColumnHeaderDragStart(e, 'a');
|
|
151
|
-
expect(ctx.colDragId).toBeNull();
|
|
152
|
-
expect(e.dataTransfer.setData).not.toHaveBeenCalled();
|
|
153
|
-
});
|
|
154
|
-
it('dragStart records the drag id when reorder is enabled', () => {
|
|
155
|
-
const ctx = makeCtx({ props: { enableColumnReorder: true } });
|
|
156
|
-
const h = createColumns(ctx);
|
|
157
|
-
const e = dragEvent();
|
|
158
|
-
h.onColumnHeaderDragStart(e, 'a');
|
|
159
|
-
expect(ctx.colDragId).toBe('a');
|
|
160
|
-
expect(e.dataTransfer.setData).toHaveBeenCalledWith('text/plain', 'a');
|
|
161
|
-
expect(e.dataTransfer.effectAllowed).toBe('move');
|
|
162
|
-
});
|
|
163
|
-
it('dragOver does nothing when no drag is active or over self', () => {
|
|
164
|
-
const ctx = makeCtx();
|
|
165
|
-
const h = createColumns(ctx);
|
|
166
|
-
const e = dragEvent();
|
|
167
|
-
h.onColumnHeaderDragOver(e, 'b');
|
|
168
|
-
expect(e.preventDefault).not.toHaveBeenCalled();
|
|
169
|
-
ctx.colDragId = 'b';
|
|
170
|
-
h.onColumnHeaderDragOver(e, 'b'); // over the dragged column itself
|
|
171
|
-
expect(e.preventDefault).not.toHaveBeenCalled();
|
|
172
|
-
});
|
|
173
|
-
it('dragOver sets side=before on the left half', () => {
|
|
174
|
-
const ctx = makeCtx({ colDragId: 'a' });
|
|
175
|
-
const h = createColumns(ctx);
|
|
176
|
-
const e = dragEvent();
|
|
177
|
-
e.clientX = 10; // < left + width/2 (50)
|
|
178
|
-
h.onColumnHeaderDragOver(e, 'b');
|
|
179
|
-
expect(e.preventDefault).toHaveBeenCalled();
|
|
180
|
-
expect(ctx.colDropSide).toBe('before');
|
|
181
|
-
expect(ctx.colDropOnId).toBe('b');
|
|
182
|
-
});
|
|
183
|
-
it('dragOver sets side=after on the right half', () => {
|
|
184
|
-
const ctx = makeCtx({ colDragId: 'a' });
|
|
185
|
-
const h = createColumns(ctx);
|
|
186
|
-
const e = dragEvent();
|
|
187
|
-
e.clientX = 80; // > 50
|
|
188
|
-
h.onColumnHeaderDragOver(e, 'b');
|
|
189
|
-
expect(ctx.colDropSide).toBe('after');
|
|
190
|
-
});
|
|
191
|
-
it('dragLeave clears state only for the matching column', () => {
|
|
192
|
-
const ctx = makeCtx({ colDropOnId: 'b', colDropSide: 'after' });
|
|
193
|
-
const h = createColumns(ctx);
|
|
194
|
-
h.onColumnHeaderDragLeave('a'); // different column - no change
|
|
195
|
-
expect(ctx.colDropOnId).toBe('b');
|
|
196
|
-
h.onColumnHeaderDragLeave('b');
|
|
197
|
-
expect(ctx.colDropOnId).toBeNull();
|
|
198
|
-
expect(ctx.colDropSide).toBeNull();
|
|
199
|
-
});
|
|
200
|
-
it('drop applies the pending reorder then clears drag state', () => {
|
|
201
|
-
const ctx = makeCtx({ colDragId: 'a', colDropSide: 'after' });
|
|
202
|
-
const h = createColumns(ctx);
|
|
203
|
-
const e = dragEvent();
|
|
204
|
-
h.onColumnHeaderDrop(e, 'c');
|
|
205
|
-
expect(e.preventDefault).toHaveBeenCalled();
|
|
206
|
-
expect(ctx.userColumnOrder).toEqual(['b', 'c', 'a', 'd']);
|
|
207
|
-
expect(ctx.colDragId).toBeNull();
|
|
208
|
-
expect(ctx.colDropOnId).toBeNull();
|
|
209
|
-
expect(ctx.colDropSide).toBeNull();
|
|
210
|
-
});
|
|
211
|
-
it('drop without an active drag just clears state', () => {
|
|
212
|
-
const ctx = makeCtx();
|
|
213
|
-
const h = createColumns(ctx);
|
|
214
|
-
const e = dragEvent();
|
|
215
|
-
h.onColumnHeaderDrop(e, 'c');
|
|
216
|
-
expect(ctx.userColumnOrder).toBeUndefined();
|
|
217
|
-
expect(ctx.colDragId).toBeNull();
|
|
218
|
-
});
|
|
219
|
-
it('dragEnd clears all drag state', () => {
|
|
220
|
-
const ctx = makeCtx({ colDragId: 'a', colDropOnId: 'b', colDropSide: 'after' });
|
|
221
|
-
const h = createColumns(ctx);
|
|
222
|
-
h.onColumnHeaderDragEnd();
|
|
223
|
-
expect(ctx.colDragId).toBeNull();
|
|
224
|
-
expect(ctx.colDropOnId).toBeNull();
|
|
225
|
-
expect(ctx.colDropSide).toBeNull();
|
|
226
|
-
});
|
|
227
|
-
});
|
|
228
|
-
describe('pinning handlers', () => {
|
|
229
|
-
it('pinColumnLeft appends to left, removes from right, closes menus', () => {
|
|
230
|
-
const ctx = makeCtx({ columnPinning: { left: ['a'], right: ['b'] } });
|
|
231
|
-
const h = createColumns(ctx);
|
|
232
|
-
h.pinColumnLeft('b');
|
|
233
|
-
expect(ctx.columnPinning).toEqual({ left: ['a', 'b'], right: [] });
|
|
234
|
-
expect(ctx.closeMenus).toHaveBeenCalled();
|
|
235
|
-
});
|
|
236
|
-
it('pinColumnLeft de-duplicates a column already pinned left', () => {
|
|
237
|
-
const ctx = makeCtx({ columnPinning: { left: ['a', 'b'], right: [] } });
|
|
238
|
-
const h = createColumns(ctx);
|
|
239
|
-
h.pinColumnLeft('a');
|
|
240
|
-
expect(ctx.columnPinning).toEqual({ left: ['b', 'a'], right: [] });
|
|
241
|
-
});
|
|
242
|
-
it('pinColumnRight prepends to right, removes from left', () => {
|
|
243
|
-
const ctx = makeCtx({ columnPinning: { left: ['a'], right: ['c'] } });
|
|
244
|
-
const h = createColumns(ctx);
|
|
245
|
-
h.pinColumnRight('a');
|
|
246
|
-
expect(ctx.columnPinning).toEqual({ left: [], right: ['a', 'c'] });
|
|
247
|
-
expect(ctx.closeMenus).toHaveBeenCalled();
|
|
248
|
-
});
|
|
249
|
-
it('unpinColumn removes from both sides', () => {
|
|
250
|
-
const ctx = makeCtx({ columnPinning: { left: ['a'], right: ['a'] } });
|
|
251
|
-
const h = createColumns(ctx);
|
|
252
|
-
h.unpinColumn('a');
|
|
253
|
-
expect(ctx.columnPinning).toEqual({ left: [], right: [] });
|
|
254
|
-
expect(ctx.closeMenus).toHaveBeenCalled();
|
|
255
|
-
});
|
|
256
|
-
});
|
|
257
|
-
describe('toggleColumnVisibleInPanel', () => {
|
|
258
|
-
it('hides a visible column', () => {
|
|
259
|
-
const ctx = makeCtx();
|
|
260
|
-
const h = createColumns(ctx);
|
|
261
|
-
h.toggleColumnVisibleInPanel('a');
|
|
262
|
-
expect(ctx.hiddenColumns).toEqual({ a: true });
|
|
263
|
-
});
|
|
264
|
-
it('shows a hidden column (removes the key)', () => {
|
|
265
|
-
const ctx = makeCtx({ hiddenColumns: { a: true, b: true } });
|
|
266
|
-
const h = createColumns(ctx);
|
|
267
|
-
h.toggleColumnVisibleInPanel('a');
|
|
268
|
-
expect(ctx.hiddenColumns).toEqual({ b: true });
|
|
269
|
-
});
|
|
270
|
-
});
|
|
271
|
-
describe('moveColumnInPanel', () => {
|
|
272
|
-
it('moves a column down (dir +1)', () => {
|
|
273
|
-
const ctx = makeCtx();
|
|
274
|
-
const h = createColumns(ctx);
|
|
275
|
-
h.moveColumnInPanel('a', 1);
|
|
276
|
-
expect(ctx.userColumnOrder).toEqual(['b', 'a', 'c', 'd']);
|
|
277
|
-
});
|
|
278
|
-
it('moves a column up (dir -1)', () => {
|
|
279
|
-
const ctx = makeCtx();
|
|
280
|
-
const h = createColumns(ctx);
|
|
281
|
-
h.moveColumnInPanel('c', -1);
|
|
282
|
-
expect(ctx.userColumnOrder).toEqual(['a', 'c', 'b', 'd']);
|
|
283
|
-
});
|
|
284
|
-
it('refuses to move the first column up (out of bounds)', () => {
|
|
285
|
-
const ctx = makeCtx();
|
|
286
|
-
const h = createColumns(ctx);
|
|
287
|
-
h.moveColumnInPanel('a', -1);
|
|
288
|
-
expect(ctx.userColumnOrder).toBeUndefined();
|
|
289
|
-
});
|
|
290
|
-
it('refuses to move the last column down (out of bounds)', () => {
|
|
291
|
-
const ctx = makeCtx();
|
|
292
|
-
const h = createColumns(ctx);
|
|
293
|
-
h.moveColumnInPanel('d', 1);
|
|
294
|
-
expect(ctx.userColumnOrder).toBeUndefined();
|
|
295
|
-
});
|
|
296
|
-
it('does nothing for an unknown column id', () => {
|
|
297
|
-
const ctx = makeCtx();
|
|
298
|
-
const h = createColumns(ctx);
|
|
299
|
-
h.moveColumnInPanel('zzz', 1);
|
|
300
|
-
expect(ctx.userColumnOrder).toBeUndefined();
|
|
301
|
-
});
|
|
302
|
-
});
|
|
303
|
-
describe('toggleGroupInPanel', () => {
|
|
304
|
-
it('adds a column to grouping when absent', () => {
|
|
305
|
-
const setGrouping = vi.fn();
|
|
306
|
-
const ctx = makeCtx();
|
|
307
|
-
ctx.grid.getState = () => ({ grouping: ['x'] });
|
|
308
|
-
ctx.grid.setGrouping = setGrouping;
|
|
309
|
-
const h = createColumns(ctx);
|
|
310
|
-
h.toggleGroupInPanel('a');
|
|
311
|
-
expect(setGrouping).toHaveBeenCalledWith(['x', 'a']);
|
|
312
|
-
});
|
|
313
|
-
it('removes a column from grouping when present', () => {
|
|
314
|
-
const setGrouping = vi.fn();
|
|
315
|
-
const ctx = makeCtx();
|
|
316
|
-
ctx.grid.getState = () => ({ grouping: ['x', 'a'] });
|
|
317
|
-
ctx.grid.setGrouping = setGrouping;
|
|
318
|
-
const h = createColumns(ctx);
|
|
319
|
-
h.toggleGroupInPanel('a');
|
|
320
|
-
expect(setGrouping).toHaveBeenCalledWith(['x']);
|
|
321
|
-
});
|
|
322
|
-
it('handles a missing grouping array (defaults to empty)', () => {
|
|
323
|
-
const setGrouping = vi.fn();
|
|
324
|
-
const ctx = makeCtx();
|
|
325
|
-
ctx.grid.getState = () => ({});
|
|
326
|
-
ctx.grid.setGrouping = setGrouping;
|
|
327
|
-
const h = createColumns(ctx);
|
|
328
|
-
h.toggleGroupInPanel('a');
|
|
329
|
-
expect(setGrouping).toHaveBeenCalledWith(['a']);
|
|
330
|
-
});
|
|
331
|
-
});
|
|
332
|
-
describe('getColumnBaseWidth', () => {
|
|
333
|
-
it('returns an explicit override from columnWidths', () => {
|
|
334
|
-
const ctx = makeCtx({ columnWidths: { a: 333 } });
|
|
335
|
-
const h = createColumns(ctx);
|
|
336
|
-
expect(h.getColumnBaseWidth('a')).toBe(333);
|
|
337
|
-
});
|
|
338
|
-
it('falls back to the columnDef width', () => {
|
|
339
|
-
const h = createColumns(makeCtx());
|
|
340
|
-
expect(h.getColumnBaseWidth('c')).toBe(200);
|
|
341
|
-
});
|
|
342
|
-
it('falls back to props.columnWidth when neither override nor def width set', () => {
|
|
343
|
-
const ctx = makeCtx({ props: { columnWidth: 99 } });
|
|
344
|
-
const h = createColumns(ctx);
|
|
345
|
-
expect(h.getColumnBaseWidth('b')).toBe(99);
|
|
346
|
-
});
|
|
347
|
-
it('falls back to the hard default 140 with no config', () => {
|
|
348
|
-
const h = createColumns(makeCtx());
|
|
349
|
-
expect(h.getColumnBaseWidth('b')).toBe(140);
|
|
350
|
-
});
|
|
351
|
-
it('returns the default for an unknown column id', () => {
|
|
352
|
-
const h = createColumns(makeCtx());
|
|
353
|
-
expect(h.getColumnBaseWidth('zzz')).toBe(140);
|
|
354
|
-
});
|
|
355
|
-
});
|
|
356
|
-
describe('getColumnWidth', () => {
|
|
357
|
-
it('uses the fitted width when no explicit override exists', () => {
|
|
358
|
-
const ctx = makeCtx({ fittedColumnWidths: { a: 77 } });
|
|
359
|
-
const h = createColumns(ctx);
|
|
360
|
-
expect(h.getColumnWidth('a')).toBe(77);
|
|
361
|
-
});
|
|
362
|
-
it('prefers an explicit override over the fitted width', () => {
|
|
363
|
-
const ctx = makeCtx({
|
|
364
|
-
fittedColumnWidths: { a: 77 },
|
|
365
|
-
columnWidths: { a: 300 },
|
|
366
|
-
});
|
|
367
|
-
const h = createColumns(ctx);
|
|
368
|
-
expect(h.getColumnWidth('a')).toBe(300);
|
|
369
|
-
});
|
|
370
|
-
it('falls through to base width when there is no fitted width', () => {
|
|
371
|
-
const h = createColumns(makeCtx());
|
|
372
|
-
expect(h.getColumnWidth('c')).toBe(200);
|
|
373
|
-
});
|
|
374
|
-
});
|
|
375
|
-
describe('column resize lifecycle', () => {
|
|
376
|
-
let rafCb;
|
|
377
|
-
beforeEach(() => {
|
|
378
|
-
rafCb = null;
|
|
379
|
-
vi.stubGlobal('requestAnimationFrame', (cb) => {
|
|
380
|
-
rafCb = cb;
|
|
381
|
-
return 1;
|
|
382
|
-
});
|
|
383
|
-
vi.stubGlobal('cancelAnimationFrame', vi.fn());
|
|
384
|
-
});
|
|
385
|
-
it('startColumnResize seeds the resize state and registers listeners', () => {
|
|
386
|
-
const ctx = makeCtx({ columnWidths: { a: 150 } });
|
|
387
|
-
const h = createColumns(ctx);
|
|
388
|
-
const addSpy = vi.spyOn(document, 'addEventListener');
|
|
389
|
-
const e = {
|
|
390
|
-
stopPropagation: vi.fn(),
|
|
391
|
-
preventDefault: vi.fn(),
|
|
392
|
-
clientX: 500,
|
|
393
|
-
};
|
|
394
|
-
h.startColumnResize(e, 'a');
|
|
395
|
-
expect(ctx.resizingColumnId).toBe('a');
|
|
396
|
-
expect(ctx.resizeStartX).toBe(500);
|
|
397
|
-
expect(ctx.resizeStartWidth).toBe(150);
|
|
398
|
-
expect(addSpy).toHaveBeenCalledWith('pointermove', expect.any(Function));
|
|
399
|
-
addSpy.mockRestore();
|
|
400
|
-
});
|
|
401
|
-
it('onColumnResizeMove bails when no column is resizing', () => {
|
|
402
|
-
const ctx = makeCtx({ resizingColumnId: null });
|
|
403
|
-
const h = createColumns(ctx);
|
|
404
|
-
h.onColumnResizeMove({ clientX: 100 });
|
|
405
|
-
expect(ctx.resizePendingWidth).toBe(0);
|
|
406
|
-
});
|
|
407
|
-
it('onColumnResizeMove clamps to MIN_COLUMN_WIDTH and commits on the frame', () => {
|
|
408
|
-
const ctx = makeCtx({
|
|
409
|
-
resizingColumnId: 'a',
|
|
410
|
-
resizeStartX: 100,
|
|
411
|
-
resizeStartWidth: 120,
|
|
412
|
-
});
|
|
413
|
-
const h = createColumns(ctx);
|
|
414
|
-
// moving far left should clamp at MIN_COLUMN_WIDTH (40)
|
|
415
|
-
h.onColumnResizeMove({ clientX: -1000 });
|
|
416
|
-
expect(ctx.resizePendingWidth).toBe(40);
|
|
417
|
-
expect(rafCb).not.toBeNull();
|
|
418
|
-
rafCb(0);
|
|
419
|
-
expect(ctx.columnWidths.a).toBe(40);
|
|
420
|
-
});
|
|
421
|
-
it('onColumnResizeMove grows the column and coalesces multiple moves into one frame', () => {
|
|
422
|
-
const ctx = makeCtx({
|
|
423
|
-
resizingColumnId: 'a',
|
|
424
|
-
resizeStartX: 100,
|
|
425
|
-
resizeStartWidth: 120,
|
|
426
|
-
});
|
|
427
|
-
const h = createColumns(ctx);
|
|
428
|
-
h.onColumnResizeMove({ clientX: 150 }); // +50 -> 170, schedules raf
|
|
429
|
-
const firstCb = rafCb;
|
|
430
|
-
h.onColumnResizeMove({ clientX: 200 }); // +100 -> 220, raf already pending
|
|
431
|
-
expect(ctx.resizePendingWidth).toBe(220);
|
|
432
|
-
expect(rafCb).toBe(firstCb); // not rescheduled
|
|
433
|
-
rafCb(0);
|
|
434
|
-
expect(ctx.columnWidths.a).toBe(220);
|
|
435
|
-
});
|
|
436
|
-
it('onColumnResizeMove skips the commit if the column stopped resizing mid-frame', () => {
|
|
437
|
-
const ctx = makeCtx({
|
|
438
|
-
resizingColumnId: 'a',
|
|
439
|
-
resizeStartX: 100,
|
|
440
|
-
resizeStartWidth: 120,
|
|
441
|
-
});
|
|
442
|
-
const h = createColumns(ctx);
|
|
443
|
-
h.onColumnResizeMove({ clientX: 150 });
|
|
444
|
-
ctx.resizingColumnId = null; // resize ended before the frame ran
|
|
445
|
-
rafCb(0);
|
|
446
|
-
expect(ctx.columnWidths.a).toBeUndefined();
|
|
447
|
-
});
|
|
448
|
-
it('endColumnResize commits the final pending width and tears down listeners', () => {
|
|
449
|
-
const ctx = makeCtx({
|
|
450
|
-
resizingColumnId: 'a',
|
|
451
|
-
resizePendingWidth: 222,
|
|
452
|
-
resizeRaf: 9,
|
|
453
|
-
});
|
|
454
|
-
const h = createColumns(ctx);
|
|
455
|
-
const removeSpy = vi.spyOn(document, 'removeEventListener');
|
|
456
|
-
h.endColumnResize();
|
|
457
|
-
expect(ctx.columnWidths.a).toBe(222);
|
|
458
|
-
expect(ctx.resizingColumnId).toBeNull();
|
|
459
|
-
expect(ctx.resizeRaf).toBeNull();
|
|
460
|
-
expect(removeSpy).toHaveBeenCalledWith('pointermove', expect.any(Function));
|
|
461
|
-
removeSpy.mockRestore();
|
|
462
|
-
});
|
|
463
|
-
it('endColumnResize is safe when nothing was resizing', () => {
|
|
464
|
-
const ctx = makeCtx();
|
|
465
|
-
const h = createColumns(ctx);
|
|
466
|
-
expect(() => h.endColumnResize()).not.toThrow();
|
|
467
|
-
expect(ctx.columnWidths).toEqual({});
|
|
468
|
-
});
|
|
469
|
-
});
|
|
470
|
-
describe('measureText', () => {
|
|
471
|
-
it('returns 0 for empty text', () => {
|
|
472
|
-
const h = createColumns(makeCtx());
|
|
473
|
-
expect(h.measureText('', '16px sans-serif')).toBe(0);
|
|
474
|
-
});
|
|
475
|
-
it('measures via a lazily-created canvas and caches it', () => {
|
|
476
|
-
const fakeCtx2d = { font: '', measureText: vi.fn(() => ({ width: 42 })) };
|
|
477
|
-
const fakeCanvas = { getContext: vi.fn(() => fakeCtx2d) };
|
|
478
|
-
const createElement = vi
|
|
479
|
-
.spyOn(document, 'createElement')
|
|
480
|
-
.mockReturnValue(fakeCanvas);
|
|
481
|
-
const ctx = makeCtx();
|
|
482
|
-
const h = createColumns(ctx);
|
|
483
|
-
expect(h.measureText('hello', '16px sans-serif')).toBe(42);
|
|
484
|
-
expect(ctx.measureCanvas).toBe(fakeCanvas);
|
|
485
|
-
// second call reuses the cached canvas
|
|
486
|
-
h.measureText('again', '16px sans-serif');
|
|
487
|
-
expect(createElement).toHaveBeenCalledTimes(1);
|
|
488
|
-
createElement.mockRestore();
|
|
489
|
-
});
|
|
490
|
-
it('returns 0 when the 2d context is unavailable', () => {
|
|
491
|
-
const fakeCanvas = { getContext: vi.fn(() => null) };
|
|
492
|
-
const createElement = vi
|
|
493
|
-
.spyOn(document, 'createElement')
|
|
494
|
-
.mockReturnValue(fakeCanvas);
|
|
495
|
-
const h = createColumns(makeCtx());
|
|
496
|
-
expect(h.measureText('hello', '16px sans-serif')).toBe(0);
|
|
497
|
-
createElement.mockRestore();
|
|
498
|
-
});
|
|
499
|
-
});
|
|
500
|
-
describe('autosizeColumn / autosizeAllColumns', () => {
|
|
501
|
-
// jsdom doesn't provide the global CSS object; autosizeColumn uses
|
|
502
|
-
// CSS.escape to build attribute selectors. Provide a minimal stub.
|
|
503
|
-
beforeEach(() => {
|
|
504
|
-
if (typeof globalThis.CSS === 'undefined') {
|
|
505
|
-
;
|
|
506
|
-
globalThis.CSS = { escape: (s) => s };
|
|
507
|
-
}
|
|
508
|
-
else if (typeof globalThis.CSS.escape !== 'function') {
|
|
509
|
-
;
|
|
510
|
-
globalThis.CSS.escape = (s) => s;
|
|
511
|
-
}
|
|
512
|
-
});
|
|
513
|
-
function fakeRoot(opts) {
|
|
514
|
-
const { sampleCell, header, cells = [] } = opts;
|
|
515
|
-
return {
|
|
516
|
-
querySelector: (sel) => {
|
|
517
|
-
if (sel.startsWith('[data-col-id='))
|
|
518
|
-
return sampleCell ?? null;
|
|
519
|
-
if (sel.startsWith('[data-svgrid-header-col='))
|
|
520
|
-
return header ?? null;
|
|
521
|
-
return null;
|
|
522
|
-
},
|
|
523
|
-
querySelectorAll: () => cells,
|
|
524
|
-
};
|
|
525
|
-
}
|
|
526
|
-
it('bails when there is no grid root element', () => {
|
|
527
|
-
const ctx = makeCtx({ gridRootEl: null });
|
|
528
|
-
const h = createColumns(ctx);
|
|
529
|
-
expect(() => h.autosizeColumn('a')).not.toThrow();
|
|
530
|
-
expect(ctx.columnWidths).toEqual({});
|
|
531
|
-
});
|
|
532
|
-
it('bails when no sample cell is found', () => {
|
|
533
|
-
const ctx = makeCtx({ gridRootEl: fakeRoot({ sampleCell: null }) });
|
|
534
|
-
const h = createColumns(ctx);
|
|
535
|
-
h.autosizeColumn('a');
|
|
536
|
-
expect(ctx.columnWidths).toEqual({});
|
|
537
|
-
});
|
|
538
|
-
it('snaps the width to the widest measured content', () => {
|
|
539
|
-
// header label is 100px wide, one body cell is 200px wide.
|
|
540
|
-
const labelEl = { textContent: 'Header' };
|
|
541
|
-
const header = {
|
|
542
|
-
querySelector: () => labelEl,
|
|
543
|
-
};
|
|
544
|
-
const sampleCell = { textContent: 'cell' };
|
|
545
|
-
const bodyCell = { textContent: 'a very wide cell value' };
|
|
546
|
-
const ctx = makeCtx({
|
|
547
|
-
gridRootEl: fakeRoot({ sampleCell, header, cells: [bodyCell] }),
|
|
548
|
-
});
|
|
549
|
-
const h = createColumns(ctx);
|
|
550
|
-
// stub getComputedStyle so .font is defined for measureText calls
|
|
551
|
-
const gcs = vi
|
|
552
|
-
.spyOn(window, 'getComputedStyle')
|
|
553
|
-
.mockReturnValue({ font: '16px sans-serif' });
|
|
554
|
-
// Make measureText deterministic: header label -> 100, body cell -> 200.
|
|
555
|
-
const fakeCtx2d = {
|
|
556
|
-
font: '',
|
|
557
|
-
measureText: (t) => ({ width: t === 'Header' ? 100 : 200 }),
|
|
558
|
-
};
|
|
559
|
-
const createElement = vi
|
|
560
|
-
.spyOn(document, 'createElement')
|
|
561
|
-
.mockReturnValue({ getContext: () => fakeCtx2d });
|
|
562
|
-
h.autosizeColumn('a');
|
|
563
|
-
// widest is the 200px body cell -> 200 + 24 padding
|
|
564
|
-
expect(ctx.columnWidths.a).toBe(224);
|
|
565
|
-
gcs.mockRestore();
|
|
566
|
-
createElement.mockRestore();
|
|
567
|
-
});
|
|
568
|
-
it('floors at MIN_COLUMN_WIDTH (+24 padding) when content measures zero', () => {
|
|
569
|
-
// No label element -> the header itself is used as the measure target
|
|
570
|
-
// (covering the `labelEl ?? header` fallback branch). Every measureText
|
|
571
|
-
// returns 0, so max stays at MIN_COLUMN_WIDTH and the +24 padding is added.
|
|
572
|
-
const header = { querySelector: () => null };
|
|
573
|
-
const sampleCell = { textContent: '' };
|
|
574
|
-
const ctx = makeCtx({
|
|
575
|
-
MIN_COLUMN_WIDTH: 80,
|
|
576
|
-
gridRootEl: fakeRoot({ sampleCell, header, cells: [] }),
|
|
577
|
-
});
|
|
578
|
-
const h = createColumns(ctx);
|
|
579
|
-
const gcs = vi
|
|
580
|
-
.spyOn(window, 'getComputedStyle')
|
|
581
|
-
.mockReturnValue({ font: '16px sans-serif' });
|
|
582
|
-
const createElement = vi.spyOn(document, 'createElement').mockReturnValue({
|
|
583
|
-
getContext: () => ({ font: '', measureText: () => ({ width: 0 }) }),
|
|
584
|
-
});
|
|
585
|
-
h.autosizeColumn('a');
|
|
586
|
-
// max = MIN(80); width = max(80, ceil(80) + 24) = 104
|
|
587
|
-
expect(ctx.columnWidths.a).toBe(104);
|
|
588
|
-
gcs.mockRestore();
|
|
589
|
-
createElement.mockRestore();
|
|
590
|
-
});
|
|
591
|
-
it('autosizeAllColumns iterates every column and writes each width', () => {
|
|
592
|
-
// Every querySelector returns a cell, every measured width is 50.
|
|
593
|
-
const cell = { textContent: 'cell' };
|
|
594
|
-
const header = { querySelector: () => ({ textContent: 'H' }) };
|
|
595
|
-
const ctx = makeCtx({
|
|
596
|
-
gridRootEl: fakeRoot({ sampleCell: cell, header, cells: [cell] }),
|
|
597
|
-
});
|
|
598
|
-
const h = createColumns(ctx);
|
|
599
|
-
const gcs = vi
|
|
600
|
-
.spyOn(window, 'getComputedStyle')
|
|
601
|
-
.mockReturnValue({ font: '16px sans-serif' });
|
|
602
|
-
const createElement = vi.spyOn(document, 'createElement').mockReturnValue({
|
|
603
|
-
getContext: () => ({ font: '', measureText: () => ({ width: 50 }) }),
|
|
604
|
-
});
|
|
605
|
-
h.autosizeAllColumns();
|
|
606
|
-
// a width was computed and stored for every column id
|
|
607
|
-
expect(Object.keys(ctx.columnWidths).sort()).toEqual(['a', 'b', 'c', 'd']);
|
|
608
|
-
gcs.mockRestore();
|
|
609
|
-
createElement.mockRestore();
|
|
610
|
-
});
|
|
611
|
-
});
|
|
612
|
-
describe('resetColumns', () => {
|
|
613
|
-
it('clears widths, hidden columns and pinning', () => {
|
|
614
|
-
const ctx = makeCtx({
|
|
615
|
-
columnWidths: { a: 10 },
|
|
616
|
-
hiddenColumns: { b: true },
|
|
617
|
-
columnPinning: { left: ['a'], right: ['d'] },
|
|
618
|
-
});
|
|
619
|
-
const h = createColumns(ctx);
|
|
620
|
-
h.resetColumns();
|
|
621
|
-
expect(ctx.columnWidths).toEqual({});
|
|
622
|
-
expect(ctx.hiddenColumns).toEqual({});
|
|
623
|
-
expect(ctx.columnPinning).toEqual({ left: [], right: [] });
|
|
624
|
-
});
|
|
625
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|