diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c00752d3c9..8a6fe2cb1a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -39,7 +39,13 @@ jobs: run: git diff --exit-code website/routeTree.gen.ts - name: Install Playwright Browsers - run: npx playwright install chromium firefox + run: | + sudo tee /etc/apt/apt.conf.d/99-ci >/dev/null <<'EOF' + Acquire::Retries "0"; + Acquire::http::Timeout "10"; + Acquire::https::Timeout "10"; + EOF + npx playwright install chromium firefox webkit --with-deps --no-shell timeout-minutes: 2 - name: Test diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 59c00efc32..d9c733956a 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -49,7 +49,13 @@ jobs: run: git diff --exit-code website/routeTree.gen.ts - name: Install Playwright Browsers - run: npx playwright install chromium firefox + run: | + sudo tee /etc/apt/apt.conf.d/99-ci >/dev/null <<'EOF' + Acquire::Retries "0"; + Acquire::http::Timeout "10"; + Acquire::https::Timeout "10"; + EOF + npx playwright install chromium firefox webkit --with-deps --no-shell timeout-minutes: 2 - name: Test diff --git a/.github/workflows/update-screenshots.yml b/.github/workflows/update-screenshots.yml index 5201002371..3893fcfcd9 100644 --- a/.github/workflows/update-screenshots.yml +++ b/.github/workflows/update-screenshots.yml @@ -38,8 +38,15 @@ jobs: - name: Install dependencies run: npm ci + - name: Install Playwright Browsers - run: npx playwright install chromium firefox + run: | + sudo tee /etc/apt/apt.conf.d/99-ci >/dev/null <<'EOF' + Acquire::Retries "0"; + Acquire::http::Timeout "10"; + Acquire::https::Timeout "10"; + EOF + npx playwright install chromium firefox webkit --with-deps --no-shell timeout-minutes: 2 - name: Update screenshots diff --git a/src/DataGrid.tsx b/src/DataGrid.tsx index f676967c9a..877bee1e66 100644 --- a/src/DataGrid.tsx +++ b/src/DataGrid.tsx @@ -620,7 +620,8 @@ export function DataGrid(props: DataGridPr if (!(target instanceof Element)) return; - const isCellEvent = target.closest('.rdg-cell') !== null; + const cell = target.closest('.rdg-cell'); + const isCellEvent = cell !== null; const isRowEvent = isTreeGrid && target.role === 'row'; if (!isCellEvent && !isRowEvent) return; @@ -638,7 +639,7 @@ export function DataGrid(props: DataGridPr navigate(event); break; default: - handleCellInput(event); + handleCellInput(event, cell); break; } } @@ -678,7 +679,7 @@ export function DataGrid(props: DataGridPr updateRow(column, activePosition.rowIdx, updatedRow); } - function handleCellInput(event: KeyboardEvent) { + function handleCellInput(event: KeyboardEvent, cell: Element | null) { if (!activePositionIsCellInViewport) return; const row = getActiveRow(); const { key, shiftKey } = event; @@ -694,6 +695,9 @@ export function DataGrid(props: DataGridPr } if (isCellEditable(activePosition) && isDefaultCellInput(event, onCellPaste != null)) { + // ensure cell is fully visible + scrollIntoView(cell); + setActivePosition(({ idx, rowIdx }) => ({ idx, rowIdx, diff --git a/test/browser/TreeDataGrid.test.tsx b/test/browser/TreeDataGrid.test.tsx index 96963dca57..4ecc1ff171 100644 --- a/test/browser/TreeDataGrid.test.tsx +++ b/test/browser/TreeDataGrid.test.tsx @@ -4,7 +4,7 @@ import { page, userEvent } from 'vitest/browser'; import type { Column } from '../../src'; import { renderTextEditor, SelectColumn, TreeDataGrid } from '../../src'; import { rowActiveClassname } from '../../src/style/row'; -import { getCellsAtRowIndex, getRowWithCell, testCount, testRowCount } from './utils'; +import { canCopyPaste, getCellsAtRowIndex, getRowWithCell, testCount, testRowCount } from './utils'; const treeGrid = page.getTreeGrid(); const headerRow = treeGrid.getHeaderRow(); @@ -412,7 +412,7 @@ test('cell navigation in a treegrid', async () => { await testRowCount(4); }); -test('copy/paste when grouping is enabled', async () => { +test('copy/paste when grouping is enabled', { fails: !canCopyPaste }, async () => { await setup(['year']); await userEvent.click(page.getCell({ name: '2021' })); await userEvent.copy(); diff --git a/test/browser/column/renderCell.test.tsx b/test/browser/column/renderCell.test.tsx index d0bdaf7ff1..4b36de5dca 100644 --- a/test/browser/column/renderCell.test.tsx +++ b/test/browser/column/renderCell.test.tsx @@ -176,7 +176,12 @@ test('Cell should not steal focus when the focus is outside the grid and cell is return ( <> - // Chrome and Firefox on windows (width >= 327.7 && width <= 327.9) || - // Firefox on CI + // Firefox/WebKit in CI width === 400 ) ); @@ -207,21 +213,29 @@ test('should remeasure flex columns when resizing a column', async () => { onColumnResize }); - await testGridTemplateColumns('639.328px 639.328px 639.344px', '639.333px 639.333px 639.333px'); + await testGridTemplateColumns({ + chrome: '639.328px 639.328px 639.344px', + firefox: '639.333px 639.333px 639.333px', + webkit: '639.328125px 639.328125px 639.328125px' + }); await autoResize('col1'); - await testGridTemplateColumns( - '79.1406px 919.422px 919.438px', - '79.1667px 919.417px 919.417px', - '100.5px 908.75px 908.75px' - ); + await testGridTemplateColumns({ + chrome: '79.1406px 919.422px 919.438px', + firefox: '79.1667px 919.417px 919.417px', + firefoxCI: '100.5px 908.75px 908.75px', + webkit: '79.140625px 919.421875px 919.421875px', + webkitCI: '102.796875px 907.59375px 907.59375px' + }); expect(onColumnResize).toHaveBeenCalledOnce(); // onColumnResize is not called if width is not changed await autoResize('col1'); - await testGridTemplateColumns( - '79.1406px 919.422px 919.438px', - '79.1667px 919.417px 919.417px', - '100.5px 908.75px 908.75px' - ); + await testGridTemplateColumns({ + chrome: '79.1406px 919.422px 919.438px', + firefox: '79.1667px 919.417px 919.417px', + firefoxCI: '100.5px 908.75px 908.75px', + webkit: '79.140625px 919.421875px 919.421875px', + webkitCI: '102.796875px 907.59375px 907.59375px' + }); expect(onColumnResize).toHaveBeenCalledOnce(); }); @@ -312,9 +326,24 @@ test('should use columnWidths and onColumnWidthsChange props when provided', asy ); }); -async function testGridTemplateColumns(chrome: string, firefox: string, firefoxCI = firefox) { +async function testGridTemplateColumns(obj: { + chrome: string; + firefox: string; + firefoxCI?: string; + webkit: string; + webkitCI?: string; +}) { + const { browser } = server; const gridTemplateColumns = - server.browser === 'chromium' ? chrome : import.meta.env.CI ? firefoxCI : firefox; + browser === 'chromium' + ? obj.chrome + : browser === 'webkit' + ? import.meta.env.CI + ? (obj.webkitCI ?? obj.webkit) + : obj.webkit + : import.meta.env.CI + ? (obj.firefoxCI ?? obj.firefox) + : obj.firefox; await expect.element(grid).toHaveStyle({ gridTemplateColumns }); } diff --git a/test/browser/copyPaste.test.tsx b/test/browser/copyPaste.test.tsx index a063235dc1..3ca0595e4a 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, safeTab } from './utils'; +import { canCopyPaste, getCellsAtRowIndex, safeTab } from './utils'; interface Row { col: string; @@ -68,7 +68,7 @@ function setup() { return page.render(); } -test('should call onCellCopy on cell copy', async () => { +test('should call onCellCopy on cell copy', { fails: !canCopyPaste }, async () => { await setup(); await userEvent.click(getCellsAtRowIndex(0).nth(0)); await userEvent.copy(); @@ -81,7 +81,7 @@ test('should call onCellCopy on cell copy', async () => { ); }); -test('should call onCellPaste on cell paste', async () => { +test('should call onCellPaste on cell paste', { fails: !canCopyPaste }, async () => { await setup(); await userEvent.click(getCellsAtRowIndex(0).nth(0)); await userEvent.paste(); @@ -101,7 +101,7 @@ test('should not allow paste on readonly cells', async () => { expect(onCellPasteSpy).not.toHaveBeenCalled(); }); -test('should allow copying a readonly cell', async () => { +test('should allow copying a readonly cell', { fails: !canCopyPaste }, async () => { await setup(); await userEvent.click(getCellsAtRowIndex(2).nth(0)); await userEvent.copy(); diff --git a/test/browser/utils.tsx b/test/browser/utils.tsx index 6860786170..23047a5aec 100644 --- a/test/browser/utils.tsx +++ b/test/browser/utils.tsx @@ -1,8 +1,11 @@ -import { page, userEvent, type Locator } from 'vitest/browser'; +import { page, server, userEvent, type Locator } from 'vitest/browser'; import { DataGrid } from '../../src'; import type { DataGridProps } from '../../src'; +// copy/paste do not work in webkit in CI +export const canCopyPaste = !(import.meta.env.CI && server.browser === 'webkit'); + export function setup(props: DataGridProps) { return page.render(); } diff --git a/test/visual/screenshots/basicGrid.test.tsx/basic-grid-webkit-linux.png b/test/visual/screenshots/basicGrid.test.tsx/basic-grid-webkit-linux.png new file mode 100644 index 0000000000..a83255d885 Binary files /dev/null and b/test/visual/screenshots/basicGrid.test.tsx/basic-grid-webkit-linux.png differ diff --git a/test/visual/screenshots/treeGrid.test.tsx/tree-grid-webkit-linux.png b/test/visual/screenshots/treeGrid.test.tsx/tree-grid-webkit-linux.png new file mode 100644 index 0000000000..6d7e164336 Binary files /dev/null and b/test/visual/screenshots/treeGrid.test.tsx/tree-grid-webkit-linux.png differ diff --git a/vite.config.ts b/vite.config.ts index de437f2481..9b7d05e4e4 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -31,12 +31,21 @@ const resizeColumn: BrowserCommand<[name: string, resizeBy: number | readonly nu }; // TODO: remove when `userEvent.pointer` is supported -const dragFill: BrowserCommand<[from: string, to: string]> = async ({ page, iframe }, from, to) => { +const dragFill: BrowserCommand<[from: string, to: string]> = async ( + { page, iframe, project }, + 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 = iframe.getByRole('gridcell', { name: to, exact: true }); - await toCell.hover(); + await iframe.getByRole('gridcell', { name: to, exact: true }).hover(); + if (project.name.includes('webkit')) { + // let React re-render after handleDragHandlePointerMove calls setDraggedOverRowIdx() + await new Promise((resolve) => { + setTimeout(resolve, 20); + }); + } await page.mouse.up(); }; @@ -169,6 +178,10 @@ export default defineConfig(({ isPreview }): ViteUserConfig => ({ }), // TODO: remove when FF tests are stable fileParallelism: false + }, + { + browser: 'webkit', + provider: playwright(playwrightOptions) } ] },