From 773aa3a6165f65b9b39124ae712023538a9b0ada Mon Sep 17 00:00:00 2001 From: Eldar Iusupzhanov Date: Wed, 1 Jul 2026 18:01:42 +0600 Subject: [PATCH 1/9] fix --- .../grids/grid_core/views/m_grid_view.ts | 39 +++++++++++-------- .../data_controller/m_data_controller.ts | 12 ++++++ .../m_data_source_adapter.ts | 14 +++++++ 3 files changed, 49 insertions(+), 16 deletions(-) diff --git a/packages/devextreme/js/__internal/grids/grid_core/views/m_grid_view.ts b/packages/devextreme/js/__internal/grids/grid_core/views/m_grid_view.ts index 1ef32df7ad38..b121904058a5 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/views/m_grid_view.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/views/m_grid_view.ts @@ -172,38 +172,45 @@ export class ResizingController extends modules.ViewController { } private _refreshSizes(e) { - // @ts-expect-error - let resizeDeferred = new Deferred().resolve(null); const changeType = e?.changeType; const isDelayed = e?.isDelayed; - const items = this._dataController.items(); - if (!e || changeType === 'refresh' || changeType === 'prepend' || changeType === 'append') { + if (!e || ['refresh', 'prepend', 'append'].includes(changeType)) { if (!isDelayed) { - resizeDeferred = this.resize(); + return this.resize(); } - } else if (changeType === 'update') { - if (e.changeTypes?.length === 0) { - return resizeDeferred; + } + + if (changeType === 'update') { + if (!e.changeTypes?.length) { + // @ts-expect-error + return new Deferred().resolve(null); } - if ((items.length > 1 || e.changeTypes[0] !== 'insert') - && !(items.length === 0 && e.changeTypes[0] === 'remove') && !e.needUpdateDimensions) { + + const items = this._dataController.items(); + const isHidingNoDataPanel = items.length <= 1 && e.changeTypes[0] === 'insert'; + const isShowingNoDataPanel = items.length === 0 && e.changeTypes[0] === 'remove'; + + if (!isHidingNoDataPanel && !isShowingNoDataPanel && !e.needUpdateDimensions) { // @ts-expect-error - resizeDeferred = new Deferred(); + const deferred = new Deferred(); this._waitAsyncTemplates().done(() => { deferUpdate(() => deferRender(() => deferUpdate(() => { this._setScrollerSpacing(); this._rowsView.resize(); - resizeDeferred.resolve(); + deferred.resolve(); }))); - }).fail(resizeDeferred.reject); - } else { - resizeDeferred = this.resize(); + }).fail(deferred.reject); + + return deferred; } + + return this.resize(); } - return resizeDeferred; + // @ts-expect-error + return new Deferred().resolve(null); } /** diff --git a/packages/devextreme/js/__internal/grids/tree_list/data_controller/m_data_controller.ts b/packages/devextreme/js/__internal/grids/tree_list/data_controller/m_data_controller.ts index 5091bd8a65f7..389ea12c6ef8 100644 --- a/packages/devextreme/js/__internal/grids/tree_list/data_controller/m_data_controller.ts +++ b/packages/devextreme/js/__internal/grids/tree_list/data_controller/m_data_controller.ts @@ -95,6 +95,18 @@ export class TreeListDataController extends DataController { return super.publicMethods().concat(['expandRow', 'collapseRow', 'isRowExpanded', 'getRootNode', 'getNodeByKey', 'loadDescendants', 'forEachNode']); } + public updateItems(change: any = {}, isDataChanged?: boolean) { + if (isDataChanged) { + const operationTypes = this._dataSource?.operationTypes(); + + if (operationTypes?.nodeExpanding) { + change.needUpdateDimensions = true; + } + } + + super.updateItems(change, isDataChanged); + } + private changeRowExpand(key) { if (this._dataSource) { const args: any = { diff --git a/packages/devextreme/js/__internal/grids/tree_list/data_source_adapter/m_data_source_adapter.ts b/packages/devextreme/js/__internal/grids/tree_list/data_source_adapter/m_data_source_adapter.ts index ac9fc8a47263..6bf8f1ea3610 100644 --- a/packages/devextreme/js/__internal/grids/tree_list/data_source_adapter/m_data_source_adapter.ts +++ b/packages/devextreme/js/__internal/grids/tree_list/data_source_adapter/m_data_source_adapter.ts @@ -72,6 +72,8 @@ export class DataSourceAdapterTreeList extends DataSourceAdapter { private _totalItemsCount: any; + private _lastExpandedRowKeys: any; + private _createKeyGetter() { const keyExpr = this.getKeyExpr(); @@ -298,6 +300,14 @@ export class DataSourceAdapterTreeList extends DataSourceAdapter { } options.expandVisibleNodes = expandVisibleNodes; + + if (!options.isCustomLoading) { + const currentExpandedKeys = this.option('expandedRowKeys'); + + if (!equalByValue(this._lastExpandedRowKeys, currentExpandedKeys)) { + operationTypes.nodeExpanding = true; + } + } } private _getParentIdsToLoad(parentIds) { @@ -607,6 +617,10 @@ export class DataSourceAdapterTreeList extends DataSourceAdapter { this._updateHasItemsMap(options); super._handleDataLoaded(options); + if (!options.isCustomLoading) { + this._lastExpandedRowKeys = this.option('expandedRowKeys')?.slice(); + } + if (data.isConverted && this._cachedStoreData) { this._cachedStoreData.isConverted = true; } From 3f0eccc475243a554a3624fb3d25f42db8eb6050 Mon Sep 17 00:00:00 2001 From: Eldar Iusupzhanov Date: Wed, 1 Jul 2026 19:17:40 +0600 Subject: [PATCH 2/9] add tests --- .../common/treeList/column_auto_width.ts | 88 +++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 e2e/testcafe-devextreme/tests/common/treeList/column_auto_width.ts diff --git a/e2e/testcafe-devextreme/tests/common/treeList/column_auto_width.ts b/e2e/testcafe-devextreme/tests/common/treeList/column_auto_width.ts new file mode 100644 index 000000000000..3d4a1dd83aa4 --- /dev/null +++ b/e2e/testcafe-devextreme/tests/common/treeList/column_auto_width.ts @@ -0,0 +1,88 @@ +import { ClientFunction } from 'testcafe'; +import ExpandableCell from 'devextreme-testcafe-models/treeList/expandableCell'; +import TreeList from 'devextreme-testcafe-models/treeList'; +import url from '../../../helpers/getPageUrl'; +import { createWidget } from '../../../helpers/createWidget'; + +fixture.disablePageReloads`Columns Auto Width` + .page(url(__dirname, '../../container.html')); + +const getHeaderCellWidths = ClientFunction(() => { + const cells = document.querySelectorAll('#container .dx-header-row td'); + return Array.from(cells).map((cell) => Math.round(cell.getBoundingClientRect().width)); +}); + +const treeListData = [ + { + id: 1, parentId: 0, name: 'Root item with a long name', size: 1024, date: '2024-01-01', + }, + { + id: 2, parentId: 1, name: 'Child 1', size: 512, date: '2024-02-01', + }, + { + id: 3, parentId: 1, name: 'Child 2 with a longer name value', size: 256, date: '2024-03-01', + }, + { + id: 4, parentId: 0, name: 'Second root', size: 2048, date: '2024-04-01', + }, +]; + +const treeListConfig = { + dataSource: treeListData, + keyExpr: 'id', + parentIdExpr: 'parentId', + columnAutoWidth: true, + width: 500, + columns: [ + { dataField: 'name' }, + { dataField: 'size', width: 100 }, + { dataField: 'date', width: 150 }, + ], + scrolling: { + mode: 'standard', + }, +}; + +// T1328904 +test('columns should update auto width when expanding row', async (t) => { + const treeList = new TreeList('#container'); + + const widthsBefore = await getHeaderCellWidths(); + await t.expect(widthsBefore).eql([250, 100, 150]); + + const expandableCell = new ExpandableCell(treeList.getDataRow(0).getDataCell(0)); + await t.click(expandableCell.getExpandButton()); + + const widthsAfter = await getHeaderCellWidths(); + await t.expect(widthsAfter).eql([285, 100, 150]); +}).before(async () => createWidget('dxTreeList', treeListConfig)); + +// T1328904 +test('columns should update auto width when collapsing row', async (t) => { + const treeList = new TreeList('#container'); + + const widthsBefore = await getHeaderCellWidths(); + await t.expect(widthsBefore).eql([285, 100, 150]); + + const expandableCell = new ExpandableCell(treeList.getDataRow(0).getDataCell(0)); + await t.click(expandableCell.getCollapseButton()); + + const widthsAfter = await getHeaderCellWidths(); + await t.expect(widthsAfter).eql([250, 100, 150]); +}).before(async () => createWidget('dxTreeList', { + ...treeListConfig, + expandedRowKeys: [1], +})); + +// T1328904 +test('columns should update auto width when expanded row keys are updated using API', async (t) => { + const treeList = new TreeList('#container'); + + const widthsBefore = await getHeaderCellWidths(); + await t.expect(widthsBefore).eql([250, 100, 150]); + + await treeList.apiOption('expandedRowKeys', [1]); + + const widthsAfter = await getHeaderCellWidths(); + await t.expect(widthsAfter).eql([285, 100, 150]); +}).before(async () => createWidget('dxTreeList', treeListConfig)); From 9f6d19d53ccb85c417c79ab4f9290b2c814f7e68 Mon Sep 17 00:00:00 2001 From: Eldar Iusupzhanov Date: Wed, 1 Jul 2026 19:59:25 +0600 Subject: [PATCH 3/9] update tests --- .../common/treeList/column_auto_width.ts | 90 ++++++++++++++++++- packages/testcafe-models/treeList/index.ts | 10 +++ 2 files changed, 96 insertions(+), 4 deletions(-) diff --git a/e2e/testcafe-devextreme/tests/common/treeList/column_auto_width.ts b/e2e/testcafe-devextreme/tests/common/treeList/column_auto_width.ts index 3d4a1dd83aa4..f8cb8d7638c3 100644 --- a/e2e/testcafe-devextreme/tests/common/treeList/column_auto_width.ts +++ b/e2e/testcafe-devextreme/tests/common/treeList/column_auto_width.ts @@ -40,6 +40,7 @@ const treeListConfig = { ], scrolling: { mode: 'standard', + useNative: false, }, }; @@ -48,13 +49,17 @@ test('columns should update auto width when expanding row', async (t) => { const treeList = new TreeList('#container'); const widthsBefore = await getHeaderCellWidths(); + const [nameWidthBefore] = widthsBefore; await t.expect(widthsBefore).eql([250, 100, 150]); const expandableCell = new ExpandableCell(treeList.getDataRow(0).getDataCell(0)); await t.click(expandableCell.getExpandButton()); const widthsAfter = await getHeaderCellWidths(); - await t.expect(widthsAfter).eql([285, 100, 150]); + const [nameWidthAfter, sizeWidthAfter, dateWidthAfter] = widthsAfter; + await t.expect(nameWidthAfter).gt(nameWidthBefore); + await t.expect(sizeWidthAfter).eql(100); + await t.expect(dateWidthAfter).eql(150); }).before(async () => createWidget('dxTreeList', treeListConfig)); // T1328904 @@ -62,13 +67,16 @@ test('columns should update auto width when collapsing row', async (t) => { const treeList = new TreeList('#container'); const widthsBefore = await getHeaderCellWidths(); - await t.expect(widthsBefore).eql([285, 100, 150]); + const [nameWidthBefore] = widthsBefore; const expandableCell = new ExpandableCell(treeList.getDataRow(0).getDataCell(0)); await t.click(expandableCell.getCollapseButton()); const widthsAfter = await getHeaderCellWidths(); - await t.expect(widthsAfter).eql([250, 100, 150]); + const [nameWidthAfter, sizeWidthAfter, dateWidthAfter] = widthsAfter; + await t.expect(nameWidthAfter).lt(nameWidthBefore); + await t.expect(sizeWidthAfter).eql(100); + await t.expect(dateWidthAfter).eql(150); }).before(async () => createWidget('dxTreeList', { ...treeListConfig, expandedRowKeys: [1], @@ -79,10 +87,84 @@ test('columns should update auto width when expanded row keys are updated using const treeList = new TreeList('#container'); const widthsBefore = await getHeaderCellWidths(); + const [nameWidthBefore] = widthsBefore; await t.expect(widthsBefore).eql([250, 100, 150]); await treeList.apiOption('expandedRowKeys', [1]); const widthsAfter = await getHeaderCellWidths(); - await t.expect(widthsAfter).eql([285, 100, 150]); + const [nameWidthAfter, sizeWidthAfter, dateWidthAfter] = widthsAfter; + await t.expect(nameWidthAfter).gt(nameWidthBefore); + await t.expect(sizeWidthAfter).eql(100); + await t.expect(dateWidthAfter).eql(150); }).before(async () => createWidget('dxTreeList', treeListConfig)); + +test('columns should update auto width after loadDescendants call', async (t) => { + const treeList = new TreeList('#container'); + + await treeList.apiLoadDescendants(1); + + const widthsBefore = await getHeaderCellWidths(); + await t.expect(widthsBefore).eql([250, 100, 150]); + + const expandableCell = new ExpandableCell(treeList.getDataRow(0).getDataCell(0)); + await t.click(expandableCell.getExpandButton()); + + const widthsAfter = await getHeaderCellWidths(); + const [nameWidthAfter, sizeWidthAfter, dateWidthAfter] = widthsAfter; + await t.expect(nameWidthAfter).gt(250); + await t.expect(sizeWidthAfter).eql(100); + await t.expect(dateWidthAfter).eql(150); +}).before(async () => createWidget('dxTreeList', () => { + const data = [ + { + id: 1, parentId: 0, name: 'Root item with a long name', size: 1024, date: '2024-01-01', + }, + { + id: 2, parentId: 1, name: 'Child 1', size: 512, date: '2024-02-01', + }, + { + id: 3, parentId: 1, name: 'Child 2 with a longer name value', size: 256, date: '2024-03-01', + }, + { + id: 4, parentId: 0, name: 'Second root', size: 2048, date: '2024-04-01', + }, + ]; + + return { + dataSource: { + key: 'id', + load(loadOptions: any) { + let result = data; + + if (loadOptions.filter) { + const parentIds = loadOptions.filter[0] === 'parentId' + ? [loadOptions.filter[2]] + : loadOptions.filter + .filter((f: any) => Array.isArray(f) && f[0] === 'parentId') + .map((f: any) => f[2]); + + if (parentIds.length) { + result = data.filter((item) => parentIds.includes(item.parentId)); + } + } + return Promise.resolve(result); + }, + }, + keyExpr: 'id', + parentIdExpr: 'parentId', + columnAutoWidth: true, + width: 500, + columns: [ + { dataField: 'name' }, + { dataField: 'size', width: 100 }, + { dataField: 'date', width: 150 }, + ], + scrolling: { + mode: 'standard', + }, + remoteOperations: { + filtering: true, + }, + }; +})); diff --git a/packages/testcafe-models/treeList/index.ts b/packages/testcafe-models/treeList/index.ts index 4322e1ee429b..7a6ce27e167d 100644 --- a/packages/testcafe-models/treeList/index.ts +++ b/packages/testcafe-models/treeList/index.ts @@ -1,3 +1,4 @@ +import { ClientFunction } from 'testcafe'; import type { WidgetName } from '../types'; import DataGrid from '../dataGrid'; @@ -17,4 +18,13 @@ export default class TreeList extends DataGrid { getAdaptiveButtonSelector(): string { return `.${CLASS.adaptiveColumnButton}`; } + + apiLoadDescendants(key?: unknown): Promise { + const { getInstance } = this; + + return ClientFunction( + () => (getInstance() as any).loadDescendants(key), + { dependencies: { getInstance, key } }, + )(); + } } From 3f3a9a563a5a98248cbee2762c5885d6f2657f8f Mon Sep 17 00:00:00 2001 From: Eldar Iusupzhanov Date: Thu, 2 Jul 2026 21:15:39 +0600 Subject: [PATCH 4/9] apply review --- .../common/treeList/column_auto_width.ts | 110 ++++++++---------- .../data_controller/m_data_controller.ts | 11 +- .../data_controller/m_data_controller.ts | 14 +-- packages/testcafe-models/dataGrid/index.ts | 13 +++ 4 files changed, 73 insertions(+), 75 deletions(-) diff --git a/e2e/testcafe-devextreme/tests/common/treeList/column_auto_width.ts b/e2e/testcafe-devextreme/tests/common/treeList/column_auto_width.ts index f8cb8d7638c3..a8c9d754efa2 100644 --- a/e2e/testcafe-devextreme/tests/common/treeList/column_auto_width.ts +++ b/e2e/testcafe-devextreme/tests/common/treeList/column_auto_width.ts @@ -7,11 +7,6 @@ import { createWidget } from '../../../helpers/createWidget'; fixture.disablePageReloads`Columns Auto Width` .page(url(__dirname, '../../container.html')); -const getHeaderCellWidths = ClientFunction(() => { - const cells = document.querySelectorAll('#container .dx-header-row td'); - return Array.from(cells).map((cell) => Math.round(cell.getBoundingClientRect().width)); -}); - const treeListData = [ { id: 1, parentId: 0, name: 'Root item with a long name', size: 1024, date: '2024-01-01', @@ -47,15 +42,16 @@ const treeListConfig = { // T1328904 test('columns should update auto width when expanding row', async (t) => { const treeList = new TreeList('#container'); + await t.expect(treeList.isReady()).ok(); - const widthsBefore = await getHeaderCellWidths(); + const widthsBefore = await treeList.getHeaderCellWidths(); const [nameWidthBefore] = widthsBefore; await t.expect(widthsBefore).eql([250, 100, 150]); const expandableCell = new ExpandableCell(treeList.getDataRow(0).getDataCell(0)); await t.click(expandableCell.getExpandButton()); - const widthsAfter = await getHeaderCellWidths(); + const widthsAfter = await treeList.getHeaderCellWidths(); const [nameWidthAfter, sizeWidthAfter, dateWidthAfter] = widthsAfter; await t.expect(nameWidthAfter).gt(nameWidthBefore); await t.expect(sizeWidthAfter).eql(100); @@ -65,14 +61,15 @@ test('columns should update auto width when expanding row', async (t) => { // T1328904 test('columns should update auto width when collapsing row', async (t) => { const treeList = new TreeList('#container'); + await t.expect(treeList.isReady()).ok(); - const widthsBefore = await getHeaderCellWidths(); + const widthsBefore = await treeList.getHeaderCellWidths(); const [nameWidthBefore] = widthsBefore; const expandableCell = new ExpandableCell(treeList.getDataRow(0).getDataCell(0)); await t.click(expandableCell.getCollapseButton()); - const widthsAfter = await getHeaderCellWidths(); + const widthsAfter = await treeList.getHeaderCellWidths(); const [nameWidthAfter, sizeWidthAfter, dateWidthAfter] = widthsAfter; await t.expect(nameWidthAfter).lt(nameWidthBefore); await t.expect(sizeWidthAfter).eql(100); @@ -85,14 +82,15 @@ test('columns should update auto width when collapsing row', async (t) => { // T1328904 test('columns should update auto width when expanded row keys are updated using API', async (t) => { const treeList = new TreeList('#container'); + await t.expect(treeList.isReady()).ok(); - const widthsBefore = await getHeaderCellWidths(); + const widthsBefore = await treeList.getHeaderCellWidths(); const [nameWidthBefore] = widthsBefore; await t.expect(widthsBefore).eql([250, 100, 150]); await treeList.apiOption('expandedRowKeys', [1]); - const widthsAfter = await getHeaderCellWidths(); + const widthsAfter = await treeList.getHeaderCellWidths(); const [nameWidthAfter, sizeWidthAfter, dateWidthAfter] = widthsAfter; await t.expect(nameWidthAfter).gt(nameWidthBefore); await t.expect(sizeWidthAfter).eql(100); @@ -101,70 +99,54 @@ test('columns should update auto width when expanded row keys are updated using test('columns should update auto width after loadDescendants call', async (t) => { const treeList = new TreeList('#container'); + await t.expect(treeList.isReady()).ok(); await treeList.apiLoadDescendants(1); - const widthsBefore = await getHeaderCellWidths(); + const widthsBefore = await treeList.getHeaderCellWidths(); await t.expect(widthsBefore).eql([250, 100, 150]); const expandableCell = new ExpandableCell(treeList.getDataRow(0).getDataCell(0)); await t.click(expandableCell.getExpandButton()); - const widthsAfter = await getHeaderCellWidths(); + const widthsAfter = await treeList.getHeaderCellWidths(); const [nameWidthAfter, sizeWidthAfter, dateWidthAfter] = widthsAfter; await t.expect(nameWidthAfter).gt(250); await t.expect(sizeWidthAfter).eql(100); await t.expect(dateWidthAfter).eql(150); -}).before(async () => createWidget('dxTreeList', () => { - const data = [ - { - id: 1, parentId: 0, name: 'Root item with a long name', size: 1024, date: '2024-01-01', - }, - { - id: 2, parentId: 1, name: 'Child 1', size: 512, date: '2024-02-01', - }, - { - id: 3, parentId: 1, name: 'Child 2 with a longer name value', size: 256, date: '2024-03-01', - }, - { - id: 4, parentId: 0, name: 'Second root', size: 2048, date: '2024-04-01', - }, - ]; - - return { - dataSource: { - key: 'id', - load(loadOptions: any) { - let result = data; - - if (loadOptions.filter) { - const parentIds = loadOptions.filter[0] === 'parentId' - ? [loadOptions.filter[2]] - : loadOptions.filter - .filter((f: any) => Array.isArray(f) && f[0] === 'parentId') - .map((f: any) => f[2]); - - if (parentIds.length) { - result = data.filter((item) => parentIds.includes(item.parentId)); - } +}).before(async () => createWidget('dxTreeList', { + dataSource: { + key: 'id', + load: ClientFunction((loadOptions: any) => { + let result = treeListData; + + if (loadOptions.filter) { + const parentIds = loadOptions.filter[0] === 'parentId' + ? [loadOptions.filter[2]] + : loadOptions.filter + .filter((f: any) => Array.isArray(f) && f[0] === 'parentId') + .map((f: any) => f[2]); + + if (parentIds.length) { + result = treeListData.filter((item) => parentIds.includes(item.parentId)); } - return Promise.resolve(result); - }, - }, - keyExpr: 'id', - parentIdExpr: 'parentId', - columnAutoWidth: true, - width: 500, - columns: [ - { dataField: 'name' }, - { dataField: 'size', width: 100 }, - { dataField: 'date', width: 150 }, - ], - scrolling: { - mode: 'standard', - }, - remoteOperations: { - filtering: true, - }, - }; + } + return Promise.resolve(result); + }, { dependencies: { treeListData } }), + }, + keyExpr: 'id', + parentIdExpr: 'parentId', + columnAutoWidth: true, + width: 500, + columns: [ + { dataField: 'name' }, + { dataField: 'size', width: 100 }, + { dataField: 'date', width: 150 }, + ], + scrolling: { + mode: 'standard', + }, + remoteOperations: { + filtering: true, + }, })); 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 c9b720bbf9a0..5b01ecb3ba3a 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 @@ -1244,7 +1244,7 @@ export class DataController extends DataHelperMixin(modules.Controller) { change.isDataChanged = true; change.repaintChangesOnly = operationTypes && !operationTypes.grouping && !operationTypes.filtering && this.option('repaintChangesOnly'); - if (operationTypes && (operationTypes.reload || operationTypes.paging || operationTypes.groupExpanding)) { + if (this.needUpdateDimensions(operationTypes)) { change.needUpdateDimensions = true; } } @@ -1261,6 +1261,15 @@ export class DataController extends DataHelperMixin(modules.Controller) { this._fireChanged(change); } + /** + * @extended: tree_list/data_controller + */ + protected needUpdateDimensions(operationTypes) { + return operationTypes && ( + operationTypes.reload || operationTypes.paging || operationTypes.groupExpanding + ); + } + public loadingOperationTypes() { const dataSource = this.dataSource(); diff --git a/packages/devextreme/js/__internal/grids/tree_list/data_controller/m_data_controller.ts b/packages/devextreme/js/__internal/grids/tree_list/data_controller/m_data_controller.ts index 389ea12c6ef8..7f15512d86e2 100644 --- a/packages/devextreme/js/__internal/grids/tree_list/data_controller/m_data_controller.ts +++ b/packages/devextreme/js/__internal/grids/tree_list/data_controller/m_data_controller.ts @@ -95,16 +95,10 @@ export class TreeListDataController extends DataController { return super.publicMethods().concat(['expandRow', 'collapseRow', 'isRowExpanded', 'getRootNode', 'getNodeByKey', 'loadDescendants', 'forEachNode']); } - public updateItems(change: any = {}, isDataChanged?: boolean) { - if (isDataChanged) { - const operationTypes = this._dataSource?.operationTypes(); - - if (operationTypes?.nodeExpanding) { - change.needUpdateDimensions = true; - } - } - - super.updateItems(change, isDataChanged); + protected override needUpdateDimensions(operationTypes) { + return super.needUpdateDimensions(operationTypes) || ( + operationTypes && operationTypes.nodeExpanding + ); } private changeRowExpand(key) { diff --git a/packages/testcafe-models/dataGrid/index.ts b/packages/testcafe-models/dataGrid/index.ts index 95401009d7eb..4a9ce0e9f039 100644 --- a/packages/testcafe-models/dataGrid/index.ts +++ b/packages/testcafe-models/dataGrid/index.ts @@ -177,6 +177,19 @@ export default class DataGrid extends GridCore { return this.getHeadersContainer().find(`.${CLASS.scrollContainer}`); } + async getHeaderCellWidths(): Promise { + const cells = this.getHeaders().getHeaderRow(0).getHeaderCells(); + const count = await cells.count; + const widths: number[] = []; + + for (let i = 0; i < count; i += 1) { + const { width } = await cells.nth(i).boundingClientRect; + widths.push(Math.round(width)); + } + + return widths; + } + getRowsView(): Selector { return this.element.find(`.${this.addWidgetPrefix(CLASS.rowsView)}`); } From a207101d53cfffee689cc7c4195b443a416223bf Mon Sep 17 00:00:00 2001 From: Eldar Iusupzhanov Date: Fri, 3 Jul 2026 14:16:21 +0600 Subject: [PATCH 5/9] apply minor comment --- .../tests/common/treeList/column_auto_width.ts | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/e2e/testcafe-devextreme/tests/common/treeList/column_auto_width.ts b/e2e/testcafe-devextreme/tests/common/treeList/column_auto_width.ts index a8c9d754efa2..e485b3b09d3a 100644 --- a/e2e/testcafe-devextreme/tests/common/treeList/column_auto_width.ts +++ b/e2e/testcafe-devextreme/tests/common/treeList/column_auto_width.ts @@ -115,6 +115,7 @@ test('columns should update auto width after loadDescendants call', async (t) => await t.expect(sizeWidthAfter).eql(100); await t.expect(dateWidthAfter).eql(150); }).before(async () => createWidget('dxTreeList', { + ...treeListConfig, dataSource: { key: 'id', load: ClientFunction((loadOptions: any) => { @@ -134,18 +135,6 @@ test('columns should update auto width after loadDescendants call', async (t) => return Promise.resolve(result); }, { dependencies: { treeListData } }), }, - keyExpr: 'id', - parentIdExpr: 'parentId', - columnAutoWidth: true, - width: 500, - columns: [ - { dataField: 'name' }, - { dataField: 'size', width: 100 }, - { dataField: 'date', width: 150 }, - ], - scrolling: { - mode: 'standard', - }, remoteOperations: { filtering: true, }, From 5b3f1376b2536674df7bf7e33b8469b06d9cd775 Mon Sep 17 00:00:00 2001 From: Eldar Iusupzhanov Date: Fri, 3 Jul 2026 15:44:42 +0600 Subject: [PATCH 6/9] apply review --- .../data_controller/m_data_controller.ts | 3 --- .../m_data_source_adapter.ts | 8 ++++++-- .../m_data_source_adapter.ts | 17 +++++++++-------- 3 files changed, 15 insertions(+), 13 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 5b01ecb3ba3a..796133a4126f 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 @@ -1261,9 +1261,6 @@ export class DataController extends DataHelperMixin(modules.Controller) { this._fireChanged(change); } - /** - * @extended: tree_list/data_controller - */ protected needUpdateDimensions(operationTypes) { return operationTypes && ( operationTypes.reload || operationTypes.paging || operationTypes.groupExpanding diff --git a/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/m_data_source_adapter.ts b/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/m_data_source_adapter.ts index f95e1d6e6d02..563d03534e02 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/m_data_source_adapter.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/m_data_source_adapter.ts @@ -198,7 +198,7 @@ export default class DataSourceAdapter extends modules.Controller { private _needClearStoreDataCache() { const remoteOperations = this.remoteOperations(); - const operationTypes = calculateOperationTypes(this._lastLoadOptions || {}, {}); + const operationTypes = this._calculateOperationTypes(this._lastLoadOptions || {}, {}); const isLocalOperations = Object.keys(remoteOperations).every((operationName) => !operationTypes[operationName] || !remoteOperations[operationName]); return !isLocalOperations; @@ -333,6 +333,10 @@ export default class DataSourceAdapter extends modules.Controller { return currentOperationTypes.some((operationType) => remoteOperations[operationType]); } + protected _calculateOperationTypes(loadOptions, lastLoadOptions, isFullReload?: boolean) { + return calculateOperationTypes(loadOptions, lastLoadOptions, isFullReload); + } + /** * @extended: virtual_scrolling, TreeLists's data_source_adapter, DataGrid's m_grouping */ @@ -412,7 +416,7 @@ export default class DataSourceAdapter extends modules.Controller { const loadOptions = extend({ pageIndex: this.pageIndex(), pageSize: this.pageSize() }, options.storeLoadOptions); - const operationTypes = calculateOperationTypes(loadOptions, lastLoadOptions, isFullReload); + const operationTypes = this._calculateOperationTypes(loadOptions, lastLoadOptions, isFullReload); this._customizeRemoteOperations(options, operationTypes); diff --git a/packages/devextreme/js/__internal/grids/tree_list/data_source_adapter/m_data_source_adapter.ts b/packages/devextreme/js/__internal/grids/tree_list/data_source_adapter/m_data_source_adapter.ts index 6bf8f1ea3610..f3c8a877f4ed 100644 --- a/packages/devextreme/js/__internal/grids/tree_list/data_source_adapter/m_data_source_adapter.ts +++ b/packages/devextreme/js/__internal/grids/tree_list/data_source_adapter/m_data_source_adapter.ts @@ -264,6 +264,15 @@ export class DataSourceAdapterTreeList extends DataSourceAdapter { return gridCoreUtils.combineFilters(parentIdFilters, 'or'); } + protected override _calculateOperationTypes(loadOptions, lastLoadOptions, isFullReload?: boolean) { + const currentExpandedKeys = this.option('expandedRowKeys'); + + return { + ...super._calculateOperationTypes(loadOptions, lastLoadOptions, isFullReload), + nodeExpanding: !equalByValue(this._lastExpandedRowKeys, currentExpandedKeys), + }; + } + protected _customizeRemoteOperations(options, operationTypes) { super._customizeRemoteOperations.apply(this, arguments as any); @@ -300,14 +309,6 @@ export class DataSourceAdapterTreeList extends DataSourceAdapter { } options.expandVisibleNodes = expandVisibleNodes; - - if (!options.isCustomLoading) { - const currentExpandedKeys = this.option('expandedRowKeys'); - - if (!equalByValue(this._lastExpandedRowKeys, currentExpandedKeys)) { - operationTypes.nodeExpanding = true; - } - } } private _getParentIdsToLoad(parentIds) { From be50153b279b7505b938db6c895e6dbf7c559832 Mon Sep 17 00:00:00 2001 From: Eldar Iusupzhanov Date: Fri, 3 Jul 2026 17:45:21 +0600 Subject: [PATCH 7/9] fix test --- .../tests/common/treeList/column_auto_width.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/e2e/testcafe-devextreme/tests/common/treeList/column_auto_width.ts b/e2e/testcafe-devextreme/tests/common/treeList/column_auto_width.ts index e485b3b09d3a..94b4c9b27be1 100644 --- a/e2e/testcafe-devextreme/tests/common/treeList/column_auto_width.ts +++ b/e2e/testcafe-devextreme/tests/common/treeList/column_auto_width.ts @@ -28,6 +28,7 @@ const treeListConfig = { parentIdExpr: 'parentId', columnAutoWidth: true, width: 500, + repaintChangesOnly: true, columns: [ { dataField: 'name' }, { dataField: 'size', width: 100 }, From e753ff7f39018f2528f0ae90a11bb63f2d278068 Mon Sep 17 00:00:00 2001 From: Eldar Iusupzhanov Date: Fri, 3 Jul 2026 18:46:40 +0600 Subject: [PATCH 8/9] test: resize always --- .../grids/grid_core/views/m_grid_view.ts | 46 +++++++++---------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/packages/devextreme/js/__internal/grids/grid_core/views/m_grid_view.ts b/packages/devextreme/js/__internal/grids/grid_core/views/m_grid_view.ts index b121904058a5..794713aaf4f9 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/views/m_grid_view.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/views/m_grid_view.ts @@ -182,29 +182,29 @@ export class ResizingController extends modules.ViewController { } if (changeType === 'update') { - if (!e.changeTypes?.length) { - // @ts-expect-error - return new Deferred().resolve(null); - } - - const items = this._dataController.items(); - const isHidingNoDataPanel = items.length <= 1 && e.changeTypes[0] === 'insert'; - const isShowingNoDataPanel = items.length === 0 && e.changeTypes[0] === 'remove'; - - if (!isHidingNoDataPanel && !isShowingNoDataPanel && !e.needUpdateDimensions) { - // @ts-expect-error - const deferred = new Deferred(); - - this._waitAsyncTemplates().done(() => { - deferUpdate(() => deferRender(() => deferUpdate(() => { - this._setScrollerSpacing(); - this._rowsView.resize(); - deferred.resolve(); - }))); - }).fail(deferred.reject); - - return deferred; - } + // if (!e.changeTypes?.length) { + // // @ts-expect-error + // return new Deferred().resolve(null); + // } + + // const items = this._dataController.items(); + // const isHidingNoDataPanel = items.length <= 1 && e.changeTypes[0] === 'insert'; + // const isShowingNoDataPanel = items.length === 0 && e.changeTypes[0] === 'remove'; + + // if (!isHidingNoDataPanel && !isShowingNoDataPanel && !e.needUpdateDimensions) { + // // @ts-expect-error + // const deferred = new Deferred(); + + // this._waitAsyncTemplates().done(() => { + // deferUpdate(() => deferRender(() => deferUpdate(() => { + // this._setScrollerSpacing(); + // this._rowsView.resize(); + // deferred.resolve(); + // }))); + // }).fail(deferred.reject); + + // return deferred; + // } return this.resize(); } From 8dc3136664ca3808b665ec497ce13852c6653e56 Mon Sep 17 00:00:00 2001 From: Eldar Iusupzhanov Date: Fri, 3 Jul 2026 19:51:12 +0600 Subject: [PATCH 9/9] Revert "test: resize always" This reverts commit e753ff7f39018f2528f0ae90a11bb63f2d278068. --- .../grids/grid_core/views/m_grid_view.ts | 46 +++++++++---------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/packages/devextreme/js/__internal/grids/grid_core/views/m_grid_view.ts b/packages/devextreme/js/__internal/grids/grid_core/views/m_grid_view.ts index 794713aaf4f9..b121904058a5 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/views/m_grid_view.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/views/m_grid_view.ts @@ -182,29 +182,29 @@ export class ResizingController extends modules.ViewController { } if (changeType === 'update') { - // if (!e.changeTypes?.length) { - // // @ts-expect-error - // return new Deferred().resolve(null); - // } - - // const items = this._dataController.items(); - // const isHidingNoDataPanel = items.length <= 1 && e.changeTypes[0] === 'insert'; - // const isShowingNoDataPanel = items.length === 0 && e.changeTypes[0] === 'remove'; - - // if (!isHidingNoDataPanel && !isShowingNoDataPanel && !e.needUpdateDimensions) { - // // @ts-expect-error - // const deferred = new Deferred(); - - // this._waitAsyncTemplates().done(() => { - // deferUpdate(() => deferRender(() => deferUpdate(() => { - // this._setScrollerSpacing(); - // this._rowsView.resize(); - // deferred.resolve(); - // }))); - // }).fail(deferred.reject); - - // return deferred; - // } + if (!e.changeTypes?.length) { + // @ts-expect-error + return new Deferred().resolve(null); + } + + const items = this._dataController.items(); + const isHidingNoDataPanel = items.length <= 1 && e.changeTypes[0] === 'insert'; + const isShowingNoDataPanel = items.length === 0 && e.changeTypes[0] === 'remove'; + + if (!isHidingNoDataPanel && !isShowingNoDataPanel && !e.needUpdateDimensions) { + // @ts-expect-error + const deferred = new Deferred(); + + this._waitAsyncTemplates().done(() => { + deferUpdate(() => deferRender(() => deferUpdate(() => { + this._setScrollerSpacing(); + this._rowsView.resize(); + deferred.resolve(); + }))); + }).fail(deferred.reject); + + return deferred; + } return this.resize(); }