From 43de4a6c220c0aabc9336f8cb3e97ecf39867d92 Mon Sep 17 00:00:00 2001 From: Matheus Wichman Date: Thu, 6 Oct 2022 08:56:22 -0300 Subject: [PATCH 1/2] [test] Replace `React.render` with `React.createRoot` in e2e tests (#6393) --- test/e2e/index.js | 4 +-- test/e2e/index.test.ts | 58 ++++++++++++++++-------------------------- 2 files changed, 24 insertions(+), 38 deletions(-) diff --git a/test/e2e/index.js b/test/e2e/index.js index e60b6bd01177..827410c90316 100644 --- a/test/e2e/index.js +++ b/test/e2e/index.js @@ -1,5 +1,5 @@ import * as React from 'react'; -import * as ReactDOM from 'react-dom'; +import * as ReactDOM from 'react-dom/client'; import { BrowserRouter as Router, Routes, Route, Link } from 'react-router-dom'; import TestViewer from './TestViewer'; @@ -100,4 +100,4 @@ function App() { ); } -ReactDOM.render(, document.getElementById('react-root')); +ReactDOM.createRoot(document.getElementById('react-root')).render(); diff --git a/test/e2e/index.test.ts b/test/e2e/index.test.ts index a7aceecce149..3d38290125ba 100644 --- a/test/e2e/index.test.ts +++ b/test/e2e/index.test.ts @@ -94,9 +94,8 @@ describe('e2e', () => { it('should select the first column header when pressing tab key', async () => { await renderFixture('DataGrid/KeyboardNavigationFocus'); - expect( - await page.evaluate(() => document.activeElement?.getAttribute('data-testid')), - ).to.equal('initial-focus'); + const button = page.locator('text=initial focus'); + expect(await button.evaluate((node) => document.activeElement === node)); await page.keyboard.press('Tab'); expect(await page.evaluate(() => document.activeElement?.textContent)).to.equal('brand'); @@ -105,9 +104,9 @@ describe('e2e', () => { it('should implement the roving tabindex pattern', async () => { await renderFixture('DataGrid/KeyboardNavigationFocus'); - expect( - await page.evaluate(() => document.activeElement?.getAttribute('data-testid')), - ).to.equal('initial-focus'); + const button = page.locator('text=initial focus'); + expect(await button.evaluate((node) => document.activeElement === node)); + await page.keyboard.press('Tab'); await waitFor(async () => { expect(await page.evaluate(() => document.activeElement?.textContent)).to.equal('brand'); @@ -153,11 +152,9 @@ describe('e2e', () => { it('should display the rows', async () => { await renderFixture('DataGrid/ConcurrentReactUpdate'); - expect( - await page.evaluate(() => - Array.from(document.querySelectorAll('[role="cell"]')).map((node) => node.textContent), - ), - ).to.deep.equal(['1', '2']); + const cells = page.locator('[role="cell"]'); + await cells.first().waitFor(); + expect(await cells.allTextContents()).to.deep.equal(['1', '2']); }); it('should work with a select as the edit cell', async () => { @@ -174,9 +171,7 @@ describe('e2e', () => { it('should reorder columns by dropping into the header', async () => { await renderFixture('DataGrid/ColumnReorder'); - expect( - await page.evaluate(() => document.querySelector('[role="row"]')!.textContent!), - ).to.equal('brandyear'); + expect(await page.locator('[role="row"]').first().textContent()).to.equal('brandyear'); const brand = await page.$('[role="columnheader"][aria-colindex="1"] > [draggable]'); const brandBoundingBox = await brand?.boundingBox(); const year = await page.$('[role="columnheader"][aria-colindex="2"] > [draggable]'); @@ -203,9 +198,7 @@ describe('e2e', () => { it('should reorder columns by dropping into the body', async () => { await renderFixture('DataGrid/ColumnReorder'); - expect( - await page.evaluate(() => document.querySelector('[role="row"]')!.textContent!), - ).to.equal('brandyear'); + expect(await page.locator('[role="row"]').first().textContent()).to.equal('brandyear'); const brand = await page.$('[role="columnheader"][aria-colindex="1"] > [draggable]'); const brandBoundingBox = await brand?.boundingBox(); const cell = await page.$('[role="row"][data-rowindex="0"] [role="cell"][data-colindex="1"]'); @@ -225,9 +218,7 @@ describe('e2e', () => { ); await page.mouse.up(); } - expect( - await page.evaluate(() => document.querySelector('[role="row"]')!.textContent!), - ).to.equal('yearbrand'); + expect(await page.locator('[role="row"]').first().textContent()).to.equal('yearbrand'); }); it('should select one row', async () => { @@ -262,11 +253,9 @@ describe('e2e', () => { await renderFixture('DataGrid/KeyboardEditDate'); // Edit date column - expect( - await page.evaluate( - () => document.querySelector('[role="cell"][data-field="birthday"]')!.textContent!, - ), - ).to.equal('2/29/1984'); + expect(await page.locator('[role="cell"][data-field="birthday"]').textContent()).to.equal( + '2/29/1984', + ); // set 06/25/1986 await page.dblclick('[role="cell"][data-field="birthday"]'); @@ -274,17 +263,13 @@ describe('e2e', () => { await page.keyboard.press('Enter'); - expect( - await page.evaluate( - () => document.querySelector('[role="cell"][data-field="birthday"]')!.textContent!, - ), - ).to.equal('6/25/1986'); + expect(await page.locator('[role="cell"][data-field="birthday"]').textContent()).to.equal( + '6/25/1986', + ); // Edit dateTime column expect( - await page.evaluate( - () => document.querySelector('[role="cell"][data-field="lastConnection"]')!.textContent!, - ), + await page.locator('[role="cell"][data-field="lastConnection"]').textContent(), ).to.equal('2/20/2022, 6:50:00 AM'); // start editing lastConnection @@ -296,9 +281,7 @@ describe('e2e', () => { await page.keyboard.press('Enter'); expect( - await page.evaluate( - () => document.querySelector('[role="cell"][data-field="lastConnection"]')!.textContent!, - ), + await page.locator('[role="cell"][data-field="lastConnection"]').textContent(), ).to.equal('1/31/2025, 4:05:00 PM'); }); @@ -312,6 +295,9 @@ describe('e2e', () => { await sleep(100); } + const button = page.locator('text=initial focus'); + expect(await button.evaluate((node) => document.activeElement === node)); + await page.keyboard.down('Tab'); await keyDown(); // 0 From d8fb22835ecd2b92ede1709b41090b7e489bfe79 Mon Sep 17 00:00:00 2001 From: Matheus Wichman Date: Wed, 12 Oct 2022 20:42:33 -0300 Subject: [PATCH 2/2] [DataGrid] Fix start edit mode with printable character in React 18 (#6257) --- .../cellEditing.DataGridPro.new.test.tsx | 2 +- .../tests/rowEditing.DataGridPro.new.test.tsx | 2 +- .../editRows/useGridCellEditing.new.ts | 24 ++++++++++----- .../editRows/useGridRowEditing.new.ts | 29 +++++++++++++------ .../src/models/api/gridEditingApi.ts | 10 +++++++ .../src/models/params/gridEditCellParams.ts | 4 +++ .../src/models/params/gridRowParams.ts | 4 +++ .../fixtures/DataGrid/KeyboardEditInput.tsx | 15 ++++++++++ test/e2e/index.test.ts | 15 ++++++++++ 9 files changed, 86 insertions(+), 19 deletions(-) create mode 100644 test/e2e/fixtures/DataGrid/KeyboardEditInput.tsx diff --git a/packages/grid/x-data-grid-pro/src/tests/cellEditing.DataGridPro.new.test.tsx b/packages/grid/x-data-grid-pro/src/tests/cellEditing.DataGridPro.new.test.tsx index 0ea7e598ee9d..13ab67199058 100644 --- a/packages/grid/x-data-grid-pro/src/tests/cellEditing.DataGridPro.new.test.tsx +++ b/packages/grid/x-data-grid-pro/src/tests/cellEditing.DataGridPro.new.test.tsx @@ -908,7 +908,7 @@ describe(' - Cell Editing', () => { expect(spiedStartCellEditMode.lastCall.args[0]).to.deep.equal({ id: 0, field: 'currencyPair', - deleteValue: true, + initialValue: 'a', }); }); diff --git a/packages/grid/x-data-grid-pro/src/tests/rowEditing.DataGridPro.new.test.tsx b/packages/grid/x-data-grid-pro/src/tests/rowEditing.DataGridPro.new.test.tsx index b382e0020ab1..602a71c5c7d5 100644 --- a/packages/grid/x-data-grid-pro/src/tests/rowEditing.DataGridPro.new.test.tsx +++ b/packages/grid/x-data-grid-pro/src/tests/rowEditing.DataGridPro.new.test.tsx @@ -900,7 +900,7 @@ describe(' - Row Editing', () => { expect(spiedStartRowEditMode.lastCall.args[0]).to.deep.equal({ id: 0, fieldToFocus: 'currencyPair', - deleteValue: true, + initialValue: 'a', }); }); diff --git a/packages/grid/x-data-grid/src/hooks/features/editRows/useGridCellEditing.new.ts b/packages/grid/x-data-grid/src/hooks/features/editRows/useGridCellEditing.new.ts index da67e8324ef9..a255b7d5b54e 100644 --- a/packages/grid/x-data-grid/src/hooks/features/editRows/useGridCellEditing.new.ts +++ b/packages/grid/x-data-grid/src/hooks/features/editRows/useGridCellEditing.new.ts @@ -174,7 +174,7 @@ export const useGridCellEditing = ( } if (reason) { - const newParams: GridCellEditStartParams = { ...params, reason }; + const newParams: GridCellEditStartParams = { ...params, reason, key: event.key }; apiRef.current.publishEvent('cellEditStart', newParams, event); } } @@ -184,14 +184,17 @@ export const useGridCellEditing = ( const handleCellEditStart = React.useCallback>( (params) => { - const { id, field, reason } = params; + const { id, field, reason, key } = params; const startCellEditModeParams: GridStartCellEditModeParams = { id, field }; - if ( - reason === GridCellEditStartReasons.deleteKeyDown || - reason === GridCellEditStartReasons.printableKeyDown - ) { + if (reason === GridCellEditStartReasons.printableKeyDown) { + if (React.version.startsWith('18')) { + startCellEditModeParams.initialValue = key; // In React 17, cleaning the input is enough + } else { + startCellEditModeParams.deleteValue = true; + } + } else if (reason === GridCellEditStartReasons.deleteKeyDown) { startCellEditModeParams.deleteValue = true; } @@ -328,10 +331,15 @@ export const useGridCellEditing = ( const updateStateToStartCellEditMode = useEventCallback<[GridStartCellEditModeParams], void>( (params) => { - const { id, field, deleteValue } = params; + const { id, field, deleteValue, initialValue } = params; + + let newValue = apiRef.current.getCellValue(id, field); + if (deleteValue || initialValue) { + newValue = deleteValue ? '' : initialValue; + } const newProps = { - value: deleteValue ? '' : apiRef.current.getCellValue(id, field), + value: newValue, error: false, isProcessingProps: false, }; diff --git a/packages/grid/x-data-grid/src/hooks/features/editRows/useGridRowEditing.new.ts b/packages/grid/x-data-grid/src/hooks/features/editRows/useGridRowEditing.new.ts index 6d22397fdff5..e1880f392e77 100644 --- a/packages/grid/x-data-grid/src/hooks/features/editRows/useGridRowEditing.new.ts +++ b/packages/grid/x-data-grid/src/hooks/features/editRows/useGridRowEditing.new.ts @@ -237,7 +237,12 @@ export const useGridRowEditing = ( if (reason) { const rowParams = apiRef.current.getRowParams(params.id); - const newParams: GridRowEditStartParams = { ...rowParams, field: params.field, reason }; + const newParams: GridRowEditStartParams = { + ...rowParams, + field: params.field, + key: event.key, + reason, + }; apiRef.current.publishEvent('rowEditStart', newParams, event); } } @@ -247,14 +252,17 @@ export const useGridRowEditing = ( const handleRowEditStart = React.useCallback>( (params) => { - const { id, field, reason } = params; + const { id, field, reason, key } = params; const startRowEditModeParams: GridStartRowEditModeParams = { id, fieldToFocus: field }; - if ( - reason === GridRowEditStartReasons.deleteKeyDown || - reason === GridRowEditStartReasons.printableKeyDown - ) { + if (reason === GridRowEditStartReasons.printableKeyDown) { + if (React.version.startsWith('18')) { + startRowEditModeParams.initialValue = key; // In React 17, cleaning the input is enough + } else { + startRowEditModeParams.deleteValue = !!field; + } + } else if (reason === GridRowEditStartReasons.deleteKeyDown) { startRowEditModeParams.deleteValue = !!field; } @@ -400,7 +408,7 @@ export const useGridRowEditing = ( const updateStateToStartRowEditMode = useEventCallback<[GridStartRowEditModeParams], void>( (params) => { - const { id, fieldToFocus, deleteValue } = params; + const { id, fieldToFocus, deleteValue, initialValue } = params; const columnFields = gridColumnFieldsSelector(apiRef); @@ -410,10 +418,13 @@ export const useGridRowEditing = ( return acc; } - const shouldDeleteValue = deleteValue && fieldToFocus === field; + let newValue = apiRef.current.getCellValue(id, field); + if (fieldToFocus === field && (deleteValue || initialValue)) { + newValue = deleteValue ? '' : initialValue; + } acc[field] = { - value: shouldDeleteValue ? '' : apiRef.current.getCellValue(id, field), + value: newValue, error: false, isProcessingProps: false, }; diff --git a/packages/grid/x-data-grid/src/models/api/gridEditingApi.ts b/packages/grid/x-data-grid/src/models/api/gridEditingApi.ts index d1fffdc8b75f..38a43c742ae4 100644 --- a/packages/grid/x-data-grid/src/models/api/gridEditingApi.ts +++ b/packages/grid/x-data-grid/src/models/api/gridEditingApi.ts @@ -207,6 +207,11 @@ export interface GridStartCellEditModeParams { * If `true`, the value will be deleted before entering the edit mode. */ deleteValue?: boolean; + /** + * The initial value for the field. + * If `deleteValue` is also true, this value is not used. + */ + initialValue?: any; } /** @@ -249,6 +254,11 @@ export interface GridStartRowEditModeParams { * If `true`, the value in `fieldToFocus` will be deleted before entering the edit mode. */ deleteValue?: boolean; + /** + * The initial value for the given `fieldToFocus`. + * If `deleteValue` is also true, this value is not used. + */ + initialValue?: string; } /** diff --git a/packages/grid/x-data-grid/src/models/params/gridEditCellParams.ts b/packages/grid/x-data-grid/src/models/params/gridEditCellParams.ts index bdc732315599..cf97252b30b6 100644 --- a/packages/grid/x-data-grid/src/models/params/gridEditCellParams.ts +++ b/packages/grid/x-data-grid/src/models/params/gridEditCellParams.ts @@ -68,6 +68,10 @@ export interface GridCellEditStartParams< * Only applied if `props.experimentalFeatures.newEditingApi: true`. */ reason?: GridCellEditStartReasons; + /** + * If the reason is related to a keyboard event, it contains which key was pressed. + */ + key?: string; } enum GridCellEditStopReasons { diff --git a/packages/grid/x-data-grid/src/models/params/gridRowParams.ts b/packages/grid/x-data-grid/src/models/params/gridRowParams.ts index 6c55d5cc232f..db7a5b9cca8d 100644 --- a/packages/grid/x-data-grid/src/models/params/gridRowParams.ts +++ b/packages/grid/x-data-grid/src/models/params/gridRowParams.ts @@ -87,6 +87,10 @@ export interface GridRowEditStartParams * Only applied if `props.experimentalFeatures.newEditingApi: true`. */ reason?: GridRowEditStartReasons; + /** + * If the reason is related to a keyboard event, it contains which key was pressed. + */ + key?: string; } enum GridRowEditStopReasons { diff --git a/test/e2e/fixtures/DataGrid/KeyboardEditInput.tsx b/test/e2e/fixtures/DataGrid/KeyboardEditInput.tsx new file mode 100644 index 000000000000..ea4ff07e6126 --- /dev/null +++ b/test/e2e/fixtures/DataGrid/KeyboardEditInput.tsx @@ -0,0 +1,15 @@ +import * as React from 'react'; +import { DataGrid } from '@mui/x-data-grid'; + +const baselineProps = { + rows: [{ id: 0, brand: 'Nike' }], + columns: [{ field: 'brand', type: 'string', editable: true, width: 100 }], +}; + +export default function KeyboardEditInput() { + return ( +
+ +
+ ); +} diff --git a/test/e2e/index.test.ts b/test/e2e/index.test.ts index 3d38290125ba..e4d97b96d3f9 100644 --- a/test/e2e/index.test.ts +++ b/test/e2e/index.test.ts @@ -336,5 +336,20 @@ describe('e2e', () => { await page.evaluate(() => (document.activeElement as HTMLInputElement).value), ).to.equal('0-1'); }); + + it('should start editing a cell when a printable char is pressed', async () => { + await renderFixture('DataGrid/KeyboardEditInput'); + + expect(await page.locator('[role="cell"][data-field="brand"]').textContent()).to.equal( + 'Nike', + ); + + await page.click('[role="cell"][data-field="brand"]'); + await page.type('[role="cell"][data-field="brand"]', 'p'); + + expect(await page.locator('[role="cell"][data-field="brand"] input').inputValue()).to.equal( + 'p', + ); + }); }); });