|
1 | 1 | import { test, expect } from '../../fixtures.mjs'; |
2 | 2 |
|
3 | | -test.describe('Desktop (1280x720): Grid Multi-Body Neural Link Selection Validation', () => { |
4 | | - test.setTimeout(90000); // DevIndex is heavy; give 90s for page render + NL mapping |
5 | | - test.use({ viewport: { width: 1280, height: 720 } }); |
| 3 | +test.describe('Desktop (1920x1080): lockedColumns Multi-Body Neural Link Selection Validation', () => { |
| 4 | + test.setTimeout(90000); |
| 5 | + test.use({ viewport: { width: 1920, height: 1080 } }); |
6 | 6 |
|
7 | 7 | test.beforeEach(async ({ page }) => { |
8 | | - await page.goto('/apps/devindex/index.html'); |
| 8 | + await page.goto('/examples/grid/lockedColumns/'); |
9 | 9 |
|
10 | | - // Wait for grid skeleton to mount |
11 | | - await page.waitForSelector('.neo-grid-container', { state: 'visible', timeout: 30000 }); |
12 | | - |
13 | | - // Wait for initial data stream to hit (Stop button becomes visible) |
14 | | - const stopButton = page.locator('.devindex-stop-stream-button'); |
15 | | - await expect(stopButton).toBeVisible({ timeout: 10000 }); |
16 | | - |
17 | | - // Click the stop button immediately so we don't wait for thousands of rows. |
18 | | - // We use force in case it's animating, and ignore errors if it somehow already hid itself. |
19 | | - try { |
20 | | - await stopButton.click({ timeout: 2000, force: true }); |
21 | | - } catch (e) { |
22 | | - // Stream finished extremely fast and button auto-hid |
23 | | - } |
24 | | - |
25 | | - // Let UI settle for a moment after stopping |
| 10 | + await page.waitForSelector('[role="grid"]', { state: 'visible', timeout: 30000 }); |
26 | 11 | await page.waitForTimeout(500); |
27 | 12 | }); |
28 | 13 |
|
29 | 14 | test('Cross-Body Row Selection Synchronization and Verification via Neural Link', async ({ page, neuralLink }) => { |
30 | | - // Connect directly to the App Worker |
31 | | - const app = await neuralLink.connectToApp('DevIndex'); |
32 | | - |
33 | | - // Locate the main GridContainer manually via Main Thread DOM to prove it works |
34 | | - const gridId = await page.evaluate(() => document.querySelector('.neo-grid-container').id); |
| 15 | + const app = await neuralLink.connectToApp('Neo.examples.grid.lockedColumns'); |
| 16 | + const gridId = await resolveGridId(app); |
35 | 17 |
|
36 | | - // Activate RowModel natively using Neural Link (since DevIndex defaults to null on load) |
37 | 18 | await app.setProperties(gridId, { 'body.selectionModel': { ntype: 'selection-grid-rowmodel' } }); |
38 | 19 |
|
39 | | - // Let it initialize |
40 | | - await page.waitForTimeout(200); |
41 | | - |
42 | | - // Inspect the Grid Component directly |
43 | | - const gridProps = await app.getComponent(gridId, ['bodyStart.id', 'body.id', 'bodyEnd.id', 'body.selectionModel', 'store', 'ntype']); |
| 20 | + await expect.poll(async () => { |
| 21 | + const props = await app.getComponent(gridId, ['view.selectionModel.id']); |
| 22 | + return props['view.selectionModel.id']; |
| 23 | + }, { timeout: 5000 }).toBeTruthy(); |
| 24 | + |
| 25 | + const gridProps = await app.getComponent(gridId, [ |
| 26 | + 'bodyStart.id', |
| 27 | + 'body.id', |
| 28 | + 'bodyEnd.id', |
| 29 | + 'lockedStartColumns', |
| 30 | + 'lockedEndColumns', |
| 31 | + 'ntype', |
| 32 | + 'view.selectionModel.id' |
| 33 | + ]); |
44 | 34 |
|
45 | 35 | expect(gridProps.ntype).toBe('grid-container'); |
46 | | - expect(gridProps['body.selectionModel']).toBeTruthy(); |
| 36 | + expect((gridProps.lockedStartColumns || []).length, 'fixture has locked-start columns').toBeGreaterThan(0); |
| 37 | + expect((gridProps.lockedEndColumns || []).length, 'fixture has locked-end columns').toBeGreaterThan(0); |
| 38 | + expect(gridProps['view.selectionModel.id']).toBeTruthy(); |
47 | 39 |
|
48 | | - // We want to test clicking a row in the CENTER body to avoid locked-column interaction edge cases for now |
49 | | - const centerBodyId = gridProps['body.id']; |
50 | 40 | const leftBodyId = gridProps['bodyStart.id']; |
| 41 | + const centerBodyId = gridProps['body.id']; |
51 | 42 | const rightBodyId = gridProps['bodyEnd.id']; |
52 | | - const storeId = gridProps.store?.id || gridProps.store; |
53 | 43 |
|
54 | | - // Locate the first row in the center body |
| 44 | + expect(leftBodyId, 'locked-start body id').toBeTruthy(); |
| 45 | + expect(centerBodyId, 'center body id').toBeTruthy(); |
| 46 | + expect(rightBodyId, 'locked-end body id').toBeTruthy(); |
| 47 | + |
55 | 48 | const centerBodyRow = page.locator(`#${centerBodyId} .neo-grid-row`).first(); |
| 49 | + await expect(centerBodyRow).toBeVisible(); |
56 | 50 |
|
57 | | - // Grab the recordId mapped to this DOM node |
58 | 51 | const recordId = await centerBodyRow.getAttribute('data-record-id'); |
| 52 | + expect(recordId, 'center row must expose a record id').toBeTruthy(); |
59 | 53 |
|
60 | | - // It should NOT be selected yet |
61 | 54 | await expect(centerBodyRow).not.toHaveClass(/neo-selected/); |
62 | 55 |
|
63 | | - // Native User Interaction (Clicking the center column row) |
64 | 56 | await centerBodyRow.click(); |
65 | 57 |
|
66 | | - // Visual Assertion: Wait for App Worker to reconcile and add selections |
67 | 58 | await expect(centerBodyRow).toHaveClass(/neo-selected/); |
68 | 59 |
|
69 | | - // Mult-Body Validation: Check the left and right bodies for that same record ID |
70 | 60 | const leftBodyRow = page.locator(`#${leftBodyId} .neo-grid-row[data-record-id="${recordId}"]`); |
71 | 61 | const rightBodyRow = page.locator(`#${rightBodyId} .neo-grid-row[data-record-id="${recordId}"]`); |
72 | 62 |
|
73 | | - // They should BOTH be selected as well due to multi-body orchestration |
74 | 63 | await expect(leftBodyRow).toHaveClass(/neo-selected/); |
75 | 64 | await expect(rightBodyRow).toHaveClass(/neo-selected/); |
76 | 65 |
|
77 | | - // God Mode Assertion: Verify the exact backend state mirrors our action. |
78 | | - // DevIndex Grid defaults to array-based selection since selectedRecordField ('selected') |
79 | | - // does not exist on the Contributor model prototype. We check the RowModel's selectedRows array. |
80 | | - const smId = gridProps['body.selectionModel'].id; |
81 | | - const smProps = await app.getComponent(smId, ['selectedRows']); |
82 | | - expect(smProps.selectedRows.includes(recordId)).toBe(true); |
| 66 | + const selectedRows = (await app.getComponent(gridId, ['view.selectionModel.selectedRows']))['view.selectionModel.selectedRows']; |
| 67 | + expect(selectedRows).toContain(recordId); |
83 | 68 | }); |
84 | 69 |
|
85 | | - test('Live Property Updation (Column Width Alignment)', async ({ page, neuralLink }) => { |
86 | | - const app = await neuralLink.connectToApp('DevIndex'); |
87 | | - const gridId = await page.evaluate(() => document.querySelector('.neo-grid-container').id); |
| 70 | + test('Live Body Resize Path Updates Center Cells and Width Cache', async ({ page, neuralLink }) => { |
| 71 | + const app = await neuralLink.connectToApp('Neo.examples.grid.lockedColumns'); |
| 72 | + const gridId = await resolveGridId(app); |
| 73 | + const { 'body.id': centerBodyId } = await app.getComponent(gridId, ['body.id']); |
88 | 74 |
|
89 | | - // Get the structural columns from the Grid container |
90 | | - // Note: The dev index columns setup has multiple bodies. We need a visible column ID. |
91 | | - const headerCells = page.locator('.neo-grid-header-button'); |
92 | | - const firstCenterCellId = await headerCells.nth(2).getAttribute('id'); // Skip start columns |
| 75 | + expect(centerBodyId, 'center body id').toBeTruthy(); |
93 | 76 |
|
94 | | - // Just verify we can fetch the column config |
95 | | - const colProps = await app.getComponent(firstCenterCellId, ['width', 'dataField']); |
96 | | - const oldWidth = colProps.width; |
| 77 | + const centerBodyRow = page.locator(`#${centerBodyId} .neo-grid-row`).first(); |
| 78 | + await expect(centerBodyRow).toBeVisible(); |
| 79 | + |
| 80 | + const totalCell = centerBodyRow.locator('[role="gridcell"]').first(); |
| 81 | + await expect(totalCell).toBeVisible(); |
| 82 | + |
| 83 | + const beforeBox = await totalCell.boundingBox(); |
| 84 | + expect(beforeBox?.width, 'Total cell width before resize').toBeGreaterThan(0); |
| 85 | + |
| 86 | + const beforeProps = await app.getComponent(centerBodyId, ['availableWidth']); |
| 87 | + const newWidth = Math.round(beforeBox.width + 40); |
97 | 88 |
|
98 | | - // Using God Mode, forcibly alter the width of a specific column component |
99 | | - const newWidth = oldWidth + 100; |
100 | | - await app.setProperties(firstCenterCellId, { width: newWidth }); |
| 89 | + await app.callMethod(centerBodyId, 'updateCellPositions', ['totalContributions', newWidth]); |
101 | 90 |
|
102 | | - // Assert: Playwright observes the CSS strictly matches the new injected God Mode constraint |
103 | | - const targetCell = page.locator(`#${firstCenterCellId}`); |
104 | | - await expect(targetCell).toHaveCSS('width', `${newWidth}px`); |
| 91 | + await expect(totalCell).toHaveCSS('width', `${newWidth}px`); |
| 92 | + |
| 93 | + const afterProps = await app.getComponent(centerBodyId, ['availableWidth']); |
| 94 | + expect(afterProps.availableWidth).toBe(beforeProps.availableWidth + 40); |
105 | 95 | }); |
106 | 96 | }); |
| 97 | + |
| 98 | +/** |
| 99 | + * Resolves the grid-container instance id from the App Worker. |
| 100 | + * @param {Object} app |
| 101 | + * @returns {Promise<String>} |
| 102 | + */ |
| 103 | +async function resolveGridId(app) { |
| 104 | + const grids = await app.findInstances({ ntype: 'grid-container' }, ['id']); |
| 105 | + const gridId = Array.isArray(grids) ? grids[0]?.id : grids?.id; |
| 106 | + |
| 107 | + expect(gridId, 'a grid-container instance must exist in the bound App Worker').toBeTruthy(); |
| 108 | + |
| 109 | + return gridId; |
| 110 | +} |
0 commit comments