From ceed750e2cda066de73296111a379f3311a39ffc Mon Sep 17 00:00:00 2001 From: "anna.shakhova" Date: Thu, 5 Feb 2026 10:17:36 +0100 Subject: [PATCH 1/2] DataGrid: unskip e2e functional editing tests --- .../editing/editing.functional_matrix.ts | 607 ++++++++++++++++++ .../editingNewRow.functional_matrix.ts | 234 +++++++ .../tests/dataGrid/common/editing/matrix.ts | 451 ------------- .../testcafe-models/dataGrid/commandCell.ts | 5 + 4 files changed, 846 insertions(+), 451 deletions(-) create mode 100644 e2e/testcafe-devextreme/tests/dataGrid/common/editing/editing.functional_matrix.ts create mode 100644 e2e/testcafe-devextreme/tests/dataGrid/common/editing/editingNewRow.functional_matrix.ts delete mode 100644 e2e/testcafe-devextreme/tests/dataGrid/common/editing/matrix.ts diff --git a/e2e/testcafe-devextreme/tests/dataGrid/common/editing/editing.functional_matrix.ts b/e2e/testcafe-devextreme/tests/dataGrid/common/editing/editing.functional_matrix.ts new file mode 100644 index 000000000000..420bf16b0e28 --- /dev/null +++ b/e2e/testcafe-devextreme/tests/dataGrid/common/editing/editing.functional_matrix.ts @@ -0,0 +1,607 @@ +/* eslint-disable @typescript-eslint/init-declarations */ +import { Selector } from 'testcafe'; +import { CellEditor } from 'devextreme-testcafe-models/dataGrid/data/cellEditor'; +import DataGrid from 'devextreme-testcafe-models/dataGrid'; +import DataCell from 'devextreme-testcafe-models/dataGrid/data/cell'; +import EditForm from 'devextreme-testcafe-models/dataGrid/editForm'; +import { GridsEditMode } from 'devextreme/ui/data_grid'; +import { ClassNames as CLASS } from 'devextreme-testcafe-models/dataGrid/classNames'; +import { createWidget } from '../../../../helpers/createWidget'; +import url from '../../../../helpers/getPageUrl'; + +fixture.disablePageReloads`Editing.FunctionalMatrix` + .page(url(__dirname, '../../../container.html')); + +interface ColumnInfo { + columnIndex: number; + dataField: string; + newValue: string; + newMaskValue?: string; +} + +const editingModes: GridsEditMode[] = ['cell', 'batch', 'row', 'form', 'popup']; + +const textColumnInfos: ColumnInfo[] = [ + { columnIndex: 0, dataField: 'text', newValue: 'xxxx' }, + { columnIndex: 5, dataField: 'calculated', newValue: '9' }, +]; + +const expectedTextColumnResult: ColumnInfo[] = [ + ...textColumnInfos, + { columnIndex: 1, dataField: 'number', newValue: '8' }, +]; + +const maskedColumnInfos: ColumnInfo[] = [ + { + columnIndex: 0, dataField: 'text', newValue: 'xxxx', newMaskValue: 'xxxxx', + }, + { + columnIndex: 1, dataField: 'number', newValue: '-9', newMaskValue: '9-', + }, + { + columnIndex: 2, dataField: 'date', newValue: '10/1/2020', newMaskValue: '101', + }, +]; + +const expectedMaskedColumnResult: ColumnInfo[] = [ + ...maskedColumnInfos, + { columnIndex: 5, dataField: 'calculated', newValue: '-8' }, +]; + +const basicColumnInfos: ColumnInfo[] = [ + { columnIndex: 0, dataField: 'text', newValue: 'xxxx' }, + { columnIndex: 1, dataField: 'number', newValue: '-9' }, + { columnIndex: 2, dataField: 'date', newValue: '10/1/2020' }, + { columnIndex: 3, dataField: 'lookup', newValue: 'lookup 2' }, + { columnIndex: 4, dataField: 'boolean', newValue: 'true' }, +]; + +const expectedBasicColumnResult: ColumnInfo[] = [ + ...basicColumnInfos, + { columnIndex: 5, dataField: 'calculated', newValue: '-8' }, +]; + +const dataGrid = new DataGrid('#container'); + +const createDataGrid = ({ + mode, repaintChangesOnly = false, useMask = false, +}) => async (): Promise => createWidget('dxDataGrid', { + keyExpr: 'id', + dataSource: [ + { + id: 1, text: 'text 1', number: 1, date: '2020-10-27', boolean: false, lookup: 1, + }, + { + id: 2, text: 'text 2', number: 2, date: '2020-10-28', boolean: true, lookup: 2, + }, + ], + repaintChangesOnly, + editing: { + mode, + allowUpdating: true, + }, + columns: [ + { + dataField: 'text', + editorOptions: { + mask: useMask ? 'cccc' : undefined, + }, + }, + { + dataField: 'number', + editorOptions: { + format: '#0', + useMaskBehavior: useMask, + }, + }, + { + dataField: 'date', + dataType: 'date', + editorOptions: { + useMaskBehavior: useMask, + pickerType: 'calendar', + }, + }, + { + dataField: 'lookup', + lookup: { + valueExpr: 'id', + displayExpr: 'text', + dataSource: [ + { id: 1, text: 'lookup 1' }, + { id: 2, text: 'lookup 2' }, + ], + }, + }, + { dataField: 'boolean' }, + { + dataField: 'calculated', + calculateCellValue: (data): number => (data as { number: number }).number + 1, + setCellValue: (newData, value): void => { + newData.number = value - 1; + }, + }, + ], +}); + +const getEditForm = (mode: GridsEditMode): EditForm | null => { + if (mode === 'form') { + return dataGrid.getEditForm(); + } + if (mode === 'popup') { + return dataGrid.getPopupEditForm(); + } + return null; +}; + +const clickEditButtonIfExists = async (t: TestController, form: EditForm | null): Promise => { + const formAlreadyOpened = await form?.element.exists && await form?.element.visible; + + if (formAlreadyOpened) { + return; + } + + const editButton = dataGrid.getDataRow(0).getCommandCell(6).getEditButton(); + + if (await editButton.exists) { + await t.click(editButton); + } +}; + +const checkCellFocused = async ( + t: TestController, + mode: GridsEditMode, + { dataField }: ColumnInfo, + cell: DataCell | undefined, + editor: CellEditor | undefined, +): Promise => { + const isRowBasedMode = mode === 'row'; + const isFormBasedMode = mode === 'form' || mode === 'popup'; + + if (!isFormBasedMode) { + await t.expect(cell?.isFocused).ok(); + } + + if ((isRowBasedMode || isFormBasedMode) && dataField === 'boolean') { + return; + } + + await t + .expect(editor?.element.focused) + .eql(true); +}; + +const clickCellEditor = async ( + t: TestController, + mode: GridsEditMode, + columnInfo: ColumnInfo, + form: EditForm | null, + cell: DataCell, + editor: CellEditor, +): Promise => { + await clickEditButtonIfExists(t, form); + + const item = !form ? cell.element : editor.getItemLabel(); + await t.click(item, { offsetX: 25 }); + + await checkCellFocused(t, mode, columnInfo, cell, editor); +}; + +const moveToFirstCellEditor = async (t: TestController, mode: GridsEditMode): Promise => { + await t + .pressKey('tab') + .pressKey('ctrl+down') + .pressKey('enter'); + + if (mode === 'popup') { + const columns = await dataGrid.apiOption('columns'); + await t + .pressKey(columns.map(() => 'tab').join(' ')) + .pressKey('enter') + .wait(500); + } +}; + +const setEditorValue = async ( + t: TestController, + mode: GridsEditMode, + { dataField, newMaskValue, newValue }: ColumnInfo, + editor: CellEditor, + useKeyboard = false, + useMask = false, +): Promise => { + if (dataField === 'boolean') { + if (useKeyboard) { + await t.pressKey('space'); + } else { + await t.click(editor.element); + } + + return; + } + + const value: string = useMask ? newMaskValue ?? '' : newValue; + + if (dataField === 'date' && !useKeyboard && !useMask) { + await t.click(editor.getDropDownButton()); + await t.click(Selector(`.${CLASS.calendarCell}`).withText(value.split('/')[1])); + + return; + } + + if (dataField === 'lookup' && !useKeyboard) { + if (mode === 'cell' || mode === 'batch') { + await t.click(editor.getDropDownButton()); + } + await t.click(Selector(`.${CLASS.listItemContent}`).withText(value)); + + return; + } + + await t.typeText(editor.element, value, { replace: true }); + + if (dataField === 'lookup' && useKeyboard) { + await Selector(`.${CLASS.listItemContent}`).withText(value)(); + await t.pressKey('enter'); + } +}; + +const focusNextCellEditor = async ( + t: TestController, + mode: GridsEditMode, + { columnIndex }: ColumnInfo, + useKeyboard = false, +): Promise<{ nextColumnIndex: number }> => { + const form = getEditForm(mode); + const nextColumnIndex = columnIndex === 0 ? 1 : columnIndex - 1; + const nextColumnInfo = basicColumnInfos[nextColumnIndex]; + + let nextEditor: CellEditor | undefined; + let nextCell: DataCell | undefined; + + if (form) { + if (nextColumnInfo) { + nextEditor = new CellEditor(form.getItem(nextColumnInfo.dataField)); + if (useKeyboard) { + await t.pressKey(columnIndex === 0 ? 'tab' : 'shift+tab'); + } else { + await t.click(nextEditor.element); + } + } + } else { + nextCell = dataGrid.getDataCell(0, nextColumnIndex); + nextEditor = nextCell.getEditor(); + + if (useKeyboard) { + await t.pressKey(columnIndex === 0 ? 'tab' : 'shift+tab'); + } else { + const isCellRevertBug = mode === 'cell' && columnIndex < nextColumnIndex; + await t.click(nextCell.element, { offsetX: isCellRevertBug ? 50 : 5 }); + } + } + + await checkCellFocused(t, mode, nextColumnInfo, nextCell, nextEditor); + + return { nextColumnIndex }; +}; + +const getSaveButton = async ( + mode: string, + form: EditForm | null, +): Promise => { + switch (mode) { + case 'batch': + return dataGrid.getHeaderPanel().getSaveButton(); + case 'row': + return dataGrid.getDataRow(0).getCommandCell(6).getButton(0); + case 'cell': + return Selector('body'); + default: + return form?.saveButton; + } +}; + +const getCellText = async ( + dataField: string, + cell: DataCell, +): Promise => { + if (dataField === 'boolean') { + return await cell.getEditor().isChecked() ? 'true' : 'false'; + } + + return cell.element.textContent; +}; + +const checkSavedCell = async ( + t: TestController, + { dataField, newValue }: ColumnInfo, + cell: DataCell, +): Promise => { + await t + .expect(await getCellText(dataField, cell)) + .eql(newValue) + .expect(cell.isEditCell) + .eql(dataField === 'boolean') + .expect(cell.isModified) + .notOk() + .expect(DataCell.getModifiedCells().count) + .eql(0); +}; + +const getEditorValue = async ( + dataField: string, + editor: CellEditor, +): Promise => { + if (dataField === 'boolean') { + return await editor.isChecked() ? 'true' : 'false'; + } + + return editor.element.value; +}; + +const checkModifiedCell = async ( + t: TestController, + mode: string, + { dataField, newValue }: ColumnInfo, + cell: DataCell, + editor: CellEditor, + modifiedCellsCount: number, +): Promise => { + const editorText = mode === 'batch' || mode === 'cell' + ? await getCellText(dataField, cell) + : await getEditorValue(dataField, editor); + + await t + .expect(editorText) + .eql(newValue); + + if (mode !== 'form' && mode !== 'popup') { + await t + .expect(cell.isEditCell) + .eql(mode === 'row' || dataField === 'boolean') + .expect(cell.isModified) + .eql(mode === 'batch') + .expect(DataCell.getModifiedCells().count) + .eql(modifiedCellsCount); + } +}; + +editingModes.forEach((mode) => { + const configurations = [ + { + repaintChangesOnly: true, + useMask: false, + columnInfos: textColumnInfos, + expectedResult: expectedTextColumnResult, + }, + { + repaintChangesOnly: false, + useMask: true, + columnInfos: maskedColumnInfos, + expectedResult: expectedMaskedColumnResult, + }, + { + repaintChangesOnly: false, + useMask: false, + columnInfos: basicColumnInfos, + expectedResult: expectedBasicColumnResult, + }, + ]; + + configurations.forEach(({ + repaintChangesOnly, useMask, columnInfos, expectedResult, + }) => { + test( + `Update cell value, mode: ${mode}, repaintChangesOnly: ${repaintChangesOnly}, useKeyboard: false, useMask: ${useMask}`, + async (t) => { + const form = getEditForm(mode); + + if (mode === 'batch') { + await dataGrid.apiCellValue(0, 0, 'modified'); + } + + // eslint-disable-next-line no-restricted-syntax + for (const columnInfo of columnInfos) { + const cell = dataGrid.getDataCell(0, columnInfo.columnIndex); + const editor = form + ? new CellEditor(form.getItem(columnInfo.dataField)) + : cell.getEditor(); + + await clickCellEditor(t, mode, columnInfo, form, cell, editor); + await setEditorValue(t, mode, columnInfo, editor, false, useMask); + } + + const saveButton = await getSaveButton(mode, form); + + if (saveButton) { + await t.click(saveButton, { offsetX: 5, offsetY: 5 }); + } + + // eslint-disable-next-line no-restricted-syntax + for (const columnInfo of expectedResult) { + await checkSavedCell( + t, + columnInfo, + dataGrid.getDataCell(0, columnInfo.columnIndex), + ); + } + }, + ).before(createDataGrid({ + mode, + useMask, + repaintChangesOnly, + })); + }); + + [true, false].forEach((repaintChangesOnly) => { + test( + `Update calculated cell value, mode: ${mode}, repaintChangesOnly: ${repaintChangesOnly}, useKeyboard: false, useMask:false`, + async (t) => { + const columnInfo = { columnIndex: 5, dataField: 'calculated', newValue: '9' }; + const form = getEditForm(mode); + const cell = dataGrid.getDataCell(0, columnInfo.columnIndex); + const editor = form ? new CellEditor(form.getItem(columnInfo.dataField)) : cell.getEditor(); + + await clickCellEditor(t, mode, columnInfo, form, cell, editor); + await setEditorValue(t, mode, columnInfo, editor); + + const saveButton = await getSaveButton(mode, form); + + if (saveButton) { + if (!repaintChangesOnly && (mode === 'row' || mode === 'form')) { + await t.click('body'); + } + + await t.click(saveButton, { offsetX: 5, offsetY: 5 }); + } + + const expectedColumnResult: ColumnInfo[] = [ + { columnIndex: 1, dataField: 'number', newValue: '8' }, + { columnIndex: 5, dataField: 'calculated', newValue: '9' }, + ]; + + // eslint-disable-next-line no-restricted-syntax + for (const resultColumnInfo of expectedColumnResult) { + await checkSavedCell( + t, + resultColumnInfo, + dataGrid.getDataCell(0, resultColumnInfo.columnIndex), + ); + } + }, + ).before(createDataGrid({ + mode, + repaintChangesOnly, + })); + }); + + const keyboardConfigurations = [ + { + useMask: true, + columnInfos: maskedColumnInfos, + expectedResult: expectedMaskedColumnResult, + }, + { + useMask: false, + columnInfos: basicColumnInfos, + expectedResult: expectedBasicColumnResult, + }, + ]; + + keyboardConfigurations.forEach(({ useMask, columnInfos, expectedResult }) => { + test( + `Update cell value, mode: ${mode}, repaintChangesOnly: false, useKeyboard: true, useMask: ${useMask}`, + async (t) => { + const form = getEditForm(mode); + + await t.expect(dataGrid.isReady()).ok(); + await moveToFirstCellEditor(t, mode); + + // eslint-disable-next-line no-restricted-syntax + for (const columnInfo of columnInfos) { + const cell = dataGrid.getDataCell(0, columnInfo.columnIndex); + const editor = form + ? new CellEditor(form.getItem(columnInfo.dataField)) + : cell.getEditor(); + + if (columnInfo.columnIndex > 0) { + await t.pressKey('tab'); + } + + await checkCellFocused(t, mode, columnInfo, cell, editor); + await setEditorValue(t, mode, columnInfo, editor, true, useMask); + } + + await t.pressKey('enter'); + + if (mode === 'batch' || mode === 'popup') { + const saveButton = await getSaveButton(mode, form); + + if (saveButton) { + await t.click(saveButton, { offsetX: 5, offsetY: 5 }); + } + } + + // eslint-disable-next-line no-restricted-syntax + for (const columnInfo of expectedResult) { + await checkSavedCell( + t, + columnInfo, + dataGrid.getDataCell(0, columnInfo.columnIndex), + ); + } + }, + ).before(createDataGrid({ + mode, + useMask, + })); + }); + + test( + `Update cell value and focus next cell, mode: ${mode}, repaintChangesOnly: false, useKeyboard: true`, + async (t) => { + const form = getEditForm(mode); + let activeColumnIndex = 0; + let modifiedCellCount = mode === 'batch' ? 1 : 0; + + await t.expect(dataGrid.isReady()).ok(); + await moveToFirstCellEditor(t, mode); + + // eslint-disable-next-line no-restricted-syntax + for (const columnInfo of textColumnInfos) { + const cell = dataGrid.getDataCell(0, columnInfo.columnIndex); + const editor = form ? new CellEditor(form.getItem(columnInfo.dataField)) : cell.getEditor(); + + for (let i = activeColumnIndex; i < columnInfo.columnIndex; i += 1) { + await t.pressKey('tab'); + } + + await checkCellFocused(t, mode, columnInfo, cell, editor); + await setEditorValue(t, mode, columnInfo, editor, true); + + const { nextColumnIndex } = await focusNextCellEditor(t, mode, columnInfo, true); + activeColumnIndex = nextColumnIndex; + + if (mode === 'batch') { + modifiedCellCount += 1; + } + + await checkModifiedCell(t, mode, columnInfo, cell, editor, modifiedCellCount); + } + }, + ).before(createDataGrid({ + mode, + })); + + [true, false].forEach((repaintChangesOnly) => { + test( + `Update cell value and focus next cell, mode: ${mode}, repaintChangesOnly: ${repaintChangesOnly}, useKeyboard: false`, + async (t) => { + const form = getEditForm(mode); + let modifiedCellCount = mode === 'batch' ? 1 : 0; + + // eslint-disable-next-line no-restricted-syntax + for (const columnInfo of textColumnInfos) { + const cell = dataGrid.getDataCell(0, columnInfo.columnIndex); + const editor = form + ? new CellEditor(form.getItem(columnInfo.dataField)) + : cell.getEditor(); + + await clickCellEditor(t, mode, columnInfo, form, cell, editor); + await setEditorValue(t, mode, columnInfo, editor); + + await focusNextCellEditor(t, mode, columnInfo); + + if (mode === 'batch') { + modifiedCellCount += 1; + } + + await checkModifiedCell(t, mode, columnInfo, cell, editor, modifiedCellCount); + } + }, + ).before(createDataGrid({ + mode, + repaintChangesOnly, + })); + }); +}); diff --git a/e2e/testcafe-devextreme/tests/dataGrid/common/editing/editingNewRow.functional_matrix.ts b/e2e/testcafe-devextreme/tests/dataGrid/common/editing/editingNewRow.functional_matrix.ts new file mode 100644 index 000000000000..3134612bd9d8 --- /dev/null +++ b/e2e/testcafe-devextreme/tests/dataGrid/common/editing/editingNewRow.functional_matrix.ts @@ -0,0 +1,234 @@ +import { Selector } from 'testcafe'; +import { CellEditor } from 'devextreme-testcafe-models/dataGrid/data/cellEditor'; +import DataGrid from 'devextreme-testcafe-models/dataGrid'; +import DataCell from 'devextreme-testcafe-models/dataGrid/data/cell'; +import EditForm from 'devextreme-testcafe-models/dataGrid/editForm'; +import { GridsEditMode } from 'devextreme/ui/data_grid'; +import { createWidget } from '../../../../helpers/createWidget'; +import url from '../../../../helpers/getPageUrl'; + +fixture.disablePageReloads`Editing.NewRow` + .page(url(__dirname, '../../../container.html')); + +interface ColumnInfo { + columnIndex: number; + dataField: string; + newValue: string; +} + +const createDataGrid = (mode: GridsEditMode, repaintChangesOnly = false) => async (): Promise => createWidget('dxDataGrid', { + keyExpr: 'id', + dataSource: [ + { + id: 1, text: 'text 1', number: 1, date: '2020-10-27', boolean: false, lookup: 1, + }, + { + id: 2, text: 'text 2', number: 2, date: '2020-10-28', boolean: true, lookup: 2, + }, + ], + repaintChangesOnly, + editing: { + mode, + allowAdding: true, + allowUpdating: true, + }, + columns: [ + { dataField: 'text' }, + { + dataField: 'number', + editorOptions: { + format: '#0', + }, + }, + { + dataField: 'date', + dataType: 'date', + editorOptions: { + pickerType: 'calendar', + }, + }, + { + dataField: 'lookup', + lookup: { + valueExpr: 'id', + displayExpr: 'text', + dataSource: [ + { id: 1, text: 'lookup 1' }, + { id: 2, text: 'lookup 2' }, + ], + }, + }, + { dataField: 'boolean' }, + { + dataField: 'calculated', + calculateCellValue: (data): number => (data as { number: number }).number + 1, + setCellValue: (newData, value): void => { + newData.number = value - 1; + }, + }, + ], +}); + +const expectedCalculatedColumnResult: ColumnInfo[] = [ + { columnIndex: 1, dataField: 'number', newValue: '8' }, + { columnIndex: 5, dataField: 'calculated', newValue: '9' }, +]; + +const dataGrid = new DataGrid('#container'); + +const getEditForm = (mode: GridsEditMode): EditForm | null => { + if (mode === 'form') { + return dataGrid.getEditForm(); + } + if (mode === 'popup') { + return dataGrid.getPopupEditForm(); + } + return null; +}; + +const addRow = async ( + t: TestController, + { columnIndex }: { columnIndex: number }, + form: EditForm | null, + cell: DataCell, + editor: CellEditor, +): Promise => { + const addRowButton = dataGrid.getHeaderPanel().getAddRowButton(); + await t.click(addRowButton); + + if (columnIndex > 0) { + const item = !form ? cell.element : editor.getItemLabel(); + await t.click(item, { offsetX: 5 }); + } +}; + +const checkCellFocused = async ( + t: TestController, + mode: GridsEditMode, + { dataField }: ColumnInfo, + cell: DataCell | undefined, + editor: CellEditor | undefined, +): Promise => { + if (mode !== 'form' && mode !== 'popup') { + await t.expect(cell?.isFocused).ok(); + } + + const isRowBasedMode = mode === 'row'; + const isFormBasedMode = mode === 'form' || mode === 'popup'; + + if ((isRowBasedMode || isFormBasedMode) && dataField === 'boolean') { + return; + } + + await t + .expect(editor?.element.focused) + .eql(true); +}; + +const getSaveButton = async ( + mode: string, + form: EditForm | null, +): Promise => { + switch (mode) { + case 'batch': + return dataGrid.getHeaderPanel().getSaveButton(); + case 'row': + return dataGrid.getDataRow(0).getCommandCell(6).getButton(0); + case 'cell': + return Selector('body'); + default: + return form?.saveButton; + } +}; + +const getCellText = async ( + dataField: string, + cell: DataCell, +): Promise => { + if (dataField === 'boolean') { + return await cell.getEditor().isChecked() ? 'true' : 'false'; + } + + return cell.element.textContent; +}; + +const checkSavedCell = async ( + t: TestController, + { dataField, newValue }: ColumnInfo, + cell: DataCell, +): Promise => { + await t + .expect(await getCellText(dataField, cell)) + .eql(newValue) + .expect(cell.isEditCell) + .eql(dataField === 'boolean') + .expect(cell.isModified) + .notOk() + .expect(DataCell.getModifiedCells().count) + .eql(0); +}; + +const modes: GridsEditMode[] = ['cell', 'batch', 'row', 'form', 'popup']; + +modes.forEach((mode) => { + [true, false].forEach((repaintChangesOnly) => { + test( + `Update cell value in new row, mode: ${mode}, repaintChangesOnly: ${repaintChangesOnly}`, + async (t) => { + const columnInfo = { columnIndex: 0, dataField: 'text', newValue: 'xxxx' }; + const form = getEditForm(mode); + const cell = dataGrid.getDataCell(0, columnInfo.columnIndex); + const editor = form ? new CellEditor(form.getItem(columnInfo.dataField)) : cell.getEditor(); + + await addRow(t, columnInfo, form, cell, editor); + + await checkCellFocused(t, mode, columnInfo, cell, editor); + + await t.typeText(editor.element, columnInfo.newValue, { replace: true }); + + const saveButton = await getSaveButton(mode, form); + + if (saveButton) { + await t.click(saveButton, { offsetX: 5, offsetY: 5 }); + } + + await checkSavedCell(t, columnInfo, dataGrid.getDataCell(2, columnInfo.columnIndex)); + }, + ).before(createDataGrid(mode, repaintChangesOnly)); + + test( + `Update calculated cell value in new row, mode: ${mode}, repaintChangesOnly: ${repaintChangesOnly}`, + async (t) => { + const columnInfo = { columnIndex: 5, dataField: 'calculated', newValue: '9' }; + const form = getEditForm(mode); + const cell = dataGrid.getDataCell(0, columnInfo.columnIndex); + const editor = form ? new CellEditor(form.getItem(columnInfo.dataField)) : cell.getEditor(); + + await addRow(t, columnInfo, form, cell, editor); + + await checkCellFocused(t, mode, columnInfo, cell, editor); + + await t.typeText(editor.element, columnInfo.newValue, { replace: true }); + + if (!repaintChangesOnly && (mode === 'row' || mode === 'form')) { + await t.click('body'); + } + + const saveButton = await getSaveButton(mode, form); + + if (saveButton) { + await t.click(saveButton, { offsetX: 5, offsetY: 5 }); + } + + // eslint-disable-next-line no-restricted-syntax + for (const resultColumnInfo of expectedCalculatedColumnResult) { + await checkSavedCell( + t, + resultColumnInfo, + dataGrid.getDataCell(2, resultColumnInfo.columnIndex), + ); + } + }, + ).before(createDataGrid(mode, repaintChangesOnly)); + }); +}); diff --git a/e2e/testcafe-devextreme/tests/dataGrid/common/editing/matrix.ts b/e2e/testcafe-devextreme/tests/dataGrid/common/editing/matrix.ts deleted file mode 100644 index 9190d93a8aa8..000000000000 --- a/e2e/testcafe-devextreme/tests/dataGrid/common/editing/matrix.ts +++ /dev/null @@ -1,451 +0,0 @@ -/* eslint-disable @stylistic/max-len, @typescript-eslint/init-declarations */ -import { Selector } from 'testcafe'; -import { CellEditor } from 'devextreme-testcafe-models/dataGrid/data/cellEditor'; -import DataGrid from 'devextreme-testcafe-models/dataGrid'; -import DataCell from 'devextreme-testcafe-models/dataGrid/data/cell'; -import EditForm from 'devextreme-testcafe-models/dataGrid/editForm'; -import { ClassNames as CLASS } from 'devextreme-testcafe-models/dataGrid/classNames'; -import { createWidget } from '../../../../helpers/createWidget'; -import url from '../../../../helpers/getPageUrl'; - -fixture.disablePageReloads`Editing.Matrix` - .page(url(__dirname, '../../../container.html')); - -const editingModes = [ - 'cell', - 'batch', - 'row', - 'form', - 'popup', -]; -const columnInfos = [ - { - columnIndex: 0, dataField: 'text', newValue: 'xxxx', newMaskValue: 'xxxxx', - }, - { - columnIndex: 1, dataField: 'number', newValue: '-9', newMaskValue: '9-', - }, - { - columnIndex: 2, dataField: 'date', newValue: '10/1/2020', newMaskValue: '101', - }, - { columnIndex: 3, dataField: 'lookup', newValue: 'lookup 2' }, - { columnIndex: 4, dataField: 'boolean', newValue: 'true' }, - { columnIndex: 5, dataField: 'calculated', newValue: '9' }, -]; -const repaintChangesOnlyValues = [ - false, - true, -]; - -const useKeyboardValues = [ - false, - true, -]; - -const useMaskValues = [ - false, - true, -]; - -const isAddingValues = [ - false, - true, -]; - -const dataGrid = new DataGrid('#container'); - -const createDataGrid = ({ - repaintChangesOnly, isAdding, mode, useMask, -}) => async (): Promise => createWidget('dxDataGrid', { - keyExpr: 'id', - dataSource: [ - { - id: 1, text: 'text 1', number: 1, date: '2020-10-27', boolean: false, lookup: 1, - }, - { - id: 2, text: 'text 2', number: 2, date: '2020-10-28', boolean: true, lookup: 2, - }, - ], - repaintChangesOnly, - editing: { - mode, - allowAdding: isAdding, - allowUpdating: true, - }, - columns: [ - { - dataField: 'text', - editorOptions: { - mask: useMask ? 'cccc' : undefined, - }, - }, - { - dataField: 'number', - editorOptions: { - format: '#0', - useMaskBehavior: useMask, - }, - }, - { - dataField: 'date', - dataType: 'date', - editorOptions: { - useMaskBehavior: useMask, - }, - }, - { - dataField: 'lookup', - lookup: { - valueExpr: 'id', - displayExpr: 'text', - dataSource: [ - { id: 1, text: 'lookup 1' }, - { id: 2, text: 'lookup 2' }, - ], - }, - }, - { dataField: 'boolean' }, - { - // name: 'calculated', TODO - dataField: 'calculated', - calculateCellValue: (data): number => (data as { number: number }).number + 1, - setCellValue: (newData, value): void => { - newData.number = value - 1; - }, - }, - ], -}); - -const getEditForm = (mode): EditForm | null => { - if (mode === 'form') { - return dataGrid.getEditForm(); - } - if (mode === 'popup') { - return dataGrid.getPopupEditForm(); - } - return null; -}; - -const editCell = async (t: TestController, { - mode, dataField, useKeyboard, columnIndex, -}, rowIndex: number, modifyFirstColumn = false): Promise<{ cell: DataCell; editor: CellEditor }> => { - const cell = dataGrid.getDataCell(rowIndex, columnIndex); - let editor = cell.getEditor(); - - const form = getEditForm(mode); - if (form) { - editor = new CellEditor(form.getItem(dataField)); - } - - if (useKeyboard) { - await t - .pressKey('tab'); - } - - if (mode === 'batch' && modifyFirstColumn) { // TODO - await dataGrid.apiCellValue(rowIndex, 0, 'modified'); - } - - if (useKeyboard) { - await t - // .pressKey('tab') - .pressKey('ctrl+down') - .pressKey('enter'); - if (mode === 'popup') { // TODO - await t - .pressKey(columnInfos.map(() => 'tab').join(' ')) - .pressKey('enter') - .wait(500); - } - for (let i = 0; i < columnIndex; i += 1) { - await t.pressKey('tab'); - } - } else if (mode === 'cell' || mode === 'batch') { - await t.click(cell.element, { offsetX: 5 }); - } else { - const editButton = dataGrid.getDataRow(rowIndex).getCommandCell(6).getButton(0); - await t.click(editButton); - - if (columnIndex > 0) { - const item = !form ? cell.element : editor.getItemLabel(); - await t.click(item, { offsetX: 5 }); - } - } - - return { cell, editor }; -}; - -const addRow = async (t: TestController, { - mode, dataField, columnIndex, useKeyboard, -}): Promise<{ cell: DataCell; editor: CellEditor }> => { - const cell = dataGrid.getDataCell(0, columnIndex); - let editor = cell.getEditor(); - - const form = getEditForm(mode); - if (form) { - editor = new CellEditor(form.getItem(dataField)); - } - - if (useKeyboard) { - await t - .pressKey('tab') - .pressKey('enter') - .wait(500); - - for (let i = 0; i < columnIndex; i += 1) { - await t.pressKey('tab'); - } - } else { - const addRowButton = dataGrid.getHeaderPanel().getAddRowButton(); - await t.click(addRowButton); - - if (columnIndex > 0) { - const item = !form ? cell.element : editor.getItemLabel(); - await t.click(item, { offsetX: 5 }); - } - } - - return { cell, editor }; -}; - -const checkEditCell = async (t: TestController, { mode, dataField }, cell: DataCell | undefined, editor: CellEditor | undefined): Promise => { - if (mode !== 'form' && mode !== 'popup') { - await t.expect(cell?.isFocused).ok(); - } - - if (mode === 'row' && dataField === 'boolean') { // TODO - return; - } - - await t - .expect(editor?.element.focused) - .eql(true); -}; - -const getEditorValue = async (dataField: string, editor: CellEditor): Promise => { - if (dataField === 'boolean') { - return await editor.isChecked() ? 'true' : 'false'; - } - - return editor.element.value; -}; - -const getCellText = async (dataField: string, cell: DataCell): Promise => { - if (dataField === 'boolean') { - return getEditorValue(dataField, cell.getEditor()); - } - - return cell.element.textContent; -}; - -const checkModifiedCell = async (t: TestController, { mode, dataField }, cell: DataCell, editor: CellEditor, value: string): Promise => { - const editorText = mode === 'batch' || mode === 'cell' - ? await getCellText(dataField, cell) - : await getEditorValue(dataField, editor); - - await t - .expect(editorText) - .eql(value); - - if (mode !== 'form' && mode !== 'popup') { - await t - .expect(cell.isEditCell) - .eql(mode === 'row' || dataField === 'boolean') - .expect(cell.isModified) - .eql(mode === 'batch'); - - await t - .expect(DataCell.getModifiedCells().count) - .eql(mode === 'batch' ? 2 : 0); - } -}; - -const checkSavedCell = async (t: TestController, { dataField }, cell: DataCell, value: string): Promise => { - await t - .expect(await getCellText(dataField, cell)) - .eql(value); - - await t - .expect(cell.isEditCell) - .eql(dataField === 'boolean'); - - await t - .expect(cell.isModified) - .notOk(); - - await t - .expect(DataCell.getModifiedCells().count) - .eql(0); -}; - -const clickSaveButton = async (t: TestController, { - mode, useKeyboard, dataField, repaintChangesOnly, -}, rowIndex: number): Promise => { - const form = getEditForm(mode); - let saveButton: Selector | undefined = form?.saveButton; - if (useKeyboard) { - await t.pressKey('enter'); - if (mode === 'batch') { // TODO - saveButton = dataGrid.getHeaderPanel().getSaveButton(); - } else if (mode !== 'popup') { // TODO - saveButton = undefined; - } - } else if (mode === 'batch') { - saveButton = dataGrid.getHeaderPanel().getSaveButton(); - } else if (mode === 'row') { - saveButton = dataGrid.getDataRow(rowIndex).getCommandCell(6).getButton(0); - } else if (mode === 'cell') { - saveButton = Selector('body'); - } - if (saveButton) { - await t.click(saveButton, { offsetX: 5, offsetY: 5 }); - if (dataField === 'calculated' && !repaintChangesOnly && (mode === 'row' || mode === 'form')) { // TODO - await t.click(saveButton, { offsetX: 5, offsetY: 5 }); - } - } -}; - -const setEditorValue = async (t: TestController, { - mode, dataField, useKeyboard, useMask, newMaskValue, newValue, -}, editor: CellEditor): Promise => { - const value: string = useMask ? newMaskValue : newValue; - if (dataField === 'date' && !useKeyboard && !useMask) { - await t.click(editor.getDropDownButton()); - await t.click(Selector(`.${CLASS.calendarCell}`).withText(value.split('/')[1])); - } else if (dataField === 'lookup' && !useKeyboard) { - if (mode === 'cell' || mode === 'batch') { - await t.click(editor.getDropDownButton()); - } - await t.click(Selector(`.${CLASS.listItemContent}`).withText(value)); - } else if (dataField === 'boolean') { - if (useKeyboard) { - await t.pressKey('space'); - } else { - await t.click(editor.element); - } - } else { - await t - .pressKey('ctrl+a') - .pressKey(value.split('').map((k) => k.replace(' ', 'space')).join(' ')); - - if (dataField === 'lookup' && useKeyboard) { - await Selector(`.${CLASS.listItemContent}`).withText(value)(); - await t.pressKey('enter'); - } - } -}; - -const editNextCell = async (t: TestController, { - mode, dataField, columnInfoIndex, columnIndex, useKeyboard, -}, rowIndex: number): Promise<{ nextEditor: CellEditor | undefined; nextCell: DataCell | undefined }> => { - const form = getEditForm(mode); - - let nextEditor: CellEditor | undefined; - let nextCell: DataCell | undefined; - if (form) { - const nextColumnInfo = columnInfos[columnInfoIndex === 0 ? 1 : columnInfoIndex - 1]; - if (nextColumnInfo) { - nextEditor = new CellEditor(form.getItem(nextColumnInfo.dataField)); - if (useKeyboard) { - for ( - let i = 0; i < Math.abs(nextColumnInfo.columnIndex - columnIndex); i += 1 - ) { - await t.pressKey(columnInfoIndex === 0 ? 'tab' : 'shift+tab'); - } - } else { - await t.click(nextEditor.element); - } - } - } else { - const nextColumnIndex = columnIndex === 0 ? 1 : columnIndex - 1; - nextCell = dataGrid.getDataCell(rowIndex, nextColumnIndex); - nextEditor = nextCell.getEditor(); - - if (useKeyboard) { - await t.pressKey(columnIndex === 0 ? 'tab' : 'shift+tab'); - } else { - const isCellRevertBug = mode === 'cell' && columnIndex < nextColumnIndex; // TODO - if (mode === 'batch') await t.click(dataGrid.element());// workaround for https://github.com/DevExpress/testcafe/issues/7277 TODO remove once fixed - await t.click(nextCell.element, { offsetX: isCellRevertBug ? 50 : 5 }); - } - } - await checkEditCell(t, { mode, dataField }, nextCell, nextEditor); - - return { nextEditor, nextCell }; -}; - -editingModes.forEach((mode) => { - columnInfos.forEach(({ - columnIndex, dataField, newValue, newMaskValue, - }, columnInfoIndex) => { - const isBasicColumn = dataField === 'text' || dataField === 'calculated'; - isAddingValues.forEach((isAdding) => { - if (isAdding && !isBasicColumn) { - return; - } - useMaskValues.forEach((useMask) => { - if (useMask && !newMaskValue) { - return; - } - useKeyboardValues.forEach((useKeyboard) => { - repaintChangesOnlyValues.forEach((repaintChangesOnly) => { - if (repaintChangesOnly && (useKeyboard || useMask || !isBasicColumn)) { - return; - } - - const options = { - mode, - columnIndex, - dataField, - newValue, - newMaskValue, - columnInfoIndex, - isAdding, - useMask, - useKeyboard, - repaintChangesOnly, - }; - - test.meta({ unstable: true })(`Update cell value ${JSON.stringify({ - mode, dataField, repaintChangesOnly, useKeyboard, useMask, isAdding, - })}`, async (t) => { - const rowIndex = 0; - - const { cell, editor } = isAdding - ? await addRow(t, options) - : await editCell(t, options, rowIndex, true); - - await checkEditCell(t, options, cell, editor); - - await setEditorValue(t, options, editor); - - await clickSaveButton(t, options, isAdding ? rowIndex : 0); - await checkSavedCell( - t, - options, - isAdding ? dataGrid.getDataCell(2, columnIndex) : cell, - newValue, - ); - }).before(createDataGrid(options)); - - if (isBasicColumn && !isAdding) { - test.meta({ unstable: true })(`Edit next cell ${JSON.stringify({ - mode, dataField, repaintChangesOnly, useKeyboard, useMask, - })}`, async (t) => { - const rowIndex = 0; - - const { cell, editor } = await editCell(t, options, rowIndex); - await checkEditCell(t, options, cell, editor); - - await setEditorValue(t, options, editor); - const { nextCell, nextEditor } = await editNextCell(t, options, rowIndex); - await checkEditCell(t, options, nextCell, nextEditor); - await checkModifiedCell(t, options, cell, editor, newValue); - }).before(createDataGrid(options)); - } - }); - }); - }); - }); - }); -}); diff --git a/packages/testcafe-models/dataGrid/commandCell.ts b/packages/testcafe-models/dataGrid/commandCell.ts index dabd145a5332..fd497bc88d25 100644 --- a/packages/testcafe-models/dataGrid/commandCell.ts +++ b/packages/testcafe-models/dataGrid/commandCell.ts @@ -13,6 +13,7 @@ const CLASS = { selectCheckBox: 'dx-select-checkbox', adaptiveButton: 'dx-datagrid-adaptive-more', aiColumnHeaderButton: 'dx-command-ai-header-button', + editButton: 'dx-link-edit', }; const commandSelector = [ @@ -39,6 +40,10 @@ export default class CommandCell extends FocusableElement { return this.element.child(index); } + getEditButton(): Selector { + return this.element.find(`.${CLASS.editButton}`); + } + getSelectCheckBox(): Selector { return this.element.find(`.${CLASS.selectCheckBox}`); } From cfd887ea7872a215508b93177170fc6141cebbef Mon Sep 17 00:00:00 2001 From: "anna.shakhova" Date: Fri, 6 Feb 2026 15:41:49 +0100 Subject: [PATCH 2/2] remove unnecessary async/await --- .../common/editing/editing.functional_matrix.ts | 10 +++++----- .../common/editing/editingNewRow.functional_matrix.ts | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/e2e/testcafe-devextreme/tests/dataGrid/common/editing/editing.functional_matrix.ts b/e2e/testcafe-devextreme/tests/dataGrid/common/editing/editing.functional_matrix.ts index 420bf16b0e28..3ee950629fea 100644 --- a/e2e/testcafe-devextreme/tests/dataGrid/common/editing/editing.functional_matrix.ts +++ b/e2e/testcafe-devextreme/tests/dataGrid/common/editing/editing.functional_matrix.ts @@ -285,10 +285,10 @@ const focusNextCellEditor = async ( return { nextColumnIndex }; }; -const getSaveButton = async ( +const getSaveButton = ( mode: string, form: EditForm | null, -): Promise => { +): Selector | undefined => { switch (mode) { case 'batch': return dataGrid.getHeaderPanel().getSaveButton(); @@ -411,7 +411,7 @@ editingModes.forEach((mode) => { await setEditorValue(t, mode, columnInfo, editor, false, useMask); } - const saveButton = await getSaveButton(mode, form); + const saveButton = getSaveButton(mode, form); if (saveButton) { await t.click(saveButton, { offsetX: 5, offsetY: 5 }); @@ -445,7 +445,7 @@ editingModes.forEach((mode) => { await clickCellEditor(t, mode, columnInfo, form, cell, editor); await setEditorValue(t, mode, columnInfo, editor); - const saveButton = await getSaveButton(mode, form); + const saveButton = getSaveButton(mode, form); if (saveButton) { if (!repaintChangesOnly && (mode === 'row' || mode === 'form')) { @@ -515,7 +515,7 @@ editingModes.forEach((mode) => { await t.pressKey('enter'); if (mode === 'batch' || mode === 'popup') { - const saveButton = await getSaveButton(mode, form); + const saveButton = getSaveButton(mode, form); if (saveButton) { await t.click(saveButton, { offsetX: 5, offsetY: 5 }); diff --git a/e2e/testcafe-devextreme/tests/dataGrid/common/editing/editingNewRow.functional_matrix.ts b/e2e/testcafe-devextreme/tests/dataGrid/common/editing/editingNewRow.functional_matrix.ts index 3134612bd9d8..fb0ef59d8ff9 100644 --- a/e2e/testcafe-devextreme/tests/dataGrid/common/editing/editingNewRow.functional_matrix.ts +++ b/e2e/testcafe-devextreme/tests/dataGrid/common/editing/editingNewRow.functional_matrix.ts @@ -125,10 +125,10 @@ const checkCellFocused = async ( .eql(true); }; -const getSaveButton = async ( +const getSaveButton = ( mode: string, form: EditForm | null, -): Promise => { +): Selector | undefined => { switch (mode) { case 'batch': return dataGrid.getHeaderPanel().getSaveButton(); @@ -186,7 +186,7 @@ modes.forEach((mode) => { await t.typeText(editor.element, columnInfo.newValue, { replace: true }); - const saveButton = await getSaveButton(mode, form); + const saveButton = getSaveButton(mode, form); if (saveButton) { await t.click(saveButton, { offsetX: 5, offsetY: 5 }); @@ -214,7 +214,7 @@ modes.forEach((mode) => { await t.click('body'); } - const saveButton = await getSaveButton(mode, form); + const saveButton = getSaveButton(mode, form); if (saveButton) { await t.click(saveButton, { offsetX: 5, offsetY: 5 });