From 173e7f7d4a17503fd2cfe7d127b9547df0b04833 Mon Sep 17 00:00:00 2001 From: Nicolas Stepien <567105+nstepien@users.noreply.github.com> Date: Wed, 18 Feb 2026 15:57:46 +0000 Subject: [PATCH] Add and migrate all tests to use new locators (#3954) * Add and migrate all tests to use new locators * remove commands.scrollGrid * tweak prefer-destructuring config * tweak commands to use `iframe` * tweak ScrollToCell, hopefully resolve test issues * should we add headerAndTopSummaryRowsCount? * is it a timeout issue? * simpler implementation? * revert actionTimeout * tweak platform * small nit * rename, this may be rows instead of cells --- eslint.config.js | 4 +- rolldown.config.ts | 2 +- src/DataGrid.tsx | 4 +- src/GroupedColumnHeaderRow.tsx | 6 +- src/ScrollToCell.tsx | 22 +- src/utils/selectedCellUtils.ts | 12 +- test/browser/TreeDataGrid.test.tsx | 172 ++++++++------- test/browser/column/cellClass.test.ts | 16 +- test/browser/column/colSpan.test.ts | 66 +++--- test/browser/column/draggable.test.ts | 8 +- test/browser/column/frozen.test.ts | 17 +- test/browser/column/grouping.test.ts | 67 +++--- test/browser/column/headerCellClass.test.ts | 20 +- test/browser/column/name.test.tsx | 11 +- test/browser/column/renderCell.test.tsx | 34 +-- test/browser/column/renderEditCell.test.tsx | 99 +++++---- test/browser/column/renderHeaderCell.test.tsx | 11 +- .../browser/column/renderSummaryCell.test.tsx | 25 ++- test/browser/column/resizable.test.tsx | 25 +-- test/browser/column/summaryCellClass.test.ts | 26 +-- test/browser/columnOrder.test.tsx | 7 +- test/browser/copyPaste.test.tsx | 14 +- test/browser/direction.test.ts | 25 ++- test/browser/dragFill.test.tsx | 48 ++--- test/browser/events.test.tsx | 25 ++- test/browser/headerRowClass.test.ts | 10 +- test/browser/keyboardNavigation.test.tsx | 42 ++-- test/browser/label.test.ts | 7 +- test/browser/renderTextEditor.test.tsx | 2 +- test/browser/renderers.test.tsx | 40 ++-- test/browser/rowClass.test.ts | 44 ++-- test/browser/rowHeight.test.ts | 25 +-- test/browser/rowSelection.test.tsx | 28 +-- test/browser/scrollToCell.test.tsx | 70 +++---- test/browser/sorting.test.tsx | 18 +- test/browser/utils.tsx | 99 ++------- test/browser/virtualization.test.ts | 197 +++++++++--------- test/globals.d.ts | 5 - test/setupBrowser.ts | 68 +++++- test/visual/basicGrid.test.tsx | 5 +- test/visual/treeGrid.test.tsx | 5 +- tsconfig.js.json | 6 +- tsconfig.test.json | 2 +- tsconfig.vite.json | 4 +- vite.config.ts | 38 +--- 45 files changed, 722 insertions(+), 759 deletions(-) diff --git a/eslint.config.js b/eslint.config.js index 6db23e03ff..eb89182be5 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -25,9 +25,11 @@ export default defineConfig([ plugins: { react, + // @ts-expect-error 'react-hooks': reactHooks, 'react-x': reactX, sonarjs, + // @ts-expect-error '@typescript-eslint': typescriptEslint }, @@ -762,7 +764,7 @@ export default defineConfig([ '@typescript-eslint/non-nullable-type-assertion-style': 1, '@typescript-eslint/parameter-properties': 1, '@typescript-eslint/prefer-as-const': 1, - '@typescript-eslint/prefer-destructuring': [1, { array: false }], + '@typescript-eslint/prefer-destructuring': [1, { array: false, object: true }], '@typescript-eslint/prefer-enum-initializers': 0, '@typescript-eslint/prefer-find': 1, '@typescript-eslint/prefer-for-of': 1, diff --git a/rolldown.config.ts b/rolldown.config.ts index d3080f556b..e5ea32445b 100644 --- a/rolldown.config.ts +++ b/rolldown.config.ts @@ -13,7 +13,7 @@ export default defineConfig({ sourcemap: true, cleanDir: true }, - platform: 'browser', + platform: 'neutral', external: (id) => !id.startsWith('.') && !isAbsolute(id), plugins: [ ecij({ diff --git a/src/DataGrid.tsx b/src/DataGrid.tsx index acc1e646f1..8c760ceb81 100644 --- a/src/DataGrid.tsx +++ b/src/DataGrid.tsx @@ -541,7 +541,9 @@ export function DataGrid(props: DataGridPr ? idx : undefined; const scrollToRowIdx = - rowIdx !== undefined && isRowIdxWithinViewportBounds(rowIdx) ? rowIdx : undefined; + rowIdx !== undefined && isRowIdxWithinViewportBounds(rowIdx) + ? rowIdx + headerAndTopSummaryRowsCount + : undefined; if (scrollToIdx !== undefined || scrollToRowIdx !== undefined) { setScrollToPosition({ idx: scrollToIdx, rowIdx: scrollToRowIdx }); diff --git a/src/GroupedColumnHeaderRow.tsx b/src/GroupedColumnHeaderRow.tsx index fc0ae064b5..358338d9c5 100644 --- a/src/GroupedColumnHeaderRow.tsx +++ b/src/GroupedColumnHeaderRow.tsx @@ -23,13 +23,13 @@ function GroupedColumnHeaderRow({ const renderedParents = new Set>(); for (const column of columns) { - let { parent } = column; + if (column.parent === undefined) continue; - if (parent === undefined) continue; + let { parent } = column; while (parent.level > level) { if (parent.parent === undefined) break; - parent = parent.parent; + ({ parent } = parent); } if (parent.level === level && !renderedParents.has(parent)) { diff --git a/src/ScrollToCell.tsx b/src/ScrollToCell.tsx index 86794f1662..8b3ebda3c4 100644 --- a/src/ScrollToCell.tsx +++ b/src/ScrollToCell.tsx @@ -19,35 +19,23 @@ export default function ScrollToCell({ const ref = useRef(null); useLayoutEffect(() => { + const grid = gridRef.current!; + const { scrollLeft, scrollTop } = grid; // scroll until the cell is completely visible // this is needed if the grid has auto-sized columns // setting the behavior to auto so it can be overridden scrollIntoView(ref.current, 'auto'); - }); - - useLayoutEffect(() => { - function removeScrollToCell() { + if (grid.scrollLeft === scrollLeft && grid.scrollTop === scrollTop) { setScrollToCellPosition(null); } - - const observer = new IntersectionObserver(removeScrollToCell, { - root: gridRef.current!, - threshold: 1.0 - }); - - observer.observe(ref.current!); - - return () => { - observer.disconnect(); - }; - }, [gridRef, setScrollToCellPosition]); + }); return (
); diff --git a/src/utils/selectedCellUtils.ts b/src/utils/selectedCellUtils.ts index 62e88cbeef..db13f05da6 100644 --- a/src/utils/selectedCellUtils.ts +++ b/src/utils/selectedCellUtils.ts @@ -146,19 +146,19 @@ export function getNextSelectedCellPosition({ if (moveNext) { // find the parent at the same row level const nextColumn = columns[nextIdx]; - let parent = nextColumn.parent; + let { parent } = nextColumn; while (parent !== undefined) { const parentRowIdx = getParentRowIdx(parent); if (nextRowIdx === parentRowIdx) { nextIdx = parent.idx + parent.colSpan; break; } - parent = parent.parent; + ({ parent } = parent); } } else if (moveUp) { // find the first reachable parent const nextColumn = columns[nextIdx]; - let parent = nextColumn.parent; + let { parent } = nextColumn; let found = false; while (parent !== undefined) { const parentRowIdx = getParentRowIdx(parent); @@ -168,7 +168,7 @@ export function getNextSelectedCellPosition({ found = true; break; } - parent = parent.parent; + ({ parent } = parent); } // keep the current position if there is no parent matching the new row position @@ -212,7 +212,7 @@ export function getNextSelectedCellPosition({ // This check is needed when navigating to a column // that does not have a parent matching the new rowIdx const nextColumn = columns[nextIdx]; - let parent = nextColumn.parent; + let { parent } = nextColumn; const nextParentRowIdx = nextRowIdx; nextRowIdx = mainHeaderRowIdx; while (parent !== undefined) { @@ -221,7 +221,7 @@ export function getNextSelectedCellPosition({ nextRowIdx = parentRowIdx; nextIdx = parent.idx; } - parent = parent.parent; + ({ parent } = parent); } } diff --git a/test/browser/TreeDataGrid.test.tsx b/test/browser/TreeDataGrid.test.tsx index 2afc7bbef6..575e6f1f47 100644 --- a/test/browser/TreeDataGrid.test.tsx +++ b/test/browser/TreeDataGrid.test.tsx @@ -5,18 +5,13 @@ import type { Column } from '../../src'; import { renderTextEditor, SelectColumn, TreeDataGrid } from '../../src'; import { focusSinkClassname } from '../../src/style/core'; import { rowSelected } from '../../src/style/row'; -import { - getCell, - getCellsAtRowIndex, - getRowByCell, - getRowByCellName, - getRows, - getSelectAllCheckbox, - getSelectedCell, - getTreeGrid, - testCount, - testRowCount -} from './utils'; +import { getCellsAtRowIndex, getRowWithCell, testCount, testRowCount } from './utils'; + +const treeGrid = page.getTreeGrid(); +const headerRow = treeGrid.getHeaderRow(); +const headerCells = treeGrid.getHeaderCell(); +const rows = treeGrid.getRow(); +const selectedCell = treeGrid.getSelectedCell(); const rowSelectedClassname = 'rdg-row-selected'; @@ -140,37 +135,38 @@ function setup(groupBy: string[], groupIdGetter?: (groupKey: string, parentId?: } async function testHeaderCellsContent(expected: readonly string[]) { - const headerCells = page.getByRole('columnheader'); await testCount(headerCells, expected.length); - const content = headerCells.elements().map((cell) => cell.textContent); - expect(content).toStrictEqual(expected); + + for (const [n, text] of expected.entries()) { + await expect.element(headerCells.nth(n)).toHaveTextContent(text); + } } test('should not group if groupBy is empty', async () => { await setup([]); - await expect.element(getTreeGrid()).toHaveAttribute('aria-rowcount', '7'); + await expect.element(treeGrid).toHaveAttribute('aria-rowcount', '7'); await testHeaderCellsContent(['', 'Sport', 'Country', 'Year', 'Id']); - await testRowCount(7); + await testRowCount(6); }); test('should not group if column does not exist', async () => { await setup(['abc']); - await expect.element(getTreeGrid()).toHaveAttribute('aria-rowcount', '7'); - await testRowCount(7); + await expect.element(treeGrid).toHaveAttribute('aria-rowcount', '7'); + await testRowCount(6); }); test('should group by single column', async () => { await setup(['country']); - await expect.element(getTreeGrid()).toHaveAttribute('aria-rowcount', '9'); + await expect.element(treeGrid).toHaveAttribute('aria-rowcount', '9'); await testHeaderCellsContent(['', 'Country', 'Sport', 'Year', 'Id']); - await testRowCount(5); + await testRowCount(4); }); test('should group by multiple columns', async () => { await setup(['country', 'year']); - await expect.element(getTreeGrid()).toHaveAttribute('aria-rowcount', '13'); + await expect.element(treeGrid).toHaveAttribute('aria-rowcount', '13'); await testHeaderCellsContent(['', 'Country', 'Year', 'Sport', 'Id']); - await testRowCount(5); + await testRowCount(4); }); test('should use groupIdGetter when provided', async () => { @@ -179,69 +175,70 @@ test('should use groupIdGetter when provided', async () => { ); await setup(['country', 'year'], groupIdGetter); expect(groupIdGetter).toHaveBeenCalled(); - await expect.element(getTreeGrid()).toHaveAttribute('aria-rowcount', '13'); + await expect.element(treeGrid).toHaveAttribute('aria-rowcount', '13'); await testHeaderCellsContent(['', 'Country', 'Year', 'Sport', 'Id']); - await testRowCount(5); + await testRowCount(4); groupIdGetter.mockClear(); - await userEvent.click(getCell('USA')); - await testRowCount(7); + await userEvent.click(page.getCell({ name: 'USA' })); + await testRowCount(6); expect(groupIdGetter).toHaveBeenCalled(); - await userEvent.click(getCell('Canada')); + await userEvent.click(page.getCell({ name: 'Canada' })); + await testRowCount(8); + await userEvent.click(page.getCell({ name: '2020' })); await testRowCount(9); - await userEvent.click(getCell('2020')); - await testRowCount(10); }); test('should ignore duplicate groupBy columns', async () => { await setup(['year', 'year', 'year']); - await expect.element(getTreeGrid()).toHaveAttribute('aria-rowcount', '10'); - await testRowCount(6); + await expect.element(treeGrid).toHaveAttribute('aria-rowcount', '10'); + await testRowCount(5); }); test('should use groupBy order while grouping', async () => { await setup(['year', 'country']); - await expect.element(getTreeGrid()).toHaveAttribute('aria-rowcount', '14'); + await expect.element(treeGrid).toHaveAttribute('aria-rowcount', '14'); await testHeaderCellsContent(['', 'Year', 'Country', 'Sport', 'Id']); - await testRowCount(6); + await testRowCount(5); }); test('should toggle group when group cell is clicked', async () => { await setup(['year']); - await testRowCount(6); - const groupCell = getCell('2021'); + await testRowCount(5); + const groupCell = page.getCell({ name: '2021' }); await userEvent.click(groupCell); - await testRowCount(8); + await testRowCount(7); await userEvent.click(groupCell); - await testRowCount(6); + await testRowCount(5); }); test('should toggle group using keyboard', async () => { await setup(['year']); - await testRowCount(6); - const groupCell = getCell('2021'); + await testRowCount(5); + const groupCell = page.getCell({ name: '2021' }); await userEvent.click(groupCell); - await testRowCount(8); + await testRowCount(7); // clicking on the group cell selects the row - await expect.element(getSelectedCell()).not.toBeInTheDocument(); - await expect.element(getRowByCellName('2021')).toHaveClass(rowSelectedClassname); + await expect.element(selectedCell).not.toBeInTheDocument(); + await expect.element(getRowWithCell(groupCell)).toHaveClass(rowSelectedClassname); await userEvent.keyboard('{arrowright}{arrowright}{enter}'); - await testRowCount(6); + await testRowCount(5); await userEvent.keyboard('{enter}'); - await testRowCount(8); + await testRowCount(7); }); test('should set aria-attributes', async () => { await setup(['year', 'country']); - const groupRow1 = getRowByCellName('2020'); + const groupCell1 = page.getCell({ name: '2020' }); + const groupRow1 = getRowWithCell(groupCell1); await expect.element(groupRow1).toHaveAttribute('aria-level', '1'); await expect.element(groupRow1).toHaveAttribute('aria-setsize', '3'); await expect.element(groupRow1).toHaveAttribute('aria-posinset', '1'); await expect.element(groupRow1).toHaveAttribute('aria-rowindex', '3'); await expect.element(groupRow1).toHaveAttribute('aria-expanded', 'false'); - const groupCell2 = getCell('2021'); - const groupRow2 = getRowByCell(groupCell2); + const groupCell2 = page.getCell({ name: '2021' }); + const groupRow2 = getRowWithCell(groupCell2); await expect.element(groupRow2).toHaveAttribute('aria-level', '1'); await expect.element(groupRow2).toHaveAttribute('aria-setsize', '3'); await expect.element(groupRow2).toHaveAttribute('aria-posinset', '2'); @@ -251,8 +248,8 @@ test('should set aria-attributes', async () => { await userEvent.click(groupCell2); await expect.element(groupRow2).toHaveAttribute('aria-expanded', 'true'); - const groupCell3 = getCell('Canada'); - const groupRow3 = getRowByCell(groupCell3); + const groupCell3 = page.getCell({ name: 'Canada' }); + const groupRow3 = getRowWithCell(groupCell3); await expect.element(groupRow3).toHaveAttribute('aria-level', '2'); await expect.element(groupRow3).toHaveAttribute('aria-setsize', '2'); await expect.element(groupRow3).toHaveAttribute('aria-posinset', '2'); @@ -266,20 +263,20 @@ test('should set aria-attributes', async () => { test('should select rows in a group', async () => { await setup(['year', 'country']); - const headerCheckbox = getSelectAllCheckbox(); + const headerCheckbox = page.getSelectAllCheckbox(); await expect.element(headerCheckbox).not.toBeChecked(); // expand group - const groupCell1 = getCell('2021'); + const groupCell1 = page.getCell({ name: '2021' }); await userEvent.click(groupCell1); - const groupCell2 = getCell('Canada'); + const groupCell2 = page.getCell({ name: 'Canada' }); await userEvent.click(groupCell2); - const selectedRows = page.getByRole('row', { selected: true }); + const selectedRows = page.getRow({ selected: true }); await testCount(selectedRows, 0); // select parent row - await userEvent.click(getRowByCell(groupCell1).getByRole('checkbox', { name: 'Select Group' })); + await userEvent.click(getRowWithCell(groupCell1).getByRole('checkbox', { name: 'Select Group' })); await testCount(selectedRows, 4); await expect.element(selectedRows.nth(0)).toHaveAttribute('aria-rowindex', '6'); await expect.element(selectedRows.nth(1)).toHaveAttribute('aria-rowindex', '7'); @@ -292,7 +289,7 @@ test('should select rows in a group', async () => { await expect.element(selectedRows.nth(0)).toHaveAttribute('aria-rowindex', '7'); // select child group - const checkbox = getRowByCell(groupCell2).getByRole('checkbox', { + const checkbox = getRowWithCell(groupCell2).getByRole('checkbox', { name: 'Select Group' }); await userEvent.click(checkbox); @@ -302,8 +299,8 @@ test('should select rows in a group', async () => { await userEvent.click(checkbox); await testCount(selectedRows, 1); - await userEvent.click(getCell('2020')); - await userEvent.click(getCell('2022')); + await userEvent.click(page.getCell({ name: '2020' })); + await userEvent.click(page.getCell({ name: '2022' })); await userEvent.click(headerCheckbox); await testCount(selectedRows, 0); @@ -317,11 +314,11 @@ test('should select rows in a group', async () => { test('cell navigation in a treegrid', async () => { await setup(['country', 'year']); - await testRowCount(5); + await testRowCount(4); const focusSink = page.getBySelector(`.${focusSinkClassname}`); // expand group - const groupCell1 = getCell('USA'); + const groupCell1 = page.getCell({ name: 'USA' }); await expect.element(document.body).toHaveFocus(); await expect.element(focusSink).toHaveAttribute('tabIndex', '-1'); await userEvent.click(groupCell1); @@ -344,69 +341,70 @@ test('cell navigation in a treegrid', async () => { await expect.element(focusSink).toHaveFocus(); await expect.element(focusSink).toHaveStyle('grid-row-start:2'); await expect.element(focusSink).toHaveClass(rowSelected); - const groupCell2 = getCell('2021'); + const groupCell2 = page.getCell({ name: '2021' }); await userEvent.click(groupCell2); await expect.element(focusSink).toHaveFocus(); await expect.element(focusSink).toHaveAttribute('tabIndex', '0'); // select cell - await userEvent.click(getCellsAtRowIndex(5)[1]); - await expect.element(getCellsAtRowIndex(5)[1]).toHaveAttribute('aria-selected', 'true'); + const cells = getCellsAtRowIndex(5); + await userEvent.click(cells.nth(1)); + await expect.element(cells.nth(1)).toHaveAttribute('aria-selected', 'true'); await expect.element(focusSink).toHaveAttribute('tabIndex', '-1'); // select the previous cell await userEvent.keyboard('{arrowleft}'); - await expect.element(getCellsAtRowIndex(5)[1]).toHaveAttribute('aria-selected', 'false'); - await expect.element(getCellsAtRowIndex(5)[0]).toHaveAttribute('aria-selected', 'true'); + await expect.element(cells.nth(1)).toHaveAttribute('aria-selected', 'false'); + await expect.element(cells.nth(0)).toHaveAttribute('aria-selected', 'true'); // if the first cell is selected then arrowleft should select the row await userEvent.keyboard('{arrowleft}'); - await expect.element(getCellsAtRowIndex(5)[0]).toHaveAttribute('aria-selected', 'false'); - await expect.element(getRows()[4]).toHaveClass(rowSelectedClassname); + await expect.element(cells.nth(0)).toHaveAttribute('aria-selected', 'false'); + await expect.element(rows.nth(4)).toHaveClass(rowSelectedClassname); await expect.element(focusSink).toHaveFocus(); // if the row is selected then arrowright should select the first cell on the same row await userEvent.keyboard('{arrowright}'); - await expect.element(getCellsAtRowIndex(5)[0]).toHaveAttribute('aria-selected', 'true'); + await expect.element(cells.nth(0)).toHaveAttribute('aria-selected', 'true'); await userEvent.keyboard('{arrowleft}{arrowup}'); - await testRowCount(8); + await testRowCount(7); // left arrow should collapse the group await userEvent.keyboard('{arrowleft}'); - await testRowCount(7); + await testRowCount(6); // right arrow should expand the group await userEvent.keyboard('{arrowright}'); - await testRowCount(8); + await testRowCount(7); // left arrow on a collapsed group should select the parent group - await expect.element(getRows()[1]).not.toHaveClass(rowSelectedClassname); + await expect.element(rows.nth(1)).not.toHaveClass(rowSelectedClassname); await userEvent.keyboard('{arrowleft}{arrowleft}'); - await expect.element(getRows()[1]).toHaveClass(rowSelectedClassname); + await expect.element(rows.nth(1)).toHaveClass(rowSelectedClassname); await userEvent.keyboard('{end}'); - await expect.element(getRows()[5]).toHaveClass(rowSelectedClassname); + await expect.element(rows.nth(5)).toHaveClass(rowSelectedClassname); await userEvent.keyboard('{home}'); - await expect.element(page.getByRole('row').nth(0)).toHaveClass(rowSelectedClassname); + await expect.element(headerRow).toHaveClass(rowSelectedClassname); // collpase parent group await userEvent.keyboard('{arrowdown}{arrowdown}{arrowleft}'); - await expect.element(getCell('2021')).not.toBeInTheDocument(); - await testRowCount(5); + await expect.element(page.getCell({ name: '2021' })).not.toBeInTheDocument(); + await testRowCount(4); }); test('copy/paste when grouping is enabled', async () => { await setup(['year']); - await userEvent.click(getCell('2021')); + await userEvent.click(page.getCell({ name: '2021' })); await userEvent.copy(); expect(onCellCopySpy).not.toHaveBeenCalled(); await userEvent.paste(); expect(onCellPasteSpy).not.toHaveBeenCalled(); - await userEvent.click(getCell('USA')); + await userEvent.click(page.getCell({ name: 'USA' })); await userEvent.copy(); expect(onCellCopySpy).toHaveBeenCalledExactlyOnceWith( { @@ -435,18 +433,18 @@ test('copy/paste when grouping is enabled', async () => { test('update row using cell renderer', async () => { await setup(['year']); - await userEvent.click(getCell('2021')); - await userEvent.click(getCell('USA')); + await userEvent.click(page.getCell({ name: '2021' })); + await userEvent.click(page.getCell({ name: 'USA' })); await userEvent.keyboard('{arrowright}{arrowright}'); - await expect.element(getSelectedCell()).toHaveTextContent('value: 2'); + await expect.element(selectedCell).toHaveTextContent('value: 2'); await userEvent.click(page.getByRole('button', { name: 'value: 2' })); - await expect.element(getSelectedCell()).toHaveTextContent('value: 12'); + await expect.element(selectedCell).toHaveTextContent('value: 12'); }); test('custom renderGroupCell', async () => { await setup(['country']); - await expect.element(getRowByCellName('USA').getByRole('gridcell').nth(4)).toHaveTextContent('1'); - await expect - .element(getRowByCellName('Canada').getByRole('gridcell').nth(4)) - .toHaveTextContent('3'); + const usaCell = page.getCell({ name: 'USA' }); + const canadaCell = page.getCell({ name: 'Canada' }); + await expect.element(getRowWithCell(usaCell).getCell().nth(4)).toHaveTextContent('1'); + await expect.element(getRowWithCell(canadaCell).getCell().nth(4)).toHaveTextContent('3'); }); diff --git a/test/browser/column/cellClass.test.ts b/test/browser/column/cellClass.test.ts index 811e774ed4..08c4a8b703 100644 --- a/test/browser/column/cellClass.test.ts +++ b/test/browser/column/cellClass.test.ts @@ -1,6 +1,8 @@ +import { page } from 'vitest/browser'; + import type { Column } from '../../../src'; import { cellClassname } from '../../../src/style/cell'; -import { getCellsNew, setup } from '../utils'; +import { setup } from '../utils'; interface Row { id: number; @@ -16,7 +18,8 @@ test('cellClass is undefined', async () => { } ]; await setup({ columns, rows }); - const [cell1, cell2] = getCellsNew('0', '1'); + const cell1 = page.getCell({ name: '0' }); + const cell2 = page.getCell({ name: '1' }); await expect.element(cell1).toHaveClass(cellClassname, { exact: true }); await expect.element(cell2).toHaveClass(cellClassname, { exact: true }); }); @@ -30,7 +33,8 @@ test('cellClass is a string', async () => { } ]; await setup({ columns, rows }); - const [cell1, cell2] = getCellsNew('0', '1'); + const cell1 = page.getCell({ name: '0' }); + const cell2 = page.getCell({ name: '1' }); await expect.element(cell1).toHaveClass(`${cellClassname} my-cell`, { exact: true }); await expect.element(cell2).toHaveClass(`${cellClassname} my-cell`, { exact: true }); }); @@ -44,7 +48,8 @@ test('cellClass returns a string', async () => { } ]; await setup({ columns, rows }); - const [cell1, cell2] = getCellsNew('0', '1'); + const cell1 = page.getCell({ name: '0' }); + const cell2 = page.getCell({ name: '1' }); await expect.element(cell1).toHaveClass(`${cellClassname} my-cell-0`, { exact: true }); await expect.element(cell2).toHaveClass(`${cellClassname} my-cell-1`, { exact: true }); }); @@ -58,7 +63,8 @@ test('cellClass returns undefined', async () => { } ]; await setup({ columns, rows }); - const [cell1, cell2] = getCellsNew('0', '1'); + const cell1 = page.getCell({ name: '0' }); + const cell2 = page.getCell({ name: '1' }); await expect.element(cell1).toHaveClass(cellClassname, { exact: true }); await expect.element(cell2).toHaveClass(cellClassname, { exact: true }); }); diff --git a/test/browser/column/colSpan.test.ts b/test/browser/column/colSpan.test.ts index 8eb4447f9a..3809855f82 100644 --- a/test/browser/column/colSpan.test.ts +++ b/test/browser/column/colSpan.test.ts @@ -1,7 +1,9 @@ -import { userEvent } from 'vitest/browser'; +import { page, userEvent } from 'vitest/browser'; import type { Column } from '../../../src'; -import { getCellsAtRowIndex, getHeaderCells, setup, validateCellPosition } from '../utils'; +import { getCellsAtRowIndex, setup, validateCellPosition } from '../utils'; + +const headerCells = page.getHeaderCell(); describe('colSpan', () => { function setupColSpan(colCount = 15) { @@ -40,62 +42,62 @@ describe('colSpan', () => { it('should merges cells', async () => { await setupColSpan(); // header - expect(getHeaderCells()).toHaveLength(13); + await expect.element(headerCells).toHaveLength(13); // top summary rows const topSummarryRow1 = getCellsAtRowIndex(0); - expect(topSummarryRow1).toHaveLength(14); + await expect.element(topSummarryRow1).toHaveLength(14); // 7th-8th cells are merged - await expect.element(topSummarryRow1[7]).toHaveAttribute('aria-colindex', '8'); - await expect.element(topSummarryRow1[7]).toHaveAttribute('aria-colspan', '2'); - await expect.element(topSummarryRow1[7]).toHaveStyle({ + await expect.element(topSummarryRow1.nth(7)).toHaveAttribute('aria-colindex', '8'); + await expect.element(topSummarryRow1.nth(7)).toHaveAttribute('aria-colspan', '2'); + await expect.element(topSummarryRow1.nth(7)).toHaveStyle({ gridColumnStart: '8', gridColumnEnd: '10' }); - expect(getCellsAtRowIndex(1)).toHaveLength(15); + await expect.element(getCellsAtRowIndex(1)).toHaveLength(15); // rows const row1 = getCellsAtRowIndex(3); - expect(row1).toHaveLength(14); + await expect.element(row1).toHaveLength(14); // 7th-8th cells are merged - await expect.element(row1[6]).toHaveAttribute('aria-colindex', '7'); - await expect.element(row1[6]).toHaveAttribute('aria-colspan', '2'); - await expect.element(row1[6]).toHaveStyle({ + await expect.element(row1.nth(6)).toHaveAttribute('aria-colindex', '7'); + await expect.element(row1.nth(6)).toHaveAttribute('aria-colspan', '2'); + await expect.element(row1.nth(6)).toHaveStyle({ gridTemplateColumns: '7', gridColumnEnd: '9' }); - await expect.element(row1[7]).toHaveAttribute('aria-colindex', '9'); - await expect.element(row1[7]).not.toHaveAttribute('aria-colspan'); + await expect.element(row1.nth(7)).toHaveAttribute('aria-colindex', '9'); + await expect.element(row1.nth(7)).not.toHaveAttribute('aria-colspan'); // 3rd-5th, 7th-8th cells are merged const row2 = getCellsAtRowIndex(4); - expect(row2).toHaveLength(12); - await expect.element(row2[2]).toHaveAttribute('aria-colindex', '3'); - await expect.element(row2[2]).toHaveStyle({ + await expect.element(row2).toHaveLength(12); + await expect.element(row2.nth(2)).toHaveAttribute('aria-colindex', '3'); + await expect.element(row2.nth(2)).toHaveStyle({ gridColumnStart: '3', gridColumnEnd: '6' }); - await expect.element(row2[2]).toHaveAttribute('aria-colspan', '3'); - await expect.element(row2[3]).toHaveAttribute('aria-colindex', '6'); - await expect.element(row2[4]).toHaveAttribute('aria-colindex', '7'); - await expect.element(row2[4]).toHaveStyle({ + await expect.element(row2.nth(2)).toHaveAttribute('aria-colspan', '3'); + await expect.element(row2.nth(3)).toHaveAttribute('aria-colindex', '6'); + await expect.element(row2.nth(4)).toHaveAttribute('aria-colindex', '7'); + await expect.element(row2.nth(4)).toHaveStyle({ gridColumnStart: '7', gridColumnEnd: '9' }); - await expect.element(row2[5]).toHaveAttribute('aria-colindex', '9'); + await expect.element(row2.nth(5)).toHaveAttribute('aria-colindex', '9'); - expect(getCellsAtRowIndex(6)).toHaveLength(14); // colSpan 6 won't work as there are 5 frozen columns - expect(getCellsAtRowIndex(7)).toHaveLength(10); + await expect.element(getCellsAtRowIndex(6)).toHaveLength(14); // colSpan 6 won't work as there are 5 frozen columns + await expect.element(getCellsAtRowIndex(7)).toHaveLength(10); // bottom summary row - expect(getCellsAtRowIndex(12)).toHaveLength(14); - expect(getCellsAtRowIndex(13)).toHaveLength(15); + await expect.element(getCellsAtRowIndex(12)).toHaveLength(14); + await expect.element(getCellsAtRowIndex(13)).toHaveLength(15); }); it('should navigate between merged cells', async () => { await setupColSpan(); // header row - await userEvent.click(getHeaderCells()[7]); + await userEvent.click(headerCells.nth(7)); await validateCellPosition(7, 0); await userEvent.keyboard('{arrowright}'); await validateCellPosition(8, 0); @@ -107,7 +109,7 @@ describe('colSpan', () => { await validateCellPosition(7, 0); // top summary rows - await userEvent.click(getCellsAtRowIndex(0)[6]); + await userEvent.click(getCellsAtRowIndex(0).nth(6)); await validateCellPosition(6, 1); await userEvent.keyboard('{arrowright}'); await validateCellPosition(7, 1); @@ -119,7 +121,7 @@ describe('colSpan', () => { await validateCellPosition(6, 1); // viewport rows - await userEvent.click(getCellsAtRowIndex(3)[1]); + await userEvent.click(getCellsAtRowIndex(3).nth(1)); await validateCellPosition(1, 4); await userEvent.keyboard('{arrowright}'); await validateCellPosition(2, 4); @@ -148,7 +150,7 @@ describe('colSpan', () => { await validateCellPosition(14, 7); await userEvent.tab(); await validateCellPosition(0, 8); - await userEvent.click(getCellsAtRowIndex(10)[11]); + await userEvent.click(getCellsAtRowIndex(10).nth(11)); await validateCellPosition(11, 11); await userEvent.tab(); await validateCellPosition(12, 11); @@ -158,7 +160,7 @@ describe('colSpan', () => { await validateCellPosition(12, 11); // bottom summary rows - await userEvent.click(getCellsAtRowIndex(12)[6]); + await userEvent.click(getCellsAtRowIndex(12).nth(6)); await validateCellPosition(6, 13); await userEvent.keyboard('{arrowright}'); await validateCellPosition(7, 13); @@ -172,7 +174,7 @@ describe('colSpan', () => { it('should scroll to the merged cell when selected', async () => { await setupColSpan(30); - await userEvent.click(getCellsAtRowIndex(10)[23]); // last visible cell (1920/80) + await userEvent.click(getCellsAtRowIndex(10).nth(23)); // last visible cell (1920/80) const spy = vi.spyOn(window.HTMLElement.prototype, 'scrollIntoView'); const testScrollIntoView = () => { expect(spy).toHaveBeenCalled(); diff --git a/test/browser/column/draggable.test.ts b/test/browser/column/draggable.test.ts index 0c8cf43eed..9de5c0cdca 100644 --- a/test/browser/column/draggable.test.ts +++ b/test/browser/column/draggable.test.ts @@ -1,7 +1,9 @@ -import { userEvent } from 'vitest/browser'; +import { page, userEvent } from 'vitest/browser'; import type { Column } from '../../../src'; -import { getHeaderCellsNew, setup } from '../utils'; +import { setup } from '../utils'; + +const headerCells = page.getHeaderCell(); const columns: readonly Column[] = [ { @@ -28,7 +30,7 @@ const columns: readonly Column[] = [ test('draggable columns', async () => { const onColumnsReorder = vi.fn(); await setup({ columns, rows: [], onColumnsReorder }); - const [cell1, cell2, cell3, cell4] = getHeaderCellsNew('col1', 'col2', 'col3', 'col4'); + const [cell1, cell2, cell3, cell4] = headerCells.all(); 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 7776be7ce1..7cfd10f783 100644 --- a/test/browser/column/frozen.test.ts +++ b/test/browser/column/frozen.test.ts @@ -2,7 +2,9 @@ import { page } from 'vitest/browser'; import type { Column } from '../../../src'; import { cellClassname, cellFrozenClassname } from '../../../src/style/cell'; -import { getHeaderCellsNew, setup } from '../utils'; +import { setup } from '../utils'; + +const headerCells = page.getHeaderCell(); test('frozen column have a specific class, and are stable-sorted before non-frozen columns', async () => { const columns: readonly Column[] = [ @@ -28,15 +30,20 @@ test('frozen column have a specific class, and are stable-sorted before non-froz ]; await setup({ columns, rows: [] }); - await expect.element(page.getByRole('row')).toHaveTextContent('col1col3col2col4'); - const [cell1, cell2, cell3, cell4] = getHeaderCellsNew('col1', 'col2', 'col3', 'col4'); + + const [cell1, cell2, cell3, cell4] = headerCells.all(); + + await expect.element(cell1).toHaveTextContent('col1'); + await expect.element(cell2).toHaveTextContent('col3'); + await expect.element(cell3).toHaveTextContent('col2'); + await expect.element(cell4).toHaveTextContent('col4'); await expect .element(cell1) .toHaveClass(`${cellClassname} ${cellFrozenClassname}`, { exact: true }); await expect - .element(cell3) + .element(cell2) .toHaveClass(`${cellClassname} ${cellFrozenClassname}`, { exact: true }); - await expect.element(cell2).toHaveClass(cellClassname, { exact: true }); + await expect.element(cell3).toHaveClass(cellClassname, { exact: true }); await expect.element(cell4).toHaveClass(cellClassname, { exact: true }); }); diff --git a/test/browser/column/grouping.test.ts b/test/browser/column/grouping.test.ts index cb0b3fb9d2..ac4696b7e1 100644 --- a/test/browser/column/grouping.test.ts +++ b/test/browser/column/grouping.test.ts @@ -1,14 +1,11 @@ import { page, userEvent } from 'vitest/browser'; import type { ColumnOrColumnGroup } from '../../../src'; -import { - getGrid, - getSelectedCell, - setup, - tabIntoGrid, - testCount, - validateCellPosition -} from '../utils'; +import { setup, tabIntoGrid, testCount, validateCellPosition } from '../utils'; + +const grid = page.getGrid(); +const headerRows = grid.getHeaderRow(); +const headerCells = grid.getHeaderCell(); const columns: readonly ColumnOrColumnGroup>[] = [ { key: 'col1', name: 'col 1' }, @@ -97,38 +94,24 @@ const columns: readonly ColumnOrColumnGroup>[] = [ test('grouping', async () => { await setup({ columns, rows: [{}] }); - const grid = getGrid(); await expect.element(grid).toHaveAttribute('aria-colcount', '12'); await expect.element(grid).toHaveAttribute('aria-rowcount', '5'); - const rows = page.getByRole('row'); - await testCount(rows, 5); + await testCount(headerRows, 4); - await expect.element(rows.nth(0)).toHaveAttribute('aria-rowindex', '1'); - await expect.element(rows.nth(1)).toHaveAttribute('aria-rowindex', '2'); - await expect.element(rows.nth(2)).toHaveAttribute('aria-rowindex', '3'); - await expect.element(rows.nth(3)).toHaveAttribute('aria-rowindex', '4'); - await expect.element(rows.nth(4)).toHaveAttribute('aria-rowindex', '5'); + await expect.element(headerRows.nth(0)).toHaveAttribute('aria-rowindex', '1'); + await expect.element(headerRows.nth(1)).toHaveAttribute('aria-rowindex', '2'); + await expect.element(headerRows.nth(2)).toHaveAttribute('aria-rowindex', '3'); + await expect.element(headerRows.nth(3)).toHaveAttribute('aria-rowindex', '4'); - await testCount(rows.nth(0).getByRole('columnheader'), 2); - await testCount(rows.nth(1).getByRole('columnheader'), 2); - await testCount(rows.nth(2).getByRole('columnheader'), 4); - await testCount(rows.nth(3).getByRole('columnheader'), 12); - await testCount(rows.nth(4).getByRole('columnheader'), 0); + await testCount(headerRows.nth(0).getHeaderCell(), 2); + await testCount(headerRows.nth(1).getHeaderCell(), 2); + await testCount(headerRows.nth(2).getHeaderCell(), 4); + await testCount(headerRows.nth(3).getHeaderCell(), 12); - const headerCells = page.getByRole('columnheader'); await testCount(headerCells, 20); - const headerCellDetails = headerCells.elements().map((cell) => { - return { - text: cell.textContent, - colIndex: cell.getAttribute('aria-colindex'), - colSpan: cell.getAttribute('aria-colspan'), - rowSpan: cell.getAttribute('aria-rowspan') - }; - }); - - expect(headerCellDetails).toStrictEqual([ + const expected = [ { colIndex: '5', colSpan: '4', @@ -249,14 +232,30 @@ test('grouping', async () => { rowSpan: '3', text: 'col 12' } - ]); + ] as const; + + await testCount(headerCells, expected.length); + + for (const [n, item] of expected.entries()) { + const cell = headerCells.nth(n); + await expect.element(cell).toHaveTextContent(item.text); + await expect.element(cell).toHaveAttribute('aria-colindex', item.colIndex); + if (item.colSpan == null) { + // eslint-disable-next-line vitest/no-conditional-expect + await expect.element(cell).not.toHaveAttribute('aria-colspan'); + } else { + // eslint-disable-next-line vitest/no-conditional-expect + await expect.element(cell).toHaveAttribute('aria-colspan', item.colSpan); + } + await expect.element(cell).toHaveAttribute('aria-rowspan', item.rowSpan); + } }); test('keyboard navigation', async () => { await setup({ columns, rows: [{}] }, true); // no initial selection - await expect.element(getSelectedCell()).not.toBeInTheDocument(); + await expect.element(grid.getSelectedCell()).not.toBeInTheDocument(); await tabIntoGrid(); await validateCellPosition(0, 3); diff --git a/test/browser/column/headerCellClass.test.ts b/test/browser/column/headerCellClass.test.ts index 205208902a..a143555952 100644 --- a/test/browser/column/headerCellClass.test.ts +++ b/test/browser/column/headerCellClass.test.ts @@ -1,6 +1,10 @@ +import { page } from 'vitest/browser'; + import type { Column, ColumnGroup } from '../../../src'; import { cellClassname } from '../../../src/style/cell'; -import { getHeaderCells, setup } from '../utils'; +import { setup } from '../utils'; + +const headerCells = page.getHeaderCell(); test('headerCellClass is either nullish or a string', async () => { const columns: readonly Column[] = [ @@ -16,9 +20,10 @@ test('headerCellClass is either nullish or a string', async () => { ]; await setup({ columns, rows: [] }); - const [cell1, cell2] = getHeaderCells(); - await expect.element(cell1).toHaveClass(cellClassname, { exact: true }); - await expect.element(cell2).toHaveClass(`${cellClassname} my-header`, { exact: true }); + await expect.element(headerCells.nth(0)).toHaveClass(cellClassname, { exact: true }); + await expect + .element(headerCells.nth(1)) + .toHaveClass(`${cellClassname} my-header`, { exact: true }); }); test('columnGroup.headerCellClass is either nullish or a string', async () => { @@ -35,7 +40,8 @@ test('columnGroup.headerCellClass is either nullish or a string', async () => { ]; await setup({ columns, rows: [] }); - const [cell1, cell2] = getHeaderCells(); - await expect.element(cell1).toHaveClass(cellClassname, { exact: true }); - await expect.element(cell2).toHaveClass(`${cellClassname} my-header`, { exact: true }); + await expect.element(headerCells.nth(0)).toHaveClass(cellClassname, { exact: true }); + await expect + .element(headerCells.nth(1)) + .toHaveClass(`${cellClassname} my-header`, { exact: true }); }); diff --git a/test/browser/column/name.test.tsx b/test/browser/column/name.test.tsx index 21bf572fbd..137a67fcf7 100644 --- a/test/browser/column/name.test.tsx +++ b/test/browser/column/name.test.tsx @@ -1,5 +1,9 @@ +import { page } from 'vitest/browser'; + import type { Column } from '../../../src'; -import { getHeaderCellsNew, setup } from '../utils'; +import { setup } from '../utils'; + +const headerCells = page.getHeaderCell(); test('name is either a string or an element', async () => { function Header() { @@ -18,7 +22,6 @@ test('name is either a string or an element', async () => { ]; await setup({ columns, rows: [] }); - const [cell1, cell2] = getHeaderCellsNew('ID', 'Fancy'); - await expect.element(cell1).toBeVisible(); - await expect.element(cell2).toBeVisible(); + await expect.element(headerCells.nth(0)).toBeVisible(); + await expect.element(headerCells.nth(1)).toBeVisible(); }); diff --git a/test/browser/column/renderCell.test.tsx b/test/browser/column/renderCell.test.tsx index 90ae4af3e2..7abd91f0f3 100644 --- a/test/browser/column/renderCell.test.tsx +++ b/test/browser/column/renderCell.test.tsx @@ -4,7 +4,9 @@ import { page, userEvent } from 'vitest/browser'; import { DataGrid } from '../../../src'; import type { Column } from '../../../src'; import defaultRenderHeaderCell from '../../../src/renderHeaderCell'; -import { getCells, getCellsAtRowIndex, setup } from '../utils'; +import { getCellsAtRowIndex, setup } from '../utils'; + +const cells = page.getCell(); interface Row { id: number; @@ -20,16 +22,14 @@ describe('renderValue', () => { it('should be used by default', async () => { await setup({ columns, rows }); - const [cell1, cell2] = getCells(); - await expect.element(cell1).toHaveTextContent('101'); - await expect.element(cell2).toBeEmptyDOMElement(); + await expect.element(cells.nth(0)).toHaveTextContent('101'); + await expect.element(cells.nth(1)).toBeEmptyDOMElement(); }); it('should handle non-object values', async () => { await setup({ columns, rows: [null] }); - const [cell1, cell2] = getCells(); - await expect.element(cell1).toBeEmptyDOMElement(); - await expect.element(cell2).toBeEmptyDOMElement(); + await expect.element(cells.nth(0)).toBeEmptyDOMElement(); + await expect.element(cells.nth(1)).toBeEmptyDOMElement(); }); }); @@ -51,9 +51,8 @@ describe('Custom cell renderer', () => { it('should replace the default cell renderer', async () => { await setup({ columns, rows }); - const [cell1, cell2] = getCells(); - await expect.element(cell1).toHaveTextContent('#101'); - await expect.element(cell2).toHaveTextContent('No name'); + await expect.element(cells.nth(0)).toHaveTextContent('#101'); + await expect.element(cells.nth(1)).toHaveTextContent('No name'); }); it('can update rows', async () => { @@ -92,9 +91,9 @@ describe('Custom cell renderer', () => { await page.render(); - const [cell] = getCells(); + const cell = cells.first(); await expect.element(cell).toHaveTextContent('value: 1'); - await userEvent.click(page.getByRole('button')); + await userEvent.click(cell.getByRole('button')); await expect.element(cell).toHaveTextContent('value: 2'); expect(onChange).toHaveBeenCalledExactlyOnceWith([{ id: 2 }], { column: { @@ -139,7 +138,7 @@ test('Focus child if it sets tabIndex', async () => { const button1 = page.getByRole('button', { name: 'Button 1' }); const button2 = page.getByRole('button', { name: 'Button 2' }); - const cell = page.getByRole('gridcell', { name: 'Button 1 Text Button 2' }); + const cell = page.getCell({ name: 'Button 1 Text Button 2' }); await expect.element(button1).toHaveAttribute('tabindex', '-1'); await expect.element(cell).toHaveAttribute('tabindex', '-1'); await userEvent.click(page.getByText('Text')); @@ -192,12 +191,13 @@ test('Cell should not steal focus when the focus is outside the grid and cell is await page.render(); - await userEvent.click(getCellsAtRowIndex(0)[0]); - await expect.element(getCellsAtRowIndex(0)[0]).toHaveFocus(); + const cell = getCellsAtRowIndex(0).nth(0); + await userEvent.click(cell); + await expect.element(cell).toHaveFocus(); - const button = page.getByRole('button', { name: 'Test' }).element(); + const button = page.getByRole('button', { name: 'Test' }); await expect.element(button).not.toHaveFocus(); await userEvent.click(button); - await expect.element(getCellsAtRowIndex(0)[0]).not.toHaveFocus(); + await expect.element(cell).not.toHaveFocus(); await expect.element(button).toHaveFocus(); }); diff --git a/test/browser/column/renderEditCell.test.tsx b/test/browser/column/renderEditCell.test.tsx index 01b12d6f0c..2294e72ce9 100644 --- a/test/browser/column/renderEditCell.test.tsx +++ b/test/browser/column/renderEditCell.test.tsx @@ -1,10 +1,12 @@ import { useMemo, useState } from 'react'; import { createPortal } from 'react-dom'; -import { commands, page, userEvent } from 'vitest/browser'; +import { page, userEvent } from 'vitest/browser'; import { DataGrid } from '../../../src'; import type { Column, DataGridProps } from '../../../src'; -import { getCell, getCellsAtRowIndex, getGrid, getSelectedCell, testCount } from '../utils'; +import { getCellsAtRowIndex, getRowWithCell, scrollGrid, testCount } from '../utils'; + +const grid = page.getGrid(); interface Row { col1: number; @@ -15,63 +17,63 @@ describe('Editor', () => { it('should open editor on double click', async () => { await page.render(); const editor = page.getByRole('spinbutton', { name: 'col1-editor' }); - await userEvent.click(getCellsAtRowIndex(0)[0]); + await userEvent.click(getCellsAtRowIndex(0).nth(0)); await expect.element(editor).not.toBeInTheDocument(); - await userEvent.dblClick(getCellsAtRowIndex(0)[0]); + await userEvent.dblClick(getCellsAtRowIndex(0).nth(0)); await expect.element(editor).toHaveValue(1); await userEvent.keyboard('2'); await userEvent.tab(); await expect.element(editor).not.toBeInTheDocument(); - await expect.element(getCellsAtRowIndex(0)[0]).toHaveTextContent(/^12$/); + await expect.element(getCellsAtRowIndex(0).nth(0)).toHaveTextContent(/^12$/); }); it('should open and commit changes on enter', async () => { await page.render(); const editor = page.getByRole('spinbutton', { name: 'col1-editor' }); - await userEvent.click(getCellsAtRowIndex(0)[0]); + await userEvent.click(getCellsAtRowIndex(0).nth(0)); await expect.element(editor).not.toBeInTheDocument(); await userEvent.keyboard('{enter}'); await expect.element(editor).toHaveValue(1); await userEvent.keyboard('3{enter}'); - await expect.element(getCellsAtRowIndex(0)[0]).toHaveTextContent(/^13$/); - await expect.element(getCellsAtRowIndex(0)[0]).toHaveFocus(); + await expect.element(getCellsAtRowIndex(0).nth(0)).toHaveTextContent(/^13$/); + await expect.element(getCellsAtRowIndex(0).nth(0)).toHaveFocus(); await expect.element(editor).not.toBeInTheDocument(); }); it('should open editor when user types', async () => { await page.render(); - await userEvent.click(getCellsAtRowIndex(0)[0]); + await userEvent.click(getCellsAtRowIndex(0).nth(0)); // TODO: await userEvent.keyboard('123{enter}'); fails in FF await userEvent.keyboard('{enter}123{enter}'); - await expect.element(getCellsAtRowIndex(0)[0]).toHaveTextContent(/^1123$/); + await expect.element(getCellsAtRowIndex(0).nth(0)).toHaveTextContent(/^1123$/); }); it('should close editor and discard changes on escape', async () => { await page.render(); - await userEvent.dblClick(getCellsAtRowIndex(0)[0]); + await userEvent.dblClick(getCellsAtRowIndex(0).nth(0)); const editor = page.getByRole('spinbutton', { name: 'col1-editor' }); await expect.element(editor).toHaveValue(1); await userEvent.keyboard('2222{escape}'); await expect.element(editor).not.toBeInTheDocument(); - await expect.element(getCellsAtRowIndex(0)[0]).toHaveTextContent(/^1$/); - await expect.element(getCellsAtRowIndex(0)[0]).toHaveFocus(); + await expect.element(getCellsAtRowIndex(0).nth(0)).toHaveTextContent(/^1$/); + await expect.element(getCellsAtRowIndex(0).nth(0)).toHaveFocus(); }); it('should commit changes and close editor when clicked outside', async () => { await page.render(); - await userEvent.dblClick(getCellsAtRowIndex(0)[0]); + await userEvent.dblClick(getCellsAtRowIndex(0).nth(0)); const editor = page.getByRole('spinbutton', { name: 'col1-editor' }); await expect.element(editor).toHaveValue(1); await userEvent.keyboard('2222'); await userEvent.click(page.getByText('outside')); await expect.element(editor).not.toBeInTheDocument(); - await expect.element(getCellsAtRowIndex(0)[0]).toHaveTextContent(/^12222$/); + await expect.element(getCellsAtRowIndex(0).nth(0)).toHaveTextContent(/^12222$/); }); it('should commit quickly enough on outside clicks so click event handlers access the latest rows state', async () => { const onSave = vi.fn(); await page.render(); - await userEvent.dblClick(getCellsAtRowIndex(0)[0]); + await userEvent.dblClick(getCellsAtRowIndex(0).nth(0)); await userEvent.keyboard('234'); expect(onSave).not.toHaveBeenCalled(); const saveButton = page.getByRole('button', { name: 'save' }); @@ -93,28 +95,25 @@ describe('Editor', () => { } await page.render(); - await userEvent.click(getCellsAtRowIndex(0)[0]); - const selectedRowCells = page - .getByRole('row') - .filter({ has: getSelectedCell() }) - .getByRole('gridcell'); + await userEvent.click(getCellsAtRowIndex(0).nth(0)); + const selectedRowCells = getRowWithCell(page.getSelectedCell()).getCell(); await testCount(selectedRowCells, 2); - await commands.scrollGrid({ scrollTop: 2001 }); + await scrollGrid({ top: 2001 }); await testCount(selectedRowCells, 1); - const editor = page.getByRole('spinbutton', { name: 'col1-editor' }); + const editor = grid.getByRole('spinbutton', { name: 'col1-editor' }); await expect.element(editor).not.toBeInTheDocument(); - expect(getGrid().element().scrollTop).toBe(2001); + await expect.element(grid).toHaveProperty('scrollTop', 2001); // TODO: await userEvent.keyboard('123'); fails in FF await userEvent.keyboard('{enter}123'); await testCount(selectedRowCells, 2); await expect.element(editor).toHaveValue(123); - expect(getGrid().element().scrollTop).toBe(0); + await expect.element(grid).toHaveProperty('scrollTop', 0); }); describe('editable', () => { it('should be editable if an editor is specified and editable is undefined/null', async () => { await page.render(); - const cell = getCellsAtRowIndex(0)[1]; + const cell = getCellsAtRowIndex(0).nth(1); await expect.element(cell).not.toHaveAttribute('aria-readonly'); await userEvent.dblClick(cell); await expect.element(page.getByRole('textbox', { name: 'col2-editor' })).toBeInTheDocument(); @@ -122,13 +121,13 @@ describe('Editor', () => { it('should be editable if an editor is specified and editable is set to true', async () => { await page.render(); - await userEvent.dblClick(getCellsAtRowIndex(0)[1]); + await userEvent.dblClick(getCellsAtRowIndex(0).nth(1)); await expect.element(page.getByRole('textbox', { name: 'col2-editor' })).toBeInTheDocument(); }); it('should not be editable if editable is false', async () => { await page.render(); - const cell = getCellsAtRowIndex(0)[1]; + const cell = getCellsAtRowIndex(0).nth(1); await expect.element(cell).toHaveAttribute('aria-readonly', 'true'); await userEvent.dblClick(cell); @@ -139,11 +138,11 @@ describe('Editor', () => { it('should not be editable if editable function returns false', async () => { await page.render( row.col1 === 2} />); - await userEvent.dblClick(getCellsAtRowIndex(0)[1]); + await userEvent.dblClick(getCellsAtRowIndex(0).nth(1)); const editor = page.getByRole('textbox', { name: 'col2-editor' }); await expect.element(editor).not.toBeInTheDocument(); - await userEvent.dblClick(getCellsAtRowIndex(1)[1]); + await userEvent.dblClick(getCellsAtRowIndex(1).nth(1)); await expect.element(editor).toBeInTheDocument(); }); }); @@ -153,24 +152,24 @@ describe('Editor', () => { await page.render( ); - await userEvent.dblClick(getCellsAtRowIndex(0)[1]); + await userEvent.dblClick(getCellsAtRowIndex(0).nth(1)); const editor1 = page.getByRole('textbox', { name: 'col2-editor' }); await expect.element(editor1).toHaveValue('a1'); await userEvent.keyboard('23'); // The cell value should update as the editor value is changed - await expect.element(getCellsAtRowIndex(0)[1]).toHaveTextContent(/^a123$/); + await expect.element(getCellsAtRowIndex(0).nth(1)).toHaveTextContent(/^a123$/); // clicking in a portal does not count as an outside click await userEvent.click(editor1); await expect.element(editor1).toBeInTheDocument(); // true outside clicks are still detected await userEvent.click(page.getByText('outside')); await expect.element(editor1).not.toBeInTheDocument(); - await expect.element(getCellsAtRowIndex(0)[1]).not.toHaveFocus(); + await expect.element(getCellsAtRowIndex(0).nth(1)).not.toHaveFocus(); - await userEvent.dblClick(getCellsAtRowIndex(0)[1]); + await userEvent.dblClick(getCellsAtRowIndex(0).nth(1)); await userEvent.click(page.getByRole('textbox', { name: 'col2-editor' })); await userEvent.keyboard('{enter}'); - await expect.element(getCellsAtRowIndex(0)[1]).toHaveFocus(); + await expect.element(getCellsAtRowIndex(0).nth(1)).toHaveFocus(); }); it('should not commit on outside click if commitOnOutsideClick is false', async () => { @@ -181,7 +180,7 @@ describe('Editor', () => { }} /> ); - await userEvent.dblClick(getCellsAtRowIndex(0)[1]); + await userEvent.dblClick(getCellsAtRowIndex(0).nth(1)); const editor = page.getByRole('textbox', { name: 'col2-editor' }); await expect.element(editor).toBeInTheDocument(); await userEvent.click(page.getByText('outside')); @@ -201,10 +200,10 @@ describe('Editor', () => { }} /> ); - await userEvent.click(getCellsAtRowIndex(0)[1]); + await userEvent.click(getCellsAtRowIndex(0).nth(1)); // TODO: await userEvent.keyboard('yz{enter}'); fails in FF await userEvent.keyboard('{enter}yz{enter}'); - await expect.element(getCellsAtRowIndex(0)[1]).toHaveTextContent(/^a1yz$/); + await expect.element(getCellsAtRowIndex(0).nth(1)).toHaveTextContent(/^a1yz$/); await userEvent.keyboard('x'); await expect .element(page.getByRole('textbox', { name: 'col2-editor' })) @@ -222,9 +221,9 @@ describe('Editor', () => { }} /> ); - await userEvent.dblClick(getCellsAtRowIndex(0)[1]); + await userEvent.dblClick(getCellsAtRowIndex(0).nth(1)); await userEvent.keyboard('a{arrowleft}b{arrowright}c{arrowdown}'); // should commit changes on arrowdown - await expect.element(getCellsAtRowIndex(0)[1]).toHaveTextContent(/^a1bac$/); + await expect.element(getCellsAtRowIndex(0).nth(1)).toHaveTextContent(/^a1bac$/); }); it('should close the editor when closeOnExternalRowChange is true or undefined and row is changed from outside', async () => { @@ -236,7 +235,7 @@ describe('Editor', () => { }} /> ); - await userEvent.dblClick(getCellsAtRowIndex(0)[1]); + await userEvent.dblClick(getCellsAtRowIndex(0).nth(1)); const editor = page.getByRole('textbox', { name: 'col2-editor' }); await expect.element(editor).toBeInTheDocument(); await userEvent.click(page.getByRole('button', { name: 'update' })); @@ -252,7 +251,7 @@ describe('Editor', () => { }} /> ); - await userEvent.dblClick(getCellsAtRowIndex(0)[1]); + await userEvent.dblClick(getCellsAtRowIndex(0).nth(1)); const editor = page.getByRole('textbox', { name: 'col2-editor' }); await expect.element(editor).toBeInTheDocument(); await userEvent.click(page.getByRole('button', { name: 'update' })); @@ -269,14 +268,14 @@ describe('Editor', () => { await page.render(); - await userEvent.dblClick(getCell('name0')); + await userEvent.dblClick(page.getCell({ name: 'name0' })); await userEvent.keyboard('abc'); - await commands.scrollGrid({ scrollTop: 1500 }); - await userEvent.click(getCell('name43')); - await expect.element(getSelectedCell()).toHaveTextContent(/^name43$/); - await commands.scrollGrid({ scrollTop: 0 }); - await expect.element(getCell('name0abc')).toBeVisible(); + await scrollGrid({ top: 1500 }); + await userEvent.click(page.getCell({ name: 'name43' })); + await expect.element(page.getSelectedCell()).toHaveTextContent(/^name43$/); + await scrollGrid({ top: 0 }); + await expect.element(page.getCell({ name: 'name0abc' })).toBeVisible(); }); it('should not steal focus back to the cell after being closed by clicking outside the grid', async () => { @@ -318,7 +317,7 @@ describe('Editor', () => { ); const outerInput = page.getByRole('textbox', { name: 'outer-input' }); - await userEvent.dblClick(getCellsAtRowIndex(0)[0]); + await userEvent.dblClick(getCellsAtRowIndex(0).nth(0)); const col1Input = page.getByRole('textbox', { name: 'col1-input' }); await expect.element(col1Input).toHaveFocus(); await userEvent.click(outerInput); @@ -326,7 +325,7 @@ describe('Editor', () => { await expect.element(col1Input).not.toBeInTheDocument(); await expect.element(outerInput).toHaveFocus(); - await userEvent.dblClick(getCellsAtRowIndex(0)[1]); + await userEvent.dblClick(getCellsAtRowIndex(0).nth(1)); const col2Input = page.getByRole('textbox', { name: 'col2-input' }); await expect.element(col2Input).toHaveFocus(); await userEvent.click(outerInput); diff --git a/test/browser/column/renderHeaderCell.test.tsx b/test/browser/column/renderHeaderCell.test.tsx index 91ffe3d080..bf3a7ff0f1 100644 --- a/test/browser/column/renderHeaderCell.test.tsx +++ b/test/browser/column/renderHeaderCell.test.tsx @@ -1,5 +1,9 @@ +import { page } from 'vitest/browser'; + import type { Column } from '../../../src'; -import { getHeaderCells, setup } from '../utils'; +import { setup } from '../utils'; + +const headerCells = page.getHeaderCell(); test('renderHeaderCell is either undefined or a component', async () => { const columns: readonly Column[] = [ @@ -15,7 +19,6 @@ test('renderHeaderCell is either undefined or a component', async () => { ]; await setup({ columns, rows: [] }); - const [cell1, cell2] = getHeaderCells(); - await expect.element(cell1).toHaveTextContent('ID'); - await expect.element(cell2).toHaveTextContent('Fancy! Name'); + await expect.element(headerCells.nth(0)).toHaveTextContent('ID'); + await expect.element(headerCells.nth(1)).toHaveTextContent('Fancy! Name'); }); diff --git a/test/browser/column/renderSummaryCell.test.tsx b/test/browser/column/renderSummaryCell.test.tsx index b6a374747e..dabc7eeabe 100644 --- a/test/browser/column/renderSummaryCell.test.tsx +++ b/test/browser/column/renderSummaryCell.test.tsx @@ -1,5 +1,9 @@ +import { page } from 'vitest/browser'; + import type { Column } from '../../../src'; -import { getCells, setup } from '../utils'; +import { setup } from '../utils'; + +const cells = page.getCell(); interface SummaryRow { id: number; @@ -35,15 +39,14 @@ test('renderSummaryCell', async () => { ] }); - const cells = getCells(); - expect(cells).toHaveLength(8); - await expect.element(cells[0]).toHaveTextContent('Summary: 1'); - await expect.element(cells[2]).toHaveTextContent('Summary: 2'); - await expect.element(cells[4]).toHaveTextContent('Summary: 3'); - await expect.element(cells[6]).toHaveTextContent('Summary: 4'); + await expect.element(cells).toHaveLength(8); + await expect.element(cells.nth(0)).toHaveTextContent('Summary: 1'); + await expect.element(cells.nth(2)).toHaveTextContent('Summary: 2'); + await expect.element(cells.nth(4)).toHaveTextContent('Summary: 3'); + await expect.element(cells.nth(6)).toHaveTextContent('Summary: 4'); // nothing is rendered when renderSummaryCell is not defined - await expect.element(cells[1]).toBeEmptyDOMElement(); - await expect.element(cells[3]).toBeEmptyDOMElement(); - await expect.element(cells[5]).toBeEmptyDOMElement(); - await expect.element(cells[7]).toBeEmptyDOMElement(); + await expect.element(cells.nth(1)).toBeEmptyDOMElement(); + await expect.element(cells.nth(3)).toBeEmptyDOMElement(); + await expect.element(cells.nth(5)).toBeEmptyDOMElement(); + await expect.element(cells.nth(7)).toBeEmptyDOMElement(); }); diff --git a/test/browser/column/resizable.test.tsx b/test/browser/column/resizable.test.tsx index 914e8be388..6e9f6984a8 100644 --- a/test/browser/column/resizable.test.tsx +++ b/test/browser/column/resizable.test.tsx @@ -2,7 +2,9 @@ import { useState } from 'react'; import { commands, page, userEvent } from 'vitest/browser'; import { DataGrid, type Column, type ColumnWidth, type ColumnWidths } from '../../../src'; -import { getGrid, getHeaderCell, setup } from '../utils'; +import { setup } from '../utils'; + +const grid = page.getGrid(); interface Row { readonly col1: number; @@ -10,8 +12,7 @@ interface Row { } function getResizeHandle(name: string) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return getHeaderCell(name).getBySelector('.rdg-resize-handle'); + return page.getHeaderCell({ name }).getBySelector('.rdg-resize-handle'); } async function resize(columnName: string, resizeBy: number | readonly number[]) { @@ -50,7 +51,6 @@ test('cannot resize or auto resize column when resizable is not specified', asyn test('should resize column when dragging the handle', async () => { const onColumnResize = vi.fn(); await setup({ columns, rows: [], onColumnResize }); - const grid = getGrid(); expect(onColumnResize).not.toHaveBeenCalled(); await expect.element(grid).toHaveStyle({ gridTemplateColumns: '100px 200px' }); await resize('col2', -50); @@ -60,7 +60,6 @@ test('should resize column when dragging the handle', async () => { test('should use the maxWidth if specified when dragging the handle', async () => { await setup({ columns, rows: [] }); - const grid = getGrid(); await expect.element(grid).toHaveStyle({ gridTemplateColumns: '100px 200px' }); await resize('col2', 1000); await expect.element(grid).toHaveStyle({ gridTemplateColumns: '100px 400px' }); @@ -68,7 +67,6 @@ test('should use the maxWidth if specified when dragging the handle', async () = test('should use the minWidth if specified when dragging the handle', async () => { await setup({ columns, rows: [] }); - const grid = getGrid(); await expect.element(grid).toHaveStyle({ gridTemplateColumns: '100px 200px' }); await resize('col2', -150); await expect.element(grid).toHaveStyle({ gridTemplateColumns: '100px 100px' }); @@ -77,10 +75,9 @@ test('should use the minWidth if specified when dragging the handle', async () = test('should resize column using keboard', async () => { const onColumnResize = vi.fn(); await setup({ columns, rows: [], onColumnResize }); - const grid = getGrid(); expect(onColumnResize).not.toHaveBeenCalled(); await expect.element(grid).toHaveStyle({ gridTemplateColumns: '100px 200px' }); - const col2 = getHeaderCell('col2'); + const col2 = page.getHeaderCell({ name: 'col2' }); await userEvent.click(col2); await userEvent.keyboard('{Control>}{ArrowRight}{/Control}'); @@ -96,9 +93,8 @@ test('should resize column using keboard', async () => { test('should use the maxWidth if specified when resizing using keyboard', async () => { const onColumnResize = vi.fn(); await setup({ columns, rows: [], onColumnResize }); - const grid = getGrid(); await expect.element(grid).toHaveStyle({ gridTemplateColumns: '100px 200px' }); - const col2 = getHeaderCell('col2'); + const col2 = page.getHeaderCell({ name: 'col2' }); await userEvent.click(col2); await userEvent.keyboard(`{Control>}${'{ArrowRight}'.repeat(22)}{/Control}`); await expect.element(grid).toHaveStyle({ gridTemplateColumns: '100px 400px' }); @@ -108,9 +104,8 @@ test('should use the maxWidth if specified when resizing using keyboard', async test('should use the minWidth if specified resizing using keyboard', async () => { const onColumnResize = vi.fn(); await setup({ columns, rows: [], onColumnResize }); - const grid = getGrid(); await expect.element(grid).toHaveStyle({ gridTemplateColumns: '100px 200px' }); - const col2 = getHeaderCell('col2'); + const col2 = page.getHeaderCell({ name: 'col2' }); await userEvent.click(col2); await userEvent.keyboard(`{Control>}${'{ArrowLeft}'.repeat(12)}{/Control}`); await expect.element(grid).toHaveStyle({ gridTemplateColumns: '100px 100px' }); @@ -129,7 +124,6 @@ test('should auto resize column when resize handle is double clicked', async () ], onColumnResize }); - const grid = getGrid(); await expect.element(grid).toHaveStyle({ gridTemplateColumns: '100px 200px' }); await autoResize('col2'); await testGridTemplateColumns('100px 327.703px', '100px 327.833px', '100px 400px'); @@ -156,7 +150,6 @@ test('should use the maxWidth if specified on auto resize', async () => { } ] }); - const grid = getGrid(); await expect.element(grid).toHaveStyle({ gridTemplateColumns: '100px 200px' }); await autoResize('col2'); await expect.element(grid).toHaveStyle({ gridTemplateColumns: '100px 400px' }); @@ -172,7 +165,6 @@ test('should use the minWidth if specified on auto resize', async () => { } ] }); - const grid = getGrid(); await expect.element(grid).toHaveStyle({ gridTemplateColumns: '100px 200px' }); await autoResize('col2'); await expect.element(grid).toHaveStyle({ gridTemplateColumns: '100px 100px' }); @@ -278,7 +270,6 @@ test('should use columnWidths and onColumnWidthsChange props when provided', asy await page.render(); - const grid = getGrid(); await expect.element(grid).toHaveStyle({ gridTemplateColumns: '101px 201px' }); await autoResize('col2'); expect(onColumnWidthsChangeSpy).toHaveBeenCalledExactlyOnceWith( @@ -322,8 +313,6 @@ test('should use columnWidths and onColumnWidthsChange props when provided', asy }); async function testGridTemplateColumns(chrome: string, firefox: string, firefoxCI = firefox) { - const grid = getGrid(); - const gridTemplateColumns = navigator.userAgent.includes('Chrome') ? chrome : __IS_CI__ diff --git a/test/browser/column/summaryCellClass.test.ts b/test/browser/column/summaryCellClass.test.ts index 45d433b679..6586e48350 100644 --- a/test/browser/column/summaryCellClass.test.ts +++ b/test/browser/column/summaryCellClass.test.ts @@ -1,7 +1,11 @@ +import { page } from 'vitest/browser'; + import type { Column } from '../../../src'; import { cellClassname as cellClass } from '../../../src/style/cell'; import { summaryCellClassname } from '../../../src/SummaryCell'; -import { getCells, setup } from '../utils'; +import { setup } from '../utils'; + +const cells = page.getCell(); interface SummaryRow { id: number; @@ -19,9 +23,8 @@ test('summaryCellClass is undefined', async () => { } ]; await setup({ columns, topSummaryRows, bottomSummaryRows, rows: [] }); - const [cell1, cell2] = getCells(); - await expect.element(cell1).toHaveClass(cellClassname, { exact: true }); - await expect.element(cell2).toHaveClass(cellClassname, { exact: true }); + await expect.element(cells.nth(0)).toHaveClass(cellClassname, { exact: true }); + await expect.element(cells.nth(1)).toHaveClass(cellClassname, { exact: true }); }); test('summaryCellClass is a string', async () => { @@ -33,8 +36,7 @@ test('summaryCellClass is a string', async () => { } ]; await setup({ columns, topSummaryRows, bottomSummaryRows, rows: [] }); - const cells = getCells(); - for (const cell of cells) { + for (const cell of cells.all()) { await expect.element(cell).toHaveClass(`${cellClassname} my-cell`, { exact: true }); } }); @@ -48,11 +50,10 @@ test('summaryCellClass returns a string', async () => { } ]; await setup({ columns, topSummaryRows, bottomSummaryRows, rows: [] }); - const [cell1, cell2, cell3, cell4] = getCells(); - await expect.element(cell1).toHaveClass(`${cellClassname} my-cell-0`, { exact: true }); - await expect.element(cell2).toHaveClass(`${cellClassname} my-cell-1`, { exact: true }); - await expect.element(cell3).toHaveClass(`${cellClassname} my-cell-2`, { exact: true }); - await expect.element(cell4).toHaveClass(`${cellClassname} my-cell-3`, { exact: true }); + await expect.element(cells.nth(0)).toHaveClass(`${cellClassname} my-cell-0`, { exact: true }); + await expect.element(cells.nth(1)).toHaveClass(`${cellClassname} my-cell-1`, { exact: true }); + await expect.element(cells.nth(2)).toHaveClass(`${cellClassname} my-cell-2`, { exact: true }); + await expect.element(cells.nth(3)).toHaveClass(`${cellClassname} my-cell-3`, { exact: true }); }); test('summaryCellClass returns undefined', async () => { @@ -64,8 +65,7 @@ test('summaryCellClass returns undefined', async () => { } ]; await setup({ columns, topSummaryRows, bottomSummaryRows, rows: [] }); - const cells = getCells(); - for (const cell of cells) { + for (const cell of cells.all()) { await expect.element(cell).toHaveClass(cellClassname, { exact: true }); } }); diff --git a/test/browser/columnOrder.test.tsx b/test/browser/columnOrder.test.tsx index 2e58efe31d..345e93c96d 100644 --- a/test/browser/columnOrder.test.tsx +++ b/test/browser/columnOrder.test.tsx @@ -4,6 +4,8 @@ import { DataGrid, SelectColumn, TreeDataGrid } from '../../src'; import type { Column } from '../../src'; import { testCount } from './utils'; +const headerCells = page.getHeaderCell(); + const frozen1: Column = { key: 'f1', name: 'frz1', @@ -46,9 +48,10 @@ test('column order', async () => { )); } - const headerCells = page.getByRole('columnheader'); await testCount(headerCells, expected.length); - expect(headerCells.elements().map((c) => c.textContent)).toStrictEqual(expected); + for (const [n, text] of expected.entries()) { + await expect.element(headerCells.nth(n)).toHaveTextContent(text); + } await unmount(); } diff --git a/test/browser/copyPaste.test.tsx b/test/browser/copyPaste.test.tsx index c0fe2c30b6..e9ace61216 100644 --- a/test/browser/copyPaste.test.tsx +++ b/test/browser/copyPaste.test.tsx @@ -3,7 +3,7 @@ import { page, userEvent } from 'vitest/browser'; import { DataGrid } from '../../src'; import type { CellPasteArgs, Column } from '../../src'; -import { getCellsAtRowIndex, getSelectedCell } from './utils'; +import { getCellsAtRowIndex } from './utils'; interface Row { col: string; @@ -70,7 +70,7 @@ function setup() { test('should call onCellCopy on cell copy', async () => { await setup(); - await userEvent.click(getCellsAtRowIndex(0)[0]); + await userEvent.click(getCellsAtRowIndex(0).nth(0)); await userEvent.copy(); expect(onCellCopySpy).toHaveBeenCalledExactlyOnceWith( { @@ -83,7 +83,7 @@ test('should call onCellCopy on cell copy', async () => { test('should call onCellPaste on cell paste', async () => { await setup(); - await userEvent.click(getCellsAtRowIndex(0)[0]); + await userEvent.click(getCellsAtRowIndex(0).nth(0)); await userEvent.paste(); expect(onCellPasteSpy).toHaveBeenCalledExactlyOnceWith( { @@ -96,14 +96,14 @@ test('should call onCellPaste on cell paste', async () => { test('should not allow paste on readonly cells', async () => { await setup(); - await userEvent.click(getCellsAtRowIndex(2)[0]); + await userEvent.click(getCellsAtRowIndex(2).nth(0)); await userEvent.paste(); expect(onCellPasteSpy).not.toHaveBeenCalled(); }); test('should allow copying a readonly cell', async () => { await setup(); - await userEvent.click(getCellsAtRowIndex(2)[0]); + await userEvent.click(getCellsAtRowIndex(2).nth(0)); await userEvent.copy(); expect(onCellCopySpy).toHaveBeenCalledExactlyOnceWith( { @@ -131,7 +131,7 @@ test('should not allow copy/paste on header or summary cells', async () => { test('should not start editing when pressing ctrl+', async () => { await setup(); - await userEvent.click(getCellsAtRowIndex(1)[0]); + await userEvent.click(getCellsAtRowIndex(1).nth(0)); await userEvent.keyboard('{Control>}b'); - await expect.element(getSelectedCell()).not.toHaveClass('rdg-editor-container'); + await expect.element(page.getSelectedCell()).not.toHaveClass('rdg-editor-container'); }); diff --git a/test/browser/direction.test.ts b/test/browser/direction.test.ts index b6c57b491a..6c31733aa9 100644 --- a/test/browser/direction.test.ts +++ b/test/browser/direction.test.ts @@ -1,7 +1,10 @@ -import { userEvent } from 'vitest/browser'; +import { page, userEvent } from 'vitest/browser'; import type { Column } from '../../src'; -import { getGrid, getSelectedCell, setup, tabIntoGrid } from './utils'; +import { setup, tabIntoGrid } from './utils'; + +const grid = page.getGrid(); +const selectedCell = grid.getSelectedCell(); interface Row { id: number; @@ -23,27 +26,27 @@ const rows: readonly Row[] = []; test('should use left to right direction by default', async () => { await setup({ rows, columns }, true); - await expect.element(getGrid()).toHaveAttribute('dir', 'ltr'); + await expect.element(grid).toHaveAttribute('dir', 'ltr'); await tabIntoGrid(); - await expect.element(getSelectedCell()).toHaveTextContent('ID'); + await expect.element(selectedCell).toHaveTextContent('ID'); await userEvent.keyboard('{ArrowRight}'); - await expect.element(getSelectedCell()).toHaveTextContent('Name'); + await expect.element(selectedCell).toHaveTextContent('Name'); }); test('should use left to right direction if direction prop is set to ltr', async () => { await setup({ rows, columns, direction: 'ltr' }, true); - await expect.element(getGrid()).toHaveAttribute('dir', 'ltr'); + await expect.element(grid).toHaveAttribute('dir', 'ltr'); await tabIntoGrid(); - await expect.element(getSelectedCell()).toHaveTextContent('ID'); + await expect.element(selectedCell).toHaveTextContent('ID'); await userEvent.keyboard('{ArrowRight}'); - await expect.element(getSelectedCell()).toHaveTextContent('Name'); + await expect.element(selectedCell).toHaveTextContent('Name'); }); test('should use right to left direction if direction prop is set to rtl', async () => { await setup({ rows, columns, direction: 'rtl' }, true); - await expect.element(getGrid()).toHaveAttribute('dir', 'rtl'); + await expect.element(grid).toHaveAttribute('dir', 'rtl'); await tabIntoGrid(); - await expect.element(getSelectedCell()).toHaveTextContent('ID'); + await expect.element(selectedCell).toHaveTextContent('ID'); await userEvent.keyboard('{ArrowLeft}'); - await expect.element(getSelectedCell()).toHaveTextContent('Name'); + await expect.element(selectedCell).toHaveTextContent('Name'); }); diff --git a/test/browser/dragFill.test.tsx b/test/browser/dragFill.test.tsx index 20bc601816..3bc715fef9 100644 --- a/test/browser/dragFill.test.tsx +++ b/test/browser/dragFill.test.tsx @@ -5,6 +5,8 @@ import { DataGrid } from '../../src'; import type { Column, FillEvent } from '../../src'; import { getCellsAtRowIndex } from './utils'; +const dragHandle = page.getDragHandle(); + interface Row { col: string; } @@ -43,56 +45,52 @@ function DragFillTest({ allowDragFill = true }: { allowDragFill?: boolean }) { ); } -function getDragHandle() { - return document.querySelector('.rdg-cell-drag-handle'); -} - test('should not allow dragFill if onFill is undefined', async () => { await setup(false); - await userEvent.click(getCellsAtRowIndex(0)[0]); - expect(getDragHandle()).not.toBeInTheDocument(); + await userEvent.click(getCellsAtRowIndex(0).nth(0)); + await expect.element(dragHandle).not.toBeInTheDocument(); }); test('should allow dragFill if onFill is specified', async () => { await setup(); - await userEvent.click(getCellsAtRowIndex(0)[0]); - await expect.element(getCellsAtRowIndex(0)[0]).toHaveFocus(); - await userEvent.dblClick(getDragHandle()!); - await expect.element(getCellsAtRowIndex(0)[0]).toHaveFocus(); - await expect.element(getCellsAtRowIndex(1)[0]).toHaveTextContent('a1'); - await expect.element(getCellsAtRowIndex(2)[0]).toHaveTextContent('a1'); - await expect.element(getCellsAtRowIndex(3)[0]).toHaveTextContent('a4'); // readonly cell + await userEvent.click(getCellsAtRowIndex(0).nth(0)); + await expect.element(getCellsAtRowIndex(0).nth(0)).toHaveFocus(); + await userEvent.dblClick(dragHandle); + await expect.element(getCellsAtRowIndex(0).nth(0)).toHaveFocus(); + await expect.element(getCellsAtRowIndex(1).nth(0)).toHaveTextContent('a1'); + await expect.element(getCellsAtRowIndex(2).nth(0)).toHaveTextContent('a1'); + await expect.element(getCellsAtRowIndex(3).nth(0)).toHaveTextContent('a4'); // readonly cell }); test('should update single row using mouse', async () => { await setup(); await commands.dragFill('a1', 'a2'); - await expect.element(getCellsAtRowIndex(1)[0]).toHaveTextContent('a1'); - await expect.element(getCellsAtRowIndex(2)[0]).toHaveTextContent('a3'); - await expect.element(getCellsAtRowIndex(0)[0]).toHaveFocus(); + await expect.element(getCellsAtRowIndex(1).nth(0)).toHaveTextContent('a1'); + await expect.element(getCellsAtRowIndex(2).nth(0)).toHaveTextContent('a3'); + await expect.element(getCellsAtRowIndex(0).nth(0)).toHaveFocus(); }); test('should update multiple rows using mouse', async () => { await setup(); await commands.dragFill('a1', 'a4'); - await expect.element(getCellsAtRowIndex(1)[0]).toHaveTextContent('a1'); - await expect.element(getCellsAtRowIndex(2)[0]).toHaveTextContent('a1'); - await expect.element(getCellsAtRowIndex(3)[0]).toHaveTextContent('a4'); // readonly cell + await expect.element(getCellsAtRowIndex(1).nth(0)).toHaveTextContent('a1'); + await expect.element(getCellsAtRowIndex(2).nth(0)).toHaveTextContent('a1'); + await expect.element(getCellsAtRowIndex(3).nth(0)).toHaveTextContent('a4'); // readonly cell }); test('should allow drag up using mouse', async () => { await setup(); await commands.dragFill('a4', 'a1'); - await expect.element(getCellsAtRowIndex(0)[0]).toHaveTextContent('a4'); - await expect.element(getCellsAtRowIndex(1)[0]).toHaveTextContent('a4'); - await expect.element(getCellsAtRowIndex(2)[0]).toHaveTextContent('a4'); + await expect.element(getCellsAtRowIndex(0).nth(0)).toHaveTextContent('a4'); + await expect.element(getCellsAtRowIndex(1).nth(0)).toHaveTextContent('a4'); + await expect.element(getCellsAtRowIndex(2).nth(0)).toHaveTextContent('a4'); }); test('should focus the cell when drag handle is clicked', async () => { await setup(); - await userEvent.click(getCellsAtRowIndex(0)[0]); + await userEvent.click(getCellsAtRowIndex(0).nth(0)); await userEvent.click(document.body); await expect.element(document.body).toHaveFocus(); - await userEvent.click(getDragHandle()!); - await expect.element(getCellsAtRowIndex(0)[0]).toHaveFocus(); + await userEvent.click(dragHandle); + await expect.element(getCellsAtRowIndex(0).nth(0)).toHaveFocus(); }); diff --git a/test/browser/events.test.tsx b/test/browser/events.test.tsx index cb99f34f67..574001b9cb 100644 --- a/test/browser/events.test.tsx +++ b/test/browser/events.test.tsx @@ -2,7 +2,6 @@ import { page, userEvent } from 'vitest/browser'; import { DataGrid } from '../../src'; import type { Column, DataGridProps } from '../../src'; -import { getCell } from './utils'; interface Row { col1: number; @@ -63,10 +62,10 @@ describe('Events', () => { }} /> ); - await userEvent.click(getCell('1')); - await expect.element(getCell('1')).toHaveAttribute('aria-selected', 'false'); - await userEvent.click(getCell('a1')); - await expect.element(getCell('a1')).toHaveAttribute('aria-selected', 'true'); + await userEvent.click(page.getCell({ name: '1' })); + await expect.element(page.getCell({ name: '1' })).toHaveAttribute('aria-selected', 'false'); + await userEvent.click(page.getCell({ name: 'a1' })); + await expect.element(page.getCell({ name: 'a1' })).toHaveAttribute('aria-selected', 'true'); }); it('should be able to open editor editor on single click using onCellClick', async () => { @@ -80,9 +79,9 @@ describe('Events', () => { }} /> ); - await userEvent.click(getCell('1')); + await userEvent.click(page.getCell({ name: '1' })); await expect.element(page.getByLabelText('col1-editor')).not.toBeInTheDocument(); - await userEvent.click(getCell('a1')); + await userEvent.click(page.getCell({ name: 'a1' })); await expect.element(page.getByRole('textbox', { name: 'col2-editor' })).toBeInTheDocument(); }); @@ -96,9 +95,9 @@ describe('Events', () => { }} /> ); - await userEvent.dblClick(getCell('1')); + await userEvent.dblClick(page.getCell({ name: '1' })); await expect.element(page.getByLabelText('col1-editor')).not.toBeInTheDocument(); - await userEvent.dblClick(getCell('a1')); + await userEvent.dblClick(page.getCell({ name: 'a1' })); await expect.element(page.getByRole('textbox', { name: 'col2-editor' })).toBeInTheDocument(); }); @@ -106,7 +105,7 @@ describe('Events', () => { const onCellContextMenu = vi.fn(); await page.render(); expect(onCellContextMenu).not.toHaveBeenCalled(); - await userEvent.click(getCell('1'), { button: 'right' }); + await userEvent.click(page.getCell({ name: '1' }), { button: 'right' }); expect(onCellContextMenu).toHaveBeenCalledExactlyOnceWith( expect.objectContaining({ column: expect.objectContaining(columns[0]), @@ -127,7 +126,7 @@ describe('Events', () => { expect(onSelectedCellChange).not.toHaveBeenCalled(); // Selected by click - await userEvent.click(getCell('a1')); + await userEvent.click(page.getCell({ name: 'a1' })); expect(onSelectedCellChange).toHaveBeenLastCalledWith({ column: expect.objectContaining(columns[1]), row: rows[0], @@ -136,7 +135,7 @@ describe('Events', () => { expect(onSelectedCellChange).toHaveBeenCalledOnce(); // Selected by double click - await userEvent.dblClick(getCell('1')); + await userEvent.dblClick(page.getCell({ name: '1' })); expect(onSelectedCellChange).toHaveBeenLastCalledWith({ column: expect.objectContaining(columns[0]), row: rows[0], @@ -145,7 +144,7 @@ describe('Events', () => { expect(onSelectedCellChange).toHaveBeenCalledTimes(2); // Selected by right-click - await userEvent.click(getCell('2'), { button: 'right' }); + await userEvent.click(page.getCell({ name: '2' }), { button: 'right' }); expect(onSelectedCellChange).toHaveBeenLastCalledWith({ column: expect.objectContaining(columns[0]), row: rows[1], diff --git a/test/browser/headerRowClass.test.ts b/test/browser/headerRowClass.test.ts index 9c88e9508a..a532b5a46b 100644 --- a/test/browser/headerRowClass.test.ts +++ b/test/browser/headerRowClass.test.ts @@ -4,6 +4,8 @@ import type { Column } from '../../src'; import { headerRowClassname } from '../../src/HeaderRow'; import { setup } from './utils'; +const headerRow = page.getHeaderRow(); + interface Row { id: number; } @@ -17,8 +19,7 @@ test('headerRowClass is undefined', async () => { rows, headerRowClass: undefined }); - const header = page.getByRole('row'); - await expect.element(header).toHaveClass(headerRowClassname, { exact: true }); + await expect.element(headerRow).toHaveClass(headerRowClassname, { exact: true }); }); test('headerRowClass is a string', async () => { @@ -27,6 +28,7 @@ test('headerRowClass is a string', async () => { rows, headerRowClass: 'my-header-row' }); - const header = page.getByRole('row'); - await expect.element(header).toHaveClass(`${headerRowClassname} my-header-row`, { exact: true }); + await expect + .element(headerRow) + .toHaveClass(`${headerRowClassname} my-header-row`, { exact: true }); }); diff --git a/test/browser/keyboardNavigation.test.tsx b/test/browser/keyboardNavigation.test.tsx index a6e0d04bed..91cf9f3a01 100644 --- a/test/browser/keyboardNavigation.test.tsx +++ b/test/browser/keyboardNavigation.test.tsx @@ -1,8 +1,17 @@ -import { commands, page, userEvent } from 'vitest/browser'; +import { page, userEvent } from 'vitest/browser'; import { DataGrid, SelectColumn } from '../../src'; import type { Column } from '../../src'; -import { getSelectedCell, setup, tabIntoGrid, testCount, validateCellPosition } from './utils'; +import { + getRowWithCell, + scrollGrid, + setup, + tabIntoGrid, + testCount, + validateCellPosition +} from './utils'; + +const selectedCell = page.getSelectedCell(); type Row = undefined; @@ -24,7 +33,7 @@ test('keyboard navigation', async () => { await setup({ columns, rows, topSummaryRows, bottomSummaryRows }, true); // no initial selection - await expect.element(getSelectedCell()).not.toBeInTheDocument(); + await expect.element(selectedCell).not.toBeInTheDocument(); // tab into the grid await tabIntoGrid(); @@ -128,7 +137,7 @@ test('grid enter/exit', async () => { const afterButton = page.getByRole('button', { name: 'After' }); // no initial selection - await expect.element(getSelectedCell()).not.toBeInTheDocument(); + await expect.element(selectedCell).not.toBeInTheDocument(); // tab into the grid await tabIntoGrid(); @@ -155,7 +164,7 @@ test('grid enter/exit', async () => { await userEvent.click(afterButton); await userEvent.tab({ shift: true }); await validateCellPosition(0, 3); - await expect.element(getSelectedCell().getByRole('checkbox')).toHaveFocus(); + await expect.element(selectedCell.getByRole('checkbox')).toHaveFocus(); // tab tabs out of the grid if we are at the last cell await userEvent.keyboard('{Control>}{end}{/Control}'); @@ -170,15 +179,15 @@ test('navigation with focusable cell renderer', async () => { await validateCellPosition(0, 1); // cell should not set tabIndex to 0 if it contains a focusable cell renderer - await expect.element(getSelectedCell()).toHaveAttribute('tabIndex', '-1'); - const checkbox = getSelectedCell().getByRole('checkbox'); + await expect.element(selectedCell).toHaveAttribute('tabIndex', '-1'); + const checkbox = selectedCell.getByRole('checkbox'); await expect.element(checkbox).toHaveFocus(); await expect.element(checkbox).toHaveAttribute('tabIndex', '0'); await userEvent.tab(); await validateCellPosition(1, 1); // cell should set tabIndex to 0 if it does not have focusable cell renderer - await expect.element(getSelectedCell()).toHaveAttribute('tabIndex', '0'); + await expect.element(selectedCell).toHaveAttribute('tabIndex', '0'); }); test('navigation when header and summary rows have focusable elements', async () => { @@ -240,15 +249,12 @@ test('navigation when header and summary rows have focusable elements', async () await userEvent.tab({ shift: true }); await userEvent.tab({ shift: true }); await validateCellPosition(1, 2); - await expect.element(getSelectedCell()).toHaveFocus(); + await expect.element(selectedCell).toHaveFocus(); }); test('navigation when selected cell not in the viewport', async () => { const columns: Column[] = [SelectColumn]; - const selectedRowCells = page - .getByRole('row') - .filter({ has: getSelectedCell() }) - .getByRole('gridcell'); + const selectedRowCells = getRowWithCell(selectedCell).getCell(); for (let i = 0; i < 99; i++) { columns.push({ key: `col${i}`, name: `col${i}`, frozen: i < 5 }); } @@ -259,13 +265,13 @@ test('navigation when selected cell not in the viewport', async () => { await userEvent.keyboard('{Control>}{end}{/Control}{arrowup}{arrowup}'); await validateCellPosition(99, 100); await expect.element(selectedRowCells).not.toHaveLength(1); - await commands.scrollGrid({ scrollTop: 0 }); + await scrollGrid({ top: 0 }); await testCount(selectedRowCells, 1); await userEvent.keyboard('{arrowup}'); await validateCellPosition(99, 99); await expect.element(selectedRowCells).not.toHaveLength(1); - await commands.scrollGrid({ scrollLeft: 0 }); + await scrollGrid({ left: 0 }); await userEvent.keyboard('{arrowdown}'); await validateCellPosition(99, 100); @@ -273,7 +279,7 @@ test('navigation when selected cell not in the viewport', async () => { '{home}{arrowright}{arrowright}{arrowright}{arrowright}{arrowright}{arrowright}{arrowright}' ); await validateCellPosition(7, 100); - await commands.scrollGrid({ scrollLeft: 2000 }); + await scrollGrid({ left: 2000 }); await userEvent.keyboard('{arrowleft}'); await validateCellPosition(6, 100); }); @@ -297,7 +303,7 @@ test('reset selected cell when column is removed', async () => { await rerender(); - await expect.element(getSelectedCell()).not.toBeInTheDocument(); + await expect.element(selectedCell).not.toBeInTheDocument(); }); test('reset selected cell when row is removed', async () => { @@ -319,7 +325,7 @@ test('reset selected cell when row is removed', async () => { await rerender(); - await expect.element(getSelectedCell()).not.toBeInTheDocument(); + await expect.element(selectedCell).not.toBeInTheDocument(); }); test('should not change the left and right arrow behavior for right to left languages', async () => { diff --git a/test/browser/label.test.ts b/test/browser/label.test.ts index 7d006928c1..d5cacdcfd5 100644 --- a/test/browser/label.test.ts +++ b/test/browser/label.test.ts @@ -1,4 +1,8 @@ -import { getGrid, setup } from './utils'; +import { page } from 'vitest/browser'; + +import { setup } from './utils'; + +const grid = page.getGrid(); test('should set label and description', async () => { await setup({ @@ -12,7 +16,6 @@ test('should set label and description', async () => { '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'); diff --git a/test/browser/renderTextEditor.test.tsx b/test/browser/renderTextEditor.test.tsx index 8638f1dc90..a4b4d3e91a 100644 --- a/test/browser/renderTextEditor.test.tsx +++ b/test/browser/renderTextEditor.test.tsx @@ -28,7 +28,7 @@ function Test() { test('renderTextEditor', async () => { await page.render(); - const cell = page.getByRole('gridcell'); + const cell = page.getCell(); await expect.element(cell).toHaveTextContent(/^Tacitus Kilgore$/); await userEvent.dblClick(cell); const input = page.getByRole('textbox'); diff --git a/test/browser/renderers.test.tsx b/test/browser/renderers.test.tsx index aa6b200a41..54059ee126 100644 --- a/test/browser/renderers.test.tsx +++ b/test/browser/renderers.test.tsx @@ -17,7 +17,7 @@ import type { RenderSortStatusProps, SortColumn } from '../../src'; -import { getCell, getHeaderCell, getRowByCellName, setup, testCount, testRowCount } from './utils'; +import { getRowWithCell, setup, testCount, testRowCount } from './utils'; interface Row { id: number; @@ -116,21 +116,21 @@ function setupContext(props: DataGridProps test('fallback defined using renderers prop with no rows', async () => { await setup({ columns, rows: noRows, renderers: { noRowsFallback: } }); - await testRowCount(1); + await testRowCount(0); await expect.element(page.getByText('Local no rows fallback')).toBeInTheDocument(); }); test('fallback defined using context with no rows', async () => { await setupContext({ columns, rows: noRows }); - await testRowCount(1); + await testRowCount(0); await expect.element(page.getByText('Global no rows fallback')).toBeInTheDocument(); }); test('fallback defined using both context and renderers with no rows', async () => { await setupContext({ columns, rows: noRows, renderers: { noRowsFallback: } }); - await testRowCount(1); + await testRowCount(0); await expect.element(page.getByText('Local no rows fallback')).toBeInTheDocument(); }); @@ -141,14 +141,14 @@ test('fallback defined using renderers prop with a row', async () => { renderers: { noRowsFallback: } }); - await testRowCount(2); + await testRowCount(1); await expect.element(page.getByText('Local no rows fallback')).not.toBeInTheDocument(); }); test('fallback defined using context with a row', async () => { await setupContext({ columns, rows: [{ id: 1, col1: 'value 1', col2: 'value 2' }] }); - await testRowCount(2); + await testRowCount(1); await expect.element(page.getByText('Global no rows fallback')).not.toBeInTheDocument(); }); @@ -159,7 +159,7 @@ test('fallback defined using both context and renderers with a row', async () => renderers: { noRowsFallback: } }); - await testRowCount(2); + await testRowCount(1); await expect.element(page.getByText('Global no rows fallback')).not.toBeInTheDocument(); await expect.element(page.getByText('Local no rows fallback')).not.toBeInTheDocument(); }); @@ -167,21 +167,21 @@ test('fallback defined using both context and renderers with a row', async () => test('checkbox defined using renderers prop', async () => { await setup({ columns, rows: noRows, renderers: { renderCheckbox: renderLocalCheckbox } }); - await testRowCount(1); + await testRowCount(0); await expect.element(page.getByText('Local checkbox')).toBeInTheDocument(); }); test('checkbox defined using context', async () => { await setupContext({ columns, rows: noRows }); - await testRowCount(1); + await testRowCount(0); await expect.element(page.getByText('Global checkbox')).toBeInTheDocument(); }); test('checkbox defined using both context and renderers', async () => { await setupContext({ columns, rows: noRows, renderers: { renderCheckbox: renderLocalCheckbox } }); - await testRowCount(1); + await testRowCount(0); await expect.element(page.getByText('Local checkbox')).toBeInTheDocument(); await expect.element(page.getByText('Global checkbox')).not.toBeInTheDocument(); }); @@ -189,8 +189,8 @@ test('checkbox defined using both context and renderers', async () => { test('sortPriority defined using both contexts', async () => { await setupContext({ columns, rows: noRows }); - const column1 = getHeaderCell('Column1'); - const column2 = getHeaderCell('Column2'); + const column1 = page.getHeaderCell({ name: 'Column1' }); + const column2 = page.getHeaderCell({ name: 'Column2' }); await userEvent.click(column1); await userEvent.keyboard('{Control>}'); await userEvent.click(column2); @@ -210,8 +210,8 @@ test('sortPriority defined using both contexts and renderers', async () => { renderers: { renderSortStatus: renderLocalSortStatus } }); - const column1 = getHeaderCell('Column1'); - const column2 = getHeaderCell('Column2'); + const column1 = page.getHeaderCell({ name: 'Column1' }); + const column2 = page.getHeaderCell({ name: 'Column2' }); await userEvent.click(column2); await userEvent.keyboard('{Control>}'); await userEvent.click(column1); @@ -227,8 +227,8 @@ test('sortPriority defined using both contexts and renderers', async () => { test('renderCell defined using context', async () => { await setupContext({ columns, rows: [{ id: 1, col1: 'value 1', col2: 'value 2' }] }); - const cell1 = getCell('value 1'); - const cell2 = getCell('value 2'); + const cell1 = page.getCell({ name: 'value 1' }); + const cell2 = page.getCell({ name: 'value 2' }); await expect.element(cell1).toHaveClass('global'); await expect.element(cell1).not.toHaveClass('local'); await expect.element(cell1).toHaveStyle({ fontStyle: 'italic' }); @@ -245,8 +245,8 @@ test('renderCell defined using both contexts and renderers', async () => { renderers: { renderCell: renderLocalCell } }); - const cell1 = getCell('value 1'); - const cell2 = getCell('value 2'); + const cell1 = page.getCell({ name: 'value 1' }); + const cell2 = page.getCell({ name: 'value 2' }); await expect.element(cell1).toHaveClass('local'); await expect.element(cell1).not.toHaveClass('global'); await expect.element(cell1).toHaveStyle({ fontStyle: 'normal' }); @@ -259,7 +259,7 @@ test('renderCell defined using both contexts and renderers', async () => { test('renderRow defined using context', async () => { await setupContext({ columns, rows: [{ id: 1, col1: 'value 1', col2: 'value 2' }] }); - const row = getRowByCellName('value 1'); + const row = getRowWithCell(page.getCell({ name: 'value 1' })); await expect.element(row).toHaveClass('global'); await expect.element(row).not.toHaveClass('local'); }); @@ -271,7 +271,7 @@ test('renderRow defined using both contexts and renderers', async () => { renderers: { renderRow: renderLocalRow } }); - const row = getRowByCellName('value 1'); + const row = getRowWithCell(page.getCell({ name: 'value 1' })); await expect.element(row).toHaveClass('local'); await expect.element(row).not.toHaveClass('global'); }); diff --git a/test/browser/rowClass.test.ts b/test/browser/rowClass.test.ts index 4742a95e50..7b5659dab2 100644 --- a/test/browser/rowClass.test.ts +++ b/test/browser/rowClass.test.ts @@ -1,61 +1,53 @@ +import { page } from 'vitest/browser'; + import type { Column } from '../../src'; import { rowClassname } from '../../src/style/row'; -import { getRowByCellName, setup } from './utils'; +import { setup } from './utils'; + +const rows = page.getRow(); interface Row { id: number; } const columns: readonly Column[] = [{ key: 'id', name: 'ID' }]; -const rows: readonly Row[] = [{ id: 0 }, { id: 1 }, { id: 2 }]; +const initialRows: readonly Row[] = [{ id: 0 }, { id: 1 }, { id: 2 }]; test('rowClass is undefined', async () => { await setup({ columns, - rows, + rows: initialRows, rowClass: undefined }); - await expect - .element(getRowByCellName('0')) - .toHaveClass(`${rowClassname} rdg-row-even`, { exact: true }); - await expect - .element(getRowByCellName('1')) - .toHaveClass(`${rowClassname} rdg-row-odd`, { exact: true }); - await expect - .element(getRowByCellName('2')) - .toHaveClass(`${rowClassname} rdg-row-even`, { exact: true }); + await expect.element(rows.nth(0)).toHaveClass(`${rowClassname} rdg-row-even`, { exact: true }); + await expect.element(rows.nth(1)).toHaveClass(`${rowClassname} rdg-row-odd`, { exact: true }); + await expect.element(rows.nth(2)).toHaveClass(`${rowClassname} rdg-row-even`, { exact: true }); }); test('rowClass returns a string', async () => { await setup({ columns, - rows, + rows: initialRows, rowClass: (row) => `my-row-${row.id}` }); await expect - .element(getRowByCellName('0')) + .element(rows.nth(0)) .toHaveClass(`${rowClassname} rdg-row-even my-row-0`, { exact: true }); await expect - .element(getRowByCellName('1')) + .element(rows.nth(1)) .toHaveClass(`${rowClassname} rdg-row-odd my-row-1`, { exact: true }); await expect - .element(getRowByCellName('2')) + .element(rows.nth(2)) .toHaveClass(`${rowClassname} rdg-row-even my-row-2`, { exact: true }); }); test('rowClass returns undefined', async () => { await setup({ columns, - rows, + rows: initialRows, rowClass: () => undefined }); - await expect - .element(getRowByCellName('0')) - .toHaveClass(`${rowClassname} rdg-row-even`, { exact: true }); - await expect - .element(getRowByCellName('1')) - .toHaveClass(`${rowClassname} rdg-row-odd`, { exact: true }); - await expect - .element(getRowByCellName('2')) - .toHaveClass(`${rowClassname} rdg-row-even`, { exact: true }); + await expect.element(rows.nth(0)).toHaveClass(`${rowClassname} rdg-row-even`, { exact: true }); + await expect.element(rows.nth(1)).toHaveClass(`${rowClassname} rdg-row-odd`, { exact: true }); + await expect.element(rows.nth(2)).toHaveClass(`${rowClassname} rdg-row-even`, { exact: true }); }); diff --git a/test/browser/rowHeight.test.ts b/test/browser/rowHeight.test.ts index 829e25d07e..b4c5087e0b 100644 --- a/test/browser/rowHeight.test.ts +++ b/test/browser/rowHeight.test.ts @@ -1,7 +1,9 @@ -import { userEvent } from 'vitest/browser'; +import { page, userEvent } from 'vitest/browser'; import type { Column, DataGridProps } from '../../src'; -import { getGrid, setup, tabIntoGrid, testRowCount } from './utils'; +import { setup, tabIntoGrid, testRowCount } from './utils'; + +const grid = page.getGrid(); type Row = number; @@ -23,42 +25,37 @@ function setupGrid(rowHeight: DataGridProps['rowHeight']) { async function expectGridRows(rowHeightFn: (row: number) => number, expected: string) { await setupGrid(rowHeightFn); - const grid = getGrid().element() as HTMLDivElement; - const gridTemplateRows = grid.style.gridTemplateRows; - - expect(gridTemplateRows).toBe(expected); + expect(grid.element().style.gridTemplateRows).toBe(expected); } test('rowHeight is number', async () => { await setupGrid(40); - const grid = getGrid(); await expect.element(grid).toHaveStyle({ gridTemplateRows: '40px 40px 40px 40px 40px 40px 40px 40px 40px 40px 40px 40px 40px 40px 40px 40px 40px 40px 40px 40px 40px 40px 40px 40px 40px 40px 40px 40px 40px 40px 40px 40px 40px 40px 40px 40px 40px 40px 40px 40px 40px 40px 40px 40px 40px 40px 40px 40px 40px 40px 40px' }); - await testRowCount(31); + await testRowCount(30); await tabIntoGrid(); - const gridEl = grid.element(); - expect(gridEl.scrollTop).toBe(0); + await expect.element(grid).toHaveProperty('scrollTop', 0); await userEvent.keyboard('{Control>}{end}'); + const gridEl = grid.element(); expect(gridEl.scrollTop + gridEl.clientHeight).toBe(gridEl.scrollHeight); }); test('rowHeight is function', async () => { await setupGrid((row) => [40, 60, 80][row % 3]); - const grid = getGrid(); await expect.element(grid).toHaveStyle({ gridTemplateRows: '35px 40px 60px 80px 40px 60px 80px 40px 60px 80px 40px 60px 80px 40px 60px 80px 40px 60px 80px 40px 60px 80px 40px 60px 80px 40px 60px 80px 40px 60px 80px 40px 60px 80px 40px 60px 80px 40px 60px 80px 40px 60px 80px 40px 60px 80px 40px 60px 80px 40px 60px' }); - await testRowCount(23); + await testRowCount(22); await tabIntoGrid(); - const gridEl = grid.element(); - expect(gridEl.scrollTop).toBe(0); + await expect.element(grid).toHaveProperty('scrollTop', 0); await userEvent.keyboard('{Control>}{end}'); + const gridEl = grid.element(); expect(gridEl.scrollTop + gridEl.clientHeight).toBe(gridEl.scrollHeight); }); diff --git a/test/browser/rowSelection.test.tsx b/test/browser/rowSelection.test.tsx index 6065c47e50..b48b387334 100644 --- a/test/browser/rowSelection.test.tsx +++ b/test/browser/rowSelection.test.tsx @@ -3,7 +3,9 @@ import { page, userEvent } from 'vitest/browser'; import { DataGrid, SelectColumn } from '../../src'; import type { Column } from '../../src'; -import { getCell, getRowByCell, getSelectAllCheckbox } from './utils'; + +const rows = page.getRow(); +const headerCheckbox = page.getSelectAllCheckbox(); interface Row { id: number; @@ -48,18 +50,14 @@ function setup(initialRows = defaultRows) { return page.render(); } -function getRowByText(rowIdx: number) { - return getRowByCell(getCell(String(rowIdx + 1))); -} - function testSelection(rowIdx: number, isSelected: boolean) { return expect - .element(getRowByText(rowIdx)) + .element(rows.nth(rowIdx)) .toHaveAttribute('aria-selected', isSelected ? 'true' : 'false'); } async function toggleSelection(rowIdx: number, shift = false, force = false) { - const checkbox = getRowByText(rowIdx).getByRole('checkbox', { name: 'Select' }); + const checkbox = rows.nth(rowIdx).getByRole('checkbox', { name: 'Select' }); if (shift) await userEvent.keyboard('{Shift>}'); await userEvent.click(checkbox, { force }); if (shift) await userEvent.keyboard('{/Shift}'); @@ -81,7 +79,7 @@ test('toggle selection when checkbox is clicked', async () => { test('toggle selection using keyboard', async () => { await setup(); await testSelection(0, false); - await userEvent.click(getRowByText(0).getByRole('checkbox', { name: 'Select' })); + await userEvent.click(rows.nth(0).getByRole('checkbox', { name: 'Select' })); await testSelection(0, true); await userEvent.keyboard(' '); await testSelection(0, false); @@ -95,7 +93,6 @@ test('toggle selection using keyboard', async () => { test('should partially select header checkbox', async () => { await setup(); - const headerCheckbox = getSelectAllCheckbox(); await expect.element(headerCheckbox).not.toBeChecked(); await expect.element(headerCheckbox).not.toBePartiallyChecked(); @@ -137,7 +134,7 @@ test('should not select row when isRowSelectionDisabled returns true', async () await toggleSelection(2); await testSelection(2, true); - await userEvent.click(getSelectAllCheckbox()); + await userEvent.click(headerCheckbox); await toggleSelection(0); await toggleSelection(2, true); await testSelection(0, true); @@ -147,7 +144,6 @@ test('should not select row when isRowSelectionDisabled returns true', async () test('select/deselect all rows when header checkbox is clicked', async () => { await setup(); - const headerCheckbox = getSelectAllCheckbox(); await expect.element(headerCheckbox).not.toBeChecked(); await userEvent.click(headerCheckbox); await testSelection(0, true); @@ -168,7 +164,7 @@ test('select/deselect all rows when header checkbox is clicked', async () => { test('header checkbox is not checked when there are no rows', async () => { await setup([]); - await expect.element(getSelectAllCheckbox()).not.toBeChecked(); + await expect.element(headerCheckbox).not.toBeChecked(); }); test('header checkbox is not necessarily checked when selectedRows.size === rows.length', async () => { @@ -181,7 +177,7 @@ test('header checkbox is not necessarily checked when selectedRows.size === rows /> ); - await expect.element(getSelectAllCheckbox()).not.toBeChecked(); + await expect.element(headerCheckbox).not.toBeChecked(); }); test('header checkbox is not necessarily checked when selectedRows.size > rows.length', async () => { @@ -194,7 +190,7 @@ test('header checkbox is not necessarily checked when selectedRows.size > rows.l /> ); - await expect.element(getSelectAllCheckbox()).not.toBeChecked(); + await expect.element(headerCheckbox).not.toBeChecked(); }); test('extra keys are preserved when updating the selectedRows Set', async () => { @@ -222,8 +218,6 @@ test('extra keys are preserved when updating the selectedRows Set', async () => await page.render(); - const headerCheckbox = getSelectAllCheckbox(); - await toggleSelection(1); expect(set).toStrictEqual(new Set([...initialSet, 2])); @@ -259,7 +253,7 @@ test('select/deselect rows using shift click', async () => { test('select rows using shift space', async () => { await setup(); - await userEvent.click(getCell('1')); + await userEvent.click(page.getCell({ name: '1' })); await userEvent.keyboard('{Shift>} {/Shift}'); await testSelection(0, true); await userEvent.keyboard(' '); diff --git a/test/browser/scrollToCell.test.tsx b/test/browser/scrollToCell.test.tsx index 192266fdc4..1e03a020c6 100644 --- a/test/browser/scrollToCell.test.tsx +++ b/test/browser/scrollToCell.test.tsx @@ -1,9 +1,8 @@ -import { useRef } from 'react'; -import { page, userEvent } from 'vitest/browser'; +import { createRef } from 'react'; +import { page } from 'vitest/browser'; -import { DataGrid } from '../../src'; import type { Column, DataGridHandle } from '../../src'; -import type { PartialPosition } from '../../src/ScrollToCell'; +import { setup } from './utils'; const rows: readonly number[] = Array.from({ length: 50 }, (_, i) => i); const summaryRows: readonly number[] = Array.from({ length: 2 }, (_, i) => i + 50); @@ -24,86 +23,67 @@ for (let i = 0; i < 50; i++) { }); } -let position: PartialPosition; - -function Grid() { - const ref = useRef(null); - return ( - <> - - - - ); -} +test('scrollToCell', async () => { + const ref = createRef(); + await setup({ + ref, + columns, + rows, + topSummaryRows: summaryRows, + rowHeight: 60 + }); -async function testScroll(p: PartialPosition) { - position = p; - await userEvent.click(page.getByRole('button')); -} + expect(ref.current).toBeDefined(); -test('scrollToCell', async () => { - await 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 }); + ref.current!.scrollToCell({ idx: 40, rowIdx: 30 }); 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 }); + ref.current!.scrollToCell({ idx: 6 }); await validateCellVisibility('6×30', true); await validateCellVisibility('40×30', false); - await testScroll({ idx: 40 }); + ref.current!.scrollToCell({ idx: 40 }); 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 }); + ref.current!.scrollToCell({ rowIdx: 1 }); await validateCellVisibility('40×1', true); await validateCellVisibility('40×30', false); - await testScroll({ rowIdx: 30 }); + ref.current!.scrollToCell({ rowIdx: 30 }); await validateCellVisibility('40×1', false); await validateCellVisibility('40×30', true); // should not scroll if scroll to column is frozen - await testScroll({ idx: 2 }); + ref.current!.scrollToCell({ idx: 2 }); await validateCellVisibility('40×30', true); // should not scroll if rowIdx is header row - await testScroll({ idx: -1 }); + ref.current!.scrollToCell({ idx: -1 }); await validateCellVisibility('40×30', true); // should not scroll if rowIdx is summary row - await testScroll({ idx: 50 }); + ref.current!.scrollToCell({ idx: 50 }); await validateCellVisibility('40×30', true); // should not scroll if position is out of bound - await testScroll({ idx: 60, rowIdx: 60 }); + ref.current!.scrollToCell({ idx: 60, rowIdx: 60 }); await validateCellVisibility('40×30', true); // should not scroll vertically when scrolling to summary row - await testScroll({ idx: 49, rowIdx: 51 }); + ref.current!.scrollToCell({ idx: 49, rowIdx: 51 }); await validateCellVisibility('49×30', true); }); function validateCellVisibility(name: string, isVisible: boolean) { - const cell = page.getByRole('gridcell', { name, exact: true }); + const cell = page.getCell({ name }); + if (isVisible) { return expect.element(cell).toBeVisible(); } diff --git a/test/browser/sorting.test.tsx b/test/browser/sorting.test.tsx index 31eb775a3d..9a442c0ea7 100644 --- a/test/browser/sorting.test.tsx +++ b/test/browser/sorting.test.tsx @@ -3,7 +3,6 @@ import { page, userEvent } from 'vitest/browser'; import { DataGrid } from '../../src'; import type { Column, SortColumn } from '../../src/types'; -import { getHeaderCell, getHeaderCellsNew } from './utils'; const columns: readonly Column[] = [ { key: 'colA', name: 'colA' }, @@ -41,7 +40,7 @@ function testSortColumns(expectedValue: readonly SortColumn[]) { test('should not sort if sortable is false', async () => { await setup(); - const headerCell = getHeaderCell('colD'); + const headerCell = page.getHeaderCell({ name: 'colD' }); await userEvent.click(headerCell); await expect.element(headerCell).not.toHaveAttribute('aria-sort'); await testSortColumns([]); @@ -49,7 +48,7 @@ test('should not sort if sortable is false', async () => { test('single column sort', async () => { await setup(); - const headerCell = getHeaderCell('colA'); + const headerCell = page.getHeaderCell({ name: 'colA' }); await userEvent.click(headerCell); await expect.element(headerCell).toHaveAttribute('aria-sort', 'ascending'); // priority is not shown for single sort @@ -65,9 +64,9 @@ test('single column sort', async () => { test('multi column sort', async () => { await setup(); - const headerCell1 = getHeaderCell('colA', false); - const headerCell2 = getHeaderCell('colB', false); - const headerCell3 = getHeaderCell('colC', false); + const headerCell1 = page.getHeaderCell({ name: 'colA', exact: false }); + const headerCell2 = page.getHeaderCell({ name: 'colB', exact: false }); + const headerCell3 = page.getHeaderCell({ name: 'colC', exact: false }); await userEvent.click(headerCell1); await userEvent.keyboard('{Control>}'); await userEvent.click(headerCell2); @@ -109,10 +108,9 @@ test('multi column sort', async () => { test('multi column sort with metakey', async () => { await setup(); - const [headerCell1, headerCell2] = getHeaderCellsNew('colA', 'colB'); - await userEvent.click(headerCell1); + await userEvent.click(page.getHeaderCell({ name: 'colA' })); await userEvent.keyboard('{Meta>}'); - await userEvent.click(headerCell2); + await userEvent.click(page.getHeaderCell({ name: 'colB' })); await testSortColumns([ { columnKey: 'colA', direction: 'ASC' }, { columnKey: 'colB', direction: 'DESC' } @@ -121,7 +119,7 @@ test('multi column sort with metakey', async () => { test('multi column sort with keyboard', async () => { await setup(); - const headerCell1 = getHeaderCell('colA'); + const headerCell1 = page.getHeaderCell({ name: '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 a2ce698997..af5f8d709e 100644 --- a/test/browser/utils.tsx +++ b/test/browser/utils.tsx @@ -31,99 +31,28 @@ export function setup( return page.render(grid); } -export function getGrid() { - return page.getByRole('grid'); -} - -export function getTreeGrid() { - return page.getByRole('treegrid'); -} - -export function getHeaderCell(name: string, exact = true) { - return page.getByRole('columnheader', { name, exact }); -} - -export function getHeaderCellsNew(...names: readonly string[]) { - return names.map((name) => getHeaderCell(name)); -} - -export function getRowByCell(cell: Locator) { - return page.getByRole('row').filter({ has: cell }); -} - -export function getRowByCellName(cell: string) { - return getRowByCell(getCell(cell)); -} - -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' }); -} - -export function getRows() { - return page.getByRole('row').elements().slice(1); -} - -export function getRowsNew() { - return page.getByRole('row'); +export function getRowWithCell(cell: Locator) { + return page.getRow().filter({ has: cell }); } export function getCellsAtRowIndex(rowIdx: number) { - return Array.from( - document.querySelectorAll(`[aria-rowindex="${rowIdx + 2}"] > .rdg-cell`) - ); -} - -export function getCells() { - return page.getByRole('gridcell').elements(); -} - -export function getHeaderCells() { - return page.getByRole('columnheader').elements(); -} - -export function getSelectedCell() { return page - .getByRole('gridcell', { selected: true }) - .or(page.getByRole('columnheader', { selected: true })) - .first(); + .getRow() + .and(page.getBySelector(`[aria-rowindex="${rowIdx + 2}"]`)) + .getCell(); } export async function validateCellPosition(columnIdx: number, rowIdx: number) { - const cell = getSelectedCell(); + const cell = page.getSelectedCell(); + const row = page.getRow().or(page.getHeaderRow()).filter({ has: cell }); await expect.element(cell).toHaveAttribute('aria-colindex', `${columnIdx + 1}`); - await expect - .element(page.getByRole('row').filter({ has: cell })) - .toHaveAttribute('aria-rowindex', `${rowIdx + 1}`); + await expect.element(row).toHaveAttribute('aria-rowindex', `${rowIdx + 1}`); } -export async function scrollGrid({ - scrollLeft, - scrollTop -}: { - scrollLeft?: number; - scrollTop?: number; -}) { - const grid = getGrid().element(); - - if (scrollLeft !== undefined) { - grid.scrollLeft = scrollLeft; - } - if (scrollTop !== undefined) { - grid.scrollTop = scrollTop; - } - - if (scrollLeft !== undefined || scrollTop !== undefined) { - // let the browser fire the 'scroll' event - await new Promise(requestAnimationFrame); - } +export async function scrollGrid(options: ScrollToOptions) { + page.getGrid().element().scrollTo(options); + // let the browser fire the 'scroll' event + await new Promise(requestAnimationFrame); } export async function tabIntoGrid() { @@ -135,6 +64,6 @@ export function testCount(locator: Locator, expectedCount: number) { return expect.element(locator).toHaveLength(expectedCount); } -export function testRowCount(expectedLength: number) { - return testCount(getRowsNew(), expectedLength); +export function testRowCount(expectedCount: number) { + return testCount(page.getRow(), expectedCount); } diff --git a/test/browser/virtualization.test.ts b/test/browser/virtualization.test.ts index c3868095bf..2c86542547 100644 --- a/test/browser/virtualization.test.ts +++ b/test/browser/virtualization.test.ts @@ -1,5 +1,11 @@ +import { page, type Locator } from 'vitest/browser'; + import type { Column } from '../../src'; -import { getCells, getCellsAtRowIndex, getHeaderCells, getRows, scrollGrid, setup } from './utils'; +import { getCellsAtRowIndex, scrollGrid, setup } from './utils'; + +const headerCells = page.getHeaderCell(); +const rows = page.getRow(); +const cells = page.getCell(); const rowHeight = 35; @@ -35,125 +41,132 @@ function setupGrid( }); } -function assertElements( - elements: Element[], +async function assertElements( + locator: Locator, attribute: string, count: number, startIdx: number, endIdx: number ) { - expect(elements).toHaveLength(count); - expect(elements[0]).toHaveAttribute(attribute, String(startIdx)); - expect(elements[elements.length - 1]).toHaveAttribute(attribute, String(endIdx)); + await expect.element(locator).toHaveLength(count); + await expect.element(locator.first()).toHaveAttribute(attribute, String(startIdx)); + await expect.element(locator.last()).toHaveAttribute(attribute, String(endIdx)); } -function assertIndexes( - cells: Element[], - expectedIndexes: number[], +async function assertIndexes( + locator: Locator, + indexes: number[], attribute: string, indexOffset: number ) { - const actualIndexes = cells.map( - (cell) => Number.parseInt(cell.getAttribute(attribute)!, 10) - indexOffset - ); - expect(actualIndexes).toStrictEqual(expectedIndexes); + await expect.element(locator).toHaveLength(indexes.length); + + for (const [n, index] of indexes.entries()) { + await expect.element(locator.nth(n)).toHaveAttribute(attribute, String(index + indexOffset)); + } } -function assertHeaderCells(count: number, startIdx: number, endIdx: number) { - assertElements(getHeaderCells(), 'aria-colindex', count, startIdx + 1, endIdx + 1); +async function assertHeaderCells(count: number, startIdx: number, endIdx: number) { + await assertElements(headerCells, 'aria-colindex', count, startIdx + 1, endIdx + 1); } -function assertHeaderCellIndexes(indexes: number[]) { - assertIndexes(getHeaderCells(), indexes, 'aria-colindex', 1); +async function assertHeaderCellIndexes(indexes: number[]) { + await assertIndexes(headerCells, indexes, 'aria-colindex', 1); } -function assertRows(count: number, startIdx: number, endIdx: number) { - assertElements(getRows(), 'aria-rowindex', count, startIdx + 2, endIdx + 2); +async function assertRows(count: number, startIdx: number, endIdx: number) { + await assertElements(rows, 'aria-rowindex', count, startIdx + 2, endIdx + 2); } -function assertRowIndexes(indexes: number[]) { - assertIndexes(getRows(), indexes, 'aria-rowindex', 2); +async function assertRowIndexes(indexes: number[]) { + await assertIndexes(rows, indexes, 'aria-rowindex', 2); } -function assertCells(rowIdx: number, count: number, startIdx: number, endIdx: number) { - assertElements(getCellsAtRowIndex(rowIdx), 'aria-colindex', count, startIdx + 1, endIdx + 1); +async function assertCells(rowIdx: number, count: number, startIdx: number, endIdx: number) { + await assertElements( + getCellsAtRowIndex(rowIdx), + 'aria-colindex', + count, + startIdx + 1, + endIdx + 1 + ); } -function assertCellIndexes(rowIdx: number, indexes: number[]) { - assertIndexes(getCellsAtRowIndex(rowIdx), indexes, 'aria-colindex', 1); +async function assertCellIndexes(rowIdx: number, indexes: number[]) { + await assertIndexes(getCellsAtRowIndex(rowIdx), indexes, 'aria-colindex', 1); } test('virtualization is enabled', async () => { await setupGrid(true, 30, 100); - assertHeaderCells(18, 0, 17); - assertRows(34, 0, 33); - assertCells(0, 18, 0, 17); - await scrollGrid({ scrollTop: 244 }); - assertRows(39, 2, 40); + await assertHeaderCells(18, 0, 17); + await assertRows(34, 0, 33); + await assertCells(0, 18, 0, 17); + await scrollGrid({ top: 244 }); + await assertRows(39, 2, 40); - await scrollGrid({ scrollTop: 245 }); - assertRows(38, 3, 40); + await scrollGrid({ top: 245 }); + await assertRows(38, 3, 40); - await scrollGrid({ scrollTop: 419 }); - assertRows(39, 7, 45); + await scrollGrid({ top: 419 }); + await assertRows(39, 7, 45); - await scrollGrid({ scrollTop: 420 }); - assertRows(38, 8, 45); + await scrollGrid({ top: 420 }); + await assertRows(38, 8, 45); - await scrollGrid({ scrollTop: 524 }); - assertRows(39, 10, 48); + await scrollGrid({ top: 524 }); + await assertRows(39, 10, 48); - await scrollGrid({ scrollTop: 525 }); - assertRows(38, 11, 48); + await scrollGrid({ top: 525 }); + await assertRows(38, 11, 48); - await scrollGrid({ scrollTop: 1000 }); - assertRows(39, 24, 62); + await scrollGrid({ top: 1000 }); + await assertRows(39, 24, 62); // scroll height = header height + row height * row count // max top = scroll height - grid height - await scrollGrid({ scrollTop: rowHeight + rowHeight * 100 - 1080 }); - assertRows(34, 66, 99); + await scrollGrid({ top: rowHeight + rowHeight * 100 - 1080 }); + await assertRows(34, 66, 99); - await scrollGrid({ scrollLeft: 92 }); - assertHeaderCells(18, 0, 17); - assertCells(66, 18, 0, 17); + await scrollGrid({ left: 92 }); + await assertHeaderCells(18, 0, 17); + await assertCells(66, 18, 0, 17); - await scrollGrid({ scrollLeft: 93 }); - assertHeaderCells(19, 0, 18); - assertCells(66, 19, 0, 18); + await scrollGrid({ left: 93 }); + await assertHeaderCells(19, 0, 18); + await assertCells(66, 19, 0, 18); - await scrollGrid({ scrollLeft: 209 }); - assertHeaderCells(19, 0, 18); - assertCells(66, 19, 0, 18); + await scrollGrid({ left: 209 }); + await assertHeaderCells(19, 0, 18); + await assertCells(66, 19, 0, 18); - await scrollGrid({ scrollLeft: 210 }); - assertHeaderCells(18, 1, 18); - assertCells(66, 18, 1, 18); + await scrollGrid({ left: 210 }); + await assertHeaderCells(18, 1, 18); + await assertCells(66, 18, 1, 18); // max left = row width - grid width - await scrollGrid({ scrollLeft: 3600 - 1920 }); - assertHeaderCells(17, 13, 29); - assertCells(66, 17, 13, 29); + await scrollGrid({ left: 3600 - 1920 }); + await assertHeaderCells(17, 13, 29); + await assertCells(66, 17, 13, 29); }); test('virtualization is enabled with 4 frozen columns', async () => { await setupGrid(true, 30, 30, 4); let indexes = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17]; - assertHeaderCellIndexes(indexes); - assertCellIndexes(0, indexes); + await assertHeaderCellIndexes(indexes); + await assertCellIndexes(0, indexes); - await scrollGrid({ scrollLeft: 1000 }); + await scrollGrid({ left: 1000 }); indexes = [0, 1, 2, 3, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25]; - assertHeaderCellIndexes(indexes); - assertCellIndexes(0, indexes); + await assertHeaderCellIndexes(indexes); + await assertCellIndexes(0, indexes); // max left = row width - grid width - await scrollGrid({ scrollLeft: 3600 - 1920 }); + await scrollGrid({ left: 3600 - 1920 }); indexes = [0, 1, 2, 3, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29]; - assertHeaderCellIndexes(indexes); - assertCellIndexes(0, indexes); + await assertHeaderCellIndexes(indexes); + await assertCellIndexes(0, indexes); }); test('virtualization is enabled with all columns frozen', async () => { @@ -163,29 +176,29 @@ test('virtualization is enabled with all columns frozen', async () => { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29 ]; - assertHeaderCellIndexes(indexes); - assertCellIndexes(0, indexes); + await assertHeaderCellIndexes(indexes); + await assertCellIndexes(0, indexes); - await scrollGrid({ scrollLeft: 1000 }); - assertHeaderCellIndexes(indexes); - assertCellIndexes(0, indexes); + await scrollGrid({ left: 1000 }); + await assertHeaderCellIndexes(indexes); + await assertCellIndexes(0, indexes); // max left = row width - grid width - await scrollGrid({ scrollLeft: 3600 - 1920 }); - assertHeaderCellIndexes(indexes); - assertCellIndexes(0, indexes); + await scrollGrid({ left: 3600 - 1920 }); + await assertHeaderCellIndexes(indexes); + await assertCellIndexes(0, indexes); }); test('virtualization is enabled with 2 summary rows', async () => { await setupGrid(true, 1, 100, 0, 2); - assertRowIndexes([ + await assertRowIndexes([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 102, 103 ]); - await scrollGrid({ scrollTop: 1000 }); - assertRowIndexes([ + await scrollGrid({ top: 1000 }); + await assertRowIndexes([ 0, 1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 102, 103 ]); @@ -194,35 +207,31 @@ test('virtualization is enabled with 2 summary rows', async () => { test('zero columns', async () => { await setupGrid(true, 0, 100); - expect(getHeaderCells()).toHaveLength(0); - expect(getCells()).toHaveLength(0); - expect(getRows()).toHaveLength(0); + await expect.element(headerCells).toHaveLength(0); + await expect.element(cells).toHaveLength(0); + await expect.element(rows).toHaveLength(0); }); test('zero rows', async () => { await setupGrid(true, 20, 0); - expect(getHeaderCells()).toHaveLength(18); - expect(getCells()).toHaveLength(0); - expect(getRows()).toHaveLength(0); + await expect.element(headerCells).toHaveLength(18); + await expect.element(cells).toHaveLength(0); + await expect.element(rows).toHaveLength(0); }); test('virtualization is enable with not enough columns or rows to virtualize', async () => { await setupGrid(true, 5, 5); - assertHeaderCells(5, 0, 4); - assertRows(5, 0, 4); - - const cells = getCells(); - expect(cells).toHaveLength(5 * 5); + await assertHeaderCells(5, 0, 4); + await assertRows(5, 0, 4); + await expect.element(cells).toHaveLength(5 * 5); }); test('enableVirtualization is disabled', async () => { await setupGrid(false, 40, 100); - assertHeaderCells(40, 0, 39); - assertRows(100, 0, 99); - - const cells = getCells(); - expect(cells).toHaveLength(40 * 100); + await assertHeaderCells(40, 0, 39); + await assertRows(100, 0, 99); + await expect.element(cells).toHaveLength(40 * 100); }); diff --git a/test/globals.d.ts b/test/globals.d.ts index 7094f4cec4..2f69ee4974 100644 --- a/test/globals.d.ts +++ b/test/globals.d.ts @@ -6,11 +6,6 @@ declare module 'vitest/browser' { interface BrowserCommands { dragFill: (from: string, to: string) => Promise; resizeColumn: (name: string, resizeBy: number | readonly number[]) => Promise; - scrollGrid: (position: { scrollLeft?: number; scrollTop?: number }) => Promise; - } - - interface LocatorSelectors { - getBySelector: (selector: string) => Locator; } } diff --git a/test/setupBrowser.ts b/test/setupBrowser.ts index c2f462d1c4..04518e1f6e 100644 --- a/test/setupBrowser.ts +++ b/test/setupBrowser.ts @@ -3,14 +3,80 @@ import 'vitest-browser-react'; import { configure } from 'vitest-browser-react/pure'; -import { locators } from 'vitest/browser'; +import { locators, type Locator, type LocatorByRoleOptions } from 'vitest/browser'; configure({ reactStrictMode: true }); +declare module 'vitest/browser' { + interface LocatorSelectors { + getGrid: () => Locator; + getTreeGrid: () => Locator; + getHeaderRow: (opts?: LocatorByRoleOptions) => Locator; + getHeaderCell: (opts?: LocatorByRoleOptions) => Locator; + getRow: (opts?: LocatorByRoleOptions) => Locator; + getCell: (opts?: LocatorByRoleOptions) => Locator; + getSelectAllCheckbox: () => Locator; + getSelectedCell: () => Locator; + getDragHandle: () => Locator; + getBySelector: (selector: string) => Locator; + } +} + locators.extend({ + getGrid() { + return this.getByRole('grid'); + }, + + getTreeGrid() { + return this.getByRole('treegrid'); + }, + + getHeaderRow(opts?: LocatorByRoleOptions) { + return this.getByRole('row', defaultToExactOpts(opts)).and( + this.getBySelector('.rdg-header-row') + ); + }, + + getHeaderCell(opts?: LocatorByRoleOptions) { + return this.getByRole('columnheader', defaultToExactOpts(opts)); + }, + + getRow(opts?: LocatorByRoleOptions) { + return this.getByRole('row', defaultToExactOpts(opts)).and(this.getBySelector('.rdg-row')); + }, + + getCell(opts?: LocatorByRoleOptions) { + return this.getByRole('gridcell', defaultToExactOpts(opts)); + }, + + getSelectAllCheckbox() { + return this.getByRole('checkbox', { name: 'Select All' }); + }, + + getSelectedCell() { + return this.getCell({ selected: true }).or(this.getHeaderCell({ selected: true })); + }, + + getDragHandle() { + return '.rdg-cell-drag-handle'; + }, + getBySelector(selector: string) { return selector; } }); + +function defaultToExactOpts( + opts: LocatorByRoleOptions | undefined +): LocatorByRoleOptions | undefined { + if (opts != null && opts.exact == null && typeof opts.name === 'string') { + return { + ...opts, + exact: true + }; + } + + return opts; +} diff --git a/test/visual/basicGrid.test.tsx b/test/visual/basicGrid.test.tsx index 5ecedcf1bf..9d4629e3b1 100644 --- a/test/visual/basicGrid.test.tsx +++ b/test/visual/basicGrid.test.tsx @@ -1,7 +1,8 @@ import { page } from 'vitest/browser'; import { DataGrid, SelectColumn, type Column } from '../../src'; -import { getGrid } from '../browser/utils'; + +const grid = page.getGrid(); interface Row { id: number; @@ -50,5 +51,5 @@ test('basic grid', async () => { /> ); - await expect.element(getGrid()).toMatchScreenshot('basic-grid'); + await expect.element(grid).toMatchScreenshot('basic-grid'); }); diff --git a/test/visual/treeGrid.test.tsx b/test/visual/treeGrid.test.tsx index 3b59b5893c..8d60a3cbad 100644 --- a/test/visual/treeGrid.test.tsx +++ b/test/visual/treeGrid.test.tsx @@ -1,7 +1,8 @@ import { page } from 'vitest/browser'; import { SelectColumn, TreeDataGrid, type Column } from '../../src'; -import { getTreeGrid } from '../browser/utils'; + +const treeGrid = page.getTreeGrid(); interface Row { id: number; @@ -75,7 +76,7 @@ test('tree grid', async () => { /> ); - await expect.element(getTreeGrid()).toMatchScreenshot('tree-grid'); + await expect.element(treeGrid).toMatchScreenshot('tree-grid'); }); function rowKeyGetter(row: Row) { diff --git a/tsconfig.js.json b/tsconfig.js.json index cdf7b82c33..689f0b6b48 100644 --- a/tsconfig.js.json +++ b/tsconfig.js.json @@ -2,10 +2,10 @@ "extends": "./tsconfig.base.json", "compilerOptions": { "allowJs": true, + "checkJs": true, "module": "NodeNext", - "moduleResolution": "NodeNext", - "skipLibCheck": true + "moduleResolution": "NodeNext" }, - "include": ["**/*.js", ".github/**/*.js", "package.json"], + "include": ["**/*.js"], "exclude": ["./coverage/**/*", "./dist/**/*", "./lib/**/*"] } diff --git a/tsconfig.test.json b/tsconfig.test.json index f116d1e433..12d9ca5250 100644 --- a/tsconfig.test.json +++ b/tsconfig.test.json @@ -1,8 +1,8 @@ { "extends": "./tsconfig.base.json", "compilerOptions": { - "skipLibCheck": true, "lib": ["ESNext", "DOM", "DOM.Iterable", "DOM.AsyncIterable"], + "skipLibCheck": true, "types": ["vitest/globals"] }, "include": ["test/**/*"], diff --git a/tsconfig.vite.json b/tsconfig.vite.json index 5f753ecfa3..54f86f05d6 100644 --- a/tsconfig.vite.json +++ b/tsconfig.vite.json @@ -1,8 +1,8 @@ { "extends": "./tsconfig.base.json", "compilerOptions": { - "skipLibCheck": true, - "lib": ["ESNext", "DOM"] + "lib": ["ESNext", "DOM"], + "skipLibCheck": true }, "include": ["package.json", "rolldown.config.ts", "vite.config.ts"] } diff --git a/vite.config.ts b/vite.config.ts index b92c96442e..6871bdbfd5 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -10,13 +10,11 @@ const isTest = process.env.NODE_ENV === 'test'; // TODO: remove when `userEvent.pointer` is supported const resizeColumn: BrowserCommand<[name: string, resizeBy: number | readonly number[]]> = async ( - context, + { page, iframe }, name, resizeBy ) => { - const page = context.page; - const frame = await context.frame(); - const resizeHandle = frame + const resizeHandle = iframe .getByRole('columnheader', { name, exact: true }) .locator('.rdg-resize-handle'); const { x, y } = (await resizeHandle.boundingBox())!; @@ -32,35 +30,15 @@ const resizeColumn: BrowserCommand<[name: string, resizeBy: number | readonly nu }; // TODO: remove when `userEvent.pointer` is supported -const dragFill: BrowserCommand<[from: string, to: string]> = async (context, from, to) => { - const page = context.page; - const frame = await context.frame(); - await frame.getByRole('gridcell', { name: from }).click(); - await frame.locator('.rdg-cell-drag-handle').hover(); +const dragFill: BrowserCommand<[from: string, to: string]> = async ({ page, iframe }, from, to) => { + await iframe.getByRole('gridcell', { name: from, exact: true }).click(); + await iframe.locator('.rdg-cell-drag-handle').hover(); await page.mouse.down(); - const toCell = frame.getByRole('gridcell', { name: to }); + const toCell = iframe.getByRole('gridcell', { name: to, exact: true }); await toCell.hover(); 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; // vitest modifies the instance objects, so we cannot rely on static objects @@ -146,15 +124,15 @@ export default defineConfig( name: 'browser', include: ['browser/**/*.test.*'], browser: { - ui: false, enabled: true, trace: { mode: isCI ? 'off' : 'retain-on-failure' }, instances: getInstances(), - commands: { resizeColumn, dragFill, scrollGrid }, + commands: { resizeColumn, dragFill }, viewport, headless: true, + ui: false, screenshotFailures: !isCI }, setupFiles: ['test/setupBrowser.ts', 'test/failOnConsole.ts']