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.
Files changed (53) hide show
  1. package/.github/workflows/pages.yml +68 -0
  2. package/AGENTS.md +179 -0
  3. package/README.md +222 -0
  4. package/demo-app/README.md +70 -0
  5. package/demo-app/angular.json +78 -0
  6. package/demo-app/e2e/benchmark.spec.ts +53 -0
  7. package/demo-app/e2e/demo-page.spec.ts +77 -0
  8. package/demo-app/e2e/grid-features.spec.ts +269 -0
  9. package/demo-app/package-lock.json +14023 -0
  10. package/demo-app/package.json +36 -0
  11. package/demo-app/playwright-test-menu.js +19 -0
  12. package/demo-app/playwright.config.ts +23 -0
  13. package/demo-app/src/app/app.component.ts +10 -0
  14. package/demo-app/src/app/app.config.ts +13 -0
  15. package/demo-app/src/app/app.routes.ts +7 -0
  16. package/demo-app/src/app/demo-page/demo-page.component.css +313 -0
  17. package/demo-app/src/app/demo-page/demo-page.component.html +124 -0
  18. package/demo-app/src/app/demo-page/demo-page.component.ts +366 -0
  19. package/demo-app/src/index.html +19 -0
  20. package/demo-app/src/main.ts +6 -0
  21. package/demo-app/tsconfig.json +31 -0
  22. package/ng-package.json +8 -0
  23. package/package.json +60 -0
  24. package/plan.md +131 -0
  25. package/setup-vitest.ts +18 -0
  26. package/src/lib/argent-grid.module.ts +21 -0
  27. package/src/lib/components/argent-grid.component.css +483 -0
  28. package/src/lib/components/argent-grid.component.html +320 -0
  29. package/src/lib/components/argent-grid.component.spec.ts +189 -0
  30. package/src/lib/components/argent-grid.component.ts +1188 -0
  31. package/src/lib/directives/ag-grid-compatibility.directive.ts +92 -0
  32. package/src/lib/rendering/canvas-renderer.ts +962 -0
  33. package/src/lib/rendering/render/blit.spec.ts +453 -0
  34. package/src/lib/rendering/render/blit.ts +393 -0
  35. package/src/lib/rendering/render/cells.ts +369 -0
  36. package/src/lib/rendering/render/index.ts +105 -0
  37. package/src/lib/rendering/render/lines.ts +363 -0
  38. package/src/lib/rendering/render/theme.spec.ts +282 -0
  39. package/src/lib/rendering/render/theme.ts +201 -0
  40. package/src/lib/rendering/render/types.ts +279 -0
  41. package/src/lib/rendering/render/walk.spec.ts +360 -0
  42. package/src/lib/rendering/render/walk.ts +360 -0
  43. package/src/lib/rendering/utils/damage-tracker.spec.ts +444 -0
  44. package/src/lib/rendering/utils/damage-tracker.ts +423 -0
  45. package/src/lib/rendering/utils/index.ts +7 -0
  46. package/src/lib/services/grid.service.spec.ts +1039 -0
  47. package/src/lib/services/grid.service.ts +1284 -0
  48. package/src/lib/types/ag-grid-types.ts +970 -0
  49. package/src/public-api.ts +22 -0
  50. package/tsconfig.json +32 -0
  51. package/tsconfig.lib.json +11 -0
  52. package/tsconfig.spec.json +8 -0
  53. 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
+ });