diff --git a/src/DataGrid.tsx b/src/DataGrid.tsx index 4826785dd5..e05c7f53cb 100644 --- a/src/DataGrid.tsx +++ b/src/DataGrid.tsx @@ -29,8 +29,7 @@ import { isCtrlKeyHeldDown, isDefaultCellInput, renderMeasuringCells, - scrollIntoView, - sign + scrollIntoView } from './utils'; import type { CalculatedColumn, @@ -573,8 +572,10 @@ export function DataGrid(props: DataGridPr previousRowIdx !== rowIdx && previousRowIdx < rows.length ) { - const step = sign(rowIdx - previousRowIdx); - for (let i = previousRowIdx + step; i < rowIdx; i += step) { + const [min, max] = + previousRowIdx < rowIdx ? [previousRowIdx, rowIdx] : [rowIdx, previousRowIdx]; + + for (let i = min + 1; i < max; i++) { const row = rows[i]; if (isRowSelectionDisabled?.(row) === true) continue; if (checked) { diff --git a/src/utils/index.ts b/src/utils/index.ts index ad576ba528..592b6daad0 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -8,7 +8,7 @@ export * from './keyboardUtils'; export * from './renderMeasuringCells'; export * from './styleUtils'; -export const { min, max, floor, sign, abs } = Math; +export const { min, max, floor, abs } = Math; export function assertIsValidKeyGetter( keyGetter: Maybe<(row: NoInfer) => K> diff --git a/test/browser/column/resizable.test.tsx b/test/browser/column/resizable.test.tsx index 6e9f6984a8..52994c9e61 100644 --- a/test/browser/column/resizable.test.tsx +++ b/test/browser/column/resizable.test.tsx @@ -1,5 +1,5 @@ import { useState } from 'react'; -import { commands, page, userEvent } from 'vitest/browser'; +import { commands, page, server, userEvent } from 'vitest/browser'; import { DataGrid, type Column, type ColumnWidth, type ColumnWidths } from '../../../src'; import { setup } from '../utils'; @@ -313,11 +313,8 @@ test('should use columnWidths and onColumnWidthsChange props when provided', asy }); async function testGridTemplateColumns(chrome: string, firefox: string, firefoxCI = firefox) { - const gridTemplateColumns = navigator.userAgent.includes('Chrome') - ? chrome - : __IS_CI__ - ? firefoxCI - : firefox; + const gridTemplateColumns = + server.browser === 'chromium' ? chrome : import.meta.env.CI ? firefoxCI : firefox; await expect.element(grid).toHaveStyle({ gridTemplateColumns }); } diff --git a/test/browser/rowSelection.test.tsx b/test/browser/rowSelection.test.tsx index b48b387334..8f5d885071 100644 --- a/test/browser/rowSelection.test.tsx +++ b/test/browser/rowSelection.test.tsx @@ -239,16 +239,34 @@ test('extra keys are preserved when updating the selectedRows Set', async () => test('select/deselect rows using shift click', async () => { await setup(); + + // forward selection await toggleSelection(0); await toggleSelection(2, true); await testSelection(0, true); await testSelection(1, true); await testSelection(2, true); + + // forward deselection await toggleSelection(0); await toggleSelection(2, true); await testSelection(0, false); await testSelection(1, false); await testSelection(2, false); + + // backward selection + await toggleSelection(2); + await toggleSelection(0, true); + await testSelection(0, true); + await testSelection(1, true); + await testSelection(2, true); + + // backward deselection + await toggleSelection(2); + await toggleSelection(0, true); + await testSelection(0, false); + await testSelection(1, false); + await testSelection(2, false); }); test('select rows using shift space', async () => { diff --git a/test/browser/virtualization.test.ts b/test/browser/virtualization.test.ts index 70529a94c4..d39bbf578f 100644 --- a/test/browser/virtualization.test.ts +++ b/test/browser/virtualization.test.ts @@ -220,7 +220,7 @@ test('zero rows', async () => { await expect.element(rows).toHaveLength(0); }); -test('virtualization is enable with not enough columns or rows to virtualize', async () => { +test('virtualization is enabled with not enough columns or rows to virtualize', async () => { await setupGrid(true, 5, 5); await assertHeaderCells(5, 0, 4); @@ -228,10 +228,21 @@ test('virtualization is enable with not enough columns or rows to virtualize', a await expect.element(cells).toHaveLength(5 * 5); }); -test('enableVirtualization is disabled', async () => { +test('virtualization is disabled with no frozen columns', async () => { await setupGrid(false, 40, 100); await assertHeaderCells(40, 0, 39); await assertRows(100, 0, 99); await expect.element(cells).toHaveLength(40 * 100); }); + +// failing test +// cannot use `test.fails` as console logs lead to timeout in parallel tests +// https://github.com/vitest-dev/vitest/issues/9941 +test.skip('virtualization is disabled with some frozen columns', async () => { + await setupGrid(false, 40, 100, 3); + + await assertHeaderCells(40, 0, 39); + await assertRows(100, 0, 99); + await expect.element(cells).toHaveLength(40 * 100); +}); diff --git a/test/failOnConsole.ts b/test/failOnConsole.ts index d04c4f8005..41f96a1d29 100644 --- a/test/failOnConsole.ts +++ b/test/failOnConsole.ts @@ -1,7 +1,11 @@ -let consoleErrorOrConsoleWarnWereCalled = false; +beforeEach(({ onTestFinished }) => { + vi.spyOn(console, 'warn').mockName('console.warn'); -beforeAll(() => { - console.error = (...params) => { + // use split mocks to not increase the calls count when ignoring undesired logs + const errorMock = vi.fn(console.error).mockName('console.error'); + vi.spyOn(console, 'error').mockImplementation(function error(...params) { + // https://github.com/vitest-dev/vitest/blob/0685b6f027576589464fc6109ddc071ef0079f16/packages/browser/src/client/public/error-catcher.js#L34-L38 + // https://github.com/vitest-dev/vitest/blob/0685b6f027576589464fc6109ddc071ef0079f16/test/browser/fixtures/unhandled-non-error/basic.test.ts if ( params[0] instanceof Error && params[0].message === 'ResizeObserver loop completed with undelivered notifications.' @@ -9,29 +13,25 @@ beforeAll(() => { return; } - consoleErrorOrConsoleWarnWereCalled = true; - console.log(...params); - }; - - console.warn = (...params) => { - consoleErrorOrConsoleWarnWereCalled = true; - console.log(...params); - }; -}); + return errorMock(...params); + }); -afterEach(() => { // Wait for the test and all `afterEach` hooks to complete to ensure all logs are caught - onTestFinished(({ task, signal }) => { + onTestFinished(({ expect, task, signal }) => { // avoid failing test runs twice - if (task.result!.state !== 'fail' && !signal.aborted) { - expect - .soft( - consoleErrorOrConsoleWarnWereCalled, - 'errors/warnings were logged to the console during the test' - ) - .toBe(false); - } + if (task.result?.state === 'fail' || signal.aborted) return; - consoleErrorOrConsoleWarnWereCalled = false; + expect + .soft( + console.warn, + 'console.warn() was called during the test; please resolve unexpected warnings' + ) + .toHaveBeenCalledTimes(0); + expect + .soft( + errorMock, + 'console.error() was called during the test; please resolve unexpected errors' + ) + .toHaveBeenCalledTimes(0); }); }); diff --git a/test/globals.d.ts b/test/globals.d.ts index 436818da18..ca127afd20 100644 --- a/test/globals.d.ts +++ b/test/globals.d.ts @@ -1,5 +1,9 @@ declare global { - const __IS_CI__: boolean; + interface ImportMeta { + readonly env: { + readonly CI: boolean; + }; + } } declare module 'vitest/browser' { diff --git a/vite.config.ts b/vite.config.ts index a1dc692fdc..36b7131bfc 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -3,10 +3,10 @@ import react from '@vitejs/plugin-react'; import { playwright, type PlaywrightProviderOptions } from '@vitest/browser-playwright'; import { ecij } from 'ecij/plugin'; import { defineConfig, type ViteUserConfig } from 'vitest/config'; -import type { BrowserCommand, BrowserInstanceOption } from 'vitest/node'; +import type { BrowserCommand } from 'vitest/node'; const isCI = process.env.CI === 'true'; -const isTest = process.env.NODE_ENV === 'test'; +const isTest = process.env.VITEST === 'true'; // TODO: remove when `userEvent.pointer` is supported const resizeColumn: BrowserCommand<[name: string, resizeBy: number | readonly number[]]> = async ( @@ -48,34 +48,11 @@ const playwrightOptions: PlaywrightProviderOptions = { } }; -// vitest modifies the instance objects, so we cannot rely on static objects -// https://github.com/vitest-dev/vitest/issues/9877 -function getInstances(): BrowserInstanceOption[] { - return [ - { - browser: 'chromium', - provider: playwright({ - ...playwrightOptions, - launchOptions: { - channel: 'chromium' - } - }) - }, - { - browser: 'firefox', - provider: playwright(playwrightOptions), - // TODO: remove when FF tests are stable - fileParallelism: false - } - ]; -} - export default defineConfig( ({ isPreview }): ViteUserConfig => ({ base: '/react-data-grid/', cacheDir: '.cache/vite', clearScreen: false, - define: isTest ? { __IS_CI__: JSON.stringify(isCI) } : {}, build: { modulePreload: { polyfill: false }, sourcemap: true, @@ -83,17 +60,19 @@ export default defineConfig( // https://github.com/parcel-bundler/lightningcss/issues/873 cssTarget: 'esnext' }, - plugins: [ - ecij(), - (!isTest || isPreview) && - tanstackRouter({ - target: 'react', - generatedRouteTree: 'website/routeTree.gen.ts', - routesDirectory: 'website/routes', - autoCodeSplitting: true - }), - react() - ], + plugins: isPreview + ? [] + : [ + ecij(), + !isTest && + tanstackRouter({ + target: 'react', + generatedRouteTree: 'website/routeTree.gen.ts', + routesDirectory: 'website/routes', + autoCodeSplitting: true + }), + react() + ], server: { open: true }, @@ -101,6 +80,10 @@ export default defineConfig( dir: 'test', globals: true, printConsoleTrace: true, + env: { + // @ts-expect-error + CI: isCI + }, coverage: { provider: 'istanbul', enabled: isCI, @@ -120,20 +103,51 @@ export default defineConfig( } }, slowTestThreshold: 1000, + browser: { + headless: true, + ui: false, + viewport, + commands: { resizeColumn, dragFill }, + expect: { + toMatchScreenshot: { + resolveScreenshotPath({ + root, + testFileDirectory, + testFileName, + arg, + browserName, + platform, + ext + }) { + return `${root}/${testFileDirectory}/screenshots/${testFileName}/${arg}-${browserName}-${platform}${ext}`; + } + } + }, + instances: [ + { + browser: 'chromium', + provider: playwright({ + ...playwrightOptions, + launchOptions: { + channel: 'chromium' + } + }) + }, + { + browser: 'firefox', + provider: playwright(playwrightOptions), + // TODO: remove when FF tests are stable + fileParallelism: false + } + ] + }, projects: [ { extends: true, test: { name: 'browser', include: ['browser/**/*.test.*'], - browser: { - enabled: true, - instances: getInstances(), - commands: { resizeColumn, dragFill }, - viewport, - headless: true, - ui: false - }, + browser: { enabled: true }, setupFiles: ['test/browser/styles.css', 'test/setupBrowser.ts', 'test/failOnConsole.ts'] } }, @@ -142,28 +156,7 @@ export default defineConfig( test: { name: 'visual', include: ['visual/*.test.*'], - browser: { - enabled: true, - instances: getInstances(), - viewport, - headless: true, - ui: false, - expect: { - toMatchScreenshot: { - resolveScreenshotPath({ - root, - testFileDirectory, - testFileName, - arg, - browserName, - platform, - ext - }) { - return `${root}/${testFileDirectory}/screenshots/${testFileName}/${arg}-${browserName}-${platform}${ext}`; - } - } - } - }, + browser: { enabled: true }, setupFiles: ['test/setupBrowser.ts', 'test/failOnConsole.ts'] } },