argent-grid 0.1.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 (122) hide show
  1. package/.github/workflows/ci.yml +69 -0
  2. package/.github/workflows/pages.yml +6 -12
  3. package/.storybook/main.ts +20 -0
  4. package/.storybook/preview.ts +18 -0
  5. package/.storybook/tsconfig.json +24 -0
  6. package/AGENTS.md +70 -27
  7. package/README.md +51 -34
  8. package/angular.json +66 -0
  9. package/biome.json +66 -0
  10. package/demo-app/e2e/selection-screenshot.spec.ts +20 -0
  11. package/docs/AG-GRID-COMPARISON.md +725 -0
  12. package/docs/CELL-RENDERER-GUIDE.md +241 -0
  13. package/docs/CONTEXT-MENU-GUIDE.md +371 -0
  14. package/docs/LIVE-DATA-OPTIMIZATIONS.md +497 -0
  15. package/docs/PERFORMANCE-OPTIMIZATIONS-PHASE1.md +162 -0
  16. package/docs/PERFORMANCE-REVIEW.md +571 -0
  17. package/docs/RESEARCH-STATUS.md +234 -0
  18. package/docs/STATE-PERSISTENCE-GUIDE.md +370 -0
  19. package/docs/STORYBOOK-REFACTOR.md +215 -0
  20. package/docs/STORYBOOK-STATUS.md +156 -0
  21. package/docs/TEST-COVERAGE-REPORT.md +276 -0
  22. package/docs/THEME-API-GUIDE.md +445 -0
  23. package/docs/THEME-API-PLAN.md +364 -0
  24. package/e2e/advanced.spec.ts +109 -0
  25. package/e2e/argentgrid.spec.ts +65 -0
  26. package/e2e/benchmark.spec.ts +52 -0
  27. package/e2e/cell-renderers.spec.ts +152 -0
  28. package/e2e/debug-streaming.spec.ts +31 -0
  29. package/e2e/dnd.spec.ts +73 -0
  30. package/e2e/screenshots.spec.ts +52 -0
  31. package/e2e/theming.spec.ts +35 -0
  32. package/e2e/visual.spec.ts +112 -0
  33. package/e2e/visual.spec.ts-snapshots/checkbox-renderer-mixed.png +0 -0
  34. package/e2e/visual.spec.ts-snapshots/debug.png +0 -0
  35. package/e2e/visual.spec.ts-snapshots/grid-column-group-headers.png +0 -0
  36. package/e2e/visual.spec.ts-snapshots/grid-default.png +0 -0
  37. package/e2e/visual.spec.ts-snapshots/grid-empty-state.png +0 -0
  38. package/e2e/visual.spec.ts-snapshots/grid-filter-popup.png +0 -0
  39. package/e2e/visual.spec.ts-snapshots/grid-scroll-borders.png +0 -0
  40. package/e2e/visual.spec.ts-snapshots/grid-sidebar-buttons.png +0 -0
  41. package/e2e/visual.spec.ts-snapshots/grid-text-filter.png +0 -0
  42. package/e2e/visual.spec.ts-snapshots/grid-with-selection.png +0 -0
  43. package/e2e/visual.spec.ts-snapshots/rating-renderer-varied.png +0 -0
  44. package/package.json +21 -7
  45. package/plan.md +56 -28
  46. package/playwright.config.ts +38 -0
  47. package/setup-vitest.ts +10 -13
  48. package/src/lib/argent-grid.module.ts +10 -12
  49. package/src/lib/components/argent-grid.component.css +281 -321
  50. package/src/lib/components/argent-grid.component.html +295 -207
  51. package/src/lib/components/argent-grid.component.spec.ts +120 -160
  52. package/src/lib/components/argent-grid.component.ts +1193 -290
  53. package/src/lib/components/argent-grid.regressions.spec.ts +301 -0
  54. package/src/lib/components/argent-grid.selection.spec.ts +132 -0
  55. package/src/lib/components/set-filter/set-filter.component.spec.ts +191 -0
  56. package/src/lib/components/set-filter/set-filter.component.ts +307 -0
  57. package/src/lib/directives/ag-grid-compatibility.directive.ts +16 -26
  58. package/src/lib/directives/click-outside.directive.ts +19 -0
  59. package/src/lib/rendering/canvas-renderer.spec.ts +513 -0
  60. package/src/lib/rendering/canvas-renderer.ts +456 -452
  61. package/src/lib/rendering/live-data-handler.ts +110 -0
  62. package/src/lib/rendering/live-data-optimizations.ts +133 -0
  63. package/src/lib/rendering/render/blit.spec.ts +16 -27
  64. package/src/lib/rendering/render/blit.ts +48 -36
  65. package/src/lib/rendering/render/cells.spec.ts +132 -0
  66. package/src/lib/rendering/render/cells.ts +167 -28
  67. package/src/lib/rendering/render/column-utils.ts +95 -0
  68. package/src/lib/rendering/render/hit-test.ts +50 -0
  69. package/src/lib/rendering/render/index.ts +88 -76
  70. package/src/lib/rendering/render/lines.ts +53 -47
  71. package/src/lib/rendering/render/primitives.ts +423 -0
  72. package/src/lib/rendering/render/theme.spec.ts +8 -12
  73. package/src/lib/rendering/render/theme.ts +7 -10
  74. package/src/lib/rendering/render/types.ts +3 -2
  75. package/src/lib/rendering/render/walk.spec.ts +35 -38
  76. package/src/lib/rendering/render/walk.ts +94 -64
  77. package/src/lib/rendering/utils/damage-tracker.spec.ts +8 -7
  78. package/src/lib/rendering/utils/damage-tracker.ts +6 -18
  79. package/src/lib/rendering/utils/index.ts +1 -1
  80. package/src/lib/services/grid.service.set-filter.spec.ts +219 -0
  81. package/src/lib/services/grid.service.spec.ts +1241 -201
  82. package/src/lib/services/grid.service.ts +1204 -235
  83. package/src/lib/themes/parts/color-schemes.ts +132 -0
  84. package/src/lib/themes/parts/icon-sets.ts +258 -0
  85. package/src/lib/themes/theme-builder.ts +347 -0
  86. package/src/lib/themes/theme-quartz.ts +72 -0
  87. package/src/lib/themes/types.ts +238 -0
  88. package/src/lib/types/ag-grid-types.ts +573 -14
  89. package/src/public-api.ts +39 -9
  90. package/src/stories/Advanced.stories.ts +249 -0
  91. package/src/stories/ArgentGrid.stories.ts +301 -0
  92. package/src/stories/Benchmark.stories.ts +76 -0
  93. package/src/stories/CellRenderers.stories.ts +395 -0
  94. package/src/stories/Filtering.stories.ts +292 -0
  95. package/src/stories/Grouping.stories.ts +290 -0
  96. package/src/stories/Streaming.stories.ts +57 -0
  97. package/src/stories/Theming.stories.ts +137 -0
  98. package/src/stories/Tooltips.stories.ts +381 -0
  99. package/src/stories/benchmark-wrapper.component.ts +355 -0
  100. package/src/stories/story-utils.ts +88 -0
  101. package/src/stories/streaming-wrapper.component.ts +441 -0
  102. package/tsconfig.json +1 -0
  103. package/tsconfig.storybook.json +10 -0
  104. package/vitest.config.ts +9 -9
  105. package/demo-app/README.md +0 -70
  106. package/demo-app/angular.json +0 -78
  107. package/demo-app/e2e/benchmark.spec.ts +0 -53
  108. package/demo-app/e2e/demo-page.spec.ts +0 -77
  109. package/demo-app/e2e/grid-features.spec.ts +0 -269
  110. package/demo-app/package-lock.json +0 -14023
  111. package/demo-app/package.json +0 -36
  112. package/demo-app/playwright-test-menu.js +0 -19
  113. package/demo-app/playwright.config.ts +0 -23
  114. package/demo-app/src/app/app.component.ts +0 -10
  115. package/demo-app/src/app/app.config.ts +0 -13
  116. package/demo-app/src/app/app.routes.ts +0 -7
  117. package/demo-app/src/app/demo-page/demo-page.component.css +0 -313
  118. package/demo-app/src/app/demo-page/demo-page.component.html +0 -124
  119. package/demo-app/src/app/demo-page/demo-page.component.ts +0 -366
  120. package/demo-app/src/index.html +0 -19
  121. package/demo-app/src/main.ts +0 -6
  122. package/demo-app/tsconfig.json +0 -31
@@ -0,0 +1,152 @@
1
+ /**
2
+ * E2E / Visual Tests — Cell Renderer valueGetter Regression
3
+ *
4
+ * These tests guard against the bug where renderCell() read cell values via
5
+ * `getValueByPath(rowNode.data, column.field)` directly, completely bypassing
6
+ * `ColDef.valueGetter`. The symptom was:
7
+ * - CheckboxRenderer: every row appeared checked (raw performance 60-99
8
+ * is always truthy, but the valueGetter should return `performance >= 80`)
9
+ * - RatingRenderer: every row showed 5 stars (raw performance 60-99 passed
10
+ * to Math.round() is always ≥ 5, but the valueGetter scales to 0-5)
11
+ *
12
+ * Data used by both stories (CellRenderers.stories.ts):
13
+ * performance(i) = 60 + ((i * 7) % 40)
14
+ *
15
+ * Row │ perf │ highPerf (≥80) │ stars ((perf-60)/8 rounded)
16
+ * ─────┼──────┼────────────────┼────────────────────────────
17
+ * 0 │ 60 │ false │ 0
18
+ * 1 │ 67 │ false │ 1
19
+ * 2 │ 74 │ false │ 2
20
+ * 3 │ 81 │ true │ 3
21
+ * 4 │ 88 │ true │ 4
22
+ * 5 │ 95 │ true │ 4 (rounds to 4)
23
+ * 6 │ 62 │ false │ 0
24
+ */
25
+
26
+ import { expect, test } from '@playwright/test';
27
+
28
+ // ---------------------------------------------------------------------------
29
+ // Helpers
30
+ // ---------------------------------------------------------------------------
31
+
32
+ /**
33
+ * Sample the RGBA colour of a single logical pixel from the grid's canvas
34
+ * element. Accounts for devicePixelRatio so coordinates are always in CSS
35
+ * pixel space (matching the layout calculations below).
36
+ */
37
+ async function sampleCanvasPixel(
38
+ page: import('@playwright/test').Page,
39
+ logicalX: number,
40
+ logicalY: number
41
+ ): Promise<{ r: number; g: number; b: number; a: number }> {
42
+ const canvas = page.locator('canvas.argent-grid-canvas').first();
43
+
44
+ return canvas.evaluate(
45
+ (el: HTMLCanvasElement, [lx, ly]: number[]) => {
46
+ const dpr = window.devicePixelRatio || 1;
47
+ const ctx = el.getContext('2d');
48
+ if (!ctx) throw new Error('No 2d context');
49
+ const d = ctx.getImageData(Math.round(lx * dpr), Math.round(ly * dpr), 1, 1).data;
50
+ return { r: d[0], g: d[1], b: d[2], a: d[3] };
51
+ },
52
+ [logicalX, logicalY]
53
+ );
54
+ }
55
+
56
+ /** Wait for the grid canvas to finish its first render. */
57
+ async function waitForCanvas(page: import('@playwright/test').Page) {
58
+ await page.waitForSelector('argent-grid', { state: 'visible', timeout: 15000 });
59
+ await page.waitForSelector('canvas.argent-grid-canvas', { state: 'visible', timeout: 10000 });
60
+ // Allow one rAF cycle + any Angular change detection
61
+ await page.waitForTimeout(800);
62
+ }
63
+
64
+ /**
65
+ * Canvas layout constants — must stay in sync with CellRenderers.stories.ts
66
+ * column widths and the default rowHeight (32 px).
67
+ */
68
+ const ROW_HEIGHT = 32;
69
+
70
+ /** Vertical centre of row `i` inside the canvas (canvas starts at data row 0). */
71
+ function rowCenterY(i: number) {
72
+ return i * ROW_HEIGHT + ROW_HEIGHT / 2;
73
+ }
74
+
75
+ // ---------------------------------------------------------------------------
76
+ // CheckboxRenderer story
77
+ // ---------------------------------------------------------------------------
78
+
79
+ test.describe('CheckboxRenderer — valueGetter regression', () => {
80
+ test('visual snapshot — mixed checked/unchecked checkboxes', async ({ page }) => {
81
+ await page.goto('/iframe.html?id=features-cellrenderers--checkbox-renderer');
82
+ await waitForCanvas(page);
83
+ await page.waitForTimeout(500);
84
+
85
+ await expect(page.locator('argent-grid')).toHaveScreenshot('checkbox-renderer-mixed.png');
86
+ });
87
+ });
88
+
89
+ // ---------------------------------------------------------------------------
90
+ // RatingRenderer story
91
+ // ---------------------------------------------------------------------------
92
+
93
+ test.describe('RatingRenderer — valueGetter regression', () => {
94
+ /**
95
+ * Story column layout:
96
+ * id(80) | name(200) | Performance(150) | Stars(Small)(120)
97
+ *
98
+ * "Performance" column starts at x = 280, width = 150.
99
+ * Stars: max=5, size=16, gap=2 ⟹ totalWidth = 5*16 + 4*2 = 88
100
+ * startX = 280 + (150 − 88) / 2 = 311
101
+ * star[i] centre x = 311 + i*(16+2) + 8
102
+ * star[0] ≈ 319, star[3] ≈ 373
103
+ */
104
+ const starCenterX = (starIndex: number) => 311 + starIndex * 18 + 8;
105
+
106
+ test('row 0 (performance=60) shows 0 stars — all star pixels are empty', async ({ page }) => {
107
+ await page.goto('/iframe.html?id=features-cellrenderers--rating-renderer');
108
+ await waitForCanvas(page);
109
+
110
+ // Row 0 → (60-60)/8 = 0 stars → all stars should be the empty colour #e5e7eb (light gray)
111
+ const y = rowCenterY(0);
112
+ for (let s = 0; s < 5; s++) {
113
+ const px = await sampleCanvasPixel(page, starCenterX(s), y);
114
+ // Empty star: very light, high R+G around 220-235. Not yellow (R≫G, B low).
115
+ // Just assert it's NOT the bright yellow fill colour (#ffb400 = r255 g180 b0)
116
+ const isYellow = px.r > 200 && px.g > 130 && px.g < 200 && px.b < 50;
117
+ expect(isYellow, `star ${s} of row 0 should not be yellow`).toBe(false);
118
+ }
119
+ });
120
+
121
+ test('different rows show different numbers of filled stars', async ({ page }) => {
122
+ await page.goto('/iframe.html?id=features-cellrenderers--rating-renderer');
123
+ await waitForCanvas(page);
124
+
125
+ /**
126
+ * Sample the first star of each row and check that row 6 (0 stars) and
127
+ * row 4 (~4 stars) look different. This would catch the regression where
128
+ * every row got the raw performance value (60-99) passed to drawRating,
129
+ * causing Math.round(67) = 67 ≥ 5 → all 5 stars filled.
130
+ */
131
+ const firstStarX = starCenterX(0);
132
+
133
+ const row0px = await sampleCanvasPixel(page, firstStarX, rowCenterY(0)); // 0 stars
134
+ const row4px = await sampleCanvasPixel(page, firstStarX, rowCenterY(4)); // ~4 stars
135
+
136
+ // Row 0 first star should be empty (not yellow)
137
+ const row0IsYellow = row0px.r > 200 && row0px.g > 100 && row0px.b < 80;
138
+ expect(row0IsYellow).toBe(false);
139
+
140
+ // Row 4 first star should be filled (yellow)
141
+ const row4IsYellow = row4px.r > 180 && row4px.g > 100 && row4px.b < 80;
142
+ expect(row4IsYellow).toBe(true);
143
+ });
144
+
145
+ test('visual snapshot — varying star ratings across rows', async ({ page }) => {
146
+ await page.goto('/iframe.html?id=features-cellrenderers--rating-renderer');
147
+ await waitForCanvas(page);
148
+ await page.waitForTimeout(500);
149
+
150
+ await expect(page.locator('argent-grid')).toHaveScreenshot('rating-renderer-varied.png');
151
+ });
152
+ });
@@ -0,0 +1,31 @@
1
+ import { expect, test } from '@playwright/test';
2
+
3
+ test.describe('Debug Streaming Story', () => {
4
+ test('check console errors and logs', async ({ page }) => {
5
+ page.on('console', (msg) => {
6
+ console.log(`[BROWSER ${msg.type()}] ${msg.text()}`);
7
+ });
8
+
9
+ await page.goto('http://localhost:6006/iframe.html?id=features-streaming--live-stock-feed&viewMode=story');
10
+
11
+ // Wait for grid
12
+ try {
13
+ await page.waitForSelector('argent-grid', { timeout: 10000 });
14
+ console.log('argent-grid selector found');
15
+ } catch (e) {
16
+ console.log('argent-grid selector NOT found within timeout');
17
+ }
18
+
19
+ // Wait a bit for updates
20
+ await page.waitForTimeout(5000);
21
+
22
+ const canvas = page.locator('canvas').first();
23
+ const isVisible = await canvas.isVisible();
24
+ console.log('Canvas is visible:', isVisible);
25
+
26
+ if (isVisible) {
27
+ const box = await canvas.boundingBox();
28
+ console.log('Canvas bounding box:', box);
29
+ }
30
+ });
31
+ });
@@ -0,0 +1,73 @@
1
+ import { expect, test } from '@playwright/test';
2
+
3
+ test.describe('Drag and Drop Functionality', () => {
4
+ test('should reorder columns by dragging', async ({ page }) => {
5
+ // Use simple Default story without groups for reordering test
6
+ await page.goto('/iframe.html?id=components-argentgrid--default');
7
+
8
+ // Wait for the grid to be ready
9
+ await page.waitForSelector('.argent-grid-header-cell');
10
+
11
+ const idHeader = page.locator('.argent-grid-header-cell:has-text("ID")').first();
12
+ const nameHeader = page.locator('.argent-grid-header-cell:has-text("Name")').first();
13
+
14
+ await expect(idHeader).toBeVisible();
15
+ await expect(nameHeader).toBeVisible();
16
+
17
+ const initialIdBox = await idHeader.boundingBox();
18
+ const initialNameBox = await nameHeader.boundingBox();
19
+
20
+ if (!initialIdBox || !initialNameBox) throw new Error('Could not find header bounding boxes');
21
+
22
+ // Drag ID past Name
23
+ const idHandle = idHeader.locator('.argent-grid-header-content');
24
+ const handleBox = await idHandle.boundingBox();
25
+ if (!handleBox) throw new Error('Could not find handle bounding box');
26
+
27
+ await page.mouse.move(handleBox.x + handleBox.width / 2, handleBox.y + handleBox.height / 2);
28
+ await page.mouse.down();
29
+ // Drag way past Name (which is ~200px wide) to ensure it drops at index >= 1
30
+ await page.mouse.move(initialIdBox.x + 300, initialIdBox.y + handleBox.height / 2, { steps: 30 });
31
+ await page.mouse.up();
32
+
33
+ // Wait for changes to reflect
34
+ await page.waitForTimeout(2000);
35
+
36
+ const finalIdBox = await idHeader.boundingBox();
37
+ if (!finalIdBox) throw new Error('Could not find final ID bounding box');
38
+
39
+ // ID should now have moved to the right
40
+ expect(finalIdBox.x).toBeGreaterThan(initialIdBox.x);
41
+ });
42
+
43
+ test('should group columns by dragging into group panel', async ({ page }) => {
44
+ // USE DragAndDropGrouping story because it has rowGroupPanelShow: 'always'
45
+ await page.goto('/iframe.html?id=features-grouping--drag-and-drop-grouping');
46
+ await page.waitForSelector('.argent-grid-header-cell');
47
+
48
+ const panel = page.locator('.argent-grid-row-group-panel');
49
+ await expect(panel).toBeVisible();
50
+
51
+ // Find the Department column
52
+ const deptHeader = page.locator('.argent-grid-header-cell:has-text("Department")').first();
53
+ await expect(deptHeader).toBeVisible();
54
+
55
+ // Drag Department to the group panel
56
+ const handle = deptHeader.locator('.argent-grid-header-content');
57
+ const handleBox = await handle.boundingBox();
58
+ const panelBox = await panel.boundingBox();
59
+ if (!handleBox || !panelBox) throw new Error('Could not find bounding boxes');
60
+
61
+ await page.mouse.move(handleBox.x + handleBox.width / 2, handleBox.y + handleBox.height / 2);
62
+ await page.mouse.down();
63
+ await page.mouse.move(panelBox.x + panelBox.width / 2, panelBox.y + panelBox.height / 2, { steps: 20 });
64
+ await page.mouse.up();
65
+
66
+ // Wait for changes
67
+ await page.waitForTimeout(2000);
68
+
69
+ // Verify a group pill appeared in the panel
70
+ const pill = panel.locator('.row-group-pill:has-text("Department")');
71
+ await expect(pill).toBeVisible({ timeout: 5000 });
72
+ });
73
+ });
@@ -0,0 +1,52 @@
1
+ import { test } from '@playwright/test';
2
+
3
+ test.describe('ArgentGrid Screenshots', () => {
4
+ test.beforeEach(async () => {
5
+ // Skip if not in CI
6
+ test.skip(!process.env.CI, 'Screenshot tests only run in CI');
7
+ });
8
+
9
+ test('capture default grid screenshot', async ({ page }) => {
10
+ await page.goto('/iframe.html?id=components-argentgrid--default');
11
+ await page.waitForSelector('argent-grid', { timeout: 15000 });
12
+ await page.waitForTimeout(2000);
13
+
14
+ await page.screenshot({
15
+ path: 'e2e/screenshots/grid-default.png',
16
+ fullPage: false,
17
+ });
18
+ });
19
+
20
+ test('capture dark mode screenshot', async ({ page }) => {
21
+ await page.goto('/iframe.html?id=features-theming--dark-mode');
22
+ await page.waitForSelector('argent-grid', { timeout: 15000 });
23
+ await page.waitForTimeout(2000);
24
+
25
+ await page.screenshot({
26
+ path: 'e2e/screenshots/grid-dark-mode.png',
27
+ fullPage: false,
28
+ });
29
+ });
30
+
31
+ test('capture grouping screenshot', async ({ page }) => {
32
+ await page.goto('/iframe.html?id=features-grouping--row-grouping');
33
+ await page.waitForSelector('argent-grid', { timeout: 15000 });
34
+ await page.waitForTimeout(2000);
35
+
36
+ await page.screenshot({
37
+ path: 'e2e/screenshots/grid-grouping.png',
38
+ fullPage: false,
39
+ });
40
+ });
41
+
42
+ test('capture benchmark screenshot', async ({ page }) => {
43
+ await page.goto('/iframe.html?id=features-benchmark--benchmark-10-k');
44
+ await page.waitForSelector('app-benchmark-wrapper', { timeout: 30000 });
45
+ await page.waitForTimeout(2000);
46
+
47
+ await page.screenshot({
48
+ path: 'e2e/screenshots/benchmark.png',
49
+ fullPage: false,
50
+ });
51
+ });
52
+ });
@@ -0,0 +1,35 @@
1
+ import { expect, test } from '@playwright/test';
2
+
3
+ test.describe('Theming Stories', () => {
4
+ test('should load Light Mode story', async ({ page }) => {
5
+ await page.goto('/iframe.html?id=features-theming--light-mode');
6
+ await page.waitForSelector('argent-grid', { timeout: 15000 });
7
+
8
+ const grid = page.locator('argent-grid').first();
9
+ await expect(grid).toBeVisible({ timeout: 10000 });
10
+ });
11
+
12
+ test('should load Dark Mode story', async ({ page }) => {
13
+ await page.goto('/iframe.html?id=features-theming--dark-mode');
14
+ await page.waitForSelector('argent-grid', { timeout: 15000 });
15
+
16
+ const grid = page.locator('argent-grid').first();
17
+ await expect(grid).toBeVisible({ timeout: 10000 });
18
+ });
19
+
20
+ test('should load Compact Mode story', async ({ page }) => {
21
+ await page.goto('/iframe.html?id=features-theming--compact-mode');
22
+ await page.waitForSelector('argent-grid', { timeout: 15000 });
23
+
24
+ const grid = page.locator('argent-grid').first();
25
+ await expect(grid).toBeVisible({ timeout: 10000 });
26
+ });
27
+
28
+ test('should load Compact Dark Mode story', async ({ page }) => {
29
+ await page.goto('/iframe.html?id=features-theming--compact-dark-mode');
30
+ await page.waitForSelector('argent-grid', { timeout: 15000 });
31
+
32
+ const grid = page.locator('argent-grid').first();
33
+ await expect(grid).toBeVisible({ timeout: 10000 });
34
+ });
35
+ });
@@ -0,0 +1,112 @@
1
+ import { expect, test } from '@playwright/test';
2
+
3
+ test.describe('ArgentGrid Visual Regression', () => {
4
+ // Increase timeout for visual tests as they depend on rendering
5
+ test.slow();
6
+
7
+ test('default grid should look correct', async ({ page }) => {
8
+ await page.goto('/iframe.html?id=components-argentgrid--default');
9
+ await page.waitForSelector('argent-grid', { state: 'visible' });
10
+ // Wait longer for CI rendering and font stabilization
11
+ await page.waitForTimeout(2000);
12
+
13
+ await expect(page.locator('argent-grid')).toHaveScreenshot('grid-default.png');
14
+ });
15
+
16
+ test('selection column should be centered and aligned', async ({ page }) => {
17
+ await page.goto('/iframe.html?id=components-argentgrid--with-selection');
18
+ await page.waitForSelector('argent-grid', { state: 'visible' });
19
+ await page.waitForTimeout(2000);
20
+
21
+ await expect(page.locator('argent-grid')).toHaveScreenshot('grid-with-selection.png');
22
+ });
23
+
24
+ test('text filter with floating filters should be aligned', async ({ page }) => {
25
+ await page.goto('/iframe.html?id=features-filtering--text-filter');
26
+ await page.waitForSelector('argent-grid', { state: 'visible' });
27
+ await page.waitForTimeout(2000);
28
+
29
+ await expect(page.locator('argent-grid')).toHaveScreenshot('grid-text-filter.png');
30
+ });
31
+
32
+ test('hidden floating filters with popup should be correct', async ({ page }) => {
33
+ await page.goto('/iframe.html?id=features-filtering--set-filter');
34
+ await page.waitForSelector('argent-grid', { state: 'visible' });
35
+
36
+ // Open the filter popup for the Department column (which uses Set Filter)
37
+ const deptHeader = page.locator('.argent-grid-floating-filter-cell').nth(2); // Department is 3rd
38
+ const filterBtn = deptHeader.locator('.floating-filter-btn');
39
+
40
+ await filterBtn.scrollIntoViewIfNeeded();
41
+ await filterBtn.click();
42
+
43
+ // Wait for popup animation
44
+ await page.waitForSelector('.set-filter-popup', { state: 'visible', timeout: 5000 });
45
+ await page.waitForTimeout(1000);
46
+
47
+ // Snapshot the popup area
48
+ await expect(page.locator('argent-grid')).toHaveScreenshot('grid-filter-popup.png');
49
+ });
50
+
51
+ test('empty state after filtering should be clean', async ({ page }) => {
52
+ await page.goto('/iframe.html?id=features-filtering--text-filter');
53
+ await page.waitForSelector('argent-grid', { state: 'visible' });
54
+
55
+ // Type something that matches nothing
56
+ const filterInput = page.locator('.floating-filter-input').first();
57
+ await filterInput.fill('NON_EXISTENT_VALUE_12345');
58
+ await page.waitForTimeout(1000); // Wait for debounce and render
59
+
60
+ await expect(page.locator('argent-grid')).toHaveScreenshot('grid-empty-state.png');
61
+ });
62
+
63
+ test('cell borders should remain visible after scrolling down', async ({ page }) => {
64
+ await page.goto('/iframe.html?id=components-argentgrid--default');
65
+ await page.waitForSelector('argent-grid', { state: 'visible' });
66
+
67
+ // Scroll down significantly
68
+ const viewport = page.locator('.argent-grid-viewport');
69
+ await viewport.evaluate((el) => el.scrollTop = 500);
70
+
71
+ // Wait for render
72
+ await page.waitForTimeout(2000);
73
+
74
+ await expect(page.locator('argent-grid')).toHaveScreenshot('grid-scroll-borders.png');
75
+ });
76
+
77
+ test('column group headers should show horizontal border', async ({ page }) => {
78
+ await page.goto('/iframe.html?id=features-grouping--column-groups');
79
+ await page.waitForSelector('argent-grid', { state: 'visible' });
80
+ await page.waitForTimeout(2000);
81
+
82
+ // Verify the group header cells have a bottom border via the group-cell class
83
+ const groupCell = page.locator('.argent-grid-header-group-cell').first();
84
+ await expect(groupCell).toBeVisible();
85
+
86
+ // The bottom border should be drawn — verify via computed style
87
+ const borderBottom = await groupCell.evaluate(
88
+ (el) => getComputedStyle(el).borderBottomWidth
89
+ );
90
+ expect(borderBottom).toBe('1px');
91
+
92
+ await expect(page.locator('argent-grid')).toHaveScreenshot('grid-column-group-headers.png');
93
+ });
94
+
95
+ test('sidebar buttons should be visible and not blocked by header', async ({ page }) => {
96
+ await page.goto('/iframe.html?id=features-advanced--side-bar');
97
+ await page.waitForSelector('argent-grid', { state: 'visible' });
98
+
99
+ // Check if sidebar buttons are present
100
+ const sidebar = page.locator('.side-bar-buttons');
101
+ await expect(sidebar).toBeVisible();
102
+
103
+ // Verify first button position is below top of grid
104
+ const firstButton = page.locator('.side-bar-button').first();
105
+ const box = await firstButton.boundingBox();
106
+ // In our new grid-based layout, the sidebar is a sibling of the content area
107
+ // Just ensure it's rendered and has reasonable position
108
+ expect(box?.y).toBeGreaterThanOrEqual(0);
109
+ await page.waitForTimeout(1000);
110
+ await expect(page.locator('argent-grid')).toHaveScreenshot('grid-sidebar-buttons.png');
111
+ });
112
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "argent-grid",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "A free, high-performance alternative to AG Grid Enterprise",
5
5
  "author": "hainzhao",
6
6
  "license": "MIT",
@@ -34,18 +34,27 @@
34
34
  "@angular/common": "^18.0.0",
35
35
  "@angular/compiler": "^18.0.0",
36
36
  "@angular/compiler-cli": "^18.0.0",
37
- "@angular/core": "^18.0.0",
37
+ "@angular/core": "^18.2.14",
38
38
  "@angular/platform-browser": "^18.0.0",
39
39
  "@angular/platform-browser-dynamic": "^18.0.0",
40
- "@types/exceljs": "^1.3.2",
40
+ "@biomejs/biome": "2.4.4",
41
+ "@chromatic-com/storybook": "^3.2.7",
42
+ "@playwright/test": "^1.58.2",
43
+ "@storybook/addon-actions": "^8.6.17",
44
+ "@storybook/addon-controls": "^8.6.17",
45
+ "@storybook/addon-docs": "^8.6.17",
46
+ "@storybook/addon-essentials": "^8.6.17",
47
+ "@storybook/angular": "^8.6.17",
41
48
  "@types/node": "^20.0.0",
42
49
  "@vitest/coverage-v8": "^3.0.0",
43
50
  "jsdom": "^26.0.0",
44
51
  "ng-packagr": "^18.0.0",
45
52
  "rxjs": "~7.8.0",
53
+ "storybook": "^8.6.17",
46
54
  "typescript": "~5.4.2",
47
55
  "vite": "^6.0.0",
48
- "vitest": "^3.0.0"
56
+ "vitest": "^3.0.0",
57
+ "zone.js": "^0.14.0"
49
58
  },
50
59
  "scripts": {
51
60
  "build": "ng-packagr -p ng-package.json",
@@ -53,8 +62,13 @@
53
62
  "test": "vitest run",
54
63
  "test:watch": "vitest",
55
64
  "test:coverage": "vitest run --coverage",
56
- "test:e2e": "cd demo-app && npx playwright test",
57
- "lint": "eslint src/**/*.ts",
58
- "clean": "rm -rf dist"
65
+ "test:e2e": "playwright test",
66
+ "test:e2e:update": "playwright test --update-snapshots",
67
+ "lint": "biome check src",
68
+ "lint:fix": "biome check src --write",
69
+ "lint:fix:unsafe": "biome check src --write --unsafe",
70
+ "clean": "rm -rf dist",
71
+ "storybook": "ng run argent-grid-storybook:storybook",
72
+ "build-storybook": "ng run argent-grid-storybook:build-storybook"
59
73
  }
60
74
  }