From 747a64ec4ca7c58f08cc0562c4ed386a99a02457 Mon Sep 17 00:00:00 2001 From: Raushen Date: Thu, 22 Jan 2026 18:24:53 +0200 Subject: [PATCH 1/4] T1319739 --- .../columns_controller.integration.test.ts | 113 ++++++++++++++++++ .../data_controller/m_data_controller.ts | 8 +- 2 files changed, 120 insertions(+), 1 deletion(-) create mode 100644 packages/devextreme/js/__internal/grids/grid_core/columns_controller/__tests__/columns_controller.integration.test.ts 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 new file mode 100644 index 000000000000..d719cbe48315 --- /dev/null +++ b/packages/devextreme/js/__internal/grids/grid_core/columns_controller/__tests__/columns_controller.integration.test.ts @@ -0,0 +1,113 @@ +import { + afterEach, beforeEach, describe, expect, it, jest, +} from '@jest/globals'; +import type { dxElementWrapper } from '@js/core/renderer'; +import $ from '@js/core/renderer'; +import type { Properties as DataGridProperties } from '@js/ui/data_grid'; +import DataGrid from '@js/ui/data_grid'; +import errors from '@js/ui/widget/ui.errors'; + +import { DataGridModel } from '../../../data_grid/__tests__/__mock__/model/data_grid'; + +const SELECTORS = { + gridContainer: '#gridContainer', +}; + +const GRID_CONTAINER_ID = 'gridContainer'; + +const createDataGrid = async ( + options: DataGridProperties = {}, +): Promise<{ + $container: dxElementWrapper; + component: DataGridModel; + instance: DataGrid; +}> => new Promise((resolve) => { + const $container = $('
') + .attr('id', GRID_CONTAINER_ID) + .appendTo(document.body); + + const dataGridOptions: DataGridProperties = { + keyExpr: 'id', + ...options, + }; + + const instance = new DataGrid($container.get(0) as HTMLDivElement, dataGridOptions); + const component = new DataGridModel($container.get(0) as HTMLElement); + + jest.runAllTimers(); + + resolve({ + $container, + component, + instance, + }); +}); + +const beforeTest = (): void => { + jest.useFakeTimers(); + jest.spyOn(errors, 'log').mockImplementation(jest.fn()); + jest.spyOn(errors, 'Error').mockImplementation(() => ({})); +}; + +const afterTest = (): void => { + const $container = $(SELECTORS.gridContainer); + const dataGrid = ($container as any).dxDataGrid('instance') as DataGrid; + + dataGrid.dispose(); + $container.remove(); + jest.clearAllMocks(); + jest.useRealTimers(); +}; + +describe('Bugs', () => { + beforeEach(beforeTest); + afterEach(afterTest); + + describe('T1319739 - DataGrid - Columns are misaligned after adding a column at runtime', () => { + const data = [ + { + id: 1, + field_1: 'Value 1', + field_2: 'Value 2', + }, + ]; + + it('should add column with data cell if repaintChangesOnly=true', async () => { + const { instance, component } = await createDataGrid({ + dataSource: data, + repaintChangesOnly: true, + columns: [ + { + dataField: 'field_1', + }, + ], + }); + + let visibleColumns = instance.getVisibleColumns(); + let headerCellsArray = Array.from(component.getHeaderCells()); + let dataCellsArray = Array.from(component.getDataCells(0)); + + expect(visibleColumns.length).toBe(1); + expect(headerCellsArray.length).toBe(1); + expect(dataCellsArray.length).toBe(1); + + instance.addColumn({ + dataField: 'field_2', + }); + + jest.runAllTimers(); + + visibleColumns = instance.getVisibleColumns(); + headerCellsArray = Array.from(component.getHeaderCells()); + dataCellsArray = Array.from(component.getDataCells(0)); + + expect(visibleColumns.length).toBe(2); + expect(visibleColumns[0].dataField).toBe('field_1'); + expect(visibleColumns[1].dataField).toBe('field_2'); + + expect(headerCellsArray.length).toBe(2); + expect(dataCellsArray.length).toBe(2); + expect(headerCellsArray.length).toBe(dataCellsArray.length); + }); + }); +}); diff --git a/packages/devextreme/js/__internal/grids/grid_core/data_controller/m_data_controller.ts b/packages/devextreme/js/__internal/grids/grid_core/data_controller/m_data_controller.ts index 9a5331b2b4fa..6d3eb3f1582d 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/data_controller/m_data_controller.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/data_controller/m_data_controller.ts @@ -499,7 +499,13 @@ export class DataController extends DataHelperMixin(modules.Controller) { // B255430 const updateItemsHandler = function (change) { that._columnsController.columnsChanged.remove(updateItemsHandler); - const needsFullRepaint = optionNames.visible || that._columnsController.getVisibleColumns().some((col) => col.showEditorAlways); + + const visibleColumns = that._columnsController.getVisibleColumns(); + const hasAddedOrDeletedColumns = visibleColumns.some((col) => col.added) + || that._columnsController.getColumns().some((col) => col.deleted); + const hasShowEditorAlways = visibleColumns.some((col) => col.showEditorAlways); + const needsFullRepaint = optionNames.visible || hasAddedOrDeletedColumns || hasShowEditorAlways; + that.updateItems({ repaintChangesOnly: needsFullRepaint ? false : that.option('repaintChangesOnly'), event: change?.changeTypes?.event, From 6beeb6f733fdefa498f9f91461421ac1cd400bdf Mon Sep 17 00:00:00 2001 From: Raushen Date: Thu, 22 Jan 2026 18:42:44 +0200 Subject: [PATCH 2/4] Add deleteColumn test --- .../columns_controller.integration.test.ts | 40 ++++++++++++++++++- .../data_controller/m_data_controller.ts | 19 ++++++--- 2 files changed, 52 insertions(+), 7 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 d719cbe48315..1dec9d7f097b 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 @@ -72,7 +72,7 @@ describe('Bugs', () => { }, ]; - it('should add column with data cell if repaintChangesOnly=true', async () => { + it('should add column with data cell if repaintChangesOnly=true', async () => { const { instance, component } = await createDataGrid({ dataSource: data, repaintChangesOnly: true, @@ -109,5 +109,43 @@ describe('Bugs', () => { expect(dataCellsArray.length).toBe(2); expect(headerCellsArray.length).toBe(dataCellsArray.length); }); + + it('should remove column with data cell if repaintChangesOnly=true', async () => { + const { instance, component } = await createDataGrid({ + dataSource: data, + repaintChangesOnly: true, + columns: [ + { + dataField: 'field_1', + }, + { + dataField: 'field_2', + }, + ], + }); + + let visibleColumns = instance.getVisibleColumns(); + let headerCellsArray = Array.from(component.getHeaderCells()); + let dataCellsArray = Array.from(component.getDataCells(0)); + + expect(visibleColumns.length).toBe(2); + expect(headerCellsArray.length).toBe(2); + expect(dataCellsArray.length).toBe(2); + + instance.deleteColumn('field_2'); + + jest.runAllTimers(); + + visibleColumns = instance.getVisibleColumns(); + headerCellsArray = Array.from(component.getHeaderCells()); + dataCellsArray = Array.from(component.getDataCells(0)); + + expect(visibleColumns.length).toBe(1); + expect(visibleColumns[0].dataField).toBe('field_1'); + + expect(headerCellsArray.length).toBe(1); + expect(dataCellsArray.length).toBe(1); + expect(headerCellsArray.length).toBe(dataCellsArray.length); + }); }); }); diff --git a/packages/devextreme/js/__internal/grids/grid_core/data_controller/m_data_controller.ts b/packages/devextreme/js/__internal/grids/grid_core/data_controller/m_data_controller.ts index 6d3eb3f1582d..eb2f51c8b792 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/data_controller/m_data_controller.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/data_controller/m_data_controller.ts @@ -488,6 +488,18 @@ export class DataController extends DataHelperMixin(modules.Controller) { e.group = columnsController.getGroupDataSourceParameters(!dataSource.remoteOperations().grouping); } + private needFullRepaint(optionNames) { + if (optionNames.visible) { + return true; + } + + const visibleColumns = this._columnsController.getVisibleColumns(); + const hasAddedOrDeletedColumns = visibleColumns.some((col) => col.added) + || this._columnsController.getColumns().some((col) => col.deleted); + const hasShowEditorAlways = visibleColumns.some((col) => col.showEditorAlways); + return hasAddedOrDeletedColumns || hasShowEditorAlways; + } + private _handleColumnsChanged(e) { const that = this; const { changeTypes } = e; @@ -499,12 +511,7 @@ export class DataController extends DataHelperMixin(modules.Controller) { // B255430 const updateItemsHandler = function (change) { that._columnsController.columnsChanged.remove(updateItemsHandler); - - const visibleColumns = that._columnsController.getVisibleColumns(); - const hasAddedOrDeletedColumns = visibleColumns.some((col) => col.added) - || that._columnsController.getColumns().some((col) => col.deleted); - const hasShowEditorAlways = visibleColumns.some((col) => col.showEditorAlways); - const needsFullRepaint = optionNames.visible || hasAddedOrDeletedColumns || hasShowEditorAlways; + const needsFullRepaint = that.needFullRepaint(optionNames); that.updateItems({ repaintChangesOnly: needsFullRepaint ? false : that.option('repaintChangesOnly'), From 119b02966aa5fb322426c16174aa4a61e23b95f4 Mon Sep 17 00:00:00 2001 From: Raushen Date: Thu, 22 Jan 2026 20:34:38 +0200 Subject: [PATCH 3/4] Fix test --- .../data_controller/m_data_controller.ts | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/packages/devextreme/js/__internal/grids/grid_core/data_controller/m_data_controller.ts b/packages/devextreme/js/__internal/grids/grid_core/data_controller/m_data_controller.ts index eb2f51c8b792..66fb8880e39b 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/data_controller/m_data_controller.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/data_controller/m_data_controller.ts @@ -488,16 +488,18 @@ export class DataController extends DataHelperMixin(modules.Controller) { e.group = columnsController.getGroupDataSourceParameters(!dataSource.remoteOperations().grouping); } - private needFullRepaint(optionNames) { + private needFullRepaint(optionNames, changeTypes) { if (optionNames.visible) { return true; } - const visibleColumns = this._columnsController.getVisibleColumns(); - const hasAddedOrDeletedColumns = visibleColumns.some((col) => col.added) - || this._columnsController.getColumns().some((col) => col.deleted); - const hasShowEditorAlways = visibleColumns.some((col) => col.showEditorAlways); - return hasAddedOrDeletedColumns || hasShowEditorAlways; + const columnCountChanged = changeTypes.columns && optionNames.all; + if (columnCountChanged) { + return true; + } + + const hasShowEditorAlways = this._columnsController.getVisibleColumns().some((col) => col.showEditorAlways); + return hasShowEditorAlways; } private _handleColumnsChanged(e) { @@ -511,7 +513,7 @@ export class DataController extends DataHelperMixin(modules.Controller) { // B255430 const updateItemsHandler = function (change) { that._columnsController.columnsChanged.remove(updateItemsHandler); - const needsFullRepaint = that.needFullRepaint(optionNames); + const needsFullRepaint = that.needFullRepaint(optionNames, changeTypes); that.updateItems({ repaintChangesOnly: needsFullRepaint ? false : that.option('repaintChangesOnly'), From 25e92acb5139b6850fc351bec6b1ba9f827c3a64 Mon Sep 17 00:00:00 2001 From: Raushen Date: Thu, 22 Jan 2026 20:40:20 +0200 Subject: [PATCH 4/4] remove lines --- .../__tests__/columns_controller.integration.test.ts | 3 --- 1 file changed, 3 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 1dec9d7f097b..49253d562d81 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 @@ -107,7 +107,6 @@ describe('Bugs', () => { expect(headerCellsArray.length).toBe(2); expect(dataCellsArray.length).toBe(2); - expect(headerCellsArray.length).toBe(dataCellsArray.length); }); it('should remove column with data cell if repaintChangesOnly=true', async () => { @@ -133,7 +132,6 @@ describe('Bugs', () => { expect(dataCellsArray.length).toBe(2); instance.deleteColumn('field_2'); - jest.runAllTimers(); visibleColumns = instance.getVisibleColumns(); @@ -145,7 +143,6 @@ describe('Bugs', () => { expect(headerCellsArray.length).toBe(1); expect(dataCellsArray.length).toBe(1); - expect(headerCellsArray.length).toBe(dataCellsArray.length); }); }); });