diff --git a/packages/devextreme/js/__internal/grids/grid_core/__tests__/__mock__/model/cell/data_cell.ts b/packages/devextreme/js/__internal/grids/grid_core/__tests__/__mock__/model/cell/data_cell.ts index 16488785a4be..d124f6f8870a 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/__tests__/__mock__/model/cell/data_cell.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/__tests__/__mock__/model/cell/data_cell.ts @@ -1,6 +1,9 @@ +import { TextBoxModel } from '@ts/ui/__tests__/__mock__/model/textbox'; + const SELECTORS = { editCell: 'dx-editor-cell', invalidCell: 'invalid', + textBox: 'dx-textbox', }; export class DataCellModel { @@ -27,4 +30,9 @@ export class DataCellModel { public getHTML(): string { return this.root?.innerHTML ?? ''; } + + public getEditor(): TextBoxModel { + const editorElement = this.root?.querySelector(`.${SELECTORS.textBox}`) as HTMLElement; + return new TextBoxModel(editorElement); + } } diff --git a/packages/devextreme/js/__internal/grids/grid_core/__tests__/__mock__/model/grid_core.ts b/packages/devextreme/js/__internal/grids/grid_core/__tests__/__mock__/model/grid_core.ts index 0d93490374ae..cccec361732f 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/__tests__/__mock__/model/grid_core.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/__tests__/__mock__/model/grid_core.ts @@ -25,6 +25,7 @@ const SELECTORS = { editForm: 'edit-form', headerCellIndicators: 'dx-column-indicators', headerCellFilter: 'dx-header-filter', + revertButton: 'dx-revert-button', }; export abstract class GridCoreModel { @@ -101,6 +102,10 @@ export abstract class GridCoreModel { return new ToastModel(this.getToastContainer()); } + public getRevertButton(): HTMLElement { + return document.body.querySelector(`.${SELECTORS.revertButton}`) as HTMLElement; + } + public addWidgetPrefix(classNames: string): string { const componentName = this.NAME; diff --git a/packages/devextreme/js/__internal/grids/grid_core/validating/__tests__/validating.integration.test.ts b/packages/devextreme/js/__internal/grids/grid_core/validating/__tests__/validating.integration.test.ts new file mode 100644 index 000000000000..1742301fe5f4 --- /dev/null +++ b/packages/devextreme/js/__internal/grids/grid_core/validating/__tests__/validating.integration.test.ts @@ -0,0 +1,72 @@ +import { + afterEach, beforeEach, describe, expect, it, jest, +} from '@jest/globals'; +import { + afterTest, + beforeTest, + createDataGrid, +} from '@ts/grids/grid_core/__tests__/__mock__/helpers/utils'; + +describe('Bugs', () => { + beforeEach(beforeTest); + afterEach(afterTest); + + describe('T1308327 - DataGrid - Cell value is not restored after canceling changes in cell editing mode if repaintChangesOnly is enabled', () => { + it('should restore cell value after canceling changes with validation error', async () => { + const data = [ + { id: 1, name: 'Job 1', article: 'Article A' }, + { id: 2, name: 'Job 2', article: 'Article B' }, + ]; + + const { instance, component } = await createDataGrid({ + dataSource: data, + keyExpr: 'id', + repaintChangesOnly: true, + editing: { + mode: 'cell', + allowUpdating: true, + }, + columns: [ + { + dataField: 'name', + showEditorAlways: true, + validationRules: [ + { type: 'required', message: 'Required field' }, + ], + }, + { + dataField: 'article', + }, + ], + }); + + const firstCell = component.getDataCell(0, 0); + const firstEditor = firstCell.getEditor(); + + firstEditor.setValue(''); + jest.runAllTimers(); + + expect(component.getDataCell(0, 0).isValidCell).toBe(false); + + component.getRevertButton().click(); + jest.runAllTimers(); + + expect(instance.cellValue(0, 'name')).toBe('Job 1'); + expect(component.getDataCell(0, 0).isValidCell).toBe(true); + + const secondCell = component.getDataCell(1, 0); + const secondEditor = secondCell.getEditor(); + + secondEditor.setValue(''); + jest.runAllTimers(); + + expect(component.getDataCell(1, 0).isValidCell).toBe(false); + + component.getRevertButton().click(); + jest.runAllTimers(); + + expect(instance.cellValue(1, 'name')).toBe('Job 2'); + expect(component.getDataCell(1, 0).isValidCell).toBe(true); + }); + }); +}); diff --git a/packages/devextreme/js/__internal/grids/grid_core/validating/m_validating.ts b/packages/devextreme/js/__internal/grids/grid_core/validating/m_validating.ts index 5c81455478b3..58d834edb8f4 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/validating/m_validating.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/validating/m_validating.ts @@ -936,14 +936,7 @@ export const validatingEditingExtender = (Base: ModuleType) = }); this._focusEditingCell(); } else if (!cancel) { - let shouldResetValidationState = true; - - if (isCellEditMode) { - const columns = this._columnsController.getColumns(); - const columnsWithValidatingEditors = columns.filter((col) => col.showEditorAlways && col.validationRules?.length > 0).length > 0; - - shouldResetValidationState = !columnsWithValidatingEditors; - } + const shouldResetValidationState = this._shouldResetValidationState(); if (shouldResetValidationState) { this._validatingController.initValidationState(); @@ -979,11 +972,30 @@ export const validatingEditingExtender = (Base: ModuleType) = } protected _beforeCancelEditData() { - this._validatingController.initValidationState(); + const shouldResetValidationState = this._shouldResetValidationState(); + + if (shouldResetValidationState) { + this._validatingController.initValidationState(); + } super._beforeCancelEditData(); } + private _shouldResetValidationState(): boolean { + const isCellEditMode = this.getEditMode() === EDIT_MODE_CELL; + + if (isCellEditMode) { + const columns = this._columnsController.getColumns(); + const columnsWithValidatingEditors = columns.filter( + (col) => col.showEditorAlways && col.validationRules?.length > 0, + ); + + return columnsWithValidatingEditors.length === 0; + } + + return true; + } + private _showErrorRow(change) { let $popupContent; const items = this._dataController.items();