argent-grid 0.2.0 → 0.3.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 (55) hide show
  1. package/AGENTS.md +70 -27
  2. package/e2e/advanced.spec.ts +1 -1
  3. package/e2e/benchmark.spec.ts +7 -7
  4. package/e2e/cell-renderers.spec.ts +152 -0
  5. package/e2e/debug-streaming.spec.ts +31 -0
  6. package/e2e/dnd.spec.ts +73 -0
  7. package/e2e/screenshots.spec.ts +1 -1
  8. package/e2e/visual.spec.ts +30 -9
  9. package/e2e/visual.spec.ts-snapshots/checkbox-renderer-mixed.png +0 -0
  10. package/e2e/visual.spec.ts-snapshots/debug.png +0 -0
  11. package/e2e/visual.spec.ts-snapshots/grid-column-group-headers.png +0 -0
  12. package/e2e/visual.spec.ts-snapshots/grid-default.png +0 -0
  13. package/e2e/visual.spec.ts-snapshots/grid-empty-state.png +0 -0
  14. package/e2e/visual.spec.ts-snapshots/grid-filter-popup.png +0 -0
  15. package/e2e/visual.spec.ts-snapshots/grid-scroll-borders.png +0 -0
  16. package/e2e/visual.spec.ts-snapshots/grid-sidebar-buttons.png +0 -0
  17. package/e2e/visual.spec.ts-snapshots/grid-text-filter.png +0 -0
  18. package/e2e/visual.spec.ts-snapshots/grid-with-selection.png +0 -0
  19. package/e2e/visual.spec.ts-snapshots/rating-renderer-varied.png +0 -0
  20. package/package.json +5 -5
  21. package/plan.md +30 -34
  22. package/src/lib/components/argent-grid.component.css +258 -549
  23. package/src/lib/components/argent-grid.component.html +272 -306
  24. package/src/lib/components/argent-grid.component.ts +585 -135
  25. package/src/lib/components/argent-grid.regressions.spec.ts +301 -0
  26. package/src/lib/components/argent-grid.selection.spec.ts +2 -2
  27. package/src/lib/components/set-filter/set-filter.component.spec.ts +191 -0
  28. package/src/lib/components/set-filter/set-filter.component.ts +7 -2
  29. package/src/lib/rendering/canvas-renderer.spec.ts +148 -1
  30. package/src/lib/rendering/canvas-renderer.ts +177 -286
  31. package/src/lib/rendering/render/cells.ts +122 -5
  32. package/src/lib/rendering/render/column-utils.ts +27 -5
  33. package/src/lib/rendering/render/hit-test.ts +6 -11
  34. package/src/lib/rendering/render/index.ts +15 -6
  35. package/src/lib/rendering/render/lines.ts +12 -6
  36. package/src/lib/rendering/render/primitives.ts +269 -7
  37. package/src/lib/rendering/render/types.ts +2 -1
  38. package/src/lib/rendering/render/walk.ts +39 -19
  39. package/src/lib/services/grid.service.spec.ts +76 -0
  40. package/src/lib/services/grid.service.ts +451 -114
  41. package/src/lib/themes/theme-quartz.ts +2 -2
  42. package/src/lib/types/ag-grid-types.ts +500 -0
  43. package/src/stories/Advanced.stories.ts +78 -17
  44. package/src/stories/ArgentGrid.stories.ts +50 -26
  45. package/src/stories/Benchmark.stories.ts +17 -15
  46. package/src/stories/CellRenderers.stories.ts +205 -31
  47. package/src/stories/Filtering.stories.ts +56 -16
  48. package/src/stories/Grouping.stories.ts +86 -13
  49. package/src/stories/Streaming.stories.ts +57 -0
  50. package/src/stories/Theming.stories.ts +23 -10
  51. package/src/stories/Tooltips.stories.ts +381 -0
  52. package/src/stories/benchmark-wrapper.component.ts +69 -29
  53. package/src/stories/story-utils.ts +88 -0
  54. package/src/stories/streaming-wrapper.component.ts +441 -0
  55. package/tsconfig.json +1 -0
@@ -297,6 +297,7 @@ describe('CanvasRenderer', () => {
297
297
  width: 50,
298
298
  visible: true,
299
299
  pinned: 'left',
300
+ checkboxSelection: true,
300
301
  } as any;
301
302
 
302
303
  const mockRowNode = {
@@ -312,22 +313,167 @@ describe('CanvasRenderer', () => {
312
313
  } as any;
313
314
 
314
315
  vi.spyOn(mockApi, 'getAllColumns').mockReturnValue([mockColumn]);
316
+ vi.spyOn(mockApi, 'getColumnDefs').mockReturnValue([mockColumn]);
315
317
  vi.spyOn(mockApi, 'getDisplayedRowAtIndex').mockReturnValue(mockRowNode);
316
318
  vi.spyOn(mockApi, 'getDisplayedRowCount').mockReturnValue(1);
317
319
 
318
- // Force a render
320
+ // Mark dirty so doRender doesn't skip, then force a synchronous render
321
+ renderer.invalidateAll();
319
322
  renderer.renderFrame();
320
323
 
321
324
  // Verify strokeRect was called (for checkbox border)
322
325
  expect(mockCanvasContext.strokeRect).toHaveBeenCalled();
323
326
  });
324
327
 
328
+ // ============================================================================
329
+ // REGRESSION TESTS — Canvas Renderer architectural invariants
330
+ // These tests guard against the performance / feedback-loop bugs fixed in
331
+ // Phase VII. Do NOT remove them.
332
+ // ============================================================================
333
+
334
+ describe('Damage gate — scheduleRender() must not queue rAF when nothing is dirty', () => {
335
+ it('does not call requestAnimationFrame when damage tracker has no damage', () => {
336
+ const rafSpy = vi.spyOn(globalThis, 'requestAnimationFrame').mockImplementation(() => 0);
337
+
338
+ // Brand-new renderer has no pending damage after construction — call
339
+ // scheduleRender indirectly through the private accessor by calling
340
+ // render() and then draining the dirty state first.
341
+ // Start clean: call renderFrame() to drain any initial dirty state.
342
+ renderer.renderFrame();
343
+
344
+ // Reset the spy AFTER the initial frame so we only measure new calls.
345
+ rafSpy.mockClear();
346
+
347
+ // scheduleRender() with no damage should be a no-op.
348
+ (renderer as any).scheduleRender();
349
+ expect(rafSpy).not.toHaveBeenCalled();
350
+
351
+ rafSpy.mockRestore();
352
+ });
353
+
354
+ it('calls requestAnimationFrame when damage is present', () => {
355
+ let rafCalled = false;
356
+ const rafSpy = vi.spyOn(globalThis, 'requestAnimationFrame').mockImplementation((cb) => {
357
+ rafCalled = true;
358
+ return 0;
359
+ });
360
+
361
+ // Mark dirty then schedule.
362
+ renderer.render(); // internally calls markAllDirty() + scheduleRender()
363
+ expect(rafCalled).toBe(true);
364
+
365
+ rafSpy.mockRestore();
366
+ });
367
+ });
368
+
369
+ describe('nextRenderPending coalescing — concurrent scheduleRender calls must not drop renders', () => {
370
+ it('sets nextRenderPending when a frame is already in-flight', () => {
371
+ let _capturedCb: FrameRequestCallback | null = null;
372
+ const rafSpy = vi
373
+ .spyOn(globalThis, 'requestAnimationFrame')
374
+ .mockImplementation((cb: FrameRequestCallback) => {
375
+ _capturedCb = cb;
376
+ return 1;
377
+ });
378
+
379
+ // First call — starts the rAF.
380
+ renderer.render();
381
+ expect((renderer as any).animationFrameId).not.toBeNull();
382
+ expect((renderer as any).nextRenderPending).toBe(false);
383
+
384
+ // Second call while frame is still in-flight.
385
+ renderer.render();
386
+ expect((renderer as any).nextRenderPending).toBe(true);
387
+
388
+ rafSpy.mockRestore();
389
+ });
390
+
391
+ it('schedules a follow-up render after the first frame completes if damage arrives mid-render', () => {
392
+ const frames: FrameRequestCallback[] = [];
393
+ const rafSpy = vi
394
+ .spyOn(globalThis, 'requestAnimationFrame')
395
+ .mockImplementation((cb: FrameRequestCallback) => {
396
+ frames.push(cb);
397
+ return frames.length;
398
+ });
399
+
400
+ // Patch doRender to mark dirty again mid-render (simulates data arriving during a frame).
401
+ const originalDoRender = (renderer as any).doRender.bind(renderer);
402
+ vi.spyOn(renderer as any, 'doRender').mockImplementationOnce(() => {
403
+ originalDoRender();
404
+ // Simulate new data arriving while the frame is being painted.
405
+ renderer.render(); // marks dirty + sets nextRenderPending (animationFrameId still set)
406
+ });
407
+
408
+ renderer.render(); // queues frame[0]
409
+ expect(frames.length).toBe(1);
410
+
411
+ // Fire the first frame — mid-render, render() is called again marking dirty.
412
+ frames[0](0);
413
+ // A second rAF should have been scheduled for the newly dirty state.
414
+ expect(frames.length).toBe(2);
415
+
416
+ rafSpy.mockRestore();
417
+ });
418
+ });
419
+
420
+ describe('updateCanvasSize — must NEVER touch canvas.style.width or canvas.style.height', () => {
421
+ it('does not set canvas.style.width after setViewportDimensions', () => {
422
+ mockCanvas.style.width = ''; // clear before test
423
+ renderer.setViewportDimensions(640, 480);
424
+ expect(mockCanvas.style.width).toBe('');
425
+ });
426
+
427
+ it('does not set canvas.style.height after setViewportDimensions', () => {
428
+ mockCanvas.style.height = ''; // clear before test
429
+ renderer.setViewportDimensions(640, 480);
430
+ expect(mockCanvas.style.height).toBe('');
431
+ });
432
+
433
+ it('does set canvas.width (pixel buffer) after setViewportDimensions', () => {
434
+ renderer.setViewportDimensions(640, 480);
435
+ const dpr = window.devicePixelRatio || 1;
436
+ expect(mockCanvas.width).toBe(640 * dpr);
437
+ });
438
+
439
+ it('does set canvas.height (pixel buffer) after setViewportDimensions', () => {
440
+ renderer.setViewportDimensions(640, 480);
441
+ const dpr = window.devicePixelRatio || 1;
442
+ expect(mockCanvas.height).toBe(480 * dpr);
443
+ });
444
+ });
445
+
446
+ describe('blit infrastructure — removed dead code', () => {
447
+ it('does not have a blitState field (BlitState removed as dead code)', () => {
448
+ expect((renderer as any).blitState).toBeUndefined();
449
+ });
450
+
451
+ it('does not have a columnPositions field (dead map removed)', () => {
452
+ expect((renderer as any).columnPositions).toBeUndefined();
453
+ });
454
+
455
+ it('does not have a renderPending field (redundant with animationFrameId)', () => {
456
+ expect('renderPending' in renderer).toBe(false);
457
+ });
458
+ });
459
+
460
+ describe('renderThrottleMs — setRenderThrottle must not exist (removed in Phase VII)', () => {
461
+ it('does not expose a setRenderThrottle method', () => {
462
+ expect(typeof (renderer as any).setRenderThrottle).toBe('undefined');
463
+ });
464
+
465
+ it('does not have a renderThrottleMs property', () => {
466
+ expect('renderThrottleMs' in renderer).toBe(false);
467
+ });
468
+ });
469
+
325
470
  it('should toggle row selection when clicking dedicated selection column', () => {
326
471
  const mockColumn = {
327
472
  colId: 'ag-Grid-SelectionColumn',
328
473
  width: 50,
329
474
  visible: true,
330
475
  pinned: 'left',
476
+ checkboxSelection: true,
331
477
  } as any;
332
478
 
333
479
  const mockRowNode = {
@@ -346,6 +492,7 @@ describe('CanvasRenderer', () => {
346
492
  } as any;
347
493
 
348
494
  vi.spyOn(mockApi, 'getAllColumns').mockReturnValue([mockColumn]);
495
+ vi.spyOn(mockApi, 'getColumnDefs').mockReturnValue([mockColumn]);
349
496
  vi.spyOn(mockApi, 'getDisplayedRowAtIndex').mockReturnValue(mockRowNode);
350
497
  vi.spyOn(mockApi, 'getDisplayedRowCount').mockReturnValue(1);
351
498