From 21fc8e434b1cf1e71af557729ef7f0df97709c69 Mon Sep 17 00:00:00 2001 From: Eugen Zha Date: Tue, 21 Jul 2026 19:19:56 +0300 Subject: [PATCH 1/5] feat(columns): add handling for immediate column width changes without repaint --- .../T1329677_columnOptionWidthImmediate.ts | 44 +++++++++++++++++++ .../m_columns_controller_utils.ts | 21 +++++++++ 2 files changed, 65 insertions(+) create mode 100644 e2e/testcafe-devextreme/tests/dataGrid/common/columnResizing/T1329677_columnOptionWidthImmediate.ts diff --git a/e2e/testcafe-devextreme/tests/dataGrid/common/columnResizing/T1329677_columnOptionWidthImmediate.ts b/e2e/testcafe-devextreme/tests/dataGrid/common/columnResizing/T1329677_columnOptionWidthImmediate.ts new file mode 100644 index 000000000000..2f22cc001678 --- /dev/null +++ b/e2e/testcafe-devextreme/tests/dataGrid/common/columnResizing/T1329677_columnOptionWidthImmediate.ts @@ -0,0 +1,44 @@ +import DataGrid from 'devextreme-testcafe-models/dataGrid'; +import { createWidget } from '../../../../helpers/createWidget'; +import url from '../../../../helpers/getPageUrl'; + +fixture.disablePageReloads`DataGrid - columnOption width change should be applied immediately (T1329677)` + .page(url(__dirname, '../../../container.html')); + +const GRID_SELECTOR = '#container'; +const ASSIGNED_COLUMN_INDEX = 1; +const ASSIGNED_COLUMN_WIDTH = 700; +const COLUMN_WIDTH_TOLERANCE = 1; + +// T1329677 +test('DataGrid - column width changed via columnOption should be applied immediately, without a repaint', async (t) => { + const dataGrid = new DataGrid(GRID_SELECTOR); + + await t.expect(dataGrid.isReady()).ok(); + + // Called as a plain instance method after the grid's initial render/resize has already + // settled (isReady() resolved) - just like an external app calling columnOption from a click + // handler - rather than from inside onContentReady, which runs while the grid's own + // render/resize pipeline is still on the call stack and would not expose the bug. + await dataGrid.apiColumnOption('Task_Assigned_Employee_ID', 'width', ASSIGNED_COLUMN_WIDTH); + + const assignedColumnWidth = await dataGrid.getHeaders().getHeaderRow(0) + .getHeaderCell(ASSIGNED_COLUMN_INDEX).element.clientWidth; + + await t + .expect(assignedColumnWidth) + .within( + ASSIGNED_COLUMN_WIDTH - COLUMN_WIDTH_TOLERANCE, + ASSIGNED_COLUMN_WIDTH + COLUMN_WIDTH_TOLERANCE, + 'columnOption width should be applied immediately, without an explicit repaint', + ); +}).before(async () => { + await createWidget('dxDataGrid', { + dataSource: [{ Task_Subject: 'Test' }], + columnAutoWidth: true, + columns: [ + { dataField: 'Task_Subject' }, + { dataField: 'Task_Assigned_Employee_ID', caption: 'Assigned' }, + ], + }); +}); diff --git a/packages/devextreme/js/__internal/grids/grid_core/columns_controller/m_columns_controller_utils.ts b/packages/devextreme/js/__internal/grids/grid_core/columns_controller/m_columns_controller_utils.ts index d3dcd37385d2..e8a8431af936 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/columns_controller/m_columns_controller_utils.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/columns_controller/m_columns_controller_utils.ts @@ -715,6 +715,23 @@ export const fireOptionChanged = function (that: ColumnsController, options) { } }; +const isVisibleWidthChangePendingForColumn = (that: ColumnsController, columnIndex): boolean => { + const columnChanges = that._columnChanges; + + if (!columnChanges?.optionNames?.visibleWidth) { + return false; + } + + return columnChanges.columnIndex === columnIndex + || !!columnChanges.columnIndices?.includes(columnIndex); +}; + +const invalidateStaleVisibleWidth = (that: ColumnsController, column): void => { + if (isDefined(column.visibleWidth) && !isVisibleWidthChangePendingForColumn(that, column.index)) { + column.visibleWidth = null; + } +}; + export const columnOptionCore = function (that: ColumnsController, column, optionName, value?, notFireEvent?) { const optionGetter = compileGetter(optionName); const columnIndex = column.index; @@ -740,6 +757,10 @@ export const columnOptionCore = function (that: ColumnsController, column, optio changeType = 'columns'; } + if (optionName === 'width') { + invalidateStaleVisibleWidth(that, column); + } + const optionSetter = compileSetter(optionName); // @ts-expect-error optionSetter(column, value, { functionsAsIs: true }); From 03dadd12b8b68e8af1b899cb372d384b3a5d56ca Mon Sep 17 00:00:00 2001 From: Eugen Zha Date: Wed, 22 Jul 2026 12:45:06 +0300 Subject: [PATCH 2/5] test(dataGrid): reintroduce immediate column width change test without repaint --- .../T1329677_columnOptionWidthImmediate.ts | 44 ------------------- .../common/columnResizing/functional.ts | 29 ++++++++++++ 2 files changed, 29 insertions(+), 44 deletions(-) delete mode 100644 e2e/testcafe-devextreme/tests/dataGrid/common/columnResizing/T1329677_columnOptionWidthImmediate.ts diff --git a/e2e/testcafe-devextreme/tests/dataGrid/common/columnResizing/T1329677_columnOptionWidthImmediate.ts b/e2e/testcafe-devextreme/tests/dataGrid/common/columnResizing/T1329677_columnOptionWidthImmediate.ts deleted file mode 100644 index 2f22cc001678..000000000000 --- a/e2e/testcafe-devextreme/tests/dataGrid/common/columnResizing/T1329677_columnOptionWidthImmediate.ts +++ /dev/null @@ -1,44 +0,0 @@ -import DataGrid from 'devextreme-testcafe-models/dataGrid'; -import { createWidget } from '../../../../helpers/createWidget'; -import url from '../../../../helpers/getPageUrl'; - -fixture.disablePageReloads`DataGrid - columnOption width change should be applied immediately (T1329677)` - .page(url(__dirname, '../../../container.html')); - -const GRID_SELECTOR = '#container'; -const ASSIGNED_COLUMN_INDEX = 1; -const ASSIGNED_COLUMN_WIDTH = 700; -const COLUMN_WIDTH_TOLERANCE = 1; - -// T1329677 -test('DataGrid - column width changed via columnOption should be applied immediately, without a repaint', async (t) => { - const dataGrid = new DataGrid(GRID_SELECTOR); - - await t.expect(dataGrid.isReady()).ok(); - - // Called as a plain instance method after the grid's initial render/resize has already - // settled (isReady() resolved) - just like an external app calling columnOption from a click - // handler - rather than from inside onContentReady, which runs while the grid's own - // render/resize pipeline is still on the call stack and would not expose the bug. - await dataGrid.apiColumnOption('Task_Assigned_Employee_ID', 'width', ASSIGNED_COLUMN_WIDTH); - - const assignedColumnWidth = await dataGrid.getHeaders().getHeaderRow(0) - .getHeaderCell(ASSIGNED_COLUMN_INDEX).element.clientWidth; - - await t - .expect(assignedColumnWidth) - .within( - ASSIGNED_COLUMN_WIDTH - COLUMN_WIDTH_TOLERANCE, - ASSIGNED_COLUMN_WIDTH + COLUMN_WIDTH_TOLERANCE, - 'columnOption width should be applied immediately, without an explicit repaint', - ); -}).before(async () => { - await createWidget('dxDataGrid', { - dataSource: [{ Task_Subject: 'Test' }], - columnAutoWidth: true, - columns: [ - { dataField: 'Task_Subject' }, - { dataField: 'Task_Assigned_Employee_ID', caption: 'Assigned' }, - ], - }); -}); diff --git a/e2e/testcafe-devextreme/tests/dataGrid/common/columnResizing/functional.ts b/e2e/testcafe-devextreme/tests/dataGrid/common/columnResizing/functional.ts index 4ecc04eea337..14bbb362f191 100644 --- a/e2e/testcafe-devextreme/tests/dataGrid/common/columnResizing/functional.ts +++ b/e2e/testcafe-devextreme/tests/dataGrid/common/columnResizing/functional.ts @@ -59,6 +59,35 @@ test('DataGrid – Resize indicator is moved when resizing a grouped column if s }); }); +// T1329677 +test('DataGrid - column width changed via columnOption should be applied immediately, without a repaint (T1329677)', async (t) => { + const dataGrid = new DataGrid('#container'); + + await t.expect(dataGrid.isReady()).ok(); + + await dataGrid.apiColumnOption('Task_Assigned_Employee_ID', 'width', 700); + + const assignedColumnWidth = await dataGrid.getHeaders().getHeaderRow(0) + .getHeaderCell(1).element.clientWidth; + + await t + .expect(assignedColumnWidth) + .within( + 700 - 1, + 700 + 1, + 'columnOption width should be applied immediately, without an explicit repaint', + ); +}).before(async () => { + await createWidget('dxDataGrid', { + dataSource: [{ Task_Subject: 'Test' }], + columnAutoWidth: true, + columns: [ + { dataField: 'Task_Subject' }, + { dataField: 'Task_Assigned_Employee_ID', caption: 'Assigned' }, + ], + }); +}); + const tryResizeHeaderInBandArea = ( dataGrid: DataGrid, columnIndex: number, From 1724559b052886ffd85a4b186e86752403486109 Mon Sep 17 00:00:00 2001 From: Eugen Zha Date: Wed, 29 Jul 2026 19:46:46 +0300 Subject: [PATCH 3/5] DataGrid: update column width immediately when another column width is changed via columnOption (T1329677) --- .../common/columnResizing/functional.ts | 33 +++++++++++++++++++ .../m_columns_controller_utils.ts | 15 ++++++--- 2 files changed, 43 insertions(+), 5 deletions(-) diff --git a/e2e/testcafe-devextreme/tests/dataGrid/common/columnResizing/functional.ts b/e2e/testcafe-devextreme/tests/dataGrid/common/columnResizing/functional.ts index 14bbb362f191..068b2faa33d1 100644 --- a/e2e/testcafe-devextreme/tests/dataGrid/common/columnResizing/functional.ts +++ b/e2e/testcafe-devextreme/tests/dataGrid/common/columnResizing/functional.ts @@ -88,6 +88,39 @@ test('DataGrid - column width changed via columnOption should be applied immedia }); }); +// T1329677 +test('DataGrid - other column width should be updated immediately when another column width is changed via columnOption (T1329677)', async (t) => { + const dataGrid = new DataGrid('#container'); + + await t.expect(dataGrid.isReady()).ok(); + + const firstColumnOldWidth = await dataGrid.getDataCell(0, 0).element.clientWidth; + + await dataGrid.apiColumnOption('Col2', 'width', 200); + + const firstColumnNewWidth = await dataGrid.getDataCell(0, 0).element.clientWidth; + + await t + .expect(firstColumnOldWidth).notEql(firstColumnNewWidth, 'first column width should be changed'); +}).before(async () => { + await createWidget('dxDataGrid', { + dataSource: [{ + Col1: 'Test 1', + Col2: 'Test 2', + Col3: 'Test 3', + Col4: 'Test 4', + }], + width: 400, + columnAutoWidth: true, + columns: [ + { dataField: 'Col1' }, + { dataField: 'Col2' }, + { dataField: 'Col3' }, + { dataField: 'Col4' }, + ], + }); +}); + const tryResizeHeaderInBandArea = ( dataGrid: DataGrid, columnIndex: number, diff --git a/packages/devextreme/js/__internal/grids/grid_core/columns_controller/m_columns_controller_utils.ts b/packages/devextreme/js/__internal/grids/grid_core/columns_controller/m_columns_controller_utils.ts index e8a8431af936..84d6efbc1908 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/columns_controller/m_columns_controller_utils.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/columns_controller/m_columns_controller_utils.ts @@ -726,10 +726,15 @@ const isVisibleWidthChangePendingForColumn = (that: ColumnsController, columnInd || !!columnChanges.columnIndices?.includes(columnIndex); }; -const invalidateStaleVisibleWidth = (that: ColumnsController, column): void => { - if (isDefined(column.visibleWidth) && !isVisibleWidthChangePendingForColumn(that, column.index)) { - column.visibleWidth = null; - } +const invalidateStaleVisibleWidths = (that: ColumnsController): void => { + that._columns.forEach((column) => { + const shouldInvalidateVisibleWidth = isDefined(column.visibleWidth) + && !isVisibleWidthChangePendingForColumn(that, column.index); + + if (shouldInvalidateVisibleWidth) { + column.visibleWidth = null; + } + }); }; export const columnOptionCore = function (that: ColumnsController, column, optionName, value?, notFireEvent?) { @@ -758,7 +763,7 @@ export const columnOptionCore = function (that: ColumnsController, column, optio } if (optionName === 'width') { - invalidateStaleVisibleWidth(that, column); + invalidateStaleVisibleWidths(that); } const optionSetter = compileSetter(optionName); From 5eea65ed58e6ce4339565a60583a141ff544910a Mon Sep 17 00:00:00 2001 From: Eugen Zha Date: Tue, 18 Aug 2026 18:29:44 +0300 Subject: [PATCH 4/5] fix(columns): update invalidateStaleVisibleWidths to handle changed column visibility --- .../m_columns_controller_utils.ts | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/packages/devextreme/js/__internal/grids/grid_core/columns_controller/m_columns_controller_utils.ts b/packages/devextreme/js/__internal/grids/grid_core/columns_controller/m_columns_controller_utils.ts index 84d6efbc1908..ad96b6dc8a08 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/columns_controller/m_columns_controller_utils.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/columns_controller/m_columns_controller_utils.ts @@ -726,9 +726,20 @@ const isVisibleWidthChangePendingForColumn = (that: ColumnsController, columnInd || !!columnChanges.columnIndices?.includes(columnIndex); }; -const invalidateStaleVisibleWidths = (that: ColumnsController): void => { +const invalidateStaleVisibleWidths = (that: ColumnsController, changedColumn): void => { + if (isVisibleWidthChangePendingForColumn(that, changedColumn.index)) { + return; + } + + if (isDefined(changedColumn.visibleWidth)) { + changedColumn.visibleWidth = null; + } + that._columns.forEach((column) => { - const shouldInvalidateVisibleWidth = isDefined(column.visibleWidth) + const hasCalculatedVisibleWidth = isNumeric(column.visibleWidth) + && (!isDefined(column.width) || column.width === 'auto'); + const shouldInvalidateVisibleWidth = column !== changedColumn + && hasCalculatedVisibleWidth && !isVisibleWidthChangePendingForColumn(that, column.index); if (shouldInvalidateVisibleWidth) { @@ -763,7 +774,7 @@ export const columnOptionCore = function (that: ColumnsController, column, optio } if (optionName === 'width') { - invalidateStaleVisibleWidths(that); + invalidateStaleVisibleWidths(that, column); } const optionSetter = compileSetter(optionName); From f5fea4607e9fb4f1905391a086bd8cb168e8f932 Mon Sep 17 00:00:00 2001 From: Eugen Zha Date: Tue, 18 Aug 2026 22:34:12 +0300 Subject: [PATCH 5/5] feat(columns): implement updateColumnDimensions method and enhance column resizing logic --- .../m_columns_controller.ts | 45 +++++++++++- .../m_columns_controller_utils.ts | 24 +++++-- .../m_columns_resizing_reordering.ts | 41 +++++++---- .../testing/helpers/gridBaseMocks.js | 9 +++ .../columnResizing.integration.tests.js | 70 +++++++++++++++++++ .../columnsController.tests.js | 39 +++++++++++ 6 files changed, 207 insertions(+), 21 deletions(-) diff --git a/packages/devextreme/js/__internal/grids/grid_core/columns_controller/m_columns_controller.ts b/packages/devextreme/js/__internal/grids/grid_core/columns_controller/m_columns_controller.ts index be977eb272a1..f95a6606db42 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/columns_controller/m_columns_controller.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/columns_controller/m_columns_controller.ts @@ -90,6 +90,12 @@ interface IndexedColumns { negativeIndexedColumns: Record[]; } +export interface ColumnDimensionsUpdate { + columnIndex: Column['index']; + visibleWidth?: Column['visibleWidth'] | null; + width: Column['width']; +} + export class ColumnsController extends modules.Controller { public _skipProcessingColumnsChange: any; @@ -1488,6 +1494,41 @@ export class ColumnsController extends modules.Controller { return this._columns ? this._columns.length : 0; } + /** Applies dimensions already resolved by an internal layout operation. */ + public updateColumnDimensions(updates: ColumnDimensionsUpdate[]): void { + if (!updates.length) { + return; + } + + const columnsByIndex = new Map(); + + this._columns.concat(this._commandColumns).forEach((column: Column) => { + if (!columnsByIndex.has(column.index)) { + columnsByIndex.set(column.index, column); + } + }); + + this.beginUpdate(); + try { + updates.forEach((dimensions) => { + const column = columnsByIndex.get(dimensions.columnIndex); + + if (!column) { + return; + } + + if (Object.prototype.hasOwnProperty.call(dimensions, 'visibleWidth')) { + columnOptionCore(this, column, 'visibleWidth', dimensions.visibleWidth); + } + columnOptionCore(this, column, 'width', dimensions.width, { + invalidateVisibleWidths: false, + }); + }); + } finally { + this.endUpdate(); + } + } + public columnOption(identifier, option?, value?, notFireEvent?) { const that = this; const columns = that._columns.concat(that._commandColumns); @@ -1501,10 +1542,10 @@ export class ColumnsController extends modules.Controller { if (arguments.length === 2) { return columnOptionCore(that, column, option); } - columnOptionCore(that, column, option, value, notFireEvent); + columnOptionCore(that, column, option, value, { notFireEvent }); } else if (isObject(option)) { each(option, (optionName, optionValue) => { - columnOptionCore(that, column, optionName, optionValue, notFireEvent); + columnOptionCore(that, column, optionName, optionValue, { notFireEvent }); }); } diff --git a/packages/devextreme/js/__internal/grids/grid_core/columns_controller/m_columns_controller_utils.ts b/packages/devextreme/js/__internal/grids/grid_core/columns_controller/m_columns_controller_utils.ts index ad96b6dc8a08..e500632ef741 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/columns_controller/m_columns_controller_utils.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/columns_controller/m_columns_controller_utils.ts @@ -727,11 +727,8 @@ const isVisibleWidthChangePendingForColumn = (that: ColumnsController, columnInd }; const invalidateStaleVisibleWidths = (that: ColumnsController, changedColumn): void => { - if (isVisibleWidthChangePendingForColumn(that, changedColumn.index)) { - return; - } - - if (isDefined(changedColumn.visibleWidth)) { + if (isDefined(changedColumn.visibleWidth) + && !isVisibleWidthChangePendingForColumn(that, changedColumn.index)) { changedColumn.visibleWidth = null; } @@ -748,9 +745,22 @@ const invalidateStaleVisibleWidths = (that: ColumnsController, changedColumn): v }); }; -export const columnOptionCore = function (that: ColumnsController, column, optionName, value?, notFireEvent?) { +interface ColumnOptionCoreOptions { + invalidateVisibleWidths?: boolean; + notFireEvent?: boolean; +} + +export const columnOptionCore = function ( + that: ColumnsController, + column, + optionName, + value?, + options: ColumnOptionCoreOptions = {}, +) { const optionGetter = compileGetter(optionName); const columnIndex = column.index; + const { invalidateVisibleWidths = true } = options; + let { notFireEvent } = options; let columns; let changeType; let initialColumn; @@ -773,7 +783,7 @@ export const columnOptionCore = function (that: ColumnsController, column, optio changeType = 'columns'; } - if (optionName === 'width') { + if (optionName === 'width' && invalidateVisibleWidths) { invalidateStaleVisibleWidths(that, column); } diff --git a/packages/devextreme/js/__internal/grids/grid_core/columns_resizing_reordering/m_columns_resizing_reordering.ts b/packages/devextreme/js/__internal/grids/grid_core/columns_resizing_reordering/m_columns_resizing_reordering.ts index e41d7c631827..935dd03bd496 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/columns_resizing_reordering/m_columns_resizing_reordering.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/columns_resizing_reordering/m_columns_resizing_reordering.ts @@ -29,7 +29,7 @@ import type { RowsView } from '@ts/grids/grid_core/views/m_rows_view'; import type { ColumnChooserView } from '../column_chooser/m_column_chooser'; import type { ColumnHeadersView } from '../column_headers/m_column_headers'; -import type { ColumnsController } from '../columns_controller/m_columns_controller'; +import type { ColumnDimensionsUpdate, ColumnsController } from '../columns_controller/m_columns_controller'; import type { HeaderPanel } from '../header_panel/m_header_panel'; import modules from '../m_modules'; import gridCoreUtils from '../m_utils'; @@ -1081,21 +1081,23 @@ export class ColumnsResizerViewController extends modules.ViewController { return isString(width) && width.endsWith('%'); } - function setColumnWidth(column, columnWidth, contentWidth, adaptColumnWidthByRatio) { + function getColumnDimensions(column, columnWidth, contentWidth, adaptColumnWidthByRatio): ColumnDimensionsUpdate | undefined { if (column) { const oldColumnWidth = column.width; if (oldColumnWidth) { adaptColumnWidthByRatio = isPercentWidth(oldColumnWidth); } - if (adaptColumnWidthByRatio) { - columnsController.columnOption(column.index, 'visibleWidth', columnWidth); - columnsController.columnOption(column.index, 'width', `${(columnWidth / contentWidth * 100).toFixed(3)}%`); - } else { - columnsController.columnOption(column.index, 'visibleWidth', null); - columnsController.columnOption(column.index, 'width', columnWidth); - } + return { + columnIndex: column.index, + visibleWidth: adaptColumnWidthByRatio ? columnWidth : null, + width: adaptColumnWidthByRatio + ? `${(columnWidth / contentWidth * 100).toFixed(3)}%` + : columnWidth, + }; } + + return undefined; } function correctContentWidth(contentWidth, visibleColumns) { @@ -1168,12 +1170,19 @@ export class ColumnsResizerViewController extends modules.ViewController { contentWidth = correctContentWidth(contentWidth, visibleColumns); - setColumnWidth(column, cellWidth, contentWidth, adaptColumnWidthByRatio); + const columnDimensions = getColumnDimensions(column, cellWidth, contentWidth, adaptColumnWidthByRatio); if (isNextColumnMode) { nextCellWidth = Math.floor(nextCellWidth); - setColumnWidth(nextColumn, nextCellWidth, contentWidth, adaptColumnWidthByRatio); + const nextColumnDimensions = getColumnDimensions(nextColumn, nextCellWidth, contentWidth, adaptColumnWidthByRatio); + const updates = [columnDimensions, nextColumnDimensions].filter(isDefined); + + columnsController.updateColumnDimensions(updates); } else { + if (columnDimensions) { + columnsController.updateColumnDimensions([columnDimensions]); + } + const columnWidths = this._columnHeadersView.getColumnWidths(); columnWidths[resizingInfo.currentColumnIndex] = cellWidth; const hasScroll = columnWidths.reduce((totalWidth, width) => totalWidth + width, 0) > this._rowsView.contentWidth(); @@ -1183,11 +1192,19 @@ export class ColumnsResizerViewController extends modules.ViewController { columnsController.columnOption(visibleColumns[lastColumnIndex].index, 'visibleWidth', 'auto'); } } + + const updates: ColumnDimensionsUpdate[] = []; + for (let i = 0; i < columnWidths.length; i++) { if (visibleColumns[i] && visibleColumns[i] !== column && visibleColumns[i].width === undefined) { - columnsController.columnOption(visibleColumns[i].index, 'width', columnWidths[i]); + updates.push({ + columnIndex: visibleColumns[i].index, + width: columnWidths[i], + }); } } + + columnsController.updateColumnDimensions(updates); } columnsController.endUpdate(); diff --git a/packages/devextreme/testing/helpers/gridBaseMocks.js b/packages/devextreme/testing/helpers/gridBaseMocks.js index 7479b2ed8e01..0bfe8f90dd22 100644 --- a/packages/devextreme/testing/helpers/gridBaseMocks.js +++ b/packages/devextreme/testing/helpers/gridBaseMocks.js @@ -579,6 +579,15 @@ module.exports = function($, gridCore, columnResizingReordering, domUtils, commo } }, + updateColumnDimensions: function(updates) { + updates.forEach((dimensions) => { + if(Object.prototype.hasOwnProperty.call(dimensions, 'visibleWidth')) { + this.columnOption(dimensions.columnIndex, 'visibleWidth', dimensions.visibleWidth); + } + this.columnOption(dimensions.columnIndex, 'width', dimensions.width); + }); + }, + addCommandColumn: function(commandColumn) { columns && columns.forEach(function(column, index) { if(column.command === commandColumn.command) { diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/columnResizing.integration.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/columnResizing.integration.tests.js index df58511ef7ae..81f9858e0a43 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/columnResizing.integration.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/columnResizing.integration.tests.js @@ -54,6 +54,76 @@ QUnit.module('Column Resizing', baseModuleConfig, () => { assert.equal($(rowsCols[2]).css('width'), '50px', 'width of three column - rows view'); }); + // T1329677 + QUnit.test('Calculated widths of unrelated columns should not be invalidated during repeated column resizing in widget mode', function(assert) { + const dataGrid = $('#dataGrid').dxDataGrid({ + width: 500, + allowColumnResizing: true, + columnResizingMode: 'widget', + columnAutoWidth: true, + loadingTimeout: null, + dataSource: [{ + firstName: 'John', + lastName: 'Smith', + room: '101', + birthDay: '2000-01-01', + position: 'Developer' + }], + columns: ['firstName', 'lastName', 'room', 'birthDay', 'position'] + }); + const instance = dataGrid.dxDataGrid('instance'); + const columnsController = instance.getController('columns'); + const untouchedColumns = columnsController.getColumns().slice(1, -1); + const resizeController = instance.getController('columnsResizer'); + const moveSeparator = (pageX) => resizeController._moveSeparator({ + event: { + data: resizeController, + type: 'mousemove', + pageX, + preventDefault: commonUtils.noop + } + }); + const initialResizedColumnWidth = instance.columnOption(0, 'width'); + + assert.ok( + untouchedColumns.every((column) => Number.isFinite(column.visibleWidth)), + 'untouched columns have calculated visible widths' + ); + + const invalidatedColumnIndexes = []; + untouchedColumns.forEach((column) => { + let visibleWidth = column.visibleWidth; + + Object.defineProperty(column, 'visibleWidth', { + configurable: true, + enumerable: true, + get: () => visibleWidth, + set: (value) => { + visibleWidth = value; + + if(value === null) { + invalidatedColumnIndexes.push(column.index); + } + } + }); + }); + + resizeController._isResizing = true; + resizeController._targetPoint = { columnIndex: 0 }; + resizeController._setupResizingInfo(-9900); + + moveSeparator(-9880); + + const resizedColumnWidth = instance.columnOption(0, 'width'); + + assert.notEqual(resizedColumnWidth, initialResizedColumnWidth, 'first mouse move is processed'); + + moveSeparator(-9870); + + assert.notEqual(instance.columnOption(0, 'width'), resizedColumnWidth, 'second mouse move is processed'); + assert.deepEqual(invalidatedColumnIndexes, [], 'unrelated visible widths are not invalidated'); + }); + // T804582 QUnit.test('Cursor should switch style when it was moved to columns separator if grid has only one row and big header panel', function(assert) { const dataGrid = $('#dataGrid').dxDataGrid({ diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/columnsController.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/columnsController.tests.js index 451f7c908170..c048fd17e203 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/columnsController.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/columnsController.tests.js @@ -4957,6 +4957,45 @@ QUnit.module('Move Columns', { beforeEach: setupModule, afterEach: teardownModul QUnit.module('Column Option', { beforeEach: setupModule, afterEach: teardownModule }, () => { + // T1329677 + QUnit.test('changing width via columnOption should invalidate calculated widths', function(assert) { + this.applyOptions({ columns: ['field1', 'field2', 'field3'] }); + this.columnsController.columnOption(0, 'visibleWidth', 100); + this.columnsController.columnOption(1, 'visibleWidth', 110); + this.columnsController.columnOption(2, 'visibleWidth', 120); + + this.columnsController.columnOption(1, 'width', 150); + + assert.deepEqual( + this.columnsController.getColumns().map(column => column.visibleWidth), + [null, null, null], + 'calculated widths are invalidated' + ); + }); + + // T1329677 + QUnit.test('updating resolved column dimensions should preserve calculated widths of other columns', function(assert) { + this.applyOptions({ columns: ['field1', 'field2', 'field3'] }); + this.columnsController.columnOption(0, 'visibleWidth', 100); + this.columnsController.columnOption(1, 'visibleWidth', 110); + this.columnsController.columnOption(2, 'visibleWidth', 120); + + this.columnsController.updateColumnDimensions([{ + columnIndex: 1, + visibleWidth: null, + width: 150 + }]); + + const columns = this.columnsController.getColumns(); + + assert.strictEqual(columns[1].width, 150, 'column width is updated'); + assert.deepEqual( + columns.map(column => column.visibleWidth), + [100, null, 120], + 'calculated widths of other columns are preserved' + ); + }); + QUnit.test('update exist column parameter', function(assert) { this.applyOptions({ columns: ['field1', 'field2', 'field3'] });