@handled-ai/design-system 0.14.7 → 0.14.10
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/components/data-table-filter.d.ts +9 -3
- package/dist/components/data-table-filter.js +99 -26
- package/dist/components/data-table-filter.js.map +1 -1
- package/dist/components/data-table.d.ts +1 -0
- package/dist/components/data-table.js +2 -1
- package/dist/components/data-table.js.map +1 -1
- package/dist/components/virtualized-data-table.d.ts +6 -2
- package/dist/components/virtualized-data-table.js +51 -20
- package/dist/components/virtualized-data-table.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/__tests__/virtualized-data-table-resize.test.tsx +524 -0
- package/src/components/data-table-filter.tsx +100 -33
- package/src/components/data-table.tsx +2 -1
- package/src/components/virtualized-data-table.tsx +40 -1
- package/src/index.ts +1 -0
|
@@ -0,0 +1,524 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from "vitest";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { render, fireEvent } from "@testing-library/react";
|
|
4
|
+
import { VirtualizedDataTable } from "../virtualized-data-table";
|
|
5
|
+
// Verify the barrel re-export compiles (type-only import used below in tests)
|
|
6
|
+
import type { ColumnDef } from "@tanstack/react-table";
|
|
7
|
+
import type { ColumnSizingState } from "../../index";
|
|
8
|
+
|
|
9
|
+
type TestRow = { id: string; name: string; value: number };
|
|
10
|
+
|
|
11
|
+
const testColumns: ColumnDef<TestRow, unknown>[] = [
|
|
12
|
+
{ accessorKey: "name", header: "Name", size: 200, minSize: 100 },
|
|
13
|
+
{ accessorKey: "value", header: "Value", size: 150, minSize: 80 },
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
const testData: TestRow[] = [
|
|
17
|
+
{ id: "1", name: "Alpha", value: 10 },
|
|
18
|
+
{ id: "2", name: "Beta", value: 20 },
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
// ─── Group 1: Feature disabled by default ─────────────────────────────────────
|
|
22
|
+
|
|
23
|
+
describe("VirtualizedDataTable — resize disabled by default", () => {
|
|
24
|
+
it("does not render resize handles when enableColumnResizing is omitted", () => {
|
|
25
|
+
const { container } = render(
|
|
26
|
+
<VirtualizedDataTable columns={testColumns} data={testData} height={300} />,
|
|
27
|
+
);
|
|
28
|
+
expect(container.querySelectorAll('[role="separator"]').length).toBe(0);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("does not render resize handles when enableColumnResizing={false} explicitly", () => {
|
|
32
|
+
const { container } = render(
|
|
33
|
+
<VirtualizedDataTable
|
|
34
|
+
columns={testColumns}
|
|
35
|
+
data={testData}
|
|
36
|
+
height={300}
|
|
37
|
+
enableColumnResizing={false}
|
|
38
|
+
/>,
|
|
39
|
+
);
|
|
40
|
+
expect(container.querySelectorAll('[role="separator"]').length).toBe(0);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// ─── Group 2: Handles appear and are correctly structured ─────────────────────
|
|
45
|
+
|
|
46
|
+
describe("VirtualizedDataTable — resize handles render when enabled", () => {
|
|
47
|
+
it("renders one resize handle per resizable column", () => {
|
|
48
|
+
const { container } = render(
|
|
49
|
+
<VirtualizedDataTable
|
|
50
|
+
columns={testColumns}
|
|
51
|
+
data={testData}
|
|
52
|
+
height={300}
|
|
53
|
+
enableColumnResizing
|
|
54
|
+
/>,
|
|
55
|
+
);
|
|
56
|
+
expect(container.querySelectorAll('[role="separator"]').length).toBe(2);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("each handle has aria-orientation='vertical'", () => {
|
|
60
|
+
const { container } = render(
|
|
61
|
+
<VirtualizedDataTable
|
|
62
|
+
columns={testColumns}
|
|
63
|
+
data={testData}
|
|
64
|
+
height={300}
|
|
65
|
+
enableColumnResizing
|
|
66
|
+
/>,
|
|
67
|
+
);
|
|
68
|
+
container.querySelectorAll('[role="separator"]').forEach((sep) => {
|
|
69
|
+
expect(sep.getAttribute("aria-orientation")).toBe("vertical");
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("each handle has cursor-col-resize class", () => {
|
|
74
|
+
const { container } = render(
|
|
75
|
+
<VirtualizedDataTable
|
|
76
|
+
columns={testColumns}
|
|
77
|
+
data={testData}
|
|
78
|
+
height={300}
|
|
79
|
+
enableColumnResizing
|
|
80
|
+
/>,
|
|
81
|
+
);
|
|
82
|
+
container.querySelectorAll('[role="separator"]').forEach((sep) => {
|
|
83
|
+
expect(sep.classList.contains("cursor-col-resize")).toBe(true);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("header cells have 'relative' class when resizing is enabled", () => {
|
|
88
|
+
const { container } = render(
|
|
89
|
+
<VirtualizedDataTable
|
|
90
|
+
columns={testColumns}
|
|
91
|
+
data={testData}
|
|
92
|
+
height={300}
|
|
93
|
+
enableColumnResizing
|
|
94
|
+
/>,
|
|
95
|
+
);
|
|
96
|
+
container.querySelectorAll('[role="columnheader"]').forEach((h) => {
|
|
97
|
+
expect((h as HTMLElement).classList.contains("relative")).toBe(true);
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("resizable header cells get pr-4 padding to prevent overlap with sort text", () => {
|
|
102
|
+
const { container } = render(
|
|
103
|
+
<VirtualizedDataTable
|
|
104
|
+
columns={testColumns}
|
|
105
|
+
data={testData}
|
|
106
|
+
height={300}
|
|
107
|
+
enableColumnResizing
|
|
108
|
+
/>,
|
|
109
|
+
);
|
|
110
|
+
// Every header cell whose column can resize must have pr-4
|
|
111
|
+
container.querySelectorAll('[role="columnheader"]').forEach((h) => {
|
|
112
|
+
expect((h as HTMLElement).classList.contains("pr-4")).toBe(true);
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it("non-resizable columns do NOT get pr-4 padding", () => {
|
|
117
|
+
const columns: ColumnDef<TestRow, unknown>[] = [
|
|
118
|
+
{ accessorKey: "name", header: "Name", size: 200, enableResizing: false },
|
|
119
|
+
{ accessorKey: "value", header: "Value", size: 150 },
|
|
120
|
+
];
|
|
121
|
+
const { container } = render(
|
|
122
|
+
<VirtualizedDataTable
|
|
123
|
+
columns={columns}
|
|
124
|
+
data={testData}
|
|
125
|
+
height={300}
|
|
126
|
+
enableColumnResizing
|
|
127
|
+
/>,
|
|
128
|
+
);
|
|
129
|
+
const headers = container.querySelectorAll('[role="columnheader"]');
|
|
130
|
+
// First column: enableResizing=false → no pr-4
|
|
131
|
+
expect((headers[0] as HTMLElement).classList.contains("pr-4")).toBe(false);
|
|
132
|
+
// Second column: resizable → has pr-4
|
|
133
|
+
expect((headers[1] as HTMLElement).classList.contains("pr-4")).toBe(true);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it("does not render a handle for a column with enableResizing: false", () => {
|
|
137
|
+
const columns: ColumnDef<TestRow, unknown>[] = [
|
|
138
|
+
{ accessorKey: "name", header: "Name", size: 200, enableResizing: false },
|
|
139
|
+
{ accessorKey: "value", header: "Value", size: 150 },
|
|
140
|
+
];
|
|
141
|
+
const { container } = render(
|
|
142
|
+
<VirtualizedDataTable
|
|
143
|
+
columns={columns}
|
|
144
|
+
data={testData}
|
|
145
|
+
height={300}
|
|
146
|
+
enableColumnResizing
|
|
147
|
+
/>,
|
|
148
|
+
);
|
|
149
|
+
expect(container.querySelectorAll('[role="separator"]').length).toBe(1);
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
// ─── Group 3: Initial sizing from column defs ─────────────────────────────────
|
|
154
|
+
|
|
155
|
+
describe("VirtualizedDataTable — initial column sizes", () => {
|
|
156
|
+
it("header cells render at their declared size", () => {
|
|
157
|
+
const { container } = render(
|
|
158
|
+
<VirtualizedDataTable
|
|
159
|
+
columns={testColumns}
|
|
160
|
+
data={testData}
|
|
161
|
+
height={300}
|
|
162
|
+
enableColumnResizing
|
|
163
|
+
/>,
|
|
164
|
+
);
|
|
165
|
+
const headers = container.querySelectorAll('[role="columnheader"]');
|
|
166
|
+
expect((headers[0] as HTMLElement).style.width).toBe("200px");
|
|
167
|
+
expect((headers[1] as HTMLElement).style.width).toBe("150px");
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it("column cannot be dragged below its minSize (onChange mode)", () => {
|
|
171
|
+
const { container } = render(
|
|
172
|
+
<VirtualizedDataTable
|
|
173
|
+
columns={testColumns}
|
|
174
|
+
data={testData}
|
|
175
|
+
height={300}
|
|
176
|
+
enableColumnResizing
|
|
177
|
+
columnResizeMode="onChange"
|
|
178
|
+
/>,
|
|
179
|
+
);
|
|
180
|
+
const separator = container.querySelector('[role="separator"]')!;
|
|
181
|
+
// Start at x=200, drag far left past minSize=100 → delta of -150 would give 50px
|
|
182
|
+
fireEvent.mouseDown(separator, { clientX: 200 });
|
|
183
|
+
fireEvent.mouseMove(document, { clientX: 50 });
|
|
184
|
+
const header = container.querySelectorAll('[role="columnheader"]')[0] as HTMLElement;
|
|
185
|
+
expect(parseInt(header.style.width, 10)).toBeGreaterThanOrEqual(100);
|
|
186
|
+
fireEvent.mouseUp(document);
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
// ─── Group 4: columnResizeMode="onEnd" (new default) ─────────────────────────
|
|
191
|
+
//
|
|
192
|
+
// With onEnd, sizing state is committed at mouseUp, not during mousemove.
|
|
193
|
+
// Width in the DOM should be unchanged mid-drag, then reflect the new size
|
|
194
|
+
// after mouseUp.
|
|
195
|
+
|
|
196
|
+
describe("VirtualizedDataTable — columnResizeMode onEnd (default)", () => {
|
|
197
|
+
it("header width does NOT change during mousemove (committed on mouseUp)", () => {
|
|
198
|
+
const { container } = render(
|
|
199
|
+
<VirtualizedDataTable
|
|
200
|
+
columns={testColumns}
|
|
201
|
+
data={testData}
|
|
202
|
+
height={300}
|
|
203
|
+
enableColumnResizing
|
|
204
|
+
// columnResizeMode defaults to "onEnd" — do not pass it explicitly
|
|
205
|
+
/>,
|
|
206
|
+
);
|
|
207
|
+
const separator = container.querySelector('[role="separator"]')!;
|
|
208
|
+
const header = container.querySelectorAll('[role="columnheader"]')[0] as HTMLElement;
|
|
209
|
+
const widthBeforeDrag = header.style.width;
|
|
210
|
+
|
|
211
|
+
fireEvent.mouseDown(separator, { clientX: 200 });
|
|
212
|
+
fireEvent.mouseMove(document, { clientX: 260 }); // mid-drag: should not change yet
|
|
213
|
+
expect(header.style.width).toBe(widthBeforeDrag);
|
|
214
|
+
|
|
215
|
+
fireEvent.mouseUp(document);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it("header width changes after mouseUp completes the drag", () => {
|
|
219
|
+
const { container } = render(
|
|
220
|
+
<VirtualizedDataTable
|
|
221
|
+
columns={testColumns}
|
|
222
|
+
data={testData}
|
|
223
|
+
height={300}
|
|
224
|
+
enableColumnResizing
|
|
225
|
+
// "onEnd" default
|
|
226
|
+
/>,
|
|
227
|
+
);
|
|
228
|
+
const separator = container.querySelector('[role="separator"]')!;
|
|
229
|
+
const header = container.querySelectorAll('[role="columnheader"]')[0] as HTMLElement;
|
|
230
|
+
const widthBefore = parseInt(header.style.width, 10);
|
|
231
|
+
|
|
232
|
+
fireEvent.mouseDown(separator, { clientX: 200 });
|
|
233
|
+
fireEvent.mouseMove(document, { clientX: 260 }); // +60px delta
|
|
234
|
+
fireEvent.mouseUp(document); // commits the resize
|
|
235
|
+
|
|
236
|
+
expect(parseInt(header.style.width, 10)).toBeGreaterThan(widthBefore);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it("onColumnSizingChange is called on mouseUp (not mousemove) in onEnd mode", () => {
|
|
240
|
+
const onSizingChange = vi.fn();
|
|
241
|
+
const { container } = render(
|
|
242
|
+
<VirtualizedDataTable
|
|
243
|
+
columns={testColumns}
|
|
244
|
+
data={testData}
|
|
245
|
+
height={300}
|
|
246
|
+
enableColumnResizing
|
|
247
|
+
columnSizing={{}}
|
|
248
|
+
onColumnSizingChange={onSizingChange}
|
|
249
|
+
// "onEnd" default
|
|
250
|
+
/>,
|
|
251
|
+
);
|
|
252
|
+
const separator = container.querySelector('[role="separator"]')!;
|
|
253
|
+
|
|
254
|
+
fireEvent.mouseDown(separator, { clientX: 200 });
|
|
255
|
+
fireEvent.mouseMove(document, { clientX: 250 });
|
|
256
|
+
// Should NOT have been called yet mid-drag in onEnd mode
|
|
257
|
+
expect(onSizingChange).not.toHaveBeenCalled();
|
|
258
|
+
|
|
259
|
+
fireEvent.mouseUp(document);
|
|
260
|
+
// Now it should fire
|
|
261
|
+
expect(onSizingChange).toHaveBeenCalled();
|
|
262
|
+
});
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
// ─── Group 5: Drag changes column width (onChange mode) ───────────────────────
|
|
266
|
+
|
|
267
|
+
describe("VirtualizedDataTable — drag resizing with columnResizeMode='onChange'", () => {
|
|
268
|
+
it("dragging right increases column width", () => {
|
|
269
|
+
const { container } = render(
|
|
270
|
+
<VirtualizedDataTable
|
|
271
|
+
columns={testColumns}
|
|
272
|
+
data={testData}
|
|
273
|
+
height={300}
|
|
274
|
+
enableColumnResizing
|
|
275
|
+
columnResizeMode="onChange"
|
|
276
|
+
/>,
|
|
277
|
+
);
|
|
278
|
+
const separator = container.querySelector('[role="separator"]')!;
|
|
279
|
+
const header = container.querySelectorAll('[role="columnheader"]')[0] as HTMLElement;
|
|
280
|
+
const widthBefore = parseInt(header.style.width, 10);
|
|
281
|
+
|
|
282
|
+
fireEvent.mouseDown(separator, { clientX: 200 });
|
|
283
|
+
fireEvent.mouseMove(document, { clientX: 260 }); // +60px delta
|
|
284
|
+
fireEvent.mouseUp(document);
|
|
285
|
+
|
|
286
|
+
expect(parseInt(header.style.width, 10)).toBeGreaterThan(widthBefore);
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
it("dragging left decreases column width", () => {
|
|
290
|
+
const { container } = render(
|
|
291
|
+
<VirtualizedDataTable
|
|
292
|
+
columns={testColumns}
|
|
293
|
+
data={testData}
|
|
294
|
+
height={300}
|
|
295
|
+
enableColumnResizing
|
|
296
|
+
columnResizeMode="onChange"
|
|
297
|
+
/>,
|
|
298
|
+
);
|
|
299
|
+
const separator = container.querySelector('[role="separator"]')!;
|
|
300
|
+
const header = container.querySelectorAll('[role="columnheader"]')[0] as HTMLElement;
|
|
301
|
+
const widthBefore = parseInt(header.style.width, 10);
|
|
302
|
+
|
|
303
|
+
fireEvent.mouseDown(separator, { clientX: 200 });
|
|
304
|
+
fireEvent.mouseMove(document, { clientX: 160 }); // -40px delta
|
|
305
|
+
fireEvent.mouseUp(document);
|
|
306
|
+
|
|
307
|
+
expect(parseInt(header.style.width, 10)).toBeLessThan(widthBefore);
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
it("dragging one column does not change the adjacent column's width", () => {
|
|
311
|
+
const { container } = render(
|
|
312
|
+
<VirtualizedDataTable
|
|
313
|
+
columns={testColumns}
|
|
314
|
+
data={testData}
|
|
315
|
+
height={300}
|
|
316
|
+
enableColumnResizing
|
|
317
|
+
columnResizeMode="onChange"
|
|
318
|
+
/>,
|
|
319
|
+
);
|
|
320
|
+
const separators = container.querySelectorAll('[role="separator"]');
|
|
321
|
+
const headers = container.querySelectorAll('[role="columnheader"]');
|
|
322
|
+
const secondWidthBefore = parseInt((headers[1] as HTMLElement).style.width, 10);
|
|
323
|
+
|
|
324
|
+
fireEvent.mouseDown(separators[0], { clientX: 200 });
|
|
325
|
+
fireEvent.mouseMove(document, { clientX: 260 });
|
|
326
|
+
fireEvent.mouseUp(document);
|
|
327
|
+
|
|
328
|
+
expect(parseInt((headers[1] as HTMLElement).style.width, 10)).toBe(secondWidthBefore);
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
it("does not throw during resize in uncontrolled mode (no sizing props)", () => {
|
|
332
|
+
const { container } = render(
|
|
333
|
+
<VirtualizedDataTable
|
|
334
|
+
columns={testColumns}
|
|
335
|
+
data={testData}
|
|
336
|
+
height={300}
|
|
337
|
+
enableColumnResizing
|
|
338
|
+
columnResizeMode="onChange"
|
|
339
|
+
/>,
|
|
340
|
+
);
|
|
341
|
+
const separator = container.querySelector('[role="separator"]')!;
|
|
342
|
+
expect(() => {
|
|
343
|
+
fireEvent.mouseDown(separator, { clientX: 200 });
|
|
344
|
+
fireEvent.mouseMove(document, { clientX: 250 });
|
|
345
|
+
fireEvent.mouseUp(document);
|
|
346
|
+
}).not.toThrow();
|
|
347
|
+
});
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
// ─── Group 6: Controlled mode ─────────────────────────────────────────────────
|
|
351
|
+
|
|
352
|
+
describe("VirtualizedDataTable — controlled columnSizing", () => {
|
|
353
|
+
it("external columnSizing prop overrides the column def size", () => {
|
|
354
|
+
const sizing: ColumnSizingState = { name: 300 };
|
|
355
|
+
const { container } = render(
|
|
356
|
+
<VirtualizedDataTable
|
|
357
|
+
columns={testColumns}
|
|
358
|
+
data={testData}
|
|
359
|
+
height={300}
|
|
360
|
+
enableColumnResizing
|
|
361
|
+
columnSizing={sizing}
|
|
362
|
+
/>,
|
|
363
|
+
);
|
|
364
|
+
const headers = container.querySelectorAll('[role="columnheader"]');
|
|
365
|
+
expect((headers[0] as HTMLElement).style.width).toBe("300px");
|
|
366
|
+
// Column not in sizing map keeps its column-def default
|
|
367
|
+
expect((headers[1] as HTMLElement).style.width).toBe("150px");
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
it("onColumnSizingChange is called when a resize drag occurs (onChange mode)", () => {
|
|
371
|
+
const onSizingChange = vi.fn();
|
|
372
|
+
const { container } = render(
|
|
373
|
+
<VirtualizedDataTable
|
|
374
|
+
columns={testColumns}
|
|
375
|
+
data={testData}
|
|
376
|
+
height={300}
|
|
377
|
+
enableColumnResizing
|
|
378
|
+
columnResizeMode="onChange"
|
|
379
|
+
columnSizing={{}}
|
|
380
|
+
onColumnSizingChange={onSizingChange}
|
|
381
|
+
/>,
|
|
382
|
+
);
|
|
383
|
+
const separator = container.querySelector('[role="separator"]')!;
|
|
384
|
+
fireEvent.mouseDown(separator, { clientX: 200 });
|
|
385
|
+
fireEvent.mouseMove(document, { clientX: 250 });
|
|
386
|
+
expect(onSizingChange).toHaveBeenCalled();
|
|
387
|
+
fireEvent.mouseUp(document);
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
it("renders correctly when columnSizing is provided without onColumnSizingChange", () => {
|
|
391
|
+
const sizing: ColumnSizingState = { name: 280 };
|
|
392
|
+
const { container } = render(
|
|
393
|
+
<VirtualizedDataTable
|
|
394
|
+
columns={testColumns}
|
|
395
|
+
data={testData}
|
|
396
|
+
height={300}
|
|
397
|
+
enableColumnResizing
|
|
398
|
+
columnSizing={sizing}
|
|
399
|
+
/>,
|
|
400
|
+
);
|
|
401
|
+
const header = container.querySelectorAll('[role="columnheader"]')[0] as HTMLElement;
|
|
402
|
+
expect(header.style.width).toBe("280px");
|
|
403
|
+
});
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
// ─── Group 7: Cell widths track header widths ─────────────────────────────────
|
|
407
|
+
|
|
408
|
+
describe("VirtualizedDataTable — cell widths track column widths", () => {
|
|
409
|
+
it("row cells match the column width after a resize drag (onChange mode)", () => {
|
|
410
|
+
const { container } = render(
|
|
411
|
+
<VirtualizedDataTable
|
|
412
|
+
columns={testColumns}
|
|
413
|
+
data={testData}
|
|
414
|
+
height={300}
|
|
415
|
+
enableColumnResizing
|
|
416
|
+
columnResizeMode="onChange"
|
|
417
|
+
/>,
|
|
418
|
+
);
|
|
419
|
+
const separator = container.querySelector('[role="separator"]')!;
|
|
420
|
+
fireEvent.mouseDown(separator, { clientX: 200 });
|
|
421
|
+
fireEvent.mouseMove(document, { clientX: 260 });
|
|
422
|
+
fireEvent.mouseUp(document);
|
|
423
|
+
|
|
424
|
+
const header = container.querySelectorAll('[role="columnheader"]')[0] as HTMLElement;
|
|
425
|
+
const newHeaderWidth = header.style.width;
|
|
426
|
+
|
|
427
|
+
// Data rows (aria-rowindex present) are rendered by the virtualizer.
|
|
428
|
+
// In happy-dom (no layout engine) the virtualizer may produce zero virtual
|
|
429
|
+
// items; if rows are present, verify their first cell tracks the header width.
|
|
430
|
+
const rows = container.querySelectorAll('[role="row"]');
|
|
431
|
+
for (let i = 1; i < rows.length; i++) {
|
|
432
|
+
const firstCell = rows[i].querySelector('[role="cell"]') as HTMLElement | null;
|
|
433
|
+
if (firstCell) {
|
|
434
|
+
expect(firstCell.style.width).toBe(newHeaderWidth);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
});
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
// ─── Group 8: No regressions with enableColumnResizing on ────────────────────
|
|
441
|
+
|
|
442
|
+
describe("VirtualizedDataTable — no regressions with enableColumnResizing on", () => {
|
|
443
|
+
it("onSortingChange fires when a sortable header is clicked", () => {
|
|
444
|
+
const columnsWithSort: ColumnDef<TestRow, unknown>[] = [
|
|
445
|
+
{ accessorKey: "name", header: "Name", size: 200, enableSorting: true },
|
|
446
|
+
{ accessorKey: "value", header: "Value", size: 150, enableSorting: true },
|
|
447
|
+
];
|
|
448
|
+
const onSortingChange = vi.fn();
|
|
449
|
+
const { container } = render(
|
|
450
|
+
<VirtualizedDataTable
|
|
451
|
+
columns={columnsWithSort}
|
|
452
|
+
data={testData}
|
|
453
|
+
height={300}
|
|
454
|
+
enableColumnResizing
|
|
455
|
+
sorting={[]}
|
|
456
|
+
onSortingChange={onSortingChange}
|
|
457
|
+
/>,
|
|
458
|
+
);
|
|
459
|
+
const sortButton = container.querySelector('[role="columnheader"] button')!;
|
|
460
|
+
fireEvent.click(sortButton);
|
|
461
|
+
expect(onSortingChange).toHaveBeenCalled();
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
it("onRowClick prop is accepted without error; scroll container is present", () => {
|
|
465
|
+
// The virtualizer produces no virtual rows in happy-dom (no layout engine),
|
|
466
|
+
// so we verify the component mounts cleanly with onRowClick wired and that
|
|
467
|
+
// the table scroll container is in the DOM.
|
|
468
|
+
const onRowClick = vi.fn();
|
|
469
|
+
const { container } = render(
|
|
470
|
+
<VirtualizedDataTable
|
|
471
|
+
columns={testColumns}
|
|
472
|
+
data={testData}
|
|
473
|
+
height={300}
|
|
474
|
+
enableColumnResizing
|
|
475
|
+
onRowClick={onRowClick}
|
|
476
|
+
/>,
|
|
477
|
+
);
|
|
478
|
+
expect(container.querySelector('[role="table"]')).not.toBeNull();
|
|
479
|
+
// If the virtualizer happens to render rows, clicking them calls onRowClick
|
|
480
|
+
const dataRows = Array.from(
|
|
481
|
+
container.querySelectorAll('[role="row"]'),
|
|
482
|
+
).filter((r) => r.getAttribute("aria-rowindex") !== null);
|
|
483
|
+
if (dataRows.length > 0) {
|
|
484
|
+
fireEvent.click(dataRows[0]);
|
|
485
|
+
expect(onRowClick).toHaveBeenCalled();
|
|
486
|
+
}
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
it("empty state renders correctly when data is empty and resizing is enabled", () => {
|
|
490
|
+
const { container } = render(
|
|
491
|
+
<VirtualizedDataTable
|
|
492
|
+
columns={testColumns}
|
|
493
|
+
data={[]}
|
|
494
|
+
height={300}
|
|
495
|
+
enableColumnResizing
|
|
496
|
+
emptyMessage="Nothing here"
|
|
497
|
+
/>,
|
|
498
|
+
);
|
|
499
|
+
expect(container.textContent).toContain("Nothing here");
|
|
500
|
+
});
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
// ─── Group 9: Barrel re-export of ColumnSizingState ──────────────────────────
|
|
504
|
+
|
|
505
|
+
describe("VirtualizedDataTable — ColumnSizingState barrel re-export", () => {
|
|
506
|
+
it("ColumnSizingState from the barrel index is compatible with the columnSizing prop", () => {
|
|
507
|
+
// This test is intentionally type-level: the import at the top of this file
|
|
508
|
+
// uses `import type { ColumnSizingState } from '../../index'`. If the barrel
|
|
509
|
+
// no longer re-exports it, tsc (run via `pnpm typecheck`) will error.
|
|
510
|
+
// At runtime we just verify the prop is accepted with a well-typed value.
|
|
511
|
+
const sizing: ColumnSizingState = { name: 250 };
|
|
512
|
+
const { container } = render(
|
|
513
|
+
<VirtualizedDataTable
|
|
514
|
+
columns={testColumns}
|
|
515
|
+
data={testData}
|
|
516
|
+
height={300}
|
|
517
|
+
enableColumnResizing
|
|
518
|
+
columnSizing={sizing}
|
|
519
|
+
/>,
|
|
520
|
+
);
|
|
521
|
+
const header = container.querySelectorAll('[role="columnheader"]')[0] as HTMLElement;
|
|
522
|
+
expect(header.style.width).toBe("250px");
|
|
523
|
+
});
|
|
524
|
+
});
|