diff --git a/test/browser/TextEditor.test.tsx b/test/browser/TextEditor.test.tsx index a18f40e461..2afa699dc0 100644 --- a/test/browser/TextEditor.test.tsx +++ b/test/browser/TextEditor.test.tsx @@ -3,7 +3,6 @@ import { page, userEvent } from '@vitest/browser/context'; import { DataGrid, textEditor } from '../../src'; import type { Column } from '../../src'; -import { getCells } from './utils'; interface Row { readonly name: string; @@ -29,27 +28,27 @@ function Test() { test('TextEditor', async () => { page.render(); - - await userEvent.dblClick(getCells()[0]); - let input = page.getByRole('textbox').element() as HTMLInputElement; - expect(input).toHaveClass('rdg-text-editor'); + const cell = page.getByRole('gridcell'); + await expect.element(cell).toHaveTextContent(/^Tacitus Kilgore$/); + await userEvent.dblClick(cell); + const input = page.getByRole('textbox'); + await expect.element(input).toHaveClass('rdg-text-editor'); // input value is row[column.key] - expect(input).toHaveValue(initialRows[0].name); + await expect.element(input).toHaveValue(initialRows[0].name); // input is focused - expect(input).toHaveFocus(); + await expect.element(input).toHaveFocus(); // input value is fully selected - expect(input).toHaveSelection(initialRows[0].name); + await expect.element(input).toHaveSelection(initialRows[0].name); // pressing escape closes the editor without committing await userEvent.keyboard('Test{escape}'); - expect(input).not.toBeInTheDocument(); - await expect.element(getCells()[0]).toHaveTextContent(/^Tacitus Kilgore$/); + await expect.element(input).not.toBeInTheDocument(); + await expect.element(cell).toHaveTextContent(/^Tacitus Kilgore$/); // blurring the input closes and commits the editor - await userEvent.dblClick(getCells()[0]); - input = page.getByRole('textbox').element() as HTMLInputElement; + await userEvent.dblClick(cell); await userEvent.fill(input, 'Jim Milton'); await userEvent.tab(); - expect(input).not.toBeInTheDocument(); - await expect.element(getCells()[0]).toHaveTextContent(/^Jim Milton$/); + await expect.element(input).not.toBeInTheDocument(); + await expect.element(cell).toHaveTextContent(/^Jim Milton$/); }); diff --git a/test/browser/column/cellClass.test.ts b/test/browser/column/cellClass.test.ts index 8d6f4c7afe..d86d3c98ca 100644 --- a/test/browser/column/cellClass.test.ts +++ b/test/browser/column/cellClass.test.ts @@ -1,6 +1,6 @@ import type { Column } from '../../../src'; import { cellClassname } from '../../../src/style/cell'; -import { getCells, setup } from '../utils'; +import { getCellsNew, setup } from '../utils'; interface Row { id: number; @@ -8,7 +8,7 @@ interface Row { const rows: readonly Row[] = [{ id: 0 }, { id: 1 }]; -test('cellClass is undefined', () => { +test('cellClass is undefined', async () => { const columns: readonly Column[] = [ { key: 'id', @@ -16,12 +16,12 @@ test('cellClass is undefined', () => { } ]; setup({ columns, rows }); - const [cell1, cell2] = getCells(); - expect(cell1).toHaveClass(cellClassname, { exact: true }); - expect(cell2).toHaveClass(cellClassname, { exact: true }); + const [cell1, cell2] = getCellsNew('0', '1'); + await expect.element(cell1).toHaveClass(cellClassname, { exact: true }); + await expect.element(cell2).toHaveClass(cellClassname, { exact: true }); }); -test('cellClass is a string', () => { +test('cellClass is a string', async () => { const columns: readonly Column[] = [ { key: 'id', @@ -30,12 +30,12 @@ test('cellClass is a string', () => { } ]; setup({ columns, rows }); - const [cell1, cell2] = getCells(); - expect(cell1).toHaveClass(`${cellClassname} my-cell`, { exact: true }); - expect(cell2).toHaveClass(`${cellClassname} my-cell`, { exact: true }); + const [cell1, cell2] = getCellsNew('0', '1'); + await expect.element(cell1).toHaveClass(`${cellClassname} my-cell`, { exact: true }); + await expect.element(cell2).toHaveClass(`${cellClassname} my-cell`, { exact: true }); }); -test('cellClass returns a string', () => { +test('cellClass returns a string', async () => { const columns: readonly Column[] = [ { key: 'id', @@ -44,12 +44,12 @@ test('cellClass returns a string', () => { } ]; setup({ columns, rows }); - const [cell1, cell2] = getCells(); - expect(cell1).toHaveClass(`${cellClassname} my-cell-0`, { exact: true }); - expect(cell2).toHaveClass(`${cellClassname} my-cell-1`, { exact: true }); + const [cell1, cell2] = getCellsNew('0', '1'); + await expect.element(cell1).toHaveClass(`${cellClassname} my-cell-0`, { exact: true }); + await expect.element(cell2).toHaveClass(`${cellClassname} my-cell-1`, { exact: true }); }); -test('cellClass returns undefined', () => { +test('cellClass returns undefined', async () => { const columns: readonly Column[] = [ { key: 'id', @@ -58,7 +58,7 @@ test('cellClass returns undefined', () => { } ]; setup({ columns, rows }); - const [cell1, cell2] = getCells(); - expect(cell1).toHaveClass(cellClassname, { exact: true }); - expect(cell2).toHaveClass(cellClassname, { exact: true }); + const [cell1, cell2] = getCellsNew('0', '1'); + await expect.element(cell1).toHaveClass(cellClassname, { exact: true }); + await expect.element(cell2).toHaveClass(cellClassname, { exact: true }); }); diff --git a/test/browser/column/draggable.test.ts b/test/browser/column/draggable.test.ts index 14efe0bf41..e32e0a9e23 100644 --- a/test/browser/column/draggable.test.ts +++ b/test/browser/column/draggable.test.ts @@ -1,7 +1,7 @@ import { userEvent } from '@vitest/browser/context'; import type { Column } from '../../../src'; -import { getHeaderCells, setup } from '../utils'; +import { getHeaderCellsNew, setup } from '../utils'; const columns: readonly Column[] = [ { @@ -28,12 +28,12 @@ const columns: readonly Column[] = [ test('draggable columns', async () => { const onColumnsReorder = vi.fn(); setup({ columns, rows: [], onColumnsReorder }); - const [cell1, cell2, cell3, cell4] = getHeaderCells(); + const [cell1, cell2, cell3, cell4] = getHeaderCellsNew('col1', 'col2', 'col3', 'col4'); - expect(cell1).not.toHaveAttribute('draggable'); - expect(cell2).toHaveAttribute('draggable'); - expect(cell3).toHaveAttribute('draggable'); - expect(cell4).toHaveAttribute('draggable'); + await expect.element(cell1).not.toHaveAttribute('draggable'); + await expect.element(cell2).toHaveAttribute('draggable'); + await expect.element(cell3).toHaveAttribute('draggable'); + await expect.element(cell4).toHaveAttribute('draggable'); expect(onColumnsReorder).not.toHaveBeenCalled(); diff --git a/test/browser/column/frozen.test.ts b/test/browser/column/frozen.test.ts index 9075f446ce..4223a06a4e 100644 --- a/test/browser/column/frozen.test.ts +++ b/test/browser/column/frozen.test.ts @@ -1,8 +1,10 @@ +import { page } from '@vitest/browser/context'; + import type { Column } from '../../../src'; import { cellClassname, cellFrozenClassname } from '../../../src/style/cell'; -import { getHeaderCells, setup } from '../utils'; +import { getHeaderCellsNew, setup } from '../utils'; -test('frozen column have a specific class, and are stable-sorted before non-frozen columns', () => { +test('frozen column have a specific class, and are stable-sorted before non-frozen columns', async () => { const columns: readonly Column[] = [ { key: 'col1', @@ -26,15 +28,15 @@ test('frozen column have a specific class, and are stable-sorted before non-froz ]; setup({ columns, rows: [] }); - const [cell1, cell2, cell3, cell4] = getHeaderCells(); - - expect(cell1).toHaveClass(`${cellClassname} ${cellFrozenClassname}`, { exact: true }); - expect(cell2).toHaveClass(`${cellClassname} ${cellFrozenClassname}`, { exact: true }); - expect(cell3).toHaveClass(cellClassname, { exact: true }); - expect(cell4).toHaveClass(cellClassname, { exact: true }); + await expect.element(page.getByRole('row')).toHaveTextContent('col1col3col2col4'); + const [cell1, cell2, cell3, cell4] = getHeaderCellsNew('col1', 'col2', 'col3', 'col4'); - expect(cell1).toHaveTextContent('col1'); - expect(cell2).toHaveTextContent('col3'); - expect(cell3).toHaveTextContent('col2'); - expect(cell4).toHaveTextContent('col4'); + await expect + .element(cell1) + .toHaveClass(`${cellClassname} ${cellFrozenClassname}`, { exact: true }); + await expect + .element(cell3) + .toHaveClass(`${cellClassname} ${cellFrozenClassname}`, { exact: true }); + await expect.element(cell2).toHaveClass(cellClassname, { exact: true }); + await expect.element(cell4).toHaveClass(cellClassname, { exact: true }); }); diff --git a/test/browser/column/renderEditCell.test.tsx b/test/browser/column/renderEditCell.test.tsx index deaab7b840..c4b9a77d7b 100644 --- a/test/browser/column/renderEditCell.test.tsx +++ b/test/browser/column/renderEditCell.test.tsx @@ -1,10 +1,10 @@ import { useMemo, useState } from 'react'; import { createPortal } from 'react-dom'; -import { page, userEvent } from '@vitest/browser/context'; +import { commands, page, userEvent } from '@vitest/browser/context'; import { DataGrid } from '../../../src'; import type { Column, DataGridProps } from '../../../src'; -import { getCellsAtRowIndex, getGrid, getSelectedCell, scrollGrid } from '../utils'; +import { getCell, getCellsAtRowIndex, getGrid, getSelectedCell, scrollGrid } from '../utils'; interface Row { col1: number; @@ -258,20 +258,19 @@ describe('Editor', () => { it('should not steal focus back to the cell if the editor is not in the viewport and another cell is clicked', async () => { const rows: Row[] = []; for (let i = 0; i < 99; i++) { - rows.push({ col1: i, col2: `${i}` }); + rows.push({ col1: i, col2: `name${i}` }); } page.render(); - await userEvent.dblClick(getCellsAtRowIndex(0)[1]); + await userEvent.dblClick(getCell('name0')); await userEvent.keyboard('abc'); - await scrollGrid({ scrollTop: 1500 }); - expect(getCellsAtRowIndex(40)[1]).toHaveTextContent(/^40$/); - await userEvent.click(getCellsAtRowIndex(40)[1]); - await expect.element(getSelectedCell()).toHaveTextContent(/^40$/); - await scrollGrid({ scrollTop: 0 }); - expect(getCellsAtRowIndex(0)[1]).toHaveTextContent(/^0abc$/); + await commands.scrollGrid({ scrollTop: 1500 }); + await userEvent.click(getCell('name43')); + await expect.element(getSelectedCell()).toHaveTextContent(/^name43$/); + await commands.scrollGrid({ scrollTop: 0 }); + await expect.element(getCell('name0abc')).toBeVisible(); }); it('should not steal focus back to the cell after being closed by clicking outside the grid', async () => { diff --git a/test/browser/headerRowClass.test.ts b/test/browser/headerRowClass.test.ts index 0b16cbc3a4..10d1e816c9 100644 --- a/test/browser/headerRowClass.test.ts +++ b/test/browser/headerRowClass.test.ts @@ -9,24 +9,24 @@ interface Row { } const columns: readonly Column[] = [{ key: 'id', name: 'ID' }]; -const rows: readonly Row[] = [{ id: 0 }]; +const rows: readonly Row[] = []; -test('headerRowClass is undefined', () => { +test('headerRowClass is undefined', async () => { setup({ columns, rows, - rowClass: undefined + headerRowClass: undefined }); - const header = page.getByRole('row').first(); - expect(header).toHaveClass(headerRowClassname, { exact: true }); + const header = page.getByRole('row'); + await expect.element(header).toHaveClass(headerRowClassname, { exact: true }); }); -test('headerRowClass is a string', () => { +test('headerRowClass is a string', async () => { setup({ columns, rows, headerRowClass: 'my-header-row' }); - const header = page.getByRole('row').first(); - expect(header).toHaveClass(`${headerRowClassname} my-header-row`, { exact: true }); + const header = page.getByRole('row'); + await expect.element(header).toHaveClass(`${headerRowClassname} my-header-row`, { exact: true }); }); diff --git a/test/browser/label.test.ts b/test/browser/label.test.ts index 7a10790111..aa077c0715 100644 --- a/test/browser/label.test.ts +++ b/test/browser/label.test.ts @@ -1,6 +1,6 @@ import { getGrid, setup } from './utils'; -test('should set label and description', () => { +test('should set label and description', async () => { setup({ rows: [], columns: [], @@ -12,11 +12,11 @@ test('should set label and description', () => { 'data-cy': 'cy' }); - const grid = getGrid().element(); - expect(grid).toHaveAttribute('aria-label', 'label'); - expect(grid).toHaveAttribute('aria-labelledby', 'labelledby'); - expect(grid).toHaveAttribute('aria-description', 'description'); - expect(grid).toHaveAttribute('aria-describedby', 'describedby'); - expect(grid).toHaveAttribute('data-testid', 'testid'); - expect(grid).toHaveAttribute('data-cy', 'cy'); + const grid = getGrid(); + await expect.element(grid).toHaveAttribute('aria-label', 'label'); + await expect.element(grid).toHaveAttribute('aria-labelledby', 'labelledby'); + await expect.element(grid).toHaveAttribute('aria-description', 'description'); + await expect.element(grid).toHaveAttribute('aria-describedby', 'describedby'); + await expect.element(grid).toHaveAttribute('data-testid', 'testid'); + await expect.element(grid).toHaveAttribute('data-cy', 'cy'); }); diff --git a/test/browser/rowClass.test.ts b/test/browser/rowClass.test.ts index be50467c12..ad4a41d704 100644 --- a/test/browser/rowClass.test.ts +++ b/test/browser/rowClass.test.ts @@ -1,6 +1,6 @@ import type { Column } from '../../src'; import { rowClassname } from '../../src/style/row'; -import { getRows, setup } from './utils'; +import { getRowsNew, setup } from './utils'; interface Row { id: number; @@ -9,38 +9,38 @@ interface Row { const columns: readonly Column[] = [{ key: 'id', name: 'ID' }]; const rows: readonly Row[] = [{ id: 0 }, { id: 1 }, { id: 2 }]; -test('rowClass is undefined', () => { +test('rowClass is undefined', async () => { setup({ columns, rows, rowClass: undefined }); - const [row1, row2, row3] = getRows(); - expect(row1).toHaveClass(`${rowClassname} rdg-row-even`, { exact: true }); - expect(row2).toHaveClass(`${rowClassname} rdg-row-odd`, { exact: true }); - expect(row3).toHaveClass(`${rowClassname} rdg-row-even`, { exact: true }); + const [row1, row2, row3] = getRowsNew('0', '1', '2'); + await expect.element(row1).toHaveClass(`${rowClassname} rdg-row-even`, { exact: true }); + await expect.element(row2).toHaveClass(`${rowClassname} rdg-row-odd`, { exact: true }); + await expect.element(row3).toHaveClass(`${rowClassname} rdg-row-even`, { exact: true }); }); -test('rowClass returns a string', () => { +test('rowClass returns a string', async () => { setup({ columns, rows, rowClass: (row) => `my-row-${row.id}` }); - const [row1, row2, row3] = getRows(); - expect(row1).toHaveClass(`${rowClassname} rdg-row-even my-row-0`, { exact: true }); - expect(row2).toHaveClass(`${rowClassname} rdg-row-odd my-row-1`, { exact: true }); - expect(row3).toHaveClass(`${rowClassname} rdg-row-even my-row-2`, { exact: true }); + const [row1, row2, row3] = getRowsNew('0', '1', '2'); + await expect.element(row1).toHaveClass(`${rowClassname} rdg-row-even my-row-0`, { exact: true }); + await expect.element(row2).toHaveClass(`${rowClassname} rdg-row-odd my-row-1`, { exact: true }); + await expect.element(row3).toHaveClass(`${rowClassname} rdg-row-even my-row-2`, { exact: true }); }); -test('rowClass returns undefined', () => { +test('rowClass returns undefined', async () => { setup({ columns, rows, rowClass: () => undefined }); - const [row1, row2, row3] = getRows(); - expect(row1).toHaveClass(`${rowClassname} rdg-row-even`, { exact: true }); - expect(row2).toHaveClass(`${rowClassname} rdg-row-odd`, { exact: true }); - expect(row3).toHaveClass(`${rowClassname} rdg-row-even`, { exact: true }); + const [row1, row2, row3] = getRowsNew('0', '1', '2'); + await expect.element(row1).toHaveClass(`${rowClassname} rdg-row-even`, { exact: true }); + await expect.element(row2).toHaveClass(`${rowClassname} rdg-row-odd`, { exact: true }); + await expect.element(row3).toHaveClass(`${rowClassname} rdg-row-even`, { exact: true }); }); diff --git a/test/browser/rowSelection.test.tsx b/test/browser/rowSelection.test.tsx index a37c375a91..d0f6be5684 100644 --- a/test/browser/rowSelection.test.tsx +++ b/test/browser/rowSelection.test.tsx @@ -3,7 +3,7 @@ import { page, userEvent } from '@vitest/browser/context'; import { DataGrid, SelectColumn } from '../../src'; import type { Column } from '../../src'; -import { getCellsAtRowIndex, getRows } from './utils'; +import { getCell, getRow, getSelectAllCheckbox } from './utils'; interface Row { id: number; @@ -12,8 +12,8 @@ interface Row { const columns: readonly Column[] = [ SelectColumn, { - key: 'name', - name: 'Name' + key: 'id', + name: 'ID' } ]; @@ -49,75 +49,77 @@ function setup(initialRows = defaultRows) { } function testSelection(rowIdx: number, isSelected: boolean) { - expect(getRows()[rowIdx]).toHaveAttribute('aria-selected', isSelected ? 'true' : 'false'); + return expect + .element(getRow(String(rowIdx + 1))) + .toHaveAttribute('aria-selected', isSelected ? 'true' : 'false'); } -async function toggleSelection(rowIdx: number, shift = false) { - const element = page.getByRole('row').all()[rowIdx + 1].getByRole('checkbox', { name: 'Select' }); +async function toggleSelection(rowIdx: number, shift = false, force = false) { + const checkbox = getRow(String(rowIdx + 1)).getByRole('checkbox', { name: 'Select' }); if (shift) await userEvent.keyboard('{Shift>}'); - await userEvent.click(element, { force: true }); + await userEvent.click(checkbox, { force }); if (shift) await userEvent.keyboard('{/Shift}'); } test('toggle selection when checkbox is clicked', async () => { setup(); await toggleSelection(0); - testSelection(0, true); + await testSelection(0, true); await toggleSelection(1); - testSelection(1, true); + await testSelection(1, true); await toggleSelection(0); - testSelection(0, false); + await testSelection(0, false); await toggleSelection(1); - testSelection(1, false); + await testSelection(1, false); }); test('toggle selection using keyboard', async () => { setup(); - testSelection(0, false); - await userEvent.click(getCellsAtRowIndex(0)[0]); - testSelection(0, true); + await testSelection(0, false); + await userEvent.click(getRow('1').getByRole('checkbox', { name: 'Select' })); + await testSelection(0, true); await userEvent.keyboard(' '); - testSelection(0, false); + await testSelection(0, false); await userEvent.keyboard(' '); - testSelection(0, true); + await testSelection(0, true); await userEvent.keyboard('{arrowdown} '); - testSelection(1, true); + await testSelection(1, true); await userEvent.keyboard('{arrowup} '); - testSelection(0, false); + await testSelection(0, false); }); test('should partially select header checkbox', async () => { setup(); - const headerCheckbox = page.getByRole('checkbox', { name: 'Select All' }).element(); - expect(headerCheckbox).not.toBeChecked(); - expect(headerCheckbox).not.toBePartiallyChecked(); + const headerCheckbox = getSelectAllCheckbox(); + await expect.element(headerCheckbox).not.toBeChecked(); + await expect.element(headerCheckbox).not.toBePartiallyChecked(); await toggleSelection(0); - expect(headerCheckbox).not.toBeChecked(); - expect(headerCheckbox).toBePartiallyChecked(); + await expect.element(headerCheckbox).not.toBeChecked(); + await expect.element(headerCheckbox).toBePartiallyChecked(); await toggleSelection(1); - expect(headerCheckbox).not.toBeChecked(); - expect(headerCheckbox).toBePartiallyChecked(); + await expect.element(headerCheckbox).not.toBeChecked(); + await expect.element(headerCheckbox).toBePartiallyChecked(); await toggleSelection(2); - expect(headerCheckbox).toBeChecked(); - expect(headerCheckbox).not.toBePartiallyChecked(); + await expect.element(headerCheckbox).toBeChecked(); + await expect.element(headerCheckbox).not.toBePartiallyChecked(); await toggleSelection(0); - expect(headerCheckbox).not.toBeChecked(); - expect(headerCheckbox).toBePartiallyChecked(); + await expect.element(headerCheckbox).not.toBeChecked(); + await expect.element(headerCheckbox).toBePartiallyChecked(); await userEvent.click(headerCheckbox); - testSelection(0, false); - testSelection(1, false); - testSelection(2, false); + await testSelection(0, false); + await testSelection(1, false); + await testSelection(2, false); await userEvent.click(headerCheckbox); - testSelection(0, true); - testSelection(1, true); - testSelection(2, true); + await testSelection(0, true); + await testSelection(1, true); + await testSelection(2, true); }); test('should not select row when isRowSelectionDisabled returns true', async () => { @@ -125,44 +127,44 @@ test('should not select row when isRowSelectionDisabled returns true', async () row.id === 2} /> ); await toggleSelection(0); - testSelection(0, true); - await toggleSelection(1); - testSelection(1, false); + await testSelection(0, true); + await toggleSelection(1, false, true); // force click even if disabled + await testSelection(1, false); await toggleSelection(2); - testSelection(2, true); + await testSelection(2, true); - await userEvent.click(page.getByRole('checkbox', { name: 'Select All' })); + await userEvent.click(getSelectAllCheckbox()); await toggleSelection(0); await toggleSelection(2, true); - testSelection(0, true); - testSelection(1, false); - testSelection(2, true); + await testSelection(0, true); + await testSelection(1, false); + await testSelection(2, true); }); test('select/deselect all rows when header checkbox is clicked', async () => { setup(); - const headerCheckbox = page.getByRole('checkbox', { name: 'Select All' }).element(); - expect(headerCheckbox).not.toBeChecked(); + const headerCheckbox = getSelectAllCheckbox(); + await expect.element(headerCheckbox).not.toBeChecked(); await userEvent.click(headerCheckbox); - testSelection(0, true); - testSelection(1, true); - testSelection(2, true); + await testSelection(0, true); + await testSelection(1, true); + await testSelection(2, true); // deselecting a row should toggle header await toggleSelection(0); - expect(headerCheckbox).not.toBeChecked(); + await expect.element(headerCheckbox).not.toBeChecked(); await toggleSelection(0); - expect(headerCheckbox).toBeChecked(); + await expect.element(headerCheckbox).toBeChecked(); await userEvent.click(headerCheckbox); - testSelection(0, false); - testSelection(1, false); - testSelection(2, false); + await testSelection(0, false); + await testSelection(1, false); + await testSelection(2, false); }); test('header checkbox is not checked when there are no rows', async () => { setup([]); - await expect.element(page.getByRole('checkbox', { name: 'Select All' })).not.toBeChecked(); + await expect.element(getSelectAllCheckbox()).not.toBeChecked(); }); test('header checkbox is not necessarily checked when selectedRows.size === rows.length', async () => { @@ -175,7 +177,7 @@ test('header checkbox is not necessarily checked when selectedRows.size === rows /> ); - await expect.element(page.getByRole('checkbox', { name: 'Select All' })).not.toBeChecked(); + await expect.element(getSelectAllCheckbox()).not.toBeChecked(); }); test('header checkbox is not necessarily checked when selectedRows.size > rows.length', async () => { @@ -188,7 +190,7 @@ test('header checkbox is not necessarily checked when selectedRows.size > rows.l /> ); - await expect.element(page.getByRole('checkbox', { name: 'Select All' })).not.toBeChecked(); + await expect.element(getSelectAllCheckbox()).not.toBeChecked(); }); test('extra keys are preserved when updating the selectedRows Set', async () => { @@ -216,7 +218,7 @@ test('extra keys are preserved when updating the selectedRows Set', async () => page.render(); - const headerCheckbox = page.getByRole('checkbox', { name: 'Select All' }).element(); + const headerCheckbox = getSelectAllCheckbox(); await toggleSelection(1); expect(set).toStrictEqual(new Set([...initialSet, 2])); @@ -241,23 +243,23 @@ test('select/deselect rows using shift click', async () => { setup(); await toggleSelection(0); await toggleSelection(2, true); - testSelection(0, true); - testSelection(1, true); - testSelection(2, true); + await testSelection(0, true); + await testSelection(1, true); + await testSelection(2, true); await toggleSelection(0); await toggleSelection(2, true); - testSelection(0, false); - testSelection(1, false); - testSelection(2, false); + await testSelection(0, false); + await testSelection(1, false); + await testSelection(2, false); }); test('select rows using shift space', async () => { setup(); - await userEvent.click(getCellsAtRowIndex(0)[1]); + await userEvent.click(getCell('1')); await userEvent.keyboard('{Shift>} {/Shift}'); - testSelection(0, true); + await testSelection(0, true); await userEvent.keyboard(' '); - testSelection(0, true); + await testSelection(0, true); await userEvent.keyboard('{Shift>} {/Shift}'); - testSelection(0, false); + await testSelection(0, false); }); diff --git a/test/browser/scrollToCell.test.tsx b/test/browser/scrollToCell.test.tsx index ba843a39c3..3a58c416c5 100644 --- a/test/browser/scrollToCell.test.tsx +++ b/test/browser/scrollToCell.test.tsx @@ -4,21 +4,23 @@ import { page, userEvent } from '@vitest/browser/context'; import { DataGrid } from '../../src'; import type { Column, DataGridHandle } from '../../src'; import type { PartialPosition } from '../../src/ScrollToCell'; -import { getGrid } from './utils'; -type Row = undefined; - -const rows: readonly Row[] = new Array(50); -const summaryRows: readonly Row[] = [undefined, undefined]; - -const columns: Column[] = []; +const rows: readonly number[] = Array.from({ length: 50 }, (_, i) => i); +const summaryRows: readonly number[] = Array.from({ length: 2 }, (_, i) => i + 50); +const columns: Column[] = []; for (let i = 0; i < 50; i++) { const key = String(i); columns.push({ key, name: key, - frozen: i < 5 + frozen: i < 5, + renderCell(props) { + return `${props.column.key}×${props.row}`; + }, + renderSummaryCell(props) { + return `${props.column.key}×${props.row}`; + } }); } @@ -54,43 +56,57 @@ async function testScroll(p: PartialPosition) { test('scrollToCell', async () => { page.render(); - const grid = getGrid().element(); - validateScrollPosition(0, 0); + await validateCellVisibility('0×0', true); + await validateCellVisibility('40×30', false); + await validateCellVisibility('0×51', true); // should scroll to a cell when a valid position is specified await testScroll({ idx: 40, rowIdx: 30 }); - validateScrollPosition(1572, 132); + await validateCellVisibility('0×0', false); + await validateCellVisibility('40×30', true); // should scroll to a column when a valid idx is specified await testScroll({ idx: 6 }); - validateScrollPosition(1572, 50); + await validateCellVisibility('6×30', true); + await validateCellVisibility('40×30', false); await testScroll({ idx: 40 }); - validateScrollPosition(1572, 132); + await validateCellVisibility('6×30', false); + await validateCellVisibility('40×30', true); // should scroll to a row when a valid rowIdx is specified await testScroll({ rowIdx: 1 }); - validateScrollPosition(0, 132); + await validateCellVisibility('40×1', true); + await validateCellVisibility('40×30', false); await testScroll({ rowIdx: 30 }); - validateScrollPosition(1572, 132); + await validateCellVisibility('40×1', false); + await validateCellVisibility('40×30', true); // should not scroll if scroll to column is frozen await testScroll({ idx: 2 }); - validateScrollPosition(1572, 132); + await validateCellVisibility('40×30', true); // should not scroll if rowIdx is header row await testScroll({ idx: -1 }); - validateScrollPosition(1572, 132); + await validateCellVisibility('40×30', true); // should not scroll if rowIdx is summary row await testScroll({ idx: 50 }); - validateScrollPosition(1572, 132); + await validateCellVisibility('40×30', true); // should not scroll if position is out of bound await testScroll({ idx: 60, rowIdx: 60 }); - validateScrollPosition(1572, 132); + await validateCellVisibility('40×30', true); - function validateScrollPosition(scrollTop: number, scrollLeft: number) { - expect(grid.scrollTop).toBe(scrollTop); - expect(grid.scrollLeft).toBe(scrollLeft); - } + // should not scroll vertically when scrolling to summary row + await testScroll({ idx: 49, rowIdx: 51 }); + await validateCellVisibility('49×30', true); }); + +function validateCellVisibility(name: string, isVisible: boolean) { + const cell = page.getByRole('gridcell', { name, exact: true }); + if (isVisible) { + return expect.element(cell).toBeVisible(); + } + + return expect.element(cell).not.toBeInTheDocument(); +} diff --git a/test/browser/sorting.test.tsx b/test/browser/sorting.test.tsx index 8f1e829766..5bb6ba1b8e 100644 --- a/test/browser/sorting.test.tsx +++ b/test/browser/sorting.test.tsx @@ -3,7 +3,7 @@ import { page, userEvent } from '@vitest/browser/context'; import { DataGrid } from '../../src'; import type { Column, SortColumn } from '../../src/types'; -import { getHeaderCells } from './utils'; +import { getHeaderCell, getHeaderCellsNew } from './utils'; const columns: readonly Column[] = [ { key: 'colA', name: 'colA' }, @@ -41,43 +41,45 @@ function testSortColumns(expectedValue: readonly SortColumn[]) { test('should not sort if sortable is false', async () => { setup(); - const headerCell = getHeaderCells()[3]; + const headerCell = getHeaderCell('colD'); await userEvent.click(headerCell); - expect(headerCell).not.toHaveAttribute('aria-sort'); + await expect.element(headerCell).not.toHaveAttribute('aria-sort'); await testSortColumns([]); }); test('single column sort', async () => { setup(); - const headerCell = getHeaderCells()[0]; + const headerCell = getHeaderCell('colA'); await userEvent.click(headerCell); - expect(headerCell).toHaveAttribute('aria-sort', 'ascending'); + await expect.element(headerCell).toHaveAttribute('aria-sort', 'ascending'); // priority is not shown for single sort - expect(headerCell).not.toHaveTextContent('1'); + await expect.element(headerCell).not.toHaveTextContent('1'); await testSortColumns([{ columnKey: 'colA', direction: 'ASC' }]); await userEvent.click(headerCell); - expect(headerCell).toHaveAttribute('aria-sort', 'descending'); + await expect.element(headerCell).toHaveAttribute('aria-sort', 'descending'); await testSortColumns([{ columnKey: 'colA', direction: 'DESC' }]); await userEvent.click(headerCell); - expect(headerCell).not.toHaveAttribute('aria-sort'); + await expect.element(headerCell).not.toHaveAttribute('aria-sort'); await testSortColumns([]); }); test('multi column sort', async () => { setup(); - const [headerCell1, headerCell2, headerCell3] = getHeaderCells(); + const headerCell1 = getHeaderCell('colA', false); + const headerCell2 = getHeaderCell('colB', false); + const headerCell3 = getHeaderCell('colC', false); await userEvent.click(headerCell1); await userEvent.keyboard('{Control>}'); await userEvent.click(headerCell2); await userEvent.click(headerCell3); // aria-sort is only added for single sort - expect(headerCell1).not.toHaveAttribute('aria-sort'); - expect(headerCell1).toHaveTextContent('1'); // priority - expect(headerCell2).not.toHaveAttribute('aria-sort'); - expect(headerCell2).toHaveTextContent('2'); - expect(headerCell3).not.toHaveAttribute('aria-sort'); - expect(headerCell3).toHaveTextContent('3'); + await expect.element(headerCell1).not.toHaveAttribute('aria-sort'); + await expect.element(headerCell1).toHaveTextContent('1'); // priority + await expect.element(headerCell2).not.toHaveAttribute('aria-sort'); + await expect.element(headerCell2).toHaveTextContent('2'); + await expect.element(headerCell3).not.toHaveAttribute('aria-sort'); + await expect.element(headerCell3).toHaveTextContent('3'); await testSortColumns([ { columnKey: 'colA', direction: 'ASC' }, { columnKey: 'colB', direction: 'DESC' }, @@ -95,19 +97,19 @@ test('multi column sort', async () => { { columnKey: 'colA', direction: 'ASC' }, { columnKey: 'colC', direction: 'ASC' } ]); - expect(headerCell3).toHaveTextContent('2'); + await expect.element(headerCell3).toHaveTextContent('2'); // clicking on a column without ctrlKey should remove multisort await userEvent.keyboard('{/Control}'); await userEvent.click(headerCell2); await testSortColumns([{ columnKey: 'colB', direction: 'DESC' }]); - expect(headerCell2).toHaveAttribute('aria-sort'); - expect(headerCell2).not.toHaveTextContent('2'); + await expect.element(headerCell2).toHaveAttribute('aria-sort'); + await expect.element(headerCell2).not.toHaveTextContent('2'); }); test('multi column sort with metakey', async () => { setup(); - const [headerCell1, headerCell2] = getHeaderCells(); + const [headerCell1, headerCell2] = getHeaderCellsNew('colA', 'colB'); await userEvent.click(headerCell1); await userEvent.keyboard('{Meta>}'); await userEvent.click(headerCell2); @@ -119,7 +121,7 @@ test('multi column sort with metakey', async () => { test('multi column sort with keyboard', async () => { setup(); - const [headerCell1] = getHeaderCells(); + const headerCell1 = getHeaderCell('colA'); await userEvent.click(headerCell1); await userEvent.keyboard(' {arrowright}{Control>}{enter}'); await testSortColumns([ diff --git a/test/browser/utils.tsx b/test/browser/utils.tsx index 9f01850cb2..00b0b589b7 100644 --- a/test/browser/utils.tsx +++ b/test/browser/utils.tsx @@ -40,6 +40,34 @@ 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 getRow(name: string) { + return page.getByRole('row', { name }); +} + +export function getRowsNew(...names: readonly string[]) { + return names.map(getRow); +} + +export function getCell(name: string) { + return page.getByRole('gridcell', { name, exact: true }); +} + +export function getCellsNew(...names: readonly string[]) { + return names.map(getCell); +} + +export function getSelectAllCheckbox() { + return page.getByRole('checkbox', { name: 'Select All' }); +} + export function getRows() { return page.getByRole('row').elements().slice(1); } diff --git a/test/globals.d.ts b/test/globals.d.ts index fad484533a..6ccaf2509e 100644 --- a/test/globals.d.ts +++ b/test/globals.d.ts @@ -1,7 +1,8 @@ declare module '@vitest/browser/context' { interface BrowserCommands { - resizeColumn: (resizeBy: number | readonly number[]) => Promise; dragFill: (from: string, to: string) => Promise; + resizeColumn: (resizeBy: number | readonly number[]) => Promise; + scrollGrid: (position: { scrollLeft?: number; scrollTop?: number }) => Promise; } } diff --git a/tsconfig.vite.json b/tsconfig.vite.json index 55ce01b576..57d8db9414 100644 --- a/tsconfig.vite.json +++ b/tsconfig.vite.json @@ -2,6 +2,7 @@ "extends": "./tsconfig.base.json", "compilerOptions": { "skipLibCheck": true, + "lib": ["ESNext", "DOM"], "types": ["@vitest/browser/providers/playwright"] }, "include": ["package.json", "rolldown.config.ts", "vite.config.ts"] diff --git a/vite.config.ts b/vite.config.ts index c2aefbaec4..dabe74be47 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -41,6 +41,24 @@ const dragFill: BrowserCommand<[from: string, to: string]> = async (context, fro await page.mouse.up(); }; +const scrollGrid: BrowserCommand<[{ scrollLeft?: number; scrollTop?: number }]> = async ( + context, + { scrollLeft, scrollTop } +) => { + const frame = await context.frame(); + await frame.getByRole('grid').evaluate( + (grid: HTMLDivElement, { scrollLeft, scrollTop }) => { + if (scrollLeft !== undefined) { + grid.scrollLeft = scrollLeft; + } + if (scrollTop !== undefined) { + grid.scrollTop = scrollTop; + } + }, + { scrollLeft, scrollTop } + ); +}; + const viewport = { width: 1920, height: 1080 } as const; export default defineConfig(({ command, isPreview }) => ({ @@ -108,7 +126,7 @@ export default defineConfig(({ command, isPreview }) => ({ context: { viewport } } ], - commands: { resizeColumn, dragFill }, + commands: { resizeColumn, dragFill, scrollGrid }, viewport, headless: true, screenshotFailures: !isCI