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.
- package/AGENTS.md +70 -27
- package/e2e/advanced.spec.ts +1 -1
- package/e2e/benchmark.spec.ts +7 -7
- package/e2e/cell-renderers.spec.ts +152 -0
- package/e2e/debug-streaming.spec.ts +31 -0
- package/e2e/dnd.spec.ts +73 -0
- package/e2e/screenshots.spec.ts +1 -1
- package/e2e/visual.spec.ts +30 -9
- package/e2e/visual.spec.ts-snapshots/checkbox-renderer-mixed.png +0 -0
- package/e2e/visual.spec.ts-snapshots/debug.png +0 -0
- package/e2e/visual.spec.ts-snapshots/grid-column-group-headers.png +0 -0
- package/e2e/visual.spec.ts-snapshots/grid-default.png +0 -0
- package/e2e/visual.spec.ts-snapshots/grid-empty-state.png +0 -0
- package/e2e/visual.spec.ts-snapshots/grid-filter-popup.png +0 -0
- package/e2e/visual.spec.ts-snapshots/grid-scroll-borders.png +0 -0
- package/e2e/visual.spec.ts-snapshots/grid-sidebar-buttons.png +0 -0
- package/e2e/visual.spec.ts-snapshots/grid-text-filter.png +0 -0
- package/e2e/visual.spec.ts-snapshots/grid-with-selection.png +0 -0
- package/e2e/visual.spec.ts-snapshots/rating-renderer-varied.png +0 -0
- package/package.json +5 -5
- package/plan.md +30 -34
- package/src/lib/components/argent-grid.component.css +258 -549
- package/src/lib/components/argent-grid.component.html +272 -306
- package/src/lib/components/argent-grid.component.ts +585 -135
- package/src/lib/components/argent-grid.regressions.spec.ts +301 -0
- package/src/lib/components/argent-grid.selection.spec.ts +2 -2
- package/src/lib/components/set-filter/set-filter.component.spec.ts +191 -0
- package/src/lib/components/set-filter/set-filter.component.ts +7 -2
- package/src/lib/rendering/canvas-renderer.spec.ts +148 -1
- package/src/lib/rendering/canvas-renderer.ts +177 -286
- package/src/lib/rendering/render/cells.ts +122 -5
- package/src/lib/rendering/render/column-utils.ts +27 -5
- package/src/lib/rendering/render/hit-test.ts +6 -11
- package/src/lib/rendering/render/index.ts +15 -6
- package/src/lib/rendering/render/lines.ts +12 -6
- package/src/lib/rendering/render/primitives.ts +269 -7
- package/src/lib/rendering/render/types.ts +2 -1
- package/src/lib/rendering/render/walk.ts +39 -19
- package/src/lib/services/grid.service.spec.ts +76 -0
- package/src/lib/services/grid.service.ts +451 -114
- package/src/lib/themes/theme-quartz.ts +2 -2
- package/src/lib/types/ag-grid-types.ts +500 -0
- package/src/stories/Advanced.stories.ts +78 -17
- package/src/stories/ArgentGrid.stories.ts +50 -26
- package/src/stories/Benchmark.stories.ts +17 -15
- package/src/stories/CellRenderers.stories.ts +205 -31
- package/src/stories/Filtering.stories.ts +56 -16
- package/src/stories/Grouping.stories.ts +86 -13
- package/src/stories/Streaming.stories.ts +57 -0
- package/src/stories/Theming.stories.ts +23 -10
- package/src/stories/Tooltips.stories.ts +381 -0
- package/src/stories/benchmark-wrapper.component.ts +69 -29
- package/src/stories/story-utils.ts +88 -0
- package/src/stories/streaming-wrapper.component.ts +441 -0
- 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
|
-
//
|
|
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
|
|