From 7f3a68325b225444b99dbe0ca4e396749e775d71 Mon Sep 17 00:00:00 2001 From: amahajan Date: Tue, 23 Sep 2025 10:24:47 -0500 Subject: [PATCH 01/20] Rewrite scrollToCell tests --- test/browser/scrollToCell.test.tsx | 57 ++++++++++++++++++------------ 1 file changed, 34 insertions(+), 23 deletions(-) diff --git a/test/browser/scrollToCell.test.tsx b/test/browser/scrollToCell.test.tsx index ba843a39c3..88dde10835 100644 --- a/test/browser/scrollToCell.test.tsx +++ b/test/browser/scrollToCell.test.tsx @@ -4,21 +4,23 @@ import { page, userEvent } from '@vitest/browser/context'; import { DataGrid } from '../../src'; import type { Column, DataGridHandle } from '../../src'; import type { PartialPosition } from '../../src/ScrollToCell'; -import { getGrid } from './utils'; -type Row = undefined; - -const rows: readonly Row[] = new Array(50); -const summaryRows: readonly Row[] = [undefined, undefined]; - -const columns: Column[] = []; +const rows: readonly number[] = Array.from({ length: 50 }, (_, i) => i); +const summaryRows: readonly number[] = Array.from({ length: 2 }, (_, i) => i + 50); +const columns: Column[] = []; for (let i = 0; i < 50; i++) { const key = String(i); columns.push({ key, name: key, - frozen: i < 5 + frozen: i < 5, + renderCell(props) { + return `${props.column.key}×${props.row}`; + }, + renderSummaryCell(props) { + return `${props.column.key}×${props.row}`; + } }); } @@ -54,43 +56,52 @@ async function testScroll(p: PartialPosition) { test('scrollToCell', async () => { page.render(); - const grid = getGrid().element(); - validateScrollPosition(0, 0); + await validateCellVisibility('0×0', true); + await validateCellVisibility('40×30', false); // should scroll to a cell when a valid position is specified await testScroll({ idx: 40, rowIdx: 30 }); - validateScrollPosition(1572, 132); + await validateCellVisibility('0×0', false); + await validateCellVisibility('40×30', true); // should scroll to a column when a valid idx is specified await testScroll({ idx: 6 }); - validateScrollPosition(1572, 50); + await validateCellVisibility('6×30', true); + await validateCellVisibility('40×30', false); await testScroll({ idx: 40 }); - validateScrollPosition(1572, 132); + await validateCellVisibility('6×30', false); + await validateCellVisibility('40×30', true); // should scroll to a row when a valid rowIdx is specified await testScroll({ rowIdx: 1 }); - validateScrollPosition(0, 132); + await validateCellVisibility('40×1', true); + await validateCellVisibility('40×30', false); await testScroll({ rowIdx: 30 }); - validateScrollPosition(1572, 132); + await validateCellVisibility('40×1', false); + await validateCellVisibility('40×30', true); // should not scroll if scroll to column is frozen await testScroll({ idx: 2 }); - validateScrollPosition(1572, 132); + await validateCellVisibility('40×30', true); // should not scroll if rowIdx is header row await testScroll({ idx: -1 }); - validateScrollPosition(1572, 132); + await validateCellVisibility('40×30', true); // should not scroll if rowIdx is summary row await testScroll({ idx: 50 }); - validateScrollPosition(1572, 132); + await validateCellVisibility('40×30', true); // should not scroll if position is out of bound await testScroll({ idx: 60, rowIdx: 60 }); - validateScrollPosition(1572, 132); + await validateCellVisibility('40×30', true); +}); - function validateScrollPosition(scrollTop: number, scrollLeft: number) { - expect(grid.scrollTop).toBe(scrollTop); - expect(grid.scrollLeft).toBe(scrollLeft); +function validateCellVisibility(name: string, isVisible: boolean) { + const cell = page.getByRole('gridcell', { name, exact: true }); + if (isVisible) { + return expect.element(cell).toBeVisible(); } -}); + + return expect.element(cell).not.toBeInTheDocument(); +} From ed175ebca77fad2eecdf135df067d1c07f32afdb Mon Sep 17 00:00:00 2001 From: amahajan Date: Tue, 23 Sep 2025 10:30:23 -0500 Subject: [PATCH 02/20] tweak --- test/browser/scrollToCell.test.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/browser/scrollToCell.test.tsx b/test/browser/scrollToCell.test.tsx index 88dde10835..3a58c416c5 100644 --- a/test/browser/scrollToCell.test.tsx +++ b/test/browser/scrollToCell.test.tsx @@ -58,6 +58,7 @@ test('scrollToCell', async () => { page.render(); await validateCellVisibility('0×0', true); await validateCellVisibility('40×30', false); + await validateCellVisibility('0×51', true); // should scroll to a cell when a valid position is specified await testScroll({ idx: 40, rowIdx: 30 }); @@ -95,6 +96,10 @@ test('scrollToCell', async () => { // should not scroll if position is out of bound await testScroll({ idx: 60, rowIdx: 60 }); await validateCellVisibility('40×30', true); + + // should not scroll vertically when scrolling to summary row + await testScroll({ idx: 49, rowIdx: 51 }); + await validateCellVisibility('49×30', true); }); function validateCellVisibility(name: string, isVisible: boolean) { From 370dbd325167143ed0fb2574a380957e662b9ce6 Mon Sep 17 00:00:00 2001 From: amahajan Date: Tue, 23 Sep 2025 10:42:37 -0500 Subject: [PATCH 03/20] -1 --- test/browser/TextEditor.test.tsx | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/test/browser/TextEditor.test.tsx b/test/browser/TextEditor.test.tsx index a18f40e461..2afa699dc0 100644 --- a/test/browser/TextEditor.test.tsx +++ b/test/browser/TextEditor.test.tsx @@ -3,7 +3,6 @@ import { page, userEvent } from '@vitest/browser/context'; import { DataGrid, textEditor } from '../../src'; import type { Column } from '../../src'; -import { getCells } from './utils'; interface Row { readonly name: string; @@ -29,27 +28,27 @@ function Test() { test('TextEditor', async () => { page.render(); - - await userEvent.dblClick(getCells()[0]); - let input = page.getByRole('textbox').element() as HTMLInputElement; - expect(input).toHaveClass('rdg-text-editor'); + const cell = page.getByRole('gridcell'); + await expect.element(cell).toHaveTextContent(/^Tacitus Kilgore$/); + await userEvent.dblClick(cell); + const input = page.getByRole('textbox'); + await expect.element(input).toHaveClass('rdg-text-editor'); // input value is row[column.key] - expect(input).toHaveValue(initialRows[0].name); + await expect.element(input).toHaveValue(initialRows[0].name); // input is focused - expect(input).toHaveFocus(); + await expect.element(input).toHaveFocus(); // input value is fully selected - expect(input).toHaveSelection(initialRows[0].name); + await expect.element(input).toHaveSelection(initialRows[0].name); // pressing escape closes the editor without committing await userEvent.keyboard('Test{escape}'); - expect(input).not.toBeInTheDocument(); - await expect.element(getCells()[0]).toHaveTextContent(/^Tacitus Kilgore$/); + await expect.element(input).not.toBeInTheDocument(); + await expect.element(cell).toHaveTextContent(/^Tacitus Kilgore$/); // blurring the input closes and commits the editor - await userEvent.dblClick(getCells()[0]); - input = page.getByRole('textbox').element() as HTMLInputElement; + await userEvent.dblClick(cell); await userEvent.fill(input, 'Jim Milton'); await userEvent.tab(); - expect(input).not.toBeInTheDocument(); - await expect.element(getCells()[0]).toHaveTextContent(/^Jim Milton$/); + await expect.element(input).not.toBeInTheDocument(); + await expect.element(cell).toHaveTextContent(/^Jim Milton$/); }); From b08e391fcb1328f31fc50d1be1cdd9c51036d246 Mon Sep 17 00:00:00 2001 From: amahajan Date: Tue, 23 Sep 2025 10:45:33 -0500 Subject: [PATCH 04/20] -1 --- test/browser/sorting.test.tsx | 42 ++++++++++++++++++----------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/test/browser/sorting.test.tsx b/test/browser/sorting.test.tsx index 8f1e829766..c39df38f02 100644 --- a/test/browser/sorting.test.tsx +++ b/test/browser/sorting.test.tsx @@ -3,7 +3,6 @@ import { page, userEvent } from '@vitest/browser/context'; import { DataGrid } from '../../src'; import type { Column, SortColumn } from '../../src/types'; -import { getHeaderCells } from './utils'; const columns: readonly Column[] = [ { key: 'colA', name: 'colA' }, @@ -41,43 +40,45 @@ function testSortColumns(expectedValue: readonly SortColumn[]) { test('should not sort if sortable is false', async () => { setup(); - const headerCell = getHeaderCells()[3]; + const headerCell = page.getByRole('columnheader', { name: 'colD' }); await userEvent.click(headerCell); - expect(headerCell).not.toHaveAttribute('aria-sort'); + await expect.element(headerCell).not.toHaveAttribute('aria-sort'); await testSortColumns([]); }); test('single column sort', async () => { setup(); - const headerCell = getHeaderCells()[0]; + const headerCell = page.getByRole('columnheader', { name: 'colA' }); await userEvent.click(headerCell); - expect(headerCell).toHaveAttribute('aria-sort', 'ascending'); + await expect.element(headerCell).toHaveAttribute('aria-sort', 'ascending'); // priority is not shown for single sort - expect(headerCell).not.toHaveTextContent('1'); + await expect.element(headerCell).not.toHaveTextContent('1'); await testSortColumns([{ columnKey: 'colA', direction: 'ASC' }]); await userEvent.click(headerCell); - expect(headerCell).toHaveAttribute('aria-sort', 'descending'); + await expect.element(headerCell).toHaveAttribute('aria-sort', 'descending'); await testSortColumns([{ columnKey: 'colA', direction: 'DESC' }]); await userEvent.click(headerCell); - expect(headerCell).not.toHaveAttribute('aria-sort'); + await expect.element(headerCell).not.toHaveAttribute('aria-sort'); await testSortColumns([]); }); test('multi column sort', async () => { setup(); - const [headerCell1, headerCell2, headerCell3] = getHeaderCells(); + const headerCell1 = page.getByRole('columnheader', { name: 'colA' }); + const headerCell2 = page.getByRole('columnheader', { name: 'colB' }); + const headerCell3 = page.getByRole('columnheader', { name: 'colC' }); await userEvent.click(headerCell1); await userEvent.keyboard('{Control>}'); await userEvent.click(headerCell2); await userEvent.click(headerCell3); // aria-sort is only added for single sort - expect(headerCell1).not.toHaveAttribute('aria-sort'); - expect(headerCell1).toHaveTextContent('1'); // priority - expect(headerCell2).not.toHaveAttribute('aria-sort'); - expect(headerCell2).toHaveTextContent('2'); - expect(headerCell3).not.toHaveAttribute('aria-sort'); - expect(headerCell3).toHaveTextContent('3'); + await expect.element(headerCell1).not.toHaveAttribute('aria-sort'); + await expect.element(headerCell1).toHaveTextContent('1'); // priority + await expect.element(headerCell2).not.toHaveAttribute('aria-sort'); + await expect.element(headerCell2).toHaveTextContent('2'); + await expect.element(headerCell3).not.toHaveAttribute('aria-sort'); + await expect.element(headerCell3).toHaveTextContent('3'); await testSortColumns([ { columnKey: 'colA', direction: 'ASC' }, { columnKey: 'colB', direction: 'DESC' }, @@ -95,19 +96,20 @@ test('multi column sort', async () => { { columnKey: 'colA', direction: 'ASC' }, { columnKey: 'colC', direction: 'ASC' } ]); - expect(headerCell3).toHaveTextContent('2'); + await expect.element(headerCell3).toHaveTextContent('2'); // clicking on a column without ctrlKey should remove multisort await userEvent.keyboard('{/Control}'); await userEvent.click(headerCell2); await testSortColumns([{ columnKey: 'colB', direction: 'DESC' }]); - expect(headerCell2).toHaveAttribute('aria-sort'); - expect(headerCell2).not.toHaveTextContent('2'); + await expect.element(headerCell2).toHaveAttribute('aria-sort'); + await expect.element(headerCell2).not.toHaveTextContent('2'); }); test('multi column sort with metakey', async () => { setup(); - const [headerCell1, headerCell2] = getHeaderCells(); + const headerCell1 = page.getByRole('columnheader', { name: 'colA' }); + const headerCell2 = page.getByRole('columnheader', { name: 'colB' }); await userEvent.click(headerCell1); await userEvent.keyboard('{Meta>}'); await userEvent.click(headerCell2); @@ -119,7 +121,7 @@ test('multi column sort with metakey', async () => { test('multi column sort with keyboard', async () => { setup(); - const [headerCell1] = getHeaderCells(); + const headerCell1 = page.getByRole('columnheader', { name: 'colA' }); await userEvent.click(headerCell1); await userEvent.keyboard(' {arrowright}{Control>}{enter}'); await testSortColumns([ From 9f4d6168c86dd4a10b4711c1a0530bed1167f6e3 Mon Sep 17 00:00:00 2001 From: amahajan Date: Tue, 23 Sep 2025 11:08:50 -0500 Subject: [PATCH 05/20] -1 --- test/browser/rowSelection.test.tsx | 137 +++++++++++++++-------------- test/browser/utils.tsx | 16 ++++ 2 files changed, 87 insertions(+), 66 deletions(-) diff --git a/test/browser/rowSelection.test.tsx b/test/browser/rowSelection.test.tsx index a37c375a91..4705dae64f 100644 --- a/test/browser/rowSelection.test.tsx +++ b/test/browser/rowSelection.test.tsx @@ -3,7 +3,7 @@ import { page, userEvent } from '@vitest/browser/context'; import { DataGrid, SelectColumn } from '../../src'; import type { Column } from '../../src'; -import { getCellsAtRowIndex, getRows } from './utils'; +import { getCell, getRow, getSelectAllCheckbox } from './utils'; interface Row { id: number; @@ -13,7 +13,10 @@ const columns: readonly Column[] = [ SelectColumn, { key: 'name', - name: 'Name' + name: 'Name', + renderCell(props) { + return props.row.id; + } } ]; @@ -49,75 +52,77 @@ function setup(initialRows = defaultRows) { } function testSelection(rowIdx: number, isSelected: boolean) { - expect(getRows()[rowIdx]).toHaveAttribute('aria-selected', isSelected ? 'true' : 'false'); + return expect + .element(getRow(String(rowIdx + 1))) + .toHaveAttribute('aria-selected', isSelected ? 'true' : 'false'); } -async function toggleSelection(rowIdx: number, shift = false) { - const element = page.getByRole('row').all()[rowIdx + 1].getByRole('checkbox', { name: 'Select' }); +async function toggleSelection(rowIdx: number, shift = false, force = false) { + const checkbox = getRow(String(rowIdx + 1)).getByRole('checkbox', { name: 'Select' }); if (shift) await userEvent.keyboard('{Shift>}'); - await userEvent.click(element, { force: true }); + await userEvent.click(checkbox, { force }); if (shift) await userEvent.keyboard('{/Shift}'); } test('toggle selection when checkbox is clicked', async () => { setup(); await toggleSelection(0); - testSelection(0, true); + await testSelection(0, true); await toggleSelection(1); - testSelection(1, true); + await testSelection(1, true); await toggleSelection(0); - testSelection(0, false); + await testSelection(0, false); await toggleSelection(1); - testSelection(1, false); + await testSelection(1, false); }); test('toggle selection using keyboard', async () => { setup(); - testSelection(0, false); - await userEvent.click(getCellsAtRowIndex(0)[0]); - testSelection(0, true); + await testSelection(0, false); + await userEvent.click(getRow('1').getByRole('checkbox', { name: 'Select' })); + await testSelection(0, true); await userEvent.keyboard(' '); - testSelection(0, false); + await testSelection(0, false); await userEvent.keyboard(' '); - testSelection(0, true); + await testSelection(0, true); await userEvent.keyboard('{arrowdown} '); - testSelection(1, true); + await testSelection(1, true); await userEvent.keyboard('{arrowup} '); - testSelection(0, false); + await testSelection(0, false); }); test('should partially select header checkbox', async () => { setup(); - const headerCheckbox = page.getByRole('checkbox', { name: 'Select All' }).element(); - expect(headerCheckbox).not.toBeChecked(); - expect(headerCheckbox).not.toBePartiallyChecked(); + const headerCheckbox = getSelectAllCheckbox(); + await expect.element(headerCheckbox).not.toBeChecked(); + await expect.element(headerCheckbox).not.toBePartiallyChecked(); await toggleSelection(0); - expect(headerCheckbox).not.toBeChecked(); - expect(headerCheckbox).toBePartiallyChecked(); + await expect.element(headerCheckbox).not.toBeChecked(); + await expect.element(headerCheckbox).toBePartiallyChecked(); await toggleSelection(1); - expect(headerCheckbox).not.toBeChecked(); - expect(headerCheckbox).toBePartiallyChecked(); + await expect.element(headerCheckbox).not.toBeChecked(); + await expect.element(headerCheckbox).toBePartiallyChecked(); await toggleSelection(2); - expect(headerCheckbox).toBeChecked(); - expect(headerCheckbox).not.toBePartiallyChecked(); + await expect.element(headerCheckbox).toBeChecked(); + await expect.element(headerCheckbox).not.toBePartiallyChecked(); await toggleSelection(0); - expect(headerCheckbox).not.toBeChecked(); - expect(headerCheckbox).toBePartiallyChecked(); + await expect.element(headerCheckbox).not.toBeChecked(); + await expect.element(headerCheckbox).toBePartiallyChecked(); await userEvent.click(headerCheckbox); - testSelection(0, false); - testSelection(1, false); - testSelection(2, false); + await testSelection(0, false); + await testSelection(1, false); + await testSelection(2, false); await userEvent.click(headerCheckbox); - testSelection(0, true); - testSelection(1, true); - testSelection(2, true); + await testSelection(0, true); + await testSelection(1, true); + await testSelection(2, true); }); test('should not select row when isRowSelectionDisabled returns true', async () => { @@ -125,44 +130,44 @@ test('should not select row when isRowSelectionDisabled returns true', async () row.id === 2} /> ); await toggleSelection(0); - testSelection(0, true); - await toggleSelection(1); - testSelection(1, false); + await testSelection(0, true); + await toggleSelection(1, false, true); // force click even if disabled + await testSelection(1, false); await toggleSelection(2); - testSelection(2, true); + await testSelection(2, true); - await userEvent.click(page.getByRole('checkbox', { name: 'Select All' })); + await userEvent.click(getSelectAllCheckbox()); await toggleSelection(0); await toggleSelection(2, true); - testSelection(0, true); - testSelection(1, false); - testSelection(2, true); + await testSelection(0, true); + await testSelection(1, false); + await testSelection(2, true); }); test('select/deselect all rows when header checkbox is clicked', async () => { setup(); - const headerCheckbox = page.getByRole('checkbox', { name: 'Select All' }).element(); - expect(headerCheckbox).not.toBeChecked(); + const headerCheckbox = getSelectAllCheckbox(); + await expect.element(headerCheckbox).not.toBeChecked(); await userEvent.click(headerCheckbox); - testSelection(0, true); - testSelection(1, true); - testSelection(2, true); + await testSelection(0, true); + await testSelection(1, true); + await testSelection(2, true); // deselecting a row should toggle header await toggleSelection(0); - expect(headerCheckbox).not.toBeChecked(); + await expect.element(headerCheckbox).not.toBeChecked(); await toggleSelection(0); - expect(headerCheckbox).toBeChecked(); + await expect.element(headerCheckbox).toBeChecked(); await userEvent.click(headerCheckbox); - testSelection(0, false); - testSelection(1, false); - testSelection(2, false); + await testSelection(0, false); + await testSelection(1, false); + await testSelection(2, false); }); test('header checkbox is not checked when there are no rows', async () => { setup([]); - await expect.element(page.getByRole('checkbox', { name: 'Select All' })).not.toBeChecked(); + await expect.element(getSelectAllCheckbox()).not.toBeChecked(); }); test('header checkbox is not necessarily checked when selectedRows.size === rows.length', async () => { @@ -175,7 +180,7 @@ test('header checkbox is not necessarily checked when selectedRows.size === rows /> ); - await expect.element(page.getByRole('checkbox', { name: 'Select All' })).not.toBeChecked(); + await expect.element(getSelectAllCheckbox()).not.toBeChecked(); }); test('header checkbox is not necessarily checked when selectedRows.size > rows.length', async () => { @@ -188,7 +193,7 @@ test('header checkbox is not necessarily checked when selectedRows.size > rows.l /> ); - await expect.element(page.getByRole('checkbox', { name: 'Select All' })).not.toBeChecked(); + await expect.element(getSelectAllCheckbox()).not.toBeChecked(); }); test('extra keys are preserved when updating the selectedRows Set', async () => { @@ -216,7 +221,7 @@ test('extra keys are preserved when updating the selectedRows Set', async () => page.render(); - const headerCheckbox = page.getByRole('checkbox', { name: 'Select All' }).element(); + const headerCheckbox = getSelectAllCheckbox(); await toggleSelection(1); expect(set).toStrictEqual(new Set([...initialSet, 2])); @@ -241,23 +246,23 @@ test('select/deselect rows using shift click', async () => { setup(); await toggleSelection(0); await toggleSelection(2, true); - testSelection(0, true); - testSelection(1, true); - testSelection(2, true); + await testSelection(0, true); + await testSelection(1, true); + await testSelection(2, true); await toggleSelection(0); await toggleSelection(2, true); - testSelection(0, false); - testSelection(1, false); - testSelection(2, false); + await testSelection(0, false); + await testSelection(1, false); + await testSelection(2, false); }); test('select rows using shift space', async () => { setup(); - await userEvent.click(getCellsAtRowIndex(0)[1]); + await userEvent.click(getCell('1')); await userEvent.keyboard('{Shift>} {/Shift}'); - testSelection(0, true); + await testSelection(0, true); await userEvent.keyboard(' '); - testSelection(0, true); + await testSelection(0, true); await userEvent.keyboard('{Shift>} {/Shift}'); - testSelection(0, false); + await testSelection(0, false); }); diff --git a/test/browser/utils.tsx b/test/browser/utils.tsx index 9f01850cb2..38c3c2e0dd 100644 --- a/test/browser/utils.tsx +++ b/test/browser/utils.tsx @@ -40,6 +40,22 @@ export function getTreeGrid() { return page.getByRole('treegrid'); } +export function getColumnHeader(name: string) { + return page.getByRole('columnheader', { name, exact: true }); +} + +export function getRow(name: string) { + return page.getByRole('row', { name }); +} + +export function getCell(name: string) { + return page.getByRole('gridcell', { name, exact: true }); +} + +export function getSelectAllCheckbox() { + return page.getByRole('checkbox', { name: 'Select All' }); +} + export function getRows() { return page.getByRole('row').elements().slice(1); } From 2336eda99a11d25337b387c138a334c6ab3948cb Mon Sep 17 00:00:00 2001 From: amahajan Date: Tue, 23 Sep 2025 11:12:10 -0500 Subject: [PATCH 06/20] -1 --- test/browser/rowClass.test.ts | 40 ++++++++++++++++++++--------------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/test/browser/rowClass.test.ts b/test/browser/rowClass.test.ts index be50467c12..3f692653d1 100644 --- a/test/browser/rowClass.test.ts +++ b/test/browser/rowClass.test.ts @@ -1,46 +1,52 @@ import type { Column } from '../../src'; import { rowClassname } from '../../src/style/row'; -import { getRows, setup } from './utils'; +import { getRow, setup } from './utils'; interface Row { id: number; } -const columns: readonly Column[] = [{ key: 'id', name: 'ID' }]; +const columns: readonly Column[] = [{ key: 'id', name: 'ID', renderCell: (p) => p.row.id }]; const rows: readonly Row[] = [{ id: 0 }, { id: 1 }, { id: 2 }]; -test('rowClass is undefined', () => { +test('rowClass is undefined', async () => { setup({ columns, rows, rowClass: undefined }); - const [row1, row2, row3] = getRows(); - expect(row1).toHaveClass(`${rowClassname} rdg-row-even`, { exact: true }); - expect(row2).toHaveClass(`${rowClassname} rdg-row-odd`, { exact: true }); - expect(row3).toHaveClass(`${rowClassname} rdg-row-even`, { exact: true }); + const row1 = getRow('0'); + const row2 = getRow('1'); + const row3 = getRow('2'); + await expect.element(row1).toHaveClass(`${rowClassname} rdg-row-even`, { exact: true }); + await expect.element(row2).toHaveClass(`${rowClassname} rdg-row-odd`, { exact: true }); + await expect.element(row3).toHaveClass(`${rowClassname} rdg-row-even`, { exact: true }); }); -test('rowClass returns a string', () => { +test('rowClass returns a string', async () => { setup({ columns, rows, rowClass: (row) => `my-row-${row.id}` }); - const [row1, row2, row3] = getRows(); - expect(row1).toHaveClass(`${rowClassname} rdg-row-even my-row-0`, { exact: true }); - expect(row2).toHaveClass(`${rowClassname} rdg-row-odd my-row-1`, { exact: true }); - expect(row3).toHaveClass(`${rowClassname} rdg-row-even my-row-2`, { exact: true }); + const row1 = getRow('0'); + const row2 = getRow('1'); + const row3 = getRow('2'); + await expect.element(row1).toHaveClass(`${rowClassname} rdg-row-even my-row-0`, { exact: true }); + await expect.element(row2).toHaveClass(`${rowClassname} rdg-row-odd my-row-1`, { exact: true }); + await expect.element(row3).toHaveClass(`${rowClassname} rdg-row-even my-row-2`, { exact: true }); }); -test('rowClass returns undefined', () => { +test('rowClass returns undefined', async () => { setup({ columns, rows, rowClass: () => undefined }); - const [row1, row2, row3] = getRows(); - expect(row1).toHaveClass(`${rowClassname} rdg-row-even`, { exact: true }); - expect(row2).toHaveClass(`${rowClassname} rdg-row-odd`, { exact: true }); - expect(row3).toHaveClass(`${rowClassname} rdg-row-even`, { exact: true }); + const row1 = getRow('0'); + const row2 = getRow('1'); + const row3 = getRow('2'); + await expect.element(row1).toHaveClass(`${rowClassname} rdg-row-even`, { exact: true }); + await expect.element(row2).toHaveClass(`${rowClassname} rdg-row-odd`, { exact: true }); + await expect.element(row3).toHaveClass(`${rowClassname} rdg-row-even`, { exact: true }); }); From 87a273d35be39d95d8d6e5cf2491ae2c9f9360c8 Mon Sep 17 00:00:00 2001 From: amahajan Date: Tue, 23 Sep 2025 11:27:05 -0500 Subject: [PATCH 07/20] -3 --- test/browser/column/cellClass.test.ts | 69 ++++++++++++--------------- test/browser/headerRowClass.test.ts | 16 +++---- test/browser/label.test.ts | 16 +++---- 3 files changed, 45 insertions(+), 56 deletions(-) diff --git a/test/browser/column/cellClass.test.ts b/test/browser/column/cellClass.test.ts index 8d6f4c7afe..0e8f6571a4 100644 --- a/test/browser/column/cellClass.test.ts +++ b/test/browser/column/cellClass.test.ts @@ -1,6 +1,6 @@ import type { Column } from '../../../src'; import { cellClassname } from '../../../src/style/cell'; -import { getCells, setup } from '../utils'; +import { getCell, getCells, setup } from '../utils'; interface Row { id: number; @@ -8,57 +8,48 @@ interface Row { const rows: readonly Row[] = [{ id: 0 }, { id: 1 }]; -test('cellClass is undefined', () => { - const columns: readonly Column[] = [ +function getColumns(cellClass?: Column['cellClass']): readonly Column[] { + return [ { key: 'id', - name: 'ID' + name: 'ID', + renderCell: (p) => p.row.id, + cellClass } ]; +} + +test('cellClass is undefined', async () => { + const columns = getColumns(); setup({ columns, rows }); - const [cell1, cell2] = getCells(); - expect(cell1).toHaveClass(cellClassname, { exact: true }); - expect(cell2).toHaveClass(cellClassname, { exact: true }); + const cell1 = getCell('0'); + const cell2 = getCell('1'); + await expect.element(cell1).toHaveClass(cellClassname, { exact: true }); + await expect.element(cell2).toHaveClass(cellClassname, { exact: true }); }); -test('cellClass is a string', () => { - const columns: readonly Column[] = [ - { - key: 'id', - name: 'ID', - cellClass: 'my-cell' - } - ]; +test('cellClass is a string', async () => { + const columns = getColumns('my-cell'); setup({ columns, rows }); - const [cell1, cell2] = getCells(); - expect(cell1).toHaveClass(`${cellClassname} my-cell`, { exact: true }); - expect(cell2).toHaveClass(`${cellClassname} my-cell`, { exact: true }); + const cell1 = getCell('0'); + const cell2 = getCell('1'); + await expect.element(cell1).toHaveClass(`${cellClassname} my-cell`, { exact: true }); + await expect.element(cell2).toHaveClass(`${cellClassname} my-cell`, { exact: true }); }); -test('cellClass returns a string', () => { - const columns: readonly Column[] = [ - { - key: 'id', - name: 'ID', - cellClass: (row) => `my-cell-${row.id}` - } - ]; +test('cellClass returns a string', async () => { + const columns = getColumns((row) => `my-cell-${row.id}`); setup({ columns, rows }); - const [cell1, cell2] = getCells(); - expect(cell1).toHaveClass(`${cellClassname} my-cell-0`, { exact: true }); - expect(cell2).toHaveClass(`${cellClassname} my-cell-1`, { exact: true }); + const cell1 = getCell('0'); + const cell2 = getCell('1'); + await expect.element(cell1).toHaveClass(`${cellClassname} my-cell-0`, { exact: true }); + await expect.element(cell2).toHaveClass(`${cellClassname} my-cell-1`, { exact: true }); }); -test('cellClass returns undefined', () => { - const columns: readonly Column[] = [ - { - key: 'id', - name: 'ID', - cellClass: () => undefined - } - ]; +test('cellClass returns undefined', async () => { + const columns = getColumns(() => undefined); setup({ columns, rows }); const [cell1, cell2] = getCells(); - expect(cell1).toHaveClass(cellClassname, { exact: true }); - expect(cell2).toHaveClass(cellClassname, { exact: true }); + await expect.element(cell1).toHaveClass(cellClassname, { exact: true }); + await expect.element(cell2).toHaveClass(cellClassname, { exact: true }); }); diff --git a/test/browser/headerRowClass.test.ts b/test/browser/headerRowClass.test.ts index 0b16cbc3a4..4927257bf4 100644 --- a/test/browser/headerRowClass.test.ts +++ b/test/browser/headerRowClass.test.ts @@ -1,8 +1,6 @@ -import { page } from '@vitest/browser/context'; - import type { Column } from '../../src'; import { headerRowClassname } from '../../src/HeaderRow'; -import { setup } from './utils'; +import { getRow, setup } from './utils'; interface Row { id: number; @@ -11,22 +9,22 @@ interface Row { const columns: readonly Column[] = [{ key: 'id', name: 'ID' }]; const rows: readonly Row[] = [{ id: 0 }]; -test('headerRowClass is undefined', () => { +test('headerRowClass is undefined', async () => { setup({ columns, rows, rowClass: undefined }); - const header = page.getByRole('row').first(); - expect(header).toHaveClass(headerRowClassname, { exact: true }); + const header = getRow('ID'); + await expect.element(header).toHaveClass(headerRowClassname, { exact: true }); }); -test('headerRowClass is a string', () => { +test('headerRowClass is a string', async () => { setup({ columns, rows, headerRowClass: 'my-header-row' }); - const header = page.getByRole('row').first(); - expect(header).toHaveClass(`${headerRowClassname} my-header-row`, { exact: true }); + const header = getRow('ID'); + await expect.element(header).toHaveClass(`${headerRowClassname} my-header-row`, { exact: true }); }); diff --git a/test/browser/label.test.ts b/test/browser/label.test.ts index 7a10790111..aa077c0715 100644 --- a/test/browser/label.test.ts +++ b/test/browser/label.test.ts @@ -1,6 +1,6 @@ import { getGrid, setup } from './utils'; -test('should set label and description', () => { +test('should set label and description', async () => { setup({ rows: [], columns: [], @@ -12,11 +12,11 @@ test('should set label and description', () => { 'data-cy': 'cy' }); - const grid = getGrid().element(); - expect(grid).toHaveAttribute('aria-label', 'label'); - expect(grid).toHaveAttribute('aria-labelledby', 'labelledby'); - expect(grid).toHaveAttribute('aria-description', 'description'); - expect(grid).toHaveAttribute('aria-describedby', 'describedby'); - expect(grid).toHaveAttribute('data-testid', 'testid'); - expect(grid).toHaveAttribute('data-cy', 'cy'); + const grid = getGrid(); + await expect.element(grid).toHaveAttribute('aria-label', 'label'); + await expect.element(grid).toHaveAttribute('aria-labelledby', 'labelledby'); + await expect.element(grid).toHaveAttribute('aria-description', 'description'); + await expect.element(grid).toHaveAttribute('aria-describedby', 'describedby'); + await expect.element(grid).toHaveAttribute('data-testid', 'testid'); + await expect.element(grid).toHaveAttribute('data-cy', 'cy'); }); From 11e7fc65d786b987e36abfb092e8a0bfdbe3202c Mon Sep 17 00:00:00 2001 From: amahajan Date: Tue, 23 Sep 2025 11:35:54 -0500 Subject: [PATCH 08/20] -2 --- test/browser/column/draggable.test.ts | 17 ++++++++++------- test/browser/column/frozen.test.ts | 26 ++++++++++++++------------ test/browser/utils.tsx | 2 +- 3 files changed, 25 insertions(+), 20 deletions(-) diff --git a/test/browser/column/draggable.test.ts b/test/browser/column/draggable.test.ts index 14efe0bf41..e5669e2dd5 100644 --- a/test/browser/column/draggable.test.ts +++ b/test/browser/column/draggable.test.ts @@ -1,7 +1,7 @@ import { userEvent } from '@vitest/browser/context'; import type { Column } from '../../../src'; -import { getHeaderCells, setup } from '../utils'; +import { getHeaderCell, setup } from '../utils'; const columns: readonly Column[] = [ { @@ -28,12 +28,15 @@ const columns: readonly Column[] = [ test('draggable columns', async () => { const onColumnsReorder = vi.fn(); setup({ columns, rows: [], onColumnsReorder }); - const [cell1, cell2, cell3, cell4] = getHeaderCells(); - - expect(cell1).not.toHaveAttribute('draggable'); - expect(cell2).toHaveAttribute('draggable'); - expect(cell3).toHaveAttribute('draggable'); - expect(cell4).toHaveAttribute('draggable'); + const cell1 = getHeaderCell('col1'); + const cell2 = getHeaderCell('col2'); + const cell3 = getHeaderCell('col3'); + const cell4 = getHeaderCell('col4'); + + await expect.element(cell1).not.toHaveAttribute('draggable'); + await expect.element(cell2).toHaveAttribute('draggable'); + await expect.element(cell3).toHaveAttribute('draggable'); + await expect.element(cell4).toHaveAttribute('draggable'); expect(onColumnsReorder).not.toHaveBeenCalled(); diff --git a/test/browser/column/frozen.test.ts b/test/browser/column/frozen.test.ts index 9075f446ce..86f87bd0f8 100644 --- a/test/browser/column/frozen.test.ts +++ b/test/browser/column/frozen.test.ts @@ -1,8 +1,8 @@ import type { Column } from '../../../src'; import { cellClassname, cellFrozenClassname } from '../../../src/style/cell'; -import { getHeaderCells, setup } from '../utils'; +import { getHeaderCell, setup } from '../utils'; -test('frozen column have a specific class, and are stable-sorted before non-frozen columns', () => { +test('frozen column have a specific class, and are stable-sorted before non-frozen columns', async () => { const columns: readonly Column[] = [ { key: 'col1', @@ -26,15 +26,17 @@ test('frozen column have a specific class, and are stable-sorted before non-froz ]; setup({ columns, rows: [] }); - const [cell1, cell2, cell3, cell4] = getHeaderCells(); + const cell1 = getHeaderCell('col1'); + const cell2 = getHeaderCell('col2'); + const cell3 = getHeaderCell('col3'); + const cell4 = getHeaderCell('col4'); - expect(cell1).toHaveClass(`${cellClassname} ${cellFrozenClassname}`, { exact: true }); - expect(cell2).toHaveClass(`${cellClassname} ${cellFrozenClassname}`, { exact: true }); - expect(cell3).toHaveClass(cellClassname, { exact: true }); - expect(cell4).toHaveClass(cellClassname, { exact: true }); - - expect(cell1).toHaveTextContent('col1'); - expect(cell2).toHaveTextContent('col3'); - expect(cell3).toHaveTextContent('col2'); - expect(cell4).toHaveTextContent('col4'); + await expect + .element(cell1) + .toHaveClass(`${cellClassname} ${cellFrozenClassname}`, { exact: true }); + await expect.element(cell2).toHaveClass(cellClassname, { exact: true }); + await expect + .element(cell3) + .toHaveClass(`${cellClassname} ${cellFrozenClassname}`, { exact: true }); + await expect.element(cell4).toHaveClass(cellClassname, { exact: true }); }); diff --git a/test/browser/utils.tsx b/test/browser/utils.tsx index 38c3c2e0dd..2ca66c39ba 100644 --- a/test/browser/utils.tsx +++ b/test/browser/utils.tsx @@ -40,7 +40,7 @@ export function getTreeGrid() { return page.getByRole('treegrid'); } -export function getColumnHeader(name: string) { +export function getHeaderCell(name: string) { return page.getByRole('columnheader', { name, exact: true }); } From b1e130ef033471dcf17a47bc4ae4822fd352b302 Mon Sep 17 00:00:00 2001 From: amahajan Date: Tue, 23 Sep 2025 12:07:09 -0500 Subject: [PATCH 09/20] Revert a few changes --- test/browser/column/cellClass.test.ts | 36 ++++++++++++++++++--------- test/browser/rowClass.test.ts | 2 +- 2 files changed, 25 insertions(+), 13 deletions(-) diff --git a/test/browser/column/cellClass.test.ts b/test/browser/column/cellClass.test.ts index 0e8f6571a4..2c1b28c86e 100644 --- a/test/browser/column/cellClass.test.ts +++ b/test/browser/column/cellClass.test.ts @@ -8,19 +8,13 @@ interface Row { const rows: readonly Row[] = [{ id: 0 }, { id: 1 }]; -function getColumns(cellClass?: Column['cellClass']): readonly Column[] { - return [ +test('cellClass is undefined', async () => { + const columns: readonly Column[] = [ { key: 'id', - name: 'ID', - renderCell: (p) => p.row.id, - cellClass + name: 'ID' } ]; -} - -test('cellClass is undefined', async () => { - const columns = getColumns(); setup({ columns, rows }); const cell1 = getCell('0'); const cell2 = getCell('1'); @@ -29,7 +23,13 @@ test('cellClass is undefined', async () => { }); test('cellClass is a string', async () => { - const columns = getColumns('my-cell'); + const columns: readonly Column[] = [ + { + key: 'id', + name: 'ID', + cellClass: 'my-cell' + } + ]; setup({ columns, rows }); const cell1 = getCell('0'); const cell2 = getCell('1'); @@ -38,7 +38,13 @@ test('cellClass is a string', async () => { }); test('cellClass returns a string', async () => { - const columns = getColumns((row) => `my-cell-${row.id}`); + const columns: readonly Column[] = [ + { + key: 'id', + name: 'ID', + cellClass: (row) => `my-cell-${row.id}` + } + ]; setup({ columns, rows }); const cell1 = getCell('0'); const cell2 = getCell('1'); @@ -47,7 +53,13 @@ test('cellClass returns a string', async () => { }); test('cellClass returns undefined', async () => { - const columns = getColumns(() => undefined); + const columns: readonly Column[] = [ + { + key: 'id', + name: 'ID', + cellClass: () => undefined + } + ]; setup({ columns, rows }); const [cell1, cell2] = getCells(); await expect.element(cell1).toHaveClass(cellClassname, { exact: true }); diff --git a/test/browser/rowClass.test.ts b/test/browser/rowClass.test.ts index 3f692653d1..222edfbf40 100644 --- a/test/browser/rowClass.test.ts +++ b/test/browser/rowClass.test.ts @@ -6,7 +6,7 @@ interface Row { id: number; } -const columns: readonly Column[] = [{ key: 'id', name: 'ID', renderCell: (p) => p.row.id }]; +const columns: readonly Column[] = [{ key: 'id', name: 'ID' }]; const rows: readonly Row[] = [{ id: 0 }, { id: 1 }, { id: 2 }]; test('rowClass is undefined', async () => { From aac213dcfce4bb2ac10c536aff58968ed85fb75c Mon Sep 17 00:00:00 2001 From: amahajan Date: Tue, 23 Sep 2025 13:24:45 -0500 Subject: [PATCH 10/20] Fix flaky test --- test/browser/column/renderEditCell.test.tsx | 19 +++++++++---------- test/globals.d.ts | 3 ++- tsconfig.vite.json | 1 + vite.config.ts | 20 +++++++++++++++++++- 4 files changed, 31 insertions(+), 12 deletions(-) diff --git a/test/browser/column/renderEditCell.test.tsx b/test/browser/column/renderEditCell.test.tsx index deaab7b840..33da4c45ee 100644 --- a/test/browser/column/renderEditCell.test.tsx +++ b/test/browser/column/renderEditCell.test.tsx @@ -1,10 +1,10 @@ import { useMemo, useState } from 'react'; import { createPortal } from 'react-dom'; -import { page, userEvent } from '@vitest/browser/context'; +import { commands, page, userEvent } from '@vitest/browser/context'; import { DataGrid } from '../../../src'; import type { Column, DataGridProps } from '../../../src'; -import { getCellsAtRowIndex, getGrid, getSelectedCell, scrollGrid } from '../utils'; +import { getCell, getCellsAtRowIndex, getGrid, scrollGrid } from '../utils'; interface Row { col1: number; @@ -258,20 +258,19 @@ describe('Editor', () => { it('should not steal focus back to the cell if the editor is not in the viewport and another cell is clicked', async () => { const rows: Row[] = []; for (let i = 0; i < 99; i++) { - rows.push({ col1: i, col2: `${i}` }); + rows.push({ col1: i, col2: `name${i}` }); } page.render(); - await userEvent.dblClick(getCellsAtRowIndex(0)[1]); + await userEvent.dblClick(getCell('name0')); await userEvent.keyboard('abc'); - await scrollGrid({ scrollTop: 1500 }); - expect(getCellsAtRowIndex(40)[1]).toHaveTextContent(/^40$/); - await userEvent.click(getCellsAtRowIndex(40)[1]); - await expect.element(getSelectedCell()).toHaveTextContent(/^40$/); - await scrollGrid({ scrollTop: 0 }); - expect(getCellsAtRowIndex(0)[1]).toHaveTextContent(/^0abc$/); + await commands.scrollGrid({ scrollTop: 1500 }); + await expect.element(getCell('name43')).toBeVisible(); + await userEvent.click(getCell('name43')); + await commands.scrollGrid({ scrollTop: 0 }); + await expect.element(getCell('name43')).toBeVisible(); }); it('should not steal focus back to the cell after being closed by clicking outside the grid', async () => { diff --git a/test/globals.d.ts b/test/globals.d.ts index fad484533a..6ccaf2509e 100644 --- a/test/globals.d.ts +++ b/test/globals.d.ts @@ -1,7 +1,8 @@ declare module '@vitest/browser/context' { interface BrowserCommands { - resizeColumn: (resizeBy: number | readonly number[]) => Promise; dragFill: (from: string, to: string) => Promise; + resizeColumn: (resizeBy: number | readonly number[]) => Promise; + scrollGrid: (position: { scrollLeft?: number; scrollTop?: number }) => Promise; } } diff --git a/tsconfig.vite.json b/tsconfig.vite.json index 55ce01b576..755e892d75 100644 --- a/tsconfig.vite.json +++ b/tsconfig.vite.json @@ -2,6 +2,7 @@ "extends": "./tsconfig.base.json", "compilerOptions": { "skipLibCheck": true, + "lib": ["DOM"], "types": ["@vitest/browser/providers/playwright"] }, "include": ["package.json", "rolldown.config.ts", "vite.config.ts"] diff --git a/vite.config.ts b/vite.config.ts index c2aefbaec4..dabe74be47 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -41,6 +41,24 @@ const dragFill: BrowserCommand<[from: string, to: string]> = async (context, fro await page.mouse.up(); }; +const scrollGrid: BrowserCommand<[{ scrollLeft?: number; scrollTop?: number }]> = async ( + context, + { scrollLeft, scrollTop } +) => { + const frame = await context.frame(); + await frame.getByRole('grid').evaluate( + (grid: HTMLDivElement, { scrollLeft, scrollTop }) => { + if (scrollLeft !== undefined) { + grid.scrollLeft = scrollLeft; + } + if (scrollTop !== undefined) { + grid.scrollTop = scrollTop; + } + }, + { scrollLeft, scrollTop } + ); +}; + const viewport = { width: 1920, height: 1080 } as const; export default defineConfig(({ command, isPreview }) => ({ @@ -108,7 +126,7 @@ export default defineConfig(({ command, isPreview }) => ({ context: { viewport } } ], - commands: { resizeColumn, dragFill }, + commands: { resizeColumn, dragFill, scrollGrid }, viewport, headless: true, screenshotFailures: !isCI From be92fc370181f0540d41d0f83e3a56254b7ad705 Mon Sep 17 00:00:00 2001 From: amahajan Date: Tue, 23 Sep 2025 13:30:33 -0500 Subject: [PATCH 11/20] Fix types --- test/browser/column/cellClass.test.ts | 5 +++-- tsconfig.vite.json | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/test/browser/column/cellClass.test.ts b/test/browser/column/cellClass.test.ts index 2c1b28c86e..dff5baeedc 100644 --- a/test/browser/column/cellClass.test.ts +++ b/test/browser/column/cellClass.test.ts @@ -1,6 +1,6 @@ import type { Column } from '../../../src'; import { cellClassname } from '../../../src/style/cell'; -import { getCell, getCells, setup } from '../utils'; +import { getCell, setup } from '../utils'; interface Row { id: number; @@ -61,7 +61,8 @@ test('cellClass returns undefined', async () => { } ]; setup({ columns, rows }); - const [cell1, cell2] = getCells(); + const cell1 = getCell('0'); + const cell2 = getCell('1'); await expect.element(cell1).toHaveClass(cellClassname, { exact: true }); await expect.element(cell2).toHaveClass(cellClassname, { exact: true }); }); diff --git a/tsconfig.vite.json b/tsconfig.vite.json index 755e892d75..57d8db9414 100644 --- a/tsconfig.vite.json +++ b/tsconfig.vite.json @@ -2,7 +2,7 @@ "extends": "./tsconfig.base.json", "compilerOptions": { "skipLibCheck": true, - "lib": ["DOM"], + "lib": ["ESNext", "DOM"], "types": ["@vitest/browser/providers/playwright"] }, "include": ["package.json", "rolldown.config.ts", "vite.config.ts"] From a8bc9270f7f7aba5611223ea2e84efd865aa7009 Mon Sep 17 00:00:00 2001 From: amahajan Date: Tue, 23 Sep 2025 13:52:00 -0500 Subject: [PATCH 12/20] Add new utils --- test/browser/column/cellClass.test.ts | 14 +++++--------- test/browser/column/draggable.test.ts | 7 ++----- test/browser/column/frozen.test.ts | 7 ++----- test/browser/rowClass.test.ts | 14 ++++---------- test/browser/sorting.test.tsx | 14 ++++++-------- test/browser/utils.tsx | 14 +++++++++++++- 6 files changed, 32 insertions(+), 38 deletions(-) diff --git a/test/browser/column/cellClass.test.ts b/test/browser/column/cellClass.test.ts index dff5baeedc..d86d3c98ca 100644 --- a/test/browser/column/cellClass.test.ts +++ b/test/browser/column/cellClass.test.ts @@ -1,6 +1,6 @@ import type { Column } from '../../../src'; import { cellClassname } from '../../../src/style/cell'; -import { getCell, setup } from '../utils'; +import { getCellsNew, setup } from '../utils'; interface Row { id: number; @@ -16,8 +16,7 @@ test('cellClass is undefined', async () => { } ]; setup({ columns, rows }); - const cell1 = getCell('0'); - const cell2 = getCell('1'); + const [cell1, cell2] = getCellsNew('0', '1'); await expect.element(cell1).toHaveClass(cellClassname, { exact: true }); await expect.element(cell2).toHaveClass(cellClassname, { exact: true }); }); @@ -31,8 +30,7 @@ test('cellClass is a string', async () => { } ]; setup({ columns, rows }); - const cell1 = getCell('0'); - const cell2 = getCell('1'); + const [cell1, cell2] = getCellsNew('0', '1'); await expect.element(cell1).toHaveClass(`${cellClassname} my-cell`, { exact: true }); await expect.element(cell2).toHaveClass(`${cellClassname} my-cell`, { exact: true }); }); @@ -46,8 +44,7 @@ test('cellClass returns a string', async () => { } ]; setup({ columns, rows }); - const cell1 = getCell('0'); - const cell2 = getCell('1'); + const [cell1, cell2] = getCellsNew('0', '1'); await expect.element(cell1).toHaveClass(`${cellClassname} my-cell-0`, { exact: true }); await expect.element(cell2).toHaveClass(`${cellClassname} my-cell-1`, { exact: true }); }); @@ -61,8 +58,7 @@ test('cellClass returns undefined', async () => { } ]; setup({ columns, rows }); - const cell1 = getCell('0'); - const cell2 = getCell('1'); + const [cell1, cell2] = getCellsNew('0', '1'); await expect.element(cell1).toHaveClass(cellClassname, { exact: true }); await expect.element(cell2).toHaveClass(cellClassname, { exact: true }); }); diff --git a/test/browser/column/draggable.test.ts b/test/browser/column/draggable.test.ts index e5669e2dd5..e32e0a9e23 100644 --- a/test/browser/column/draggable.test.ts +++ b/test/browser/column/draggable.test.ts @@ -1,7 +1,7 @@ import { userEvent } from '@vitest/browser/context'; import type { Column } from '../../../src'; -import { getHeaderCell, setup } from '../utils'; +import { getHeaderCellsNew, setup } from '../utils'; const columns: readonly Column[] = [ { @@ -28,10 +28,7 @@ const columns: readonly Column[] = [ test('draggable columns', async () => { const onColumnsReorder = vi.fn(); setup({ columns, rows: [], onColumnsReorder }); - const cell1 = getHeaderCell('col1'); - const cell2 = getHeaderCell('col2'); - const cell3 = getHeaderCell('col3'); - const cell4 = getHeaderCell('col4'); + const [cell1, cell2, cell3, cell4] = getHeaderCellsNew('col1', 'col2', 'col3', 'col4'); await expect.element(cell1).not.toHaveAttribute('draggable'); await expect.element(cell2).toHaveAttribute('draggable'); diff --git a/test/browser/column/frozen.test.ts b/test/browser/column/frozen.test.ts index 86f87bd0f8..997eb30d05 100644 --- a/test/browser/column/frozen.test.ts +++ b/test/browser/column/frozen.test.ts @@ -1,6 +1,6 @@ import type { Column } from '../../../src'; import { cellClassname, cellFrozenClassname } from '../../../src/style/cell'; -import { getHeaderCell, setup } from '../utils'; +import { getHeaderCellsNew, setup } from '../utils'; test('frozen column have a specific class, and are stable-sorted before non-frozen columns', async () => { const columns: readonly Column[] = [ @@ -26,10 +26,7 @@ test('frozen column have a specific class, and are stable-sorted before non-froz ]; setup({ columns, rows: [] }); - const cell1 = getHeaderCell('col1'); - const cell2 = getHeaderCell('col2'); - const cell3 = getHeaderCell('col3'); - const cell4 = getHeaderCell('col4'); + const [cell1, cell2, cell3, cell4] = getHeaderCellsNew('col1', 'col2', 'col3', 'col4'); await expect .element(cell1) diff --git a/test/browser/rowClass.test.ts b/test/browser/rowClass.test.ts index 222edfbf40..ad4a41d704 100644 --- a/test/browser/rowClass.test.ts +++ b/test/browser/rowClass.test.ts @@ -1,6 +1,6 @@ import type { Column } from '../../src'; import { rowClassname } from '../../src/style/row'; -import { getRow, setup } from './utils'; +import { getRowsNew, setup } from './utils'; interface Row { id: number; @@ -15,9 +15,7 @@ test('rowClass is undefined', async () => { rows, rowClass: undefined }); - const row1 = getRow('0'); - const row2 = getRow('1'); - const row3 = getRow('2'); + const [row1, row2, row3] = getRowsNew('0', '1', '2'); await expect.element(row1).toHaveClass(`${rowClassname} rdg-row-even`, { exact: true }); await expect.element(row2).toHaveClass(`${rowClassname} rdg-row-odd`, { exact: true }); await expect.element(row3).toHaveClass(`${rowClassname} rdg-row-even`, { exact: true }); @@ -29,9 +27,7 @@ test('rowClass returns a string', async () => { rows, rowClass: (row) => `my-row-${row.id}` }); - const row1 = getRow('0'); - const row2 = getRow('1'); - const row3 = getRow('2'); + const [row1, row2, row3] = getRowsNew('0', '1', '2'); await expect.element(row1).toHaveClass(`${rowClassname} rdg-row-even my-row-0`, { exact: true }); await expect.element(row2).toHaveClass(`${rowClassname} rdg-row-odd my-row-1`, { exact: true }); await expect.element(row3).toHaveClass(`${rowClassname} rdg-row-even my-row-2`, { exact: true }); @@ -43,9 +39,7 @@ test('rowClass returns undefined', async () => { rows, rowClass: () => undefined }); - const row1 = getRow('0'); - const row2 = getRow('1'); - const row3 = getRow('2'); + const [row1, row2, row3] = getRowsNew('0', '1', '2'); await expect.element(row1).toHaveClass(`${rowClassname} rdg-row-even`, { exact: true }); await expect.element(row2).toHaveClass(`${rowClassname} rdg-row-odd`, { exact: true }); await expect.element(row3).toHaveClass(`${rowClassname} rdg-row-even`, { exact: true }); diff --git a/test/browser/sorting.test.tsx b/test/browser/sorting.test.tsx index c39df38f02..739811099d 100644 --- a/test/browser/sorting.test.tsx +++ b/test/browser/sorting.test.tsx @@ -3,6 +3,7 @@ import { page, userEvent } from '@vitest/browser/context'; import { DataGrid } from '../../src'; import type { Column, SortColumn } from '../../src/types'; +import { getHeaderCell, getHeaderCellsNew } from './utils'; const columns: readonly Column[] = [ { key: 'colA', name: 'colA' }, @@ -40,7 +41,7 @@ function testSortColumns(expectedValue: readonly SortColumn[]) { test('should not sort if sortable is false', async () => { setup(); - const headerCell = page.getByRole('columnheader', { name: 'colD' }); + const headerCell = getHeaderCell('colD'); await userEvent.click(headerCell); await expect.element(headerCell).not.toHaveAttribute('aria-sort'); await testSortColumns([]); @@ -48,7 +49,7 @@ test('should not sort if sortable is false', async () => { test('single column sort', async () => { setup(); - const headerCell = page.getByRole('columnheader', { name: 'colA' }); + const headerCell = getHeaderCell('colA'); await userEvent.click(headerCell); await expect.element(headerCell).toHaveAttribute('aria-sort', 'ascending'); // priority is not shown for single sort @@ -64,9 +65,7 @@ test('single column sort', async () => { test('multi column sort', async () => { setup(); - const headerCell1 = page.getByRole('columnheader', { name: 'colA' }); - const headerCell2 = page.getByRole('columnheader', { name: 'colB' }); - const headerCell3 = page.getByRole('columnheader', { name: 'colC' }); + const [headerCell1, headerCell2, headerCell3] = getHeaderCellsNew('colA', 'colB', 'colC'); await userEvent.click(headerCell1); await userEvent.keyboard('{Control>}'); await userEvent.click(headerCell2); @@ -108,8 +107,7 @@ test('multi column sort', async () => { test('multi column sort with metakey', async () => { setup(); - const headerCell1 = page.getByRole('columnheader', { name: 'colA' }); - const headerCell2 = page.getByRole('columnheader', { name: 'colB' }); + const [headerCell1, headerCell2] = getHeaderCellsNew('colA', 'colB'); await userEvent.click(headerCell1); await userEvent.keyboard('{Meta>}'); await userEvent.click(headerCell2); @@ -121,7 +119,7 @@ test('multi column sort with metakey', async () => { test('multi column sort with keyboard', async () => { setup(); - const headerCell1 = page.getByRole('columnheader', { name: 'colA' }); + const headerCell1 = getHeaderCell('colA'); await userEvent.click(headerCell1); await userEvent.keyboard(' {arrowright}{Control>}{enter}'); await testSortColumns([ diff --git a/test/browser/utils.tsx b/test/browser/utils.tsx index 2ca66c39ba..55eaee5155 100644 --- a/test/browser/utils.tsx +++ b/test/browser/utils.tsx @@ -41,17 +41,29 @@ export function getTreeGrid() { } export function getHeaderCell(name: string) { - return page.getByRole('columnheader', { name, exact: true }); + return page.getByRole('columnheader', { name }); +} + +export function getHeaderCellsNew(...names: readonly string[]) { + return names.map(getHeaderCell); } export function getRow(name: string) { return page.getByRole('row', { name }); } +export function getRowsNew(...names: readonly string[]) { + return names.map(getRow); +} + export function getCell(name: string) { return page.getByRole('gridcell', { name, exact: true }); } +export function getCellsNew(...names: readonly string[]) { + return names.map(getCell); +} + export function getSelectAllCheckbox() { return page.getByRole('checkbox', { name: 'Select All' }); } From 7157e11899ca2d1a474ec8bdddb967bf97e13c8d Mon Sep 17 00:00:00 2001 From: amahajan Date: Tue, 23 Sep 2025 13:59:25 -0500 Subject: [PATCH 13/20] Fix one test --- test/browser/column/renderEditCell.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/browser/column/renderEditCell.test.tsx b/test/browser/column/renderEditCell.test.tsx index 33da4c45ee..8ef11001f6 100644 --- a/test/browser/column/renderEditCell.test.tsx +++ b/test/browser/column/renderEditCell.test.tsx @@ -270,7 +270,7 @@ describe('Editor', () => { await expect.element(getCell('name43')).toBeVisible(); await userEvent.click(getCell('name43')); await commands.scrollGrid({ scrollTop: 0 }); - await expect.element(getCell('name43')).toBeVisible(); + await expect.element(getCell('name0abc')).toBeVisible(); }); it('should not steal focus back to the cell after being closed by clicking outside the grid', async () => { From 5bb3da2beddd400623a011b7f636896bae21ae09 Mon Sep 17 00:00:00 2001 From: amahajan Date: Tue, 23 Sep 2025 14:03:28 -0500 Subject: [PATCH 14/20] check focus and selection --- test/browser/column/renderEditCell.test.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/test/browser/column/renderEditCell.test.tsx b/test/browser/column/renderEditCell.test.tsx index 8ef11001f6..b67bc19a71 100644 --- a/test/browser/column/renderEditCell.test.tsx +++ b/test/browser/column/renderEditCell.test.tsx @@ -267,8 +267,11 @@ describe('Editor', () => { await userEvent.keyboard('abc'); await commands.scrollGrid({ scrollTop: 1500 }); - await expect.element(getCell('name43')).toBeVisible(); - await userEvent.click(getCell('name43')); + const cell43 = getCell('name43'); + await userEvent.click(cell43); + await expect.element(cell43).toBeVisible(); + await expect.element(cell43).toHaveFocus(); + await expect.element(cell43).toHaveAttribute('aria-selected', 'true'); await commands.scrollGrid({ scrollTop: 0 }); await expect.element(getCell('name0abc')).toBeVisible(); }); From d601c43587ac2faba7bcb27c7cbaf033e6a3215a Mon Sep 17 00:00:00 2001 From: amahajan Date: Tue, 23 Sep 2025 19:24:51 -0500 Subject: [PATCH 15/20] Revert 1 change --- test/browser/column/renderEditCell.test.tsx | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/test/browser/column/renderEditCell.test.tsx b/test/browser/column/renderEditCell.test.tsx index b67bc19a71..c4b9a77d7b 100644 --- a/test/browser/column/renderEditCell.test.tsx +++ b/test/browser/column/renderEditCell.test.tsx @@ -4,7 +4,7 @@ import { commands, page, userEvent } from '@vitest/browser/context'; import { DataGrid } from '../../../src'; import type { Column, DataGridProps } from '../../../src'; -import { getCell, getCellsAtRowIndex, getGrid, scrollGrid } from '../utils'; +import { getCell, getCellsAtRowIndex, getGrid, getSelectedCell, scrollGrid } from '../utils'; interface Row { col1: number; @@ -267,11 +267,8 @@ describe('Editor', () => { await userEvent.keyboard('abc'); await commands.scrollGrid({ scrollTop: 1500 }); - const cell43 = getCell('name43'); - await userEvent.click(cell43); - await expect.element(cell43).toBeVisible(); - await expect.element(cell43).toHaveFocus(); - await expect.element(cell43).toHaveAttribute('aria-selected', 'true'); + await userEvent.click(getCell('name43')); + await expect.element(getSelectedCell()).toHaveTextContent(/^name43$/); await commands.scrollGrid({ scrollTop: 0 }); await expect.element(getCell('name0abc')).toBeVisible(); }); From 16077fec18e58c35465e0f13d534f99a345582f9 Mon Sep 17 00:00:00 2001 From: amahajan Date: Tue, 23 Sep 2025 19:28:44 -0500 Subject: [PATCH 16/20] tweak --- test/browser/rowSelection.test.tsx | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/test/browser/rowSelection.test.tsx b/test/browser/rowSelection.test.tsx index 4705dae64f..d0f6be5684 100644 --- a/test/browser/rowSelection.test.tsx +++ b/test/browser/rowSelection.test.tsx @@ -12,11 +12,8 @@ interface Row { const columns: readonly Column[] = [ SelectColumn, { - key: 'name', - name: 'Name', - renderCell(props) { - return props.row.id; - } + key: 'id', + name: 'ID' } ]; From 2719e2b5f8fb594cb7e986545f02098474627ab9 Mon Sep 17 00:00:00 2001 From: amahajan Date: Wed, 24 Sep 2025 09:38:13 -0500 Subject: [PATCH 17/20] Check column order --- test/browser/column/frozen.test.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/browser/column/frozen.test.ts b/test/browser/column/frozen.test.ts index 997eb30d05..4223a06a4e 100644 --- a/test/browser/column/frozen.test.ts +++ b/test/browser/column/frozen.test.ts @@ -1,3 +1,5 @@ +import { page } from '@vitest/browser/context'; + import type { Column } from '../../../src'; import { cellClassname, cellFrozenClassname } from '../../../src/style/cell'; import { getHeaderCellsNew, setup } from '../utils'; @@ -26,14 +28,15 @@ test('frozen column have a specific class, and are stable-sorted before non-froz ]; setup({ columns, rows: [] }); + await expect.element(page.getByRole('row')).toHaveTextContent('col1col3col2col4'); const [cell1, cell2, cell3, cell4] = getHeaderCellsNew('col1', 'col2', 'col3', 'col4'); await expect .element(cell1) .toHaveClass(`${cellClassname} ${cellFrozenClassname}`, { exact: true }); - await expect.element(cell2).toHaveClass(cellClassname, { exact: true }); await expect .element(cell3) .toHaveClass(`${cellClassname} ${cellFrozenClassname}`, { exact: true }); + await expect.element(cell2).toHaveClass(cellClassname, { exact: true }); await expect.element(cell4).toHaveClass(cellClassname, { exact: true }); }); From 01df2aa6d8c630fa1e75061a8dc51003f6f0624c Mon Sep 17 00:00:00 2001 From: amahajan Date: Fri, 26 Sep 2025 13:10:25 -0500 Subject: [PATCH 18/20] Check exact text --- test/browser/sorting.test.tsx | 4 +++- test/browser/utils.tsx | 6 +++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/test/browser/sorting.test.tsx b/test/browser/sorting.test.tsx index 739811099d..5bb6ba1b8e 100644 --- a/test/browser/sorting.test.tsx +++ b/test/browser/sorting.test.tsx @@ -65,7 +65,9 @@ test('single column sort', async () => { test('multi column sort', async () => { setup(); - const [headerCell1, headerCell2, headerCell3] = getHeaderCellsNew('colA', 'colB', 'colC'); + const headerCell1 = getHeaderCell('colA', false); + const headerCell2 = getHeaderCell('colB', false); + const headerCell3 = getHeaderCell('colC', false); await userEvent.click(headerCell1); await userEvent.keyboard('{Control>}'); await userEvent.click(headerCell2); diff --git a/test/browser/utils.tsx b/test/browser/utils.tsx index 55eaee5155..455a4f6f1f 100644 --- a/test/browser/utils.tsx +++ b/test/browser/utils.tsx @@ -40,12 +40,12 @@ export function getTreeGrid() { return page.getByRole('treegrid'); } -export function getHeaderCell(name: string) { - return page.getByRole('columnheader', { name }); +export function getHeaderCell(name: string, exact = true) { + return page.getByRole('columnheader', { name, exact }); } export function getHeaderCellsNew(...names: readonly string[]) { - return names.map(getHeaderCell); + return names.map(name => getHeaderCell(name)); } export function getRow(name: string) { From 1fd158426eb44da7699ee4e3793e535845031e0b Mon Sep 17 00:00:00 2001 From: amahajan Date: Fri, 26 Sep 2025 13:15:39 -0500 Subject: [PATCH 19/20] format --- test/browser/utils.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/browser/utils.tsx b/test/browser/utils.tsx index 455a4f6f1f..00b0b589b7 100644 --- a/test/browser/utils.tsx +++ b/test/browser/utils.tsx @@ -45,7 +45,7 @@ export function getHeaderCell(name: string, exact = true) { } export function getHeaderCellsNew(...names: readonly string[]) { - return names.map(name => getHeaderCell(name)); + return names.map((name) => getHeaderCell(name)); } export function getRow(name: string) { From f90009c3809c9358ebcbf34192c026403f5b4570 Mon Sep 17 00:00:00 2001 From: amahajan Date: Fri, 26 Sep 2025 13:31:01 -0500 Subject: [PATCH 20/20] Tweak 1 test --- test/browser/headerRowClass.test.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/test/browser/headerRowClass.test.ts b/test/browser/headerRowClass.test.ts index 4927257bf4..10d1e816c9 100644 --- a/test/browser/headerRowClass.test.ts +++ b/test/browser/headerRowClass.test.ts @@ -1,21 +1,23 @@ +import { page } from '@vitest/browser/context'; + import type { Column } from '../../src'; import { headerRowClassname } from '../../src/HeaderRow'; -import { getRow, setup } from './utils'; +import { setup } from './utils'; interface Row { id: number; } const columns: readonly Column[] = [{ key: 'id', name: 'ID' }]; -const rows: readonly Row[] = [{ id: 0 }]; +const rows: readonly Row[] = []; test('headerRowClass is undefined', async () => { setup({ columns, rows, - rowClass: undefined + headerRowClass: undefined }); - const header = getRow('ID'); + const header = page.getByRole('row'); await expect.element(header).toHaveClass(headerRowClassname, { exact: true }); }); @@ -25,6 +27,6 @@ test('headerRowClass is a string', async () => { rows, headerRowClass: 'my-header-row' }); - const header = getRow('ID'); + const header = page.getByRole('row'); await expect.element(header).toHaveClass(`${headerRowClassname} my-header-row`, { exact: true }); });