argent-grid 0.1.0
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/.github/workflows/pages.yml +68 -0
- package/AGENTS.md +179 -0
- package/README.md +222 -0
- package/demo-app/README.md +70 -0
- package/demo-app/angular.json +78 -0
- package/demo-app/e2e/benchmark.spec.ts +53 -0
- package/demo-app/e2e/demo-page.spec.ts +77 -0
- package/demo-app/e2e/grid-features.spec.ts +269 -0
- package/demo-app/package-lock.json +14023 -0
- package/demo-app/package.json +36 -0
- package/demo-app/playwright-test-menu.js +19 -0
- package/demo-app/playwright.config.ts +23 -0
- package/demo-app/src/app/app.component.ts +10 -0
- package/demo-app/src/app/app.config.ts +13 -0
- package/demo-app/src/app/app.routes.ts +7 -0
- package/demo-app/src/app/demo-page/demo-page.component.css +313 -0
- package/demo-app/src/app/demo-page/demo-page.component.html +124 -0
- package/demo-app/src/app/demo-page/demo-page.component.ts +366 -0
- package/demo-app/src/index.html +19 -0
- package/demo-app/src/main.ts +6 -0
- package/demo-app/tsconfig.json +31 -0
- package/ng-package.json +8 -0
- package/package.json +60 -0
- package/plan.md +131 -0
- package/setup-vitest.ts +18 -0
- package/src/lib/argent-grid.module.ts +21 -0
- package/src/lib/components/argent-grid.component.css +483 -0
- package/src/lib/components/argent-grid.component.html +320 -0
- package/src/lib/components/argent-grid.component.spec.ts +189 -0
- package/src/lib/components/argent-grid.component.ts +1188 -0
- package/src/lib/directives/ag-grid-compatibility.directive.ts +92 -0
- package/src/lib/rendering/canvas-renderer.ts +962 -0
- package/src/lib/rendering/render/blit.spec.ts +453 -0
- package/src/lib/rendering/render/blit.ts +393 -0
- package/src/lib/rendering/render/cells.ts +369 -0
- package/src/lib/rendering/render/index.ts +105 -0
- package/src/lib/rendering/render/lines.ts +363 -0
- package/src/lib/rendering/render/theme.spec.ts +282 -0
- package/src/lib/rendering/render/theme.ts +201 -0
- package/src/lib/rendering/render/types.ts +279 -0
- package/src/lib/rendering/render/walk.spec.ts +360 -0
- package/src/lib/rendering/render/walk.ts +360 -0
- package/src/lib/rendering/utils/damage-tracker.spec.ts +444 -0
- package/src/lib/rendering/utils/damage-tracker.ts +423 -0
- package/src/lib/rendering/utils/index.ts +7 -0
- package/src/lib/services/grid.service.spec.ts +1039 -0
- package/src/lib/services/grid.service.ts +1284 -0
- package/src/lib/types/ag-grid-types.ts +970 -0
- package/src/public-api.ts +22 -0
- package/tsconfig.json +32 -0
- package/tsconfig.lib.json +11 -0
- package/tsconfig.spec.json +8 -0
- package/vitest.config.ts +55 -0
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for Walker Functions
|
|
3
|
+
*
|
|
4
|
+
* Tests for walkColumns, walkRows, walkCells, and related utilities.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
walkColumns,
|
|
9
|
+
getPositionedColumns,
|
|
10
|
+
getPinnedWidths,
|
|
11
|
+
walkRows,
|
|
12
|
+
getVisibleRowRange,
|
|
13
|
+
getRowY,
|
|
14
|
+
walkCells,
|
|
15
|
+
getColumnAtX,
|
|
16
|
+
getColumnIndex,
|
|
17
|
+
getTotalColumnWidth,
|
|
18
|
+
getRowAtY,
|
|
19
|
+
isRowVisible,
|
|
20
|
+
calculateVisibleRange,
|
|
21
|
+
} from './walk';
|
|
22
|
+
import { Column } from '../../types/ag-grid-types';
|
|
23
|
+
|
|
24
|
+
// Helper to create mock columns
|
|
25
|
+
function createMockColumn(overrides: Partial<Column> = {}): Column {
|
|
26
|
+
return {
|
|
27
|
+
colId: 'test-col',
|
|
28
|
+
field: 'testField',
|
|
29
|
+
width: 100,
|
|
30
|
+
pinned: false,
|
|
31
|
+
visible: true,
|
|
32
|
+
...overrides,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
describe('Walker Functions', () => {
|
|
37
|
+
describe('getPinnedWidths', () => {
|
|
38
|
+
it('should calculate left and right pinned widths', () => {
|
|
39
|
+
const columns: Column[] = [
|
|
40
|
+
createMockColumn({ colId: 'left1', pinned: 'left', width: 50 }),
|
|
41
|
+
createMockColumn({ colId: 'left2', pinned: 'left', width: 50 }),
|
|
42
|
+
createMockColumn({ colId: 'center1', width: 100 }),
|
|
43
|
+
createMockColumn({ colId: 'right1', pinned: 'right', width: 75 }),
|
|
44
|
+
createMockColumn({ colId: 'right2', pinned: 'right', width: 75 }),
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
const widths = getPinnedWidths(columns);
|
|
48
|
+
|
|
49
|
+
expect(widths.left).toBe(100);
|
|
50
|
+
expect(widths.right).toBe(150);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('should return zeros when no pinned columns', () => {
|
|
54
|
+
const columns: Column[] = [
|
|
55
|
+
createMockColumn({ colId: 'col1', width: 100 }),
|
|
56
|
+
createMockColumn({ colId: 'col2', width: 100 }),
|
|
57
|
+
];
|
|
58
|
+
|
|
59
|
+
const widths = getPinnedWidths(columns);
|
|
60
|
+
|
|
61
|
+
expect(widths.left).toBe(0);
|
|
62
|
+
expect(widths.right).toBe(0);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
describe('getVisibleRowRange', () => {
|
|
67
|
+
it('should calculate visible row range with buffer', () => {
|
|
68
|
+
const result = getVisibleRowRange(
|
|
69
|
+
100, // scrollTop
|
|
70
|
+
400, // viewportHeight
|
|
71
|
+
32, // rowHeight
|
|
72
|
+
1000, // totalRowCount
|
|
73
|
+
5 // buffer
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
// Starting row: floor(100/32) = 3, minus buffer = -2 -> clamped to 0
|
|
77
|
+
// Rows visible: ceil(400/32) = 13
|
|
78
|
+
// End: 0 + 13 + 10 (buffer*2) = 23
|
|
79
|
+
expect(result.startRow).toBe(0);
|
|
80
|
+
expect(result.endRow).toBeLessThanOrEqual(23);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('should clamp start row to 0', () => {
|
|
84
|
+
const result = getVisibleRowRange(
|
|
85
|
+
0, // scrollTop
|
|
86
|
+
400, // viewportHeight
|
|
87
|
+
32, // rowHeight
|
|
88
|
+
1000, // totalRowCount
|
|
89
|
+
5 // buffer
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
expect(result.startRow).toBe(0);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('should clamp end row to total row count', () => {
|
|
96
|
+
const result = getVisibleRowRange(
|
|
97
|
+
0, // scrollTop
|
|
98
|
+
400, // viewportHeight
|
|
99
|
+
32, // rowHeight
|
|
100
|
+
10, // totalRowCount (small)
|
|
101
|
+
5 // buffer
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
expect(result.endRow).toBeLessThanOrEqual(10);
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
describe('walkColumns', () => {
|
|
109
|
+
it('should walk through columns in order (left, center, right)', () => {
|
|
110
|
+
const columns: Column[] = [
|
|
111
|
+
createMockColumn({ colId: 'left1', pinned: 'left', width: 50 }),
|
|
112
|
+
createMockColumn({ colId: 'center1', width: 100 }),
|
|
113
|
+
createMockColumn({ colId: 'right1', pinned: 'right', width: 75 }),
|
|
114
|
+
];
|
|
115
|
+
|
|
116
|
+
const visited: string[] = [];
|
|
117
|
+
const xPositions: number[] = [];
|
|
118
|
+
|
|
119
|
+
walkColumns(columns, 0, 400, 50, 75, (col, x, width, isPinned, pinSide) => {
|
|
120
|
+
visited.push(col.colId);
|
|
121
|
+
xPositions.push(x);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
expect(visited).toEqual(['left1', 'center1', 'right1']);
|
|
125
|
+
// left1 starts at 0
|
|
126
|
+
// center1 starts after left pinned (50) minus scroll (0) = 50
|
|
127
|
+
// right1 starts at viewport width - right pinned = 400 - 75 = 325
|
|
128
|
+
expect(xPositions[0]).toBe(0);
|
|
129
|
+
expect(xPositions[1]).toBe(50);
|
|
130
|
+
expect(xPositions[2]).toBe(325);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('should skip center columns outside viewport', () => {
|
|
134
|
+
const columns: Column[] = [
|
|
135
|
+
createMockColumn({ colId: 'left1', pinned: 'left', width: 50 }),
|
|
136
|
+
createMockColumn({ colId: 'center1', width: 100 }),
|
|
137
|
+
createMockColumn({ colId: 'center2', width: 100 }),
|
|
138
|
+
createMockColumn({ colId: 'center3', width: 100 }),
|
|
139
|
+
createMockColumn({ colId: 'right1', pinned: 'right', width: 75 }),
|
|
140
|
+
];
|
|
141
|
+
|
|
142
|
+
// Scroll so center1 and part of center2 are hidden
|
|
143
|
+
const visited: string[] = [];
|
|
144
|
+
walkColumns(columns, 150, 400, 50, 75, (col, x, width, isPinned) => {
|
|
145
|
+
visited.push(col.colId);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// Left and right pinned should always be visited
|
|
149
|
+
expect(visited).toContain('left1');
|
|
150
|
+
expect(visited).toContain('right1');
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
describe('walkRows', () => {
|
|
155
|
+
it('should walk through rows with correct Y positions', () => {
|
|
156
|
+
const visited: { row: number; y: number }[] = [];
|
|
157
|
+
const getRowNode = (index: number) => ({ data: { id: index } }) as any;
|
|
158
|
+
|
|
159
|
+
walkRows(0, 5, 0, 32, getRowNode, (rowIndex, y, height, rowNode) => {
|
|
160
|
+
visited.push({ row: rowIndex, y });
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
expect(visited).toHaveLength(5);
|
|
164
|
+
expect(visited[0]).toEqual({ row: 0, y: 0 });
|
|
165
|
+
expect(visited[1]).toEqual({ row: 1, y: 32 });
|
|
166
|
+
expect(visited[4]).toEqual({ row: 4, y: 128 });
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it('should account for scroll offset', () => {
|
|
170
|
+
const visited: { row: number; y: number }[] = [];
|
|
171
|
+
const getRowNode = (index: number) => ({ data: { id: index } }) as any;
|
|
172
|
+
|
|
173
|
+
walkRows(10, 15, 320, 32, getRowNode, (rowIndex, y, height, rowNode) => {
|
|
174
|
+
visited.push({ row: rowIndex, y });
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
// Row 10 at scroll 320: y = 10*32 - 320 = 0
|
|
178
|
+
expect(visited[0]).toEqual({ row: 10, y: 0 });
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
describe('getRowY', () => {
|
|
183
|
+
it('should calculate Y position with scroll offset', () => {
|
|
184
|
+
expect(getRowY(0, 32, 0)).toBe(0);
|
|
185
|
+
expect(getRowY(10, 32, 0)).toBe(320);
|
|
186
|
+
expect(getRowY(10, 32, 320)).toBe(0);
|
|
187
|
+
expect(getRowY(10, 32, 160)).toBe(160);
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
describe('getRowAtY', () => {
|
|
192
|
+
it('should return row index for Y position', () => {
|
|
193
|
+
expect(getRowAtY(0, 32, 0)).toBe(0);
|
|
194
|
+
expect(getRowAtY(31, 32, 0)).toBe(0);
|
|
195
|
+
expect(getRowAtY(32, 32, 0)).toBe(1);
|
|
196
|
+
expect(getRowAtY(100, 32, 0)).toBe(3);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it('should account for scroll offset', () => {
|
|
200
|
+
// Y=0 with scroll=320 means we're at row 10
|
|
201
|
+
expect(getRowAtY(0, 32, 320)).toBe(10);
|
|
202
|
+
expect(getRowAtY(31, 32, 320)).toBe(10);
|
|
203
|
+
expect(getRowAtY(32, 32, 320)).toBe(11);
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
describe('getColumnAtX', () => {
|
|
208
|
+
it('should find left pinned column', () => {
|
|
209
|
+
const columns: Column[] = [
|
|
210
|
+
createMockColumn({ colId: 'left1', pinned: 'left', width: 50 }),
|
|
211
|
+
createMockColumn({ colId: 'left2', pinned: 'left', width: 50 }),
|
|
212
|
+
createMockColumn({ colId: 'center1', width: 100 }),
|
|
213
|
+
createMockColumn({ colId: 'right1', pinned: 'right', width: 75 }),
|
|
214
|
+
];
|
|
215
|
+
|
|
216
|
+
const result = getColumnAtX(columns, 25, 0, 400);
|
|
217
|
+
expect(result.column?.colId).toBe('left1');
|
|
218
|
+
expect(result.index).toBe(0);
|
|
219
|
+
expect(result.localX).toBe(25);
|
|
220
|
+
|
|
221
|
+
const result2 = getColumnAtX(columns, 75, 0, 400);
|
|
222
|
+
expect(result2.column?.colId).toBe('left2');
|
|
223
|
+
expect(result2.localX).toBe(25);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it('should find right pinned column', () => {
|
|
227
|
+
const columns: Column[] = [
|
|
228
|
+
createMockColumn({ colId: 'left1', pinned: 'left', width: 50 }),
|
|
229
|
+
createMockColumn({ colId: 'center1', width: 100 }),
|
|
230
|
+
createMockColumn({ colId: 'right1', pinned: 'right', width: 75 }),
|
|
231
|
+
];
|
|
232
|
+
|
|
233
|
+
// Right pinned starts at 400 - 75 = 325
|
|
234
|
+
const result = getColumnAtX(columns, 350, 0, 400);
|
|
235
|
+
expect(result.column?.colId).toBe('right1');
|
|
236
|
+
expect(result.index).toBe(2);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it('should find center column accounting for scroll', () => {
|
|
240
|
+
const columns: Column[] = [
|
|
241
|
+
createMockColumn({ colId: 'left1', pinned: 'left', width: 50 }),
|
|
242
|
+
createMockColumn({ colId: 'center1', width: 100 }),
|
|
243
|
+
createMockColumn({ colId: 'center2', width: 100 }),
|
|
244
|
+
createMockColumn({ colId: 'right1', pinned: 'right', width: 75 }),
|
|
245
|
+
];
|
|
246
|
+
|
|
247
|
+
// Center area: 50 to 325 (400-75)
|
|
248
|
+
// X=100 with scroll=0 should hit center1
|
|
249
|
+
const result = getColumnAtX(columns, 100, 0, 400);
|
|
250
|
+
expect(result.column?.colId).toBe('center1');
|
|
251
|
+
|
|
252
|
+
// X=160 with scroll=0 should hit center2
|
|
253
|
+
const result2 = getColumnAtX(columns, 160, 0, 400);
|
|
254
|
+
expect(result2.column?.colId).toBe('center2');
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
it('should return null for position outside columns', () => {
|
|
258
|
+
const columns: Column[] = [
|
|
259
|
+
createMockColumn({ colId: 'col1', width: 100 }),
|
|
260
|
+
];
|
|
261
|
+
|
|
262
|
+
const result = getColumnAtX(columns, 200, 0, 400);
|
|
263
|
+
expect(result.column).toBeNull();
|
|
264
|
+
expect(result.index).toBe(-1);
|
|
265
|
+
});
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
describe('getTotalColumnWidth', () => {
|
|
269
|
+
it('should sum all column widths', () => {
|
|
270
|
+
const columns: Column[] = [
|
|
271
|
+
createMockColumn({ width: 100 }),
|
|
272
|
+
createMockColumn({ width: 150 }),
|
|
273
|
+
createMockColumn({ width: 200 }),
|
|
274
|
+
];
|
|
275
|
+
|
|
276
|
+
expect(getTotalColumnWidth(columns)).toBe(450);
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
it('should return 0 for empty array', () => {
|
|
280
|
+
expect(getTotalColumnWidth([])).toBe(0);
|
|
281
|
+
});
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
describe('isRowVisible', () => {
|
|
285
|
+
it('should return true for visible rows', () => {
|
|
286
|
+
expect(isRowVisible(5, 160, 400, 32)).toBe(true); // Row 5: y=160-192, in viewport 160-560
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
it('should return false for rows above viewport', () => {
|
|
290
|
+
expect(isRowVisible(3, 200, 400, 32)).toBe(false); // Row 3: y=96-128, above 200
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
it('should return false for rows below viewport', () => {
|
|
294
|
+
expect(isRowVisible(20, 0, 400, 32)).toBe(false); // Row 20: y=640, below 400
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
it('should return true for partially visible rows', () => {
|
|
298
|
+
// Row 12: y=384-416, partially in viewport 0-400
|
|
299
|
+
expect(isRowVisible(12, 0, 400, 32)).toBe(true);
|
|
300
|
+
});
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
describe('calculateVisibleRange', () => {
|
|
304
|
+
it('should calculate complete visible range', () => {
|
|
305
|
+
const columns: Column[] = [
|
|
306
|
+
createMockColumn({ colId: 'left1', pinned: 'left', width: 50 }),
|
|
307
|
+
createMockColumn({ colId: 'center1', width: 100 }),
|
|
308
|
+
createMockColumn({ colId: 'center2', width: 100 }),
|
|
309
|
+
createMockColumn({ colId: 'right1', pinned: 'right', width: 75 }),
|
|
310
|
+
];
|
|
311
|
+
|
|
312
|
+
const range = calculateVisibleRange(
|
|
313
|
+
columns,
|
|
314
|
+
0, // scrollTop
|
|
315
|
+
0, // scrollLeft
|
|
316
|
+
400, // viewportWidth
|
|
317
|
+
400, // viewportHeight
|
|
318
|
+
32, // rowHeight
|
|
319
|
+
100, // totalRowCount
|
|
320
|
+
5 // buffer
|
|
321
|
+
);
|
|
322
|
+
|
|
323
|
+
expect(range.startRow).toBe(0);
|
|
324
|
+
expect(range.endRow).toBeGreaterThan(0);
|
|
325
|
+
expect(range.startColumnIndex).toBeDefined();
|
|
326
|
+
expect(range.endColumnIndex).toBeDefined();
|
|
327
|
+
});
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
describe('getPositionedColumns', () => {
|
|
331
|
+
it('should return all columns with positions', () => {
|
|
332
|
+
const columns: Column[] = [
|
|
333
|
+
createMockColumn({ colId: 'left1', pinned: 'left', width: 50 }),
|
|
334
|
+
createMockColumn({ colId: 'center1', width: 100 }),
|
|
335
|
+
createMockColumn({ colId: 'right1', pinned: 'right', width: 75 }),
|
|
336
|
+
];
|
|
337
|
+
|
|
338
|
+
const positioned = getPositionedColumns(columns, 0, 400, 50, 75);
|
|
339
|
+
|
|
340
|
+
expect(positioned.length).toBeGreaterThan(0);
|
|
341
|
+
expect(positioned[0].column.colId).toBe('left1');
|
|
342
|
+
expect(positioned[0].x).toBe(0);
|
|
343
|
+
});
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
describe('getColumnIndex', () => {
|
|
347
|
+
it('should find column index by colId', () => {
|
|
348
|
+
const columns: Column[] = [
|
|
349
|
+
createMockColumn({ colId: 'col1' }),
|
|
350
|
+
createMockColumn({ colId: 'col2' }),
|
|
351
|
+
createMockColumn({ colId: 'col3' }),
|
|
352
|
+
];
|
|
353
|
+
|
|
354
|
+
expect(getColumnIndex(columns, 'col1')).toBe(0);
|
|
355
|
+
expect(getColumnIndex(columns, 'col2')).toBe(1);
|
|
356
|
+
expect(getColumnIndex(columns, 'col3')).toBe(2);
|
|
357
|
+
expect(getColumnIndex(columns, 'notfound')).toBe(-1);
|
|
358
|
+
});
|
|
359
|
+
});
|
|
360
|
+
});
|