From 21fc8e434b1cf1e71af557729ef7f0df97709c69 Mon Sep 17 00:00:00 2001 From: Eugen Zha Date: Tue, 21 Jul 2026 19:19:56 +0300 Subject: [PATCH 01/10] 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 02/10] 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 03/10] 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 04/10] 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 05/10] 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'] }); From 6ea9d6a9284e697045b61db9b2cb082779c761bc Mon Sep 17 00:00:00 2001 From: Eugen Zha Date: Tue, 25 Aug 2026 16:00:13 +0300 Subject: [PATCH 06/10] test(columns): add tests for immediate column width changes and preserve calculated widths --- .../columns_controller.integration.test.ts | 43 ++++++++++++ .../columnResizing.integration.tests.js | 70 ------------------- .../columnsController.tests.js | 40 ----------- 3 files changed, 43 insertions(+), 110 deletions(-) diff --git a/packages/devextreme/js/__internal/grids/grid_core/columns_controller/__tests__/columns_controller.integration.test.ts b/packages/devextreme/js/__internal/grids/grid_core/columns_controller/__tests__/columns_controller.integration.test.ts index 37748edb8578..a6ede7192c13 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/columns_controller/__tests__/columns_controller.integration.test.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/columns_controller/__tests__/columns_controller.integration.test.ts @@ -138,4 +138,47 @@ describe('Bugs', () => { expect(dataCellsArray.length).toBe(1); }); }); + + describe('T1329677 - DataGrid - Column width changes are not applied immediately', () => { + it('should invalidate calculated widths when a column width changes through columnOption', async () => { + const { instance } = await createDataGrid({ + dataSource: [{ field1: 'value 1', field2: 'value 2', field3: 'value 3' }], + columns: ['field1', 'field2', 'field3'], + }); + const columnsController = instance.getController('columns'); + + columnsController.columnOption(0, 'visibleWidth', 100); + columnsController.columnOption(1, 'visibleWidth', 110); + columnsController.columnOption(2, 'visibleWidth', 120); + + instance.columnOption(1, 'width', 150); + + expect(columnsController.getColumns().map((column) => column.visibleWidth)).toEqual([ + null, null, null, + ]); + }); + + it('should preserve calculated widths of unrelated columns when applying resolved dimensions', async () => { + const { instance } = await createDataGrid({ + dataSource: [{ field1: 'value 1', field2: 'value 2', field3: 'value 3' }], + columns: ['field1', 'field2', 'field3'], + }); + const columnsController = instance.getController('columns'); + + columnsController.columnOption(0, 'visibleWidth', 100); + columnsController.columnOption(1, 'visibleWidth', 110); + columnsController.columnOption(2, 'visibleWidth', 120); + + columnsController.updateColumnDimensions([{ + columnIndex: 1, + visibleWidth: null, + width: 150, + }]); + + expect(columnsController.columnOption(1, 'width')).toBe(150); + expect(columnsController.getColumns().map((column) => column.visibleWidth)).toEqual([ + 100, null, 120, + ]); + }); + }); }); 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 81f9858e0a43..df58511ef7ae 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,76 +54,6 @@ 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 33dd907604a0..9797b25d2c67 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 @@ -4955,46 +4955,6 @@ 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'] }); From b612edad164771da1c0598a1fdad7e361366cdae Mon Sep 17 00:00:00 2001 From: Eugen Zha Date: Tue, 25 Aug 2026 16:33:41 +0300 Subject: [PATCH 07/10] DataGrid - Enhance handling of pending visible widths during column updates --- .../columns_controller.integration.test.ts | 63 +++++++++++++++++++ .../m_columns_controller.ts | 2 + .../m_columns_controller_utils.ts | 22 ++++--- 3 files changed, 78 insertions(+), 9 deletions(-) diff --git a/packages/devextreme/js/__internal/grids/grid_core/columns_controller/__tests__/columns_controller.integration.test.ts b/packages/devextreme/js/__internal/grids/grid_core/columns_controller/__tests__/columns_controller.integration.test.ts index a6ede7192c13..5aaed6870dd1 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/columns_controller/__tests__/columns_controller.integration.test.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/columns_controller/__tests__/columns_controller.integration.test.ts @@ -180,5 +180,68 @@ describe('Bugs', () => { 100, null, 120, ]); }); + + it('should invalidate a stale visible width when another option changed the same column in the batch', async () => { + const { instance } = await createDataGrid({ + dataSource: [{ field1: 'value 1', field2: 'value 2', field3: 'value 3' }], + columns: ['field1', 'field2', 'field3'], + }); + const columnsController = instance.getController('columns'); + + columnsController.columnOption(0, 'visibleWidth', 100); + columnsController.columnOption(1, 'visibleWidth', 110); + columnsController.columnOption(2, 'visibleWidth', 120); + + columnsController.beginUpdate(); + columnsController.columnOption(1, 'caption', 'Updated field 2'); + columnsController.columnOption(0, 'visibleWidth', 105); + columnsController.columnOption(1, 'width', 150); + columnsController.endUpdate(); + + expect(columnsController.getColumns().map((column) => column.visibleWidth)).toEqual([ + 105, null, null, + ]); + }); + + it('should preserve visible widths that are pending for their respective columns', async () => { + const { instance } = await createDataGrid({ + dataSource: [{ field1: 'value 1', field2: 'value 2', field3: 'value 3' }], + columns: ['field1', 'field2', 'field3'], + }); + const columnsController = instance.getController('columns'); + + columnsController.columnOption(0, 'visibleWidth', 100); + columnsController.columnOption(1, 'visibleWidth', 110); + columnsController.columnOption(2, 'visibleWidth', 120); + + columnsController.beginUpdate(); + columnsController.columnOption(0, 'visibleWidth', 105); + columnsController.columnOption(1, 'visibleWidth', 115); + columnsController.columnOption(1, 'width', 150); + columnsController.endUpdate(); + + expect(columnsController.getColumns().map((column) => column.visibleWidth)).toEqual([ + 105, 115, null, + ]); + }); + + it('should clear pending visible widths after the update batch completes', async () => { + const { instance } = await createDataGrid({ + dataSource: [{ field1: 'value 1', field2: 'value 2', field3: 'value 3' }], + columns: ['field1', 'field2', 'field3'], + }); + const columnsController = instance.getController('columns'); + + columnsController.columnOption(0, 'visibleWidth', 100); + + columnsController.beginUpdate(); + columnsController.columnOption(0, 'visibleWidth', 105); + columnsController.columnOption(0, 'width', 150); + columnsController.endUpdate(); + + columnsController.columnOption(0, 'width', 160); + + expect(columnsController.columnOption(0, 'visibleWidth')).toBeNull(); + }); }); }); 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 f95a6606db42..8380e15d8a89 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 @@ -139,6 +139,8 @@ export class ColumnsController extends modules.Controller { public _columnChanges?: ColumnsChanges; + public _pendingVisibleWidthColumnIndices?: Set; + protected _dataController!: DataController; protected _focusController!: FocusController; 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 e500632ef741..a53785d64f96 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 @@ -657,6 +657,10 @@ export const updateColumnChanges = ( }; export const fireColumnsChanged = function (that: ColumnsController) { + if (!that._updateLockCount) { + that._pendingVisibleWidthColumnIndices = undefined; + } + const onColumnsChanging: any = that.option('onColumnsChanging'); const columnChanges = that._columnChanges; const reinitOptionNames = ['dataField', 'lookup', 'dataType', 'columns']; @@ -715,17 +719,15 @@ export const fireOptionChanged = function (that: ColumnsController, options) { } }; -const isVisibleWidthChangePendingForColumn = (that: ColumnsController, columnIndex): boolean => { - const columnChanges = that._columnChanges; - - if (!columnChanges?.optionNames?.visibleWidth) { - return false; +const trackPendingVisibleWidthChange = (that: ColumnsController, columnIndex): void => { + if (isDefined(columnIndex)) { + that._pendingVisibleWidthColumnIndices ??= new Set(); + that._pendingVisibleWidthColumnIndices.add(columnIndex); } - - return columnChanges.columnIndex === columnIndex - || !!columnChanges.columnIndices?.includes(columnIndex); }; +const isVisibleWidthChangePendingForColumn = (that: ColumnsController, columnIndex): boolean => !!that._pendingVisibleWidthColumnIndices?.has(columnIndex); + const invalidateStaleVisibleWidths = (that: ColumnsController, changedColumn): void => { if (isDefined(changedColumn.visibleWidth) && !isVisibleWidthChangePendingForColumn(that, changedColumn.index)) { @@ -783,7 +785,9 @@ export const columnOptionCore = function ( changeType = 'columns'; } - if (optionName === 'width' && invalidateVisibleWidths) { + if (optionName === 'visibleWidth') { + trackPendingVisibleWidthChange(that, columnIndex); + } else if (optionName === 'width' && invalidateVisibleWidths) { invalidateStaleVisibleWidths(that, column); } From 422251716835896d40794847a86d1085cf7f93a0 Mon Sep 17 00:00:00 2001 From: Eugen Zha Date: Tue, 25 Aug 2026 18:02:38 +0300 Subject: [PATCH 08/10] DataGrid - Add test for command column width invalidation and update width calculation logic --- .../columns_controller.integration.test.ts | 15 +++++++++++++++ .../m_columns_controller_utils.ts | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/packages/devextreme/js/__internal/grids/grid_core/columns_controller/__tests__/columns_controller.integration.test.ts b/packages/devextreme/js/__internal/grids/grid_core/columns_controller/__tests__/columns_controller.integration.test.ts index 5aaed6870dd1..cdb489cd8194 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/columns_controller/__tests__/columns_controller.integration.test.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/columns_controller/__tests__/columns_controller.integration.test.ts @@ -158,6 +158,21 @@ describe('Bugs', () => { ]); }); + it('should invalidate the calculated width of a command column when its width changes through columnOption', async () => { + const { instance } = await createDataGrid({ + dataSource: [{ field1: 'value 1' }], + columns: ['field1'], + }); + const columnsController = instance.getController('columns'); + + columnsController.addCommandColumn({ command: 'test', width: 'auto' }); + columnsController.columnOption('command:test', 'visibleWidth', 100); + + instance.columnOption('command:test', 'width', 150); + + expect(columnsController.columnOption('command:test', 'visibleWidth')).toBeNull(); + }); + it('should preserve calculated widths of unrelated columns when applying resolved dimensions', async () => { const { instance } = await createDataGrid({ dataSource: [{ field1: 'value 1', field2: 'value 2', field3: 'value 3' }], 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 a53785d64f96..024b737dfa2d 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 @@ -734,7 +734,7 @@ const invalidateStaleVisibleWidths = (that: ColumnsController, changedColumn): v changedColumn.visibleWidth = null; } - that._columns.forEach((column) => { + that._columns.concat(that._commandColumns).forEach((column) => { const hasCalculatedVisibleWidth = isNumeric(column.visibleWidth) && (!isDefined(column.width) || column.width === 'auto'); const shouldInvalidateVisibleWidth = column !== changedColumn From 9e45df529a97cc3d4d827fb4680a077c3bb8233f Mon Sep 17 00:00:00 2001 From: Eugen Zha Date: Tue, 25 Aug 2026 18:09:34 +0300 Subject: [PATCH 09/10] DataGrid - Update test for command column width invalidation to reflect changes in other column widths --- .../__tests__/columns_controller.integration.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/devextreme/js/__internal/grids/grid_core/columns_controller/__tests__/columns_controller.integration.test.ts b/packages/devextreme/js/__internal/grids/grid_core/columns_controller/__tests__/columns_controller.integration.test.ts index cdb489cd8194..0491fda2d83a 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/columns_controller/__tests__/columns_controller.integration.test.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/columns_controller/__tests__/columns_controller.integration.test.ts @@ -158,7 +158,7 @@ describe('Bugs', () => { ]); }); - it('should invalidate the calculated width of a command column when its width changes through columnOption', async () => { + it('should invalidate calculated widths of command columns when another column width changes through columnOption', async () => { const { instance } = await createDataGrid({ dataSource: [{ field1: 'value 1' }], columns: ['field1'], @@ -168,7 +168,7 @@ describe('Bugs', () => { columnsController.addCommandColumn({ command: 'test', width: 'auto' }); columnsController.columnOption('command:test', 'visibleWidth', 100); - instance.columnOption('command:test', 'width', 150); + instance.columnOption('field1', 'width', 150); expect(columnsController.columnOption('command:test', 'visibleWidth')).toBeNull(); }); From 7f1be8b871fb9dc2360774df6b1e7f9ff8fbb3f3 Mon Sep 17 00:00:00 2001 From: Eugen Zha Date: Tue, 25 Aug 2026 18:16:37 +0300 Subject: [PATCH 10/10] DataGrid - Add tests for adaptive column width changes and improve visible width invalidation logic --- .../columns_controller.integration.test.ts | 29 +++++++++++++++++++ .../m_columns_controller_utils.ts | 8 +++-- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/packages/devextreme/js/__internal/grids/grid_core/columns_controller/__tests__/columns_controller.integration.test.ts b/packages/devextreme/js/__internal/grids/grid_core/columns_controller/__tests__/columns_controller.integration.test.ts index 0491fda2d83a..8ce3c92fdf98 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/columns_controller/__tests__/columns_controller.integration.test.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/columns_controller/__tests__/columns_controller.integration.test.ts @@ -8,6 +8,7 @@ import { beforeTest, createDataGrid, } from '../../__tests__/__mock__/helpers/utils'; +import { HIDDEN_COLUMNS_WIDTH } from '../../adaptivity/const'; describe('getFilteringColumns', () => { beforeEach(beforeTest); @@ -158,6 +159,34 @@ describe('Bugs', () => { ]); }); + it('should preserve an adaptive-hidden marker when a column width changes through columnOption', async () => { + const { instance } = await createDataGrid({ + dataSource: [{ field1: 'value 1' }], + columns: ['field1'], + }); + const columnsController = instance.getController('columns'); + + columnsController.columnOption(0, 'visibleWidth', HIDDEN_COLUMNS_WIDTH); + + instance.columnOption(0, 'width', 150); + + expect(columnsController.columnOption(0, 'visibleWidth')).toBe(HIDDEN_COLUMNS_WIDTH); + }); + + it('should invalidate an auto visible width when a column width changes through columnOption', async () => { + const { instance } = await createDataGrid({ + dataSource: [{ field1: 'value 1' }], + columns: ['field1'], + }); + const columnsController = instance.getController('columns'); + + columnsController.columnOption(0, 'visibleWidth', 'auto'); + + instance.columnOption(0, 'width', 150); + + expect(columnsController.columnOption(0, 'visibleWidth')).toBeNull(); + }); + it('should invalidate calculated widths of command columns when another column width changes through columnOption', async () => { const { instance } = await createDataGrid({ dataSource: [{ field1: 'value 1' }], 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 024b737dfa2d..a754b2e5bdb7 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 @@ -729,8 +729,12 @@ const trackPendingVisibleWidthChange = (that: ColumnsController, columnIndex): v const isVisibleWidthChangePendingForColumn = (that: ColumnsController, columnIndex): boolean => !!that._pendingVisibleWidthColumnIndices?.has(columnIndex); const invalidateStaleVisibleWidths = (that: ColumnsController, changedColumn): void => { - if (isDefined(changedColumn.visibleWidth) - && !isVisibleWidthChangePendingForColumn(that, changedColumn.index)) { + const hasCalculatedVisibleWidth = isNumeric(changedColumn.visibleWidth) + || changedColumn.visibleWidth === 'auto'; + const shouldInvalidateChangedColumnVisibleWidth = hasCalculatedVisibleWidth + && !isVisibleWidthChangePendingForColumn(that, changedColumn.index); + + if (shouldInvalidateChangedColumnVisibleWidth) { changedColumn.visibleWidth = null; }