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.
- package/.github/workflows/ci.yml +69 -0
- package/.github/workflows/pages.yml +6 -12
- package/.storybook/main.ts +20 -0
- package/.storybook/preview.ts +18 -0
- package/.storybook/tsconfig.json +24 -0
- package/AGENTS.md +70 -27
- package/README.md +51 -34
- package/angular.json +66 -0
- package/biome.json +66 -0
- package/demo-app/e2e/selection-screenshot.spec.ts +20 -0
- package/docs/AG-GRID-COMPARISON.md +725 -0
- package/docs/CELL-RENDERER-GUIDE.md +241 -0
- package/docs/CONTEXT-MENU-GUIDE.md +371 -0
- package/docs/LIVE-DATA-OPTIMIZATIONS.md +497 -0
- package/docs/PERFORMANCE-OPTIMIZATIONS-PHASE1.md +162 -0
- package/docs/PERFORMANCE-REVIEW.md +571 -0
- package/docs/RESEARCH-STATUS.md +234 -0
- package/docs/STATE-PERSISTENCE-GUIDE.md +370 -0
- package/docs/STORYBOOK-REFACTOR.md +215 -0
- package/docs/STORYBOOK-STATUS.md +156 -0
- package/docs/TEST-COVERAGE-REPORT.md +276 -0
- package/docs/THEME-API-GUIDE.md +445 -0
- package/docs/THEME-API-PLAN.md +364 -0
- package/e2e/advanced.spec.ts +109 -0
- package/e2e/argentgrid.spec.ts +65 -0
- package/e2e/benchmark.spec.ts +52 -0
- 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 +52 -0
- package/e2e/theming.spec.ts +35 -0
- package/e2e/visual.spec.ts +112 -0
- 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 +21 -7
- package/plan.md +56 -28
- package/playwright.config.ts +38 -0
- package/setup-vitest.ts +10 -13
- package/src/lib/argent-grid.module.ts +10 -12
- package/src/lib/components/argent-grid.component.css +281 -321
- package/src/lib/components/argent-grid.component.html +295 -207
- package/src/lib/components/argent-grid.component.spec.ts +120 -160
- package/src/lib/components/argent-grid.component.ts +1193 -290
- package/src/lib/components/argent-grid.regressions.spec.ts +301 -0
- package/src/lib/components/argent-grid.selection.spec.ts +132 -0
- package/src/lib/components/set-filter/set-filter.component.spec.ts +191 -0
- package/src/lib/components/set-filter/set-filter.component.ts +307 -0
- package/src/lib/directives/ag-grid-compatibility.directive.ts +16 -26
- package/src/lib/directives/click-outside.directive.ts +19 -0
- package/src/lib/rendering/canvas-renderer.spec.ts +513 -0
- package/src/lib/rendering/canvas-renderer.ts +456 -452
- package/src/lib/rendering/live-data-handler.ts +110 -0
- package/src/lib/rendering/live-data-optimizations.ts +133 -0
- package/src/lib/rendering/render/blit.spec.ts +16 -27
- package/src/lib/rendering/render/blit.ts +48 -36
- package/src/lib/rendering/render/cells.spec.ts +132 -0
- package/src/lib/rendering/render/cells.ts +167 -28
- package/src/lib/rendering/render/column-utils.ts +95 -0
- package/src/lib/rendering/render/hit-test.ts +50 -0
- package/src/lib/rendering/render/index.ts +88 -76
- package/src/lib/rendering/render/lines.ts +53 -47
- package/src/lib/rendering/render/primitives.ts +423 -0
- package/src/lib/rendering/render/theme.spec.ts +8 -12
- package/src/lib/rendering/render/theme.ts +7 -10
- package/src/lib/rendering/render/types.ts +3 -2
- package/src/lib/rendering/render/walk.spec.ts +35 -38
- package/src/lib/rendering/render/walk.ts +94 -64
- package/src/lib/rendering/utils/damage-tracker.spec.ts +8 -7
- package/src/lib/rendering/utils/damage-tracker.ts +6 -18
- package/src/lib/rendering/utils/index.ts +1 -1
- package/src/lib/services/grid.service.set-filter.spec.ts +219 -0
- package/src/lib/services/grid.service.spec.ts +1241 -201
- package/src/lib/services/grid.service.ts +1204 -235
- package/src/lib/themes/parts/color-schemes.ts +132 -0
- package/src/lib/themes/parts/icon-sets.ts +258 -0
- package/src/lib/themes/theme-builder.ts +347 -0
- package/src/lib/themes/theme-quartz.ts +72 -0
- package/src/lib/themes/types.ts +238 -0
- package/src/lib/types/ag-grid-types.ts +573 -14
- package/src/public-api.ts +39 -9
- package/src/stories/Advanced.stories.ts +249 -0
- package/src/stories/ArgentGrid.stories.ts +301 -0
- package/src/stories/Benchmark.stories.ts +76 -0
- package/src/stories/CellRenderers.stories.ts +395 -0
- package/src/stories/Filtering.stories.ts +292 -0
- package/src/stories/Grouping.stories.ts +290 -0
- package/src/stories/Streaming.stories.ts +57 -0
- package/src/stories/Theming.stories.ts +137 -0
- package/src/stories/Tooltips.stories.ts +381 -0
- package/src/stories/benchmark-wrapper.component.ts +355 -0
- package/src/stories/story-utils.ts +88 -0
- package/src/stories/streaming-wrapper.component.ts +441 -0
- package/tsconfig.json +1 -0
- package/tsconfig.storybook.json +10 -0
- package/vitest.config.ts +9 -9
- package/demo-app/README.md +0 -70
- package/demo-app/angular.json +0 -78
- package/demo-app/e2e/benchmark.spec.ts +0 -53
- package/demo-app/e2e/demo-page.spec.ts +0 -77
- package/demo-app/e2e/grid-features.spec.ts +0 -269
- package/demo-app/package-lock.json +0 -14023
- package/demo-app/package.json +0 -36
- package/demo-app/playwright-test-menu.js +0 -19
- package/demo-app/playwright.config.ts +0 -23
- package/demo-app/src/app/app.component.ts +0 -10
- package/demo-app/src/app/app.config.ts +0 -13
- package/demo-app/src/app/app.routes.ts +0 -7
- package/demo-app/src/app/demo-page/demo-page.component.css +0 -313
- package/demo-app/src/app/demo-page/demo-page.component.html +0 -124
- package/demo-app/src/app/demo-page/demo-page.component.ts +0 -366
- package/demo-app/src/index.html +0 -19
- package/demo-app/src/main.ts +0 -6
- 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
|
+
});
|
package/e2e/dnd.spec.ts
ADDED
|
@@ -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
|
+
});
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "argent-grid",
|
|
3
|
-
"version": "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.
|
|
37
|
+
"@angular/core": "^18.2.14",
|
|
38
38
|
"@angular/platform-browser": "^18.0.0",
|
|
39
39
|
"@angular/platform-browser-dynamic": "^18.0.0",
|
|
40
|
-
"@
|
|
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": "
|
|
57
|
-
"
|
|
58
|
-
"
|
|
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
|
}
|