argent-grid 0.1.0 → 0.2.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 +2 -2
- 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/screenshots.spec.ts +52 -0
- package/e2e/theming.spec.ts +35 -0
- package/e2e/visual.spec.ts +91 -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/package.json +20 -6
- package/plan.md +50 -18
- 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 +327 -76
- package/src/lib/components/argent-grid.component.html +186 -64
- package/src/lib/components/argent-grid.component.spec.ts +120 -160
- package/src/lib/components/argent-grid.component.ts +642 -189
- package/src/lib/components/argent-grid.selection.spec.ts +132 -0
- package/src/lib/components/set-filter/set-filter.component.ts +302 -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 +366 -0
- package/src/lib/rendering/canvas-renderer.ts +418 -305
- 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 +46 -24
- package/src/lib/rendering/render/column-utils.ts +73 -0
- package/src/lib/rendering/render/hit-test.ts +55 -0
- package/src/lib/rendering/render/index.ts +79 -76
- package/src/lib/rendering/render/lines.ts +43 -43
- package/src/lib/rendering/render/primitives.ts +161 -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 +2 -2
- package/src/lib/rendering/render/walk.spec.ts +35 -38
- package/src/lib/rendering/render/walk.ts +60 -50
- 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 +1165 -201
- package/src/lib/services/grid.service.ts +819 -187
- 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 +73 -14
- package/src/public-api.ts +39 -9
- package/src/stories/Advanced.stories.ts +188 -0
- package/src/stories/ArgentGrid.stories.ts +277 -0
- package/src/stories/Benchmark.stories.ts +74 -0
- package/src/stories/CellRenderers.stories.ts +221 -0
- package/src/stories/Filtering.stories.ts +252 -0
- package/src/stories/Grouping.stories.ts +217 -0
- package/src/stories/Theming.stories.ts +124 -0
- package/src/stories/benchmark-wrapper.component.ts +315 -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
package/demo-app/README.md
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
# ArgentGrid Demo App
|
|
2
|
-
|
|
3
|
-
Live demo showcasing the canvas-based high-performance Angular grid.
|
|
4
|
-
|
|
5
|
-
## Quick Start
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
# Install dependencies
|
|
9
|
-
npm install
|
|
10
|
-
|
|
11
|
-
# Start dev server
|
|
12
|
-
npm start
|
|
13
|
-
|
|
14
|
-
# Build for production
|
|
15
|
-
npm run build
|
|
16
|
-
|
|
17
|
-
# Build for GitHub Pages
|
|
18
|
-
npm run build:gh-pages
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
## E2E Testing (Playwright)
|
|
22
|
-
|
|
23
|
-
End-to-end tests are located in the `e2e/` folder. They verify critical visual and interactive features that cannot be easily tested with unit tests (like Canvas rendering and Drag & Drop).
|
|
24
|
-
|
|
25
|
-
**Prerequisites:**
|
|
26
|
-
Ensure the dev server is running (`npm start`).
|
|
27
|
-
|
|
28
|
-
**Run All Tests:**
|
|
29
|
-
```bash
|
|
30
|
-
npx playwright test
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
**Run with UI:**
|
|
34
|
-
```bash
|
|
35
|
-
npx playwright test --ui
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
**Key Scenarios Covered:**
|
|
39
|
-
- Row Grouping & Hierarchical Expansion
|
|
40
|
-
- Floating Filters & Quick Clear
|
|
41
|
-
- Inline Cell Editing (Enter/Escape/Tab)
|
|
42
|
-
- Column Pinning (Sticky Columns)
|
|
43
|
-
- Column Re-ordering via Drag & Drop
|
|
44
|
-
|
|
45
|
-
## Demo Features
|
|
46
|
-
|
|
47
|
-
- **100K rows** - Standard load test
|
|
48
|
-
- **500K rows** - Heavy load test
|
|
49
|
-
- **1M rows** - Extreme stress test
|
|
50
|
-
- **Real-time FPS** - Monitor rendering performance
|
|
51
|
-
- **Canvas rendering** - Zero DOM overhead
|
|
52
|
-
|
|
53
|
-
## Live Demo
|
|
54
|
-
|
|
55
|
-
https://hainanzhao.github.io/ArgentGrid/
|
|
56
|
-
|
|
57
|
-
## Tech Stack
|
|
58
|
-
|
|
59
|
-
- Angular 18
|
|
60
|
-
- Canvas 2D API
|
|
61
|
-
- RequestAnimationFrame for 60fps
|
|
62
|
-
- Zero dependencies beyond Angular
|
|
63
|
-
|
|
64
|
-
## Performance Targets
|
|
65
|
-
|
|
66
|
-
| Rows | Load Time | FPS | Memory |
|
|
67
|
-
|------|-----------|-----|--------|
|
|
68
|
-
| 100K | < 500ms | 60 | ~50MB |
|
|
69
|
-
| 500K | < 2s | 60 | ~150MB |
|
|
70
|
-
| 1M | < 5s | 55-60 | ~300MB |
|
package/demo-app/angular.json
DELETED
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
|
3
|
-
"version": 1,
|
|
4
|
-
"newProjectRoot": "projects",
|
|
5
|
-
"projects": {
|
|
6
|
-
"argent-grid-demo": {
|
|
7
|
-
"projectType": "application",
|
|
8
|
-
"schematics": {},
|
|
9
|
-
"root": "",
|
|
10
|
-
"sourceRoot": "src",
|
|
11
|
-
"prefix": "app",
|
|
12
|
-
"architect": {
|
|
13
|
-
"build": {
|
|
14
|
-
"builder": "@angular-devkit/build-angular:application",
|
|
15
|
-
"options": {
|
|
16
|
-
"outputPath": "dist/argent-grid-demo",
|
|
17
|
-
"index": "src/index.html",
|
|
18
|
-
"browser": "src/main.ts",
|
|
19
|
-
"polyfills": [],
|
|
20
|
-
"tsConfig": "tsconfig.json",
|
|
21
|
-
"preserveSymlinks": true,
|
|
22
|
-
"sourceMap": true,
|
|
23
|
-
"assets": [
|
|
24
|
-
{
|
|
25
|
-
"glob": "**/*",
|
|
26
|
-
"input": "public"
|
|
27
|
-
}
|
|
28
|
-
],
|
|
29
|
-
"styles": [],
|
|
30
|
-
"scripts": []
|
|
31
|
-
},
|
|
32
|
-
"configurations": {
|
|
33
|
-
"production": {
|
|
34
|
-
"budgets": [
|
|
35
|
-
{
|
|
36
|
-
"type": "initial",
|
|
37
|
-
"maximumWarning": "500kB",
|
|
38
|
-
"maximumError": "2MB"
|
|
39
|
-
},
|
|
40
|
-
{
|
|
41
|
-
"type": "anyComponentStyle",
|
|
42
|
-
"maximumWarning": "10kB",
|
|
43
|
-
"maximumError": "20kB"
|
|
44
|
-
}
|
|
45
|
-
],
|
|
46
|
-
"outputHashing": "all",
|
|
47
|
-
"sourceMap": true
|
|
48
|
-
},
|
|
49
|
-
"development": {
|
|
50
|
-
"optimization": false,
|
|
51
|
-
"extractLicenses": false,
|
|
52
|
-
"sourceMap": true
|
|
53
|
-
}
|
|
54
|
-
},
|
|
55
|
-
"defaultConfiguration": "production"
|
|
56
|
-
},
|
|
57
|
-
"serve": {
|
|
58
|
-
"builder": "@angular-devkit/build-angular:dev-server",
|
|
59
|
-
"configurations": {
|
|
60
|
-
"production": {
|
|
61
|
-
"buildTarget": "argent-grid-demo:build:production"
|
|
62
|
-
},
|
|
63
|
-
"development": {
|
|
64
|
-
"buildTarget": "argent-grid-demo:build:development"
|
|
65
|
-
}
|
|
66
|
-
},
|
|
67
|
-
"defaultConfiguration": "development"
|
|
68
|
-
},
|
|
69
|
-
"extract-i18n": {
|
|
70
|
-
"builder": "@angular-devkit/build-angular:extract-i18n"
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
},
|
|
75
|
-
"cli": {
|
|
76
|
-
"analytics": false
|
|
77
|
-
}
|
|
78
|
-
}
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import { test, expect } from '@playwright/test';
|
|
2
|
-
|
|
3
|
-
test.describe('ArgentGrid Performance Benchmark', () => {
|
|
4
|
-
test('should run the benchmark and report results', async ({ page }) => {
|
|
5
|
-
// Navigate to the demo page
|
|
6
|
-
await page.goto('/', { waitUntil: 'networkidle' });
|
|
7
|
-
|
|
8
|
-
// Wait for the grid to be ready
|
|
9
|
-
await page.waitForSelector('argent-grid', { timeout: 10000 });
|
|
10
|
-
|
|
11
|
-
// Click the Benchmark button
|
|
12
|
-
console.log('Starting benchmark...');
|
|
13
|
-
await page.click('.btn-benchmark');
|
|
14
|
-
|
|
15
|
-
// Wait for benchmark to complete (isBenchmarking becomes false)
|
|
16
|
-
// The benchmark-results section appears when finished
|
|
17
|
-
await page.waitForSelector('.benchmark-results', { timeout: 30000 });
|
|
18
|
-
|
|
19
|
-
// Extract results
|
|
20
|
-
const results = await page.evaluate(() => {
|
|
21
|
-
const items = document.querySelectorAll('.result-item');
|
|
22
|
-
const data: Record<string, string> = {};
|
|
23
|
-
items.forEach(item => {
|
|
24
|
-
const label = item.querySelector('label')?.textContent?.replace(':', '') || 'unknown';
|
|
25
|
-
const value = item.querySelector('span')?.textContent || '0';
|
|
26
|
-
data[label] = value;
|
|
27
|
-
});
|
|
28
|
-
return data;
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
console.log('-------------------------------------------');
|
|
32
|
-
console.log('🚀 ArgentGrid Performance Benchmark Results');
|
|
33
|
-
console.log('-------------------------------------------');
|
|
34
|
-
Object.entries(results).forEach(([label, value]) => {
|
|
35
|
-
console.log(`${label.padEnd(20)}: ${value}`);
|
|
36
|
-
});
|
|
37
|
-
console.log('-------------------------------------------');
|
|
38
|
-
|
|
39
|
-
// Optional assertions based on targets
|
|
40
|
-
const initialRender = parseFloat(results['Initial Render']);
|
|
41
|
-
const scrollFrame = parseFloat(results['Avg Scroll Frame']);
|
|
42
|
-
|
|
43
|
-
console.log(`Checking against targets...`);
|
|
44
|
-
console.log(`Initial Render: ${initialRender}ms (Target: < 30ms)`);
|
|
45
|
-
console.log(`Avg Scroll Frame: ${scrollFrame}ms (Target: < 4ms)`);
|
|
46
|
-
|
|
47
|
-
// We don't fail the test if targets aren't met yet, but we report it
|
|
48
|
-
if (initialRender > 30) console.warn('⚠️ Initial render is slower than target (30ms)');
|
|
49
|
-
if (scrollFrame > 4) console.warn('⚠️ Scroll frame time is slower than target (4ms)');
|
|
50
|
-
|
|
51
|
-
expect(results['Total Test Time']).toBeDefined();
|
|
52
|
-
});
|
|
53
|
-
});
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
import { test, expect } from '@playwright/test';
|
|
2
|
-
|
|
3
|
-
test.describe('ArgentGrid Demo', () => {
|
|
4
|
-
test('should load the demo page without errors', async ({ page }) => {
|
|
5
|
-
// Set up console error listener to catch Angular errors
|
|
6
|
-
const errors: string[] = [];
|
|
7
|
-
const consoleMessages: string[] = [];
|
|
8
|
-
|
|
9
|
-
page.on('console', (msg) => {
|
|
10
|
-
const text = msg.text();
|
|
11
|
-
consoleMessages.push(text);
|
|
12
|
-
if (msg.type() === 'error') {
|
|
13
|
-
errors.push(text);
|
|
14
|
-
console.log('Console error:', text);
|
|
15
|
-
}
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
page.on('pageerror', (error) => {
|
|
19
|
-
errors.push(error.message);
|
|
20
|
-
console.log('Page error:', error.message);
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
// Navigate to the demo page
|
|
24
|
-
await page.goto('/', { waitUntil: 'networkidle', timeout: 30000 });
|
|
25
|
-
|
|
26
|
-
// Take a screenshot for debugging
|
|
27
|
-
await page.screenshot({ path: 'e2e/screenshots/page-load.png' });
|
|
28
|
-
|
|
29
|
-
// Get page content for debugging
|
|
30
|
-
const content = await page.content();
|
|
31
|
-
console.log('Page content length:', content.length);
|
|
32
|
-
|
|
33
|
-
// Check for Angular errors in console
|
|
34
|
-
const ngErrors = errors.filter((err) => err.includes('NG0') || err.includes('ERROR'));
|
|
35
|
-
|
|
36
|
-
if (ngErrors.length > 0) {
|
|
37
|
-
console.error('Angular errors detected:', ngErrors);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// Wait for the page to load - check for app-root
|
|
41
|
-
await page.waitForSelector('app-root', { timeout: 10000 });
|
|
42
|
-
|
|
43
|
-
// Wait for demo container
|
|
44
|
-
await page.waitForSelector('.demo-container', { timeout: 10000 });
|
|
45
|
-
|
|
46
|
-
// Assert no critical Angular injection errors
|
|
47
|
-
const criticalErrors = ngErrors.filter(err =>
|
|
48
|
-
err.includes('NG0203') || err.includes('NG0201') || err.includes('NullInjectorError')
|
|
49
|
-
);
|
|
50
|
-
|
|
51
|
-
expect(criticalErrors).toHaveLength(0);
|
|
52
|
-
|
|
53
|
-
// Check that the grid container is visible
|
|
54
|
-
const gridContainer = page.locator('.argent-grid-container');
|
|
55
|
-
await expect(gridContainer).toBeVisible({ timeout: 10000 });
|
|
56
|
-
|
|
57
|
-
// Check that rows are loaded
|
|
58
|
-
await expect(page.locator('.stat-badge').first()).toBeVisible();
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
test('should load 100K rows successfully', async ({ page }) => {
|
|
62
|
-
await page.goto('/', { waitUntil: 'networkidle' });
|
|
63
|
-
|
|
64
|
-
// Wait for initial load
|
|
65
|
-
await page.waitForSelector('argent-grid', { timeout: 10000 });
|
|
66
|
-
|
|
67
|
-
// Click the 100K button
|
|
68
|
-
await page.click('button:has-text("100K")');
|
|
69
|
-
|
|
70
|
-
// Wait for loading to complete
|
|
71
|
-
await page.waitForSelector('.loading', { state: 'detached', timeout: 30000 });
|
|
72
|
-
|
|
73
|
-
// Verify row count updated
|
|
74
|
-
const stats = await page.locator('.stat-badge').first().textContent();
|
|
75
|
-
expect(stats).toContain('100,000');
|
|
76
|
-
});
|
|
77
|
-
});
|
|
@@ -1,269 +0,0 @@
|
|
|
1
|
-
import { test, expect, Page } from '@playwright/test';
|
|
2
|
-
|
|
3
|
-
test.describe('ArgentGrid Feature Guard Rails', () => {
|
|
4
|
-
test.beforeEach(async ({ page }) => {
|
|
5
|
-
await page.goto('/', { waitUntil: 'networkidle' });
|
|
6
|
-
// Wait for grid to be ready
|
|
7
|
-
await page.waitForSelector('argent-grid', { timeout: 10000 });
|
|
8
|
-
// Wait for data to load
|
|
9
|
-
await page.waitForSelector('.loading', { state: 'detached', timeout: 30000 });
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
test('should support row grouping and expansion', async ({ page }) => {
|
|
13
|
-
// 1. Toggle Grouping
|
|
14
|
-
await page.click('button:has-text("Group by Dept")');
|
|
15
|
-
|
|
16
|
-
// 2. Verify "Organization" column (Auto Group Column) appears
|
|
17
|
-
const groupHeader = page.locator('.argent-grid-header-cell').filter({ hasText: /^Organization/ });
|
|
18
|
-
await expect(groupHeader).toBeVisible();
|
|
19
|
-
|
|
20
|
-
// 3. Verify original "Department" column is hidden
|
|
21
|
-
const deptHeader = page.locator('.argent-grid-header-cell').filter({ hasText: /^Department/ });
|
|
22
|
-
await expect(deptHeader).not.toBeVisible();
|
|
23
|
-
|
|
24
|
-
// 4. Verify row count via GridApi (initially grouped)
|
|
25
|
-
const rowCountBefore = await page.evaluate(() => window.gridApi.getDisplayedRowCount());
|
|
26
|
-
expect(rowCountBefore).toBeLessThan(20);
|
|
27
|
-
|
|
28
|
-
// 5. Expand first group (click near chevron)
|
|
29
|
-
const canvas = page.locator('canvas');
|
|
30
|
-
const box = await canvas.boundingBox();
|
|
31
|
-
if (!box) throw new Error('Canvas box not found');
|
|
32
|
-
|
|
33
|
-
// Click at x=20, y=16 (roughly the first group's chevron)
|
|
34
|
-
await page.mouse.click(box.x + 20, box.y + 16);
|
|
35
|
-
|
|
36
|
-
// 6. Verify row count increased
|
|
37
|
-
await page.waitForTimeout(1000); // Wait for processing
|
|
38
|
-
const rowCountAfter = await page.evaluate(() => window.gridApi.getDisplayedRowCount());
|
|
39
|
-
expect(rowCountAfter).toBeGreaterThan(rowCountBefore);
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
test('should support floating filters', async ({ page }) => {
|
|
43
|
-
// 1. Verify floating filter row is visible
|
|
44
|
-
const filterRow = page.locator('.floating-filter-row');
|
|
45
|
-
await expect(filterRow).toBeVisible();
|
|
46
|
-
|
|
47
|
-
// 2. Type "Sales" into the Department filter (3rd input)
|
|
48
|
-
const deptFilter = page.locator('.floating-filter-input').nth(2);
|
|
49
|
-
await deptFilter.fill('Sales');
|
|
50
|
-
|
|
51
|
-
// 3. Wait for debounce and verify filtered count
|
|
52
|
-
await page.waitForTimeout(1500);
|
|
53
|
-
const filteredCount = await page.evaluate(() => window.gridApi.getDisplayedRowCount());
|
|
54
|
-
|
|
55
|
-
expect(filteredCount).toBeGreaterThan(0);
|
|
56
|
-
expect(filteredCount).toBeLessThan(100000);
|
|
57
|
-
|
|
58
|
-
// 4. Verify Clear button (x) works
|
|
59
|
-
const clearBtn = page.locator('.floating-filter-clear').first();
|
|
60
|
-
await expect(clearBtn).toBeVisible();
|
|
61
|
-
await clearBtn.click();
|
|
62
|
-
|
|
63
|
-
// 5. Verify count restored
|
|
64
|
-
await page.waitForTimeout(1000);
|
|
65
|
-
const restoredCount = await page.evaluate(() => window.gridApi.getDisplayedRowCount());
|
|
66
|
-
expect(restoredCount).toBe(100000);
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
test('should support cell editing with keyboard navigation', async ({ page }) => {
|
|
70
|
-
const canvas = page.locator('canvas');
|
|
71
|
-
const box = await canvas.boundingBox();
|
|
72
|
-
if (!box) throw new Error('Canvas box not found');
|
|
73
|
-
|
|
74
|
-
// 1. Double click to start editing (Name column, first row)
|
|
75
|
-
// Click on Name column (x=150 is roughly middle of Name column)
|
|
76
|
-
await canvas.dblclick({ position: { x: 150, y: 16 } });
|
|
77
|
-
|
|
78
|
-
const editor = page.locator('.argent-grid-editor-input');
|
|
79
|
-
await expect(editor).toBeVisible();
|
|
80
|
-
|
|
81
|
-
// 2. Type new value and Enter to save
|
|
82
|
-
await editor.fill('TEST_EDIT_SUCCESS');
|
|
83
|
-
await page.keyboard.press('Enter');
|
|
84
|
-
|
|
85
|
-
// 3. Verify editor is gone and value persisted
|
|
86
|
-
await expect(editor).not.toBeVisible();
|
|
87
|
-
const savedValue = await page.evaluate(() => window.gridApi.getDisplayedRowAtIndex(0).data.name);
|
|
88
|
-
expect(savedValue).toBe('TEST_EDIT_SUCCESS');
|
|
89
|
-
|
|
90
|
-
// 4. Test Escape to cancel
|
|
91
|
-
await canvas.dblclick({ position: { x: 150, y: 16 } });
|
|
92
|
-
await expect(editor).toBeVisible();
|
|
93
|
-
await editor.fill('WILL_CANCEL');
|
|
94
|
-
await page.keyboard.press('Escape');
|
|
95
|
-
|
|
96
|
-
await expect(editor).not.toBeVisible();
|
|
97
|
-
const afterCancelValue = await page.evaluate(() => window.gridApi.getDisplayedRowAtIndex(0).data.name);
|
|
98
|
-
expect(afterCancelValue).toBe('TEST_EDIT_SUCCESS');
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
test('should support column pinning and horizontal scroll sync', async ({ page }) => {
|
|
102
|
-
// Force horizontal scroll by reducing viewport
|
|
103
|
-
await page.setViewportSize({ width: 800, height: 600 });
|
|
104
|
-
|
|
105
|
-
// 1. Pin ID column to Left via Menu
|
|
106
|
-
const idHeader = page.locator('.argent-grid-header-cell').filter({ hasText: /^ID/ });
|
|
107
|
-
await idHeader.hover();
|
|
108
|
-
const menuIcon = idHeader.locator('.argent-grid-header-menu-icon');
|
|
109
|
-
await menuIcon.waitFor({ state: 'visible' });
|
|
110
|
-
await menuIcon.click();
|
|
111
|
-
|
|
112
|
-
// Click Pin Left
|
|
113
|
-
await page.locator('.menu-item').filter({ hasText: 'Pin Left' }).click();
|
|
114
|
-
|
|
115
|
-
// 2. Verify internal state via GridApi
|
|
116
|
-
await page.waitForFunction(() => {
|
|
117
|
-
const api = (window as any).gridApi;
|
|
118
|
-
const col = api.getAllColumns().find(c => c.colId === 'id');
|
|
119
|
-
return col && col.pinned === 'left';
|
|
120
|
-
}, { timeout: 5000 });
|
|
121
|
-
|
|
122
|
-
// 3. Scroll grid horizontally
|
|
123
|
-
const viewport = page.locator('.argent-grid-viewport');
|
|
124
|
-
await viewport.evaluate(el => el.scrollLeft = 200);
|
|
125
|
-
|
|
126
|
-
// 4. Verify header scroll sync
|
|
127
|
-
await page.waitForTimeout(1000);
|
|
128
|
-
const headerScrollable = page.locator('.argent-grid-header-scrollable').first();
|
|
129
|
-
const headerScrollLeft = await headerScrollable.evaluate(el => el.scrollLeft);
|
|
130
|
-
expect(headerScrollLeft).toBe(200);
|
|
131
|
-
|
|
132
|
-
// 5. Verify pinned column remains at the left
|
|
133
|
-
const idHeaderBox = await idHeader.boundingBox();
|
|
134
|
-
const containerBox = await page.locator('.argent-grid-container').boundingBox();
|
|
135
|
-
// Allow 2px margin for borders
|
|
136
|
-
expect(Math.abs((idHeaderBox?.x || 0) - (containerBox?.x || 0))).toBeLessThanOrEqual(2);
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
test('should support column re-ordering via drag and drop', async ({ page }) => {
|
|
140
|
-
const idHeader = page.locator('.argent-grid-header-cell').filter({ hasText: /^ID/ });
|
|
141
|
-
const nameHeader = page.locator('.argent-grid-header-cell').filter({ hasText: /^Name/ });
|
|
142
|
-
|
|
143
|
-
const idBoxBefore = await idHeader.boundingBox();
|
|
144
|
-
const nameBoxBefore = await nameHeader.boundingBox();
|
|
145
|
-
|
|
146
|
-
expect(idBoxBefore!.x).toBeLessThan(nameBoxBefore!.x);
|
|
147
|
-
|
|
148
|
-
// Drag Name to before ID
|
|
149
|
-
await nameHeader.hover();
|
|
150
|
-
await page.mouse.down();
|
|
151
|
-
await page.mouse.move(idBoxBefore!.x + 10, idBoxBefore!.y + 10, { steps: 10 });
|
|
152
|
-
await page.mouse.up();
|
|
153
|
-
|
|
154
|
-
// Verify order swapped
|
|
155
|
-
await page.waitForTimeout(1000);
|
|
156
|
-
const idBoxAfter = await idHeader.boundingBox();
|
|
157
|
-
const nameBoxAfter = await nameHeader.boundingBox();
|
|
158
|
-
|
|
159
|
-
expect(nameBoxAfter!.x).toBeLessThan(idBoxAfter!.x);
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
test('should support programmatic filter synchronization and UI reactivity', async ({ page }) => {
|
|
163
|
-
// 1. Apply filter via "Filter Engineering" button (calls gridApi.setFilterModel)
|
|
164
|
-
await page.click('button:has-text("Filter \'Engineering\'")');
|
|
165
|
-
|
|
166
|
-
// 2. Verify row count via GridApi dropped (reactivity check)
|
|
167
|
-
await page.waitForTimeout(1000);
|
|
168
|
-
const filteredCount = await page.evaluate(() => window.gridApi.getDisplayedRowCount());
|
|
169
|
-
expect(filteredCount).toBeLessThan(100000);
|
|
170
|
-
expect(filteredCount).toBeGreaterThan(0);
|
|
171
|
-
|
|
172
|
-
// 3. Verify the floating filter input shows "Eng" (bidirectional sync check)
|
|
173
|
-
const deptFilter = page.locator('.floating-filter-input').nth(2);
|
|
174
|
-
const filterValue = await deptFilter.inputValue();
|
|
175
|
-
expect(filterValue).toBe('Eng');
|
|
176
|
-
|
|
177
|
-
// 4. Clear via "Clear Filters" button
|
|
178
|
-
await page.click('button:has-text("Clear Filters")');
|
|
179
|
-
await page.waitForTimeout(500);
|
|
180
|
-
|
|
181
|
-
// 5. Verify input cleared and count restored
|
|
182
|
-
const clearedValue = await deptFilter.inputValue();
|
|
183
|
-
expect(clearedValue).toBe('');
|
|
184
|
-
const finalCount = await page.evaluate(() => window.gridApi.getDisplayedRowCount());
|
|
185
|
-
expect(finalCount).toBe(100000);
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
test('should support global options toggle via setGridOption', async ({ page }) => {
|
|
189
|
-
// 1. Hide Filters via button (calls gridApi.setGridOption)
|
|
190
|
-
await page.click('button:has-text("Hide Filters")');
|
|
191
|
-
|
|
192
|
-
// 2. Verify floating filter row is removed from DOM
|
|
193
|
-
const filterRow = page.locator('.floating-filter-row');
|
|
194
|
-
await expect(filterRow).not.toBeVisible();
|
|
195
|
-
|
|
196
|
-
// 3. Show Filters back
|
|
197
|
-
await page.click('button:has-text("Show Filters")');
|
|
198
|
-
|
|
199
|
-
// 4. Verify floating filter row returned
|
|
200
|
-
await expect(filterRow).toBeVisible();
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
test('should support column resizing by dragging the handle', async ({ page }) => {
|
|
204
|
-
const idHeader = page.locator('.argent-grid-header-cell').filter({ hasText: /^ID/ });
|
|
205
|
-
const idBoxBefore = await idHeader.boundingBox();
|
|
206
|
-
if (!idBoxBefore) throw new Error('ID header box not found');
|
|
207
|
-
|
|
208
|
-
const initialWidth = idBoxBefore.width;
|
|
209
|
-
|
|
210
|
-
// Locate resize handle
|
|
211
|
-
const resizeHandle = idHeader.locator('.argent-grid-header-resize-handle');
|
|
212
|
-
await expect(resizeHandle).toBeVisible();
|
|
213
|
-
|
|
214
|
-
// Drag to resize
|
|
215
|
-
await resizeHandle.hover();
|
|
216
|
-
await page.mouse.down();
|
|
217
|
-
await page.mouse.move(idBoxBefore.x + idBoxBefore.width + 100, idBoxBefore.y + 10, { steps: 10 });
|
|
218
|
-
await page.mouse.up();
|
|
219
|
-
|
|
220
|
-
// Verify width increased
|
|
221
|
-
await page.waitForTimeout(500);
|
|
222
|
-
const idBoxAfter = await idHeader.boundingBox();
|
|
223
|
-
expect(idBoxAfter!.width).toBeGreaterThan(initialWidth);
|
|
224
|
-
// Should be roughly initialWidth + 100 (allow 10px margin)
|
|
225
|
-
expect(Math.abs(idBoxAfter!.width - (initialWidth + 100))).toBeLessThanOrEqual(10);
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
test('should support Excel-like range selection by dragging', async ({ page }) => {
|
|
229
|
-
const canvas = page.locator('canvas');
|
|
230
|
-
const box = await canvas.boundingBox();
|
|
231
|
-
if (!box) throw new Error('Canvas box not found');
|
|
232
|
-
|
|
233
|
-
// 1. Start drag at cell (row 1, col 'name')
|
|
234
|
-
// Name column starts at x=80 (if selection col hidden) or x=112 (if selection col shown)
|
|
235
|
-
// selectionColumnWidth is 32.
|
|
236
|
-
// ID column is 80.
|
|
237
|
-
// Name column starts around x = 32 + 80 = 112.
|
|
238
|
-
const startX = 150;
|
|
239
|
-
const startY = 16; // Middle of first row (32px high)
|
|
240
|
-
|
|
241
|
-
await page.mouse.move(box.x + startX, box.y + startY);
|
|
242
|
-
await page.mouse.down();
|
|
243
|
-
|
|
244
|
-
// 2. Drag to cell (row 3, col 'department')
|
|
245
|
-
// Row 3 is at y = 32 * 2 + 16 = 80.
|
|
246
|
-
// Dept column is 180 wide.
|
|
247
|
-
const endX = 350;
|
|
248
|
-
const endY = 80;
|
|
249
|
-
|
|
250
|
-
await page.mouse.move(box.x + endX, box.y + endY, { steps: 10 });
|
|
251
|
-
await page.mouse.up();
|
|
252
|
-
|
|
253
|
-
// 3. Verify range selection via API
|
|
254
|
-
const ranges = await page.evaluate(() => window.gridApi.getCellRanges());
|
|
255
|
-
expect(ranges).toBeTruthy();
|
|
256
|
-
expect(ranges.length).toBe(1);
|
|
257
|
-
|
|
258
|
-
const range = ranges[0];
|
|
259
|
-
expect(range.startRow).toBe(0);
|
|
260
|
-
expect(range.endRow).toBe(2); // row 1 to 3
|
|
261
|
-
expect(range.columns.length).toBeGreaterThan(1);
|
|
262
|
-
});
|
|
263
|
-
});
|
|
264
|
-
|
|
265
|
-
declare global {
|
|
266
|
-
interface Window {
|
|
267
|
-
gridApi: any;
|
|
268
|
-
}
|
|
269
|
-
}
|