From 2a06b67ab86e460299dbfdf8d8c599ce68604dab Mon Sep 17 00:00:00 2001 From: AlisherAmonulloev Date: Mon, 26 Jan 2026 09:52:27 +0200 Subject: [PATCH 1/6] Fix depth calculation --- .../data_controller/m_data_controller.ts | 11 + .../exceljsParts/exceljs.pivotGrid.tests.js | 109 ++++++++++ .../pivotGrid.tests.js | 193 ++++++++++++++++++ 3 files changed, 313 insertions(+) diff --git a/packages/devextreme/js/__internal/grids/pivot_grid/data_controller/m_data_controller.ts b/packages/devextreme/js/__internal/grids/pivot_grid/data_controller/m_data_controller.ts index 2975ab9a0892..995dc75aa72d 100644 --- a/packages/devextreme/js/__internal/grids/pivot_grid/data_controller/m_data_controller.ts +++ b/packages/devextreme/js/__internal/grids/pivot_grid/data_controller/m_data_controller.ts @@ -167,6 +167,17 @@ class DataController { let depth = 0; foreachTree(headerItems, (items) => { + const item = items[0]; + let { isEmpty } = item; + + if (isEmpty?.length) { + isEmpty = item.isEmpty.filter((v) => v).length === isEmpty.length; + } + + if (item && isEmpty) { + return; + } + depth = math.max(depth, items.length); }); diff --git a/packages/devextreme/testing/tests/DevExpress.exporter/exceljsParts/exceljs.pivotGrid.tests.js b/packages/devextreme/testing/tests/DevExpress.exporter/exceljsParts/exceljs.pivotGrid.tests.js index 2307510f36b5..9e4345aaa063 100644 --- a/packages/devextreme/testing/tests/DevExpress.exporter/exceljsParts/exceljs.pivotGrid.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.exporter/exceljsParts/exceljs.pivotGrid.tests.js @@ -102,6 +102,115 @@ QUnit.module('Scenarios', moduleConfig, () => { return result; }; + QUnit.test('Export with hideEmptySummaryCells and calculateSummaryValue returning null', function(assert) { + const clock = sinon.useFakeTimers(); + const done = assert.async(); + assert.timeout(5000); + + const pivotGrid = $('#pivotGrid').dxPivotGrid({ + allowExpandAll: true, + hideEmptySummaryCells: true, + width: 600, + dataSource: { + store: [ + { region: 'North America', city: 'New York', country: 'USA', amount: 1740, date: '2013/01/06' }, + { region: 'North America', city: 'Los Angeles', country: 'USA', amount: 850, date: '2013/01/13' }, + { region: 'North America', city: 'Denver', country: 'USA', amount: 2235, date: '2013/01/07' }, + { region: 'North America', city: 'Vancouver', country: 'CAN', amount: 1965, date: '2013/01/03' }, + { region: 'North America', city: 'Edmonton', country: 'CAN', amount: 880, date: '2013/01/10' }, + { region: 'South America', city: 'Rio de Janeiro', country: 'BRA', amount: 5260, date: '2013/01/17' }, + { region: 'South America', city: 'Buenos Aires', country: 'ARG', amount: 2790, date: '2013/01/21' }, + { region: 'South America', city: 'Asuncion', country: 'PRY', amount: 3140, date: '2013/01/01' }, + { region: 'Europe', city: 'London', country: 'GBR', amount: 6175, date: '2013/01/24' }, + { region: 'North America', city: 'New York', country: 'USA', amount: 0, date: '2014/01/18' } + ], + fields: [ + { + caption: 'Region', + dataField: 'region', + area: 'row', + expanded: false + }, + { + caption: 'City', + dataField: 'city', + area: 'row', + selector(data) { + return `${data.city} (${data.country})`; + } + }, + { + dataField: 'date', + dataType: 'date', + area: 'column', + expanded: false + }, + { + caption: 'Sales', + dataField: 'amount', + dataType: 'number', + summaryType: 'sum', + area: 'data', + calculateSummaryValue(e) { + const value = e.value(); + if(value === 0) return null; + return value; + } + } + ] + } + }).dxPivotGrid('instance'); + + const dataSource = pivotGrid.getDataSource(); + + dataSource.expandAll('date'); + clock.tick(10); + + dataSource.expandAll('date', [2013]); + clock.tick(10); + + dataSource.collapseHeaderItem('column', [2013]); + clock.tick(10); + + clock.restore(); + + const expectedCells = [[ + { excelCell: { value: '', alignment: alignCenterTopWrap }, pivotCell: { alignment: 'left', colspan: 1, rowspan: 1, text: '', width: 100 } }, + { excelCell: { value: '2013', alignment: alignCenterTopWrap }, pivotCell: { area: 'column', colspan: 1, dataSourceIndex: 3, path: [2013], rowspan: 1, text: '2013', type: 'T', width: 100 } }, + { excelCell: { value: 'Grand Total', alignment: alignCenterTopWrap }, pivotCell: { area: 'column', colspan: 1, isLast: true, rowspan: 1, text: 'Grand Total', type: 'GT', width: 100 } } + ], [ + { excelCell: { value: 'Europe', alignment: alignLeftTopWrap }, pivotCell: { area: 'row', colspan: 1, dataSourceIndex: 1, isLast: true, path: ['Europe'], rowspan: 1, text: 'Europe', type: 'D' } }, + { excelCell: { value: 6175, alignment: alignRightTopWrap }, pivotCell: { area: 'data', colspan: 1, columnPath: [2013], columnType: 'T', dataIndex: 0, dataType: 'number', rowPath: ['Europe'], rowType: 'D', rowspan: 1, text: '6175' } }, + { excelCell: { value: 6175, alignment: alignRightTopWrap }, pivotCell: { area: 'data', colspan: 1, columnPath: [], columnType: 'GT', dataIndex: 0, dataType: 'number', rowPath: ['Europe'], rowType: 'D', rowspan: 1, text: '6175' } } + ], [ + { excelCell: { value: 'North America', alignment: alignLeftTopWrap }, pivotCell: { area: 'row', colspan: 1, dataSourceIndex: 2, isLast: true, path: ['North America'], rowspan: 1, text: 'North America', type: 'D' } }, + { excelCell: { value: 7670, alignment: alignRightTopWrap }, pivotCell: { area: 'data', colspan: 1, columnPath: [2013], columnType: 'T', dataIndex: 0, dataType: 'number', rowPath: ['North America'], rowType: 'D', rowspan: 1, text: '7670' } }, + { excelCell: { value: 7670, alignment: alignRightTopWrap }, pivotCell: { area: 'data', colspan: 1, columnPath: [], columnType: 'GT', dataIndex: 0, dataType: 'number', rowPath: ['North America'], rowType: 'D', rowspan: 1, text: '7670' } } + ], [ + { excelCell: { value: 'South America', alignment: alignLeftTopWrap }, pivotCell: { area: 'row', colspan: 1, dataSourceIndex: 3, isLast: true, path: ['South America'], rowspan: 1, text: 'South America', type: 'D' } }, + { excelCell: { value: 11190, alignment: alignRightTopWrap }, pivotCell: { area: 'data', colspan: 1, columnPath: [2013], columnType: 'T', dataIndex: 0, dataType: 'number', rowPath: ['South America'], rowType: 'D', rowspan: 1, text: '11190' } }, + { excelCell: { value: 11190, alignment: alignRightTopWrap }, pivotCell: { area: 'data', colspan: 1, columnPath: [], columnType: 'GT', dataIndex: 0, dataType: 'number', rowPath: ['South America'], rowType: 'D', rowspan: 1, text: '11190' } } + ], [ + { excelCell: { value: 'Grand Total', alignment: alignLeftTopWrap }, pivotCell: { area: 'row', colspan: 1, isLast: true, rowspan: 1, text: 'Grand Total', type: 'GT' } }, + { excelCell: { value: 25035, alignment: alignRightTopWrap }, pivotCell: { area: 'data', colspan: 1, columnPath: [2013], columnType: 'T', dataIndex: 0, dataType: 'number', rowPath: [], rowType: 'GT', rowspan: 1, text: '25035' } }, + { excelCell: { value: 25035, alignment: alignRightTopWrap }, pivotCell: { area: 'data', colspan: 1, columnPath: [], columnType: 'GT', dataIndex: 0, dataType: 'number', rowPath: [], rowType: 'GT', rowspan: 1, text: '25035' } } + ]]; + + helper.extendExpectedCells(expectedCells, topLeft); + + exportPivotGrid(getOptions(this, pivotGrid, undefined)).then((cellRange) => { + helper.checkRowAndColumnCount({ row: 5, column: 3 }, { row: 5, column: 3 }, topLeft); + helper.checkColumnWidths(helper.toExcelWidths(helper.getAllRealColumnWidths(pivotGrid)), topLeft.column); + helper.checkCellStyle(expectedCells); + helper.checkValues(expectedCells); + helper.checkOutlineLevel([0, 0, 0, 0, 0], topLeft.row); + helper.checkAutoFilter(false, { from: topLeft, to: topLeft }, { state: 'frozen', ySplit: topLeft.row, xSplit: topLeft.column }); + helper.checkCellRange(cellRange, { row: 5, column: 3 }, topLeft); + + done(); + }); + }); + QUnit.test('Empty pivot', function(assert) { const done = assert.async(); diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.pivotGrid/pivotGrid.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.pivotGrid/pivotGrid.tests.js index 47575050dd08..790eaa970857 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets.pivotGrid/pivotGrid.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.pivotGrid/pivotGrid.tests.js @@ -2376,6 +2376,199 @@ QUnit.module('dxPivotGrid', { assert.deepEqual(dataSource.expandAll.lastCall.args, [1], 'collapseLevel args'); }); + QUnit.test('Expand all should not mark hidden columns as expanded when hideEmptySummaryCells enabled with calculateSummaryValue returning null', function(assert) { + const pivotGrid = createPivotGrid({ + allowExpandAll: true, + hideEmptySummaryCells: true, + width: 600, + dataSource: { + store: [ + { region: 'North America', city: 'New York', country: 'USA', amount: 1740, date: '2013/01/06' }, + { region: 'North America', city: 'Los Angeles', country: 'USA', amount: 850, date: '2013/01/13' }, + { region: 'North America', city: 'Denver', country: 'USA', amount: 2235, date: '2013/01/07' }, + { region: 'North America', city: 'Vancouver', country: 'CAN', amount: 1965, date: '2013/01/03' }, + { region: 'North America', city: 'Edmonton', country: 'CAN', amount: 880, date: '2013/01/10' }, + { region: 'South America', city: 'Rio de Janeiro', country: 'BRA', amount: 5260, date: '2013/01/17' }, + { region: 'South America', city: 'Buenos Aires', country: 'ARG', amount: 2790, date: '2013/01/21' }, + { region: 'South America', city: 'Asuncion', country: 'PRY', amount: 3140, date: '2013/01/01' }, + { region: 'Europe', city: 'London', country: 'GBR', amount: 6175, date: '2013/01/24' }, + { region: 'North America', city: 'New York', country: 'USA', amount: 0, date: '2014/01/18' } + ], + fields: [ + { + caption: 'Region', + dataField: 'region', + area: 'row', + expanded: false + }, + { + caption: 'City', + dataField: 'city', + area: 'row', + selector(data) { + return `${data.city} (${data.country})`; + } + }, + { + dataField: 'date', + dataType: 'date', + area: 'column', + expanded: false + }, + { + caption: 'Sales', + dataField: 'amount', + dataType: 'number', + summaryType: 'sum', + area: 'data', + calculateSummaryValue(e) { + const value = e.value(); + if(value === 0) return null; + return value; + } + } + ] + } + }); + + const dataSource = pivotGrid.getDataSource(); + + this.clock.tick(10); + + // Check that only 2013 column and Grand Total columns are rendered + // Find and save the initial width of the Grand Total column from DOM + const $grandTotalInitial = pivotGrid.$element().find('.dx-pivotgrid-horizontal-headers .dx-grandtotal').last(); + assert.ok($grandTotalInitial.length, 'Grand Total column should exist initially in DOM'); + const initialGrandTotalWidth = getWidth($grandTotalInitial); + + // Replicate the issue sequence + dataSource.expandAll('date'); + this.clock.tick(10); + + dataSource.collapseHeaderItem('column', [2013, 1]); + this.clock.tick(10); + + dataSource.collapseHeaderItem('column', [2013]); + this.clock.tick(10); + + // Check the width of the Grand Total column after the collapse sequence + const $grandTotalAfter = pivotGrid.$element().find('.dx-pivotgrid-horizontal-headers .dx-grandtotal').last(); + assert.ok($grandTotalAfter.length, 'Grand Total column should exist after collapse in DOM'); + const finalGrandTotalWidth = getWidth($grandTotalAfter); + + // Output widths for examination + assert.ok(true, `Initial Grand Total width: ${initialGrandTotalWidth}`); + assert.ok(true, `Final Grand Total width: ${finalGrandTotalWidth}`); + + assert.strictEqual( + finalGrandTotalWidth, + initialGrandTotalWidth, + 'Grand Total column width should remain the same after collapse sequence' + ); + }); + + QUnit.test('Export should not include hidden columns when hideEmptySummaryCells enabled with calculateSummaryValue returning null after expand/collapse sequence', function(assert) { + const pivotGrid = createPivotGrid({ + allowExpandAll: true, + hideEmptySummaryCells: true, + 'export': { + enabled: true + }, + dataSource: { + store: [ + { region: 'North America', city: 'New York', country: 'USA', amount: 1740, date: '2013/01/06' }, + { region: 'North America', city: 'Los Angeles', country: 'USA', amount: 850, date: '2013/01/13' }, + { region: 'North America', city: 'Denver', country: 'USA', amount: 2235, date: '2013/01/07' }, + { region: 'North America', city: 'Vancouver', country: 'CAN', amount: 1965, date: '2013/01/03' }, + { region: 'North America', city: 'Edmonton', country: 'CAN', amount: 880, date: '2013/01/10' }, + { region: 'South America', city: 'Rio de Janeiro', country: 'BRA', amount: 5260, date: '2013/01/17' }, + { region: 'South America', city: 'Buenos Aires', country: 'ARG', amount: 2790, date: '2013/01/21' }, + { region: 'South America', city: 'Asuncion', country: 'PRY', amount: 3140, date: '2013/01/01' }, + { region: 'Europe', city: 'London', country: 'GBR', amount: 6175, date: '2013/01/24' }, + { region: 'North America', city: 'New York', country: 'USA', amount: 0, date: '2014/01/18' } + ], + fields: [ + { + caption: 'Region', + dataField: 'region', + area: 'row', + expanded: false + }, + { + caption: 'City', + dataField: 'city', + area: 'row', + selector(data) { + return `${data.city} (${data.country})`; + } + }, + { + dataField: 'date', + dataType: 'date', + area: 'column', + expanded: false + }, + { + caption: 'Sales', + dataField: 'amount', + dataType: 'number', + summaryType: 'sum', + area: 'data', + calculateSummaryValue(e) { + const value = e.value(); + if(value === 0) return null; + return value; + } + } + ] + } + }); + + const dataSource = pivotGrid.getDataSource(); + + this.clock.tick(10); + + const initialDataProvider = pivotGrid.getDataProvider(); + const initialColumnsInfo = $.extend(true, [], pivotGrid._dataController.getColumnsInfo(true)); + const initialRowsInfo = $.extend(true, [], pivotGrid._dataController.getRowsInfo(true)); + const initialCellsInfo = pivotGrid._dataController.getCellsInfo(true); + const initialItems = pivotGrid._getAllItems(initialColumnsInfo, initialRowsInfo, initialCellsInfo); + initialDataProvider.ready(); + this.clock.tick(10); + + const initialColumnCount = initialItems[0].length; + assert.ok(true, `Initial export column count: ${initialColumnCount}`); + + dataSource.expandAll('date'); + this.clock.tick(10); + + dataSource.collapseHeaderItem('column', [2013]); + this.clock.tick(10); + + const finalDataProvider = pivotGrid.getDataProvider(); + const finalColumnsInfo = $.extend(true, [], pivotGrid._dataController.getColumnsInfo(true)); + const finalRowsInfo = $.extend(true, [], pivotGrid._dataController.getRowsInfo(true)); + const finalCellsInfo = pivotGrid._dataController.getCellsInfo(true); + const finalItems = pivotGrid._getAllItems(finalColumnsInfo, finalRowsInfo, finalCellsInfo); + finalDataProvider.ready(); + this.clock.tick(10); + + const finalColumnCount = finalItems[0].length; + assert.ok(true, `Final export column count: ${finalColumnCount}`); + + assert.strictEqual( + finalColumnCount, + initialColumnCount, + 'Export column count should remain the same after collapse sequence (hidden columns should not be exported)' + ); + + assert.strictEqual( + finalDataProvider.getColumns().length, + initialDataProvider.getColumns().length, + 'Data provider column count should remain consistent' + ); + }); + QUnit.test('pivot grid render', function(assert) { const pivotGrid = createPivotGrid({}); const testElement = $('#pivotGrid'); From 566f86fec650cb2b333b8ec13552fbcad75397f0 Mon Sep 17 00:00:00 2001 From: AlisherAmonulloev Date: Sat, 31 Jan 2026 12:20:21 +0200 Subject: [PATCH 2/6] Add more tests, refactor code --- .../data_controller/m_data_controller.ts | 16 +-- .../exceljsParts/exceljs.pivotGrid.tests.js | 113 +++++++++++++++++- .../dataController.tests.js | 4 - .../pivotGrid.tests.js | 93 ++++++++++++-- 4 files changed, 206 insertions(+), 20 deletions(-) diff --git a/packages/devextreme/js/__internal/grids/pivot_grid/data_controller/m_data_controller.ts b/packages/devextreme/js/__internal/grids/pivot_grid/data_controller/m_data_controller.ts index 995dc75aa72d..e5daa6992348 100644 --- a/packages/devextreme/js/__internal/grids/pivot_grid/data_controller/m_data_controller.ts +++ b/packages/devextreme/js/__internal/grids/pivot_grid/data_controller/m_data_controller.ts @@ -163,18 +163,20 @@ class DataController { return d; }; + _isHeaderItemEmpty = (item) => { + if (!item) return false; + const { isEmpty } = item; + if (isEmpty === true) return true; + if (isEmpty === false) return false; + return Array.isArray(isEmpty) && isEmpty.length > 0 && isEmpty.every(Boolean); + }; + _getHeaderItemsDepth = (headerItems) => { let depth = 0; foreachTree(headerItems, (items) => { const item = items[0]; - let { isEmpty } = item; - - if (isEmpty?.length) { - isEmpty = item.isEmpty.filter((v) => v).length === isEmpty.length; - } - - if (item && isEmpty) { + if (this._isHeaderItemEmpty(item)) { return; } diff --git a/packages/devextreme/testing/tests/DevExpress.exporter/exceljsParts/exceljs.pivotGrid.tests.js b/packages/devextreme/testing/tests/DevExpress.exporter/exceljsParts/exceljs.pivotGrid.tests.js index 9e4345aaa063..85c2876575c2 100644 --- a/packages/devextreme/testing/tests/DevExpress.exporter/exceljsParts/exceljs.pivotGrid.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.exporter/exceljsParts/exceljs.pivotGrid.tests.js @@ -105,7 +105,6 @@ QUnit.module('Scenarios', moduleConfig, () => { QUnit.test('Export with hideEmptySummaryCells and calculateSummaryValue returning null', function(assert) { const clock = sinon.useFakeTimers(); const done = assert.async(); - assert.timeout(5000); const pivotGrid = $('#pivotGrid').dxPivotGrid({ allowExpandAll: true, @@ -211,6 +210,118 @@ QUnit.module('Scenarios', moduleConfig, () => { }); }); + QUnit.test('Export with hideEmptySummaryCells and calculateSummaryValue returning null (empty row)', function(assert) { + const clock = sinon.useFakeTimers(); + const done = assert.async(); + + const pivotGrid = $('#pivotGrid').dxPivotGrid({ + allowExpandAll: true, + hideEmptySummaryCells: true, + width: 600, + dataSource: { + store: [ + { region: 'North America', city: 'New York', country: 'USA', amount: 1740, date: '2013/01/06' }, + { region: 'North America', city: 'Los Angeles', country: 'USA', amount: 850, date: '2013/01/13' }, + { region: 'North America', city: 'Denver', country: 'USA', amount: 2235, date: '2013/01/07' }, + { region: 'North America', city: 'Vancouver', country: 'CAN', amount: 1965, date: '2013/01/03' }, + { region: 'North America', city: 'Edmonton', country: 'CAN', amount: 880, date: '2013/01/10' }, + { region: 'South America', city: 'Rio de Janeiro', country: 'BRA', amount: 5260, date: '2013/01/17' }, + { region: 'South America', city: 'Buenos Aires', country: 'ARG', amount: 2790, date: '2013/01/21' }, + { region: 'South America', city: 'Asuncion', country: 'PRY', amount: 3140, date: '2013/01/01' }, + { region: 'Europe', city: 'London', country: 'GBR', amount: 6175, date: '2013/01/24' }, + { region: 'Oceania', city: 'Sydney', country: 'AUS', amount: 0, date: '2013/01/01' } + ], + fields: [ + { + caption: 'Region', + dataField: 'region', + area: 'row', + expanded: false + }, + { + caption: 'City', + dataField: 'city', + area: 'row', + selector(data) { + return `${data.city} (${data.country})`; + } + }, + { + dataField: 'date', + dataType: 'date', + area: 'column', + expanded: false + }, + { + caption: 'Sales', + dataField: 'amount', + dataType: 'number', + summaryType: 'sum', + area: 'data', + calculateSummaryValue(e) { + const value = e.value(); + if(value === 0) return null; + return value; + } + } + ] + } + }).dxPivotGrid('instance'); + + const dataSource = pivotGrid.getDataSource(); + + dataSource.expandAll('region'); + clock.tick(10); + + + dataSource.collapseHeaderItem('row', ['Europe']); + clock.tick(10); + + dataSource.collapseHeaderItem('row', ['North America']); + clock.tick(10); + + dataSource.collapseHeaderItem('row', ['South America']); + clock.tick(10); + + clock.restore(); + + const expectedCells = [[ + { excelCell: { value: '', alignment: alignCenterTopWrap }, pivotCell: { alignment: 'left', colspan: 1, rowspan: 1, text: '', width: 100 } }, + { excelCell: { value: '2013', alignment: alignCenterTopWrap }, pivotCell: { area: 'column', colspan: 1, dataSourceIndex: 3, path: [2013], rowspan: 1, text: '2013', type: 'T', width: 100 } }, + { excelCell: { value: 'Grand Total', alignment: alignCenterTopWrap }, pivotCell: { area: 'column', colspan: 1, isLast: true, rowspan: 1, text: 'Grand Total', type: 'GT', width: 100 } } + ], [ + { excelCell: { value: 'Europe', alignment: alignLeftTopWrap }, pivotCell: { area: 'row', colspan: 1, dataSourceIndex: 1, isLast: true, path: ['Europe'], rowspan: 1, text: 'Europe', type: 'D' } }, + { excelCell: { value: 6175, alignment: alignRightTopWrap }, pivotCell: { area: 'data', colspan: 1, columnPath: [2013], columnType: 'T', dataIndex: 0, dataType: 'number', rowPath: ['Europe'], rowType: 'D', rowspan: 1, text: '6175' } }, + { excelCell: { value: 6175, alignment: alignRightTopWrap }, pivotCell: { area: 'data', colspan: 1, columnPath: [], columnType: 'GT', dataIndex: 0, dataType: 'number', rowPath: ['Europe'], rowType: 'D', rowspan: 1, text: '6175' } } + ], [ + { excelCell: { value: 'North America', alignment: alignLeftTopWrap }, pivotCell: { area: 'row', colspan: 1, dataSourceIndex: 2, isLast: true, path: ['North America'], rowspan: 1, text: 'North America', type: 'D' } }, + { excelCell: { value: 7670, alignment: alignRightTopWrap }, pivotCell: { area: 'data', colspan: 1, columnPath: [2013], columnType: 'T', dataIndex: 0, dataType: 'number', rowPath: ['North America'], rowType: 'D', rowspan: 1, text: '7670' } }, + { excelCell: { value: 7670, alignment: alignRightTopWrap }, pivotCell: { area: 'data', colspan: 1, columnPath: [], columnType: 'GT', dataIndex: 0, dataType: 'number', rowPath: ['North America'], rowType: 'D', rowspan: 1, text: '7670' } } + ], [ + { excelCell: { value: 'South America', alignment: alignLeftTopWrap }, pivotCell: { area: 'row', colspan: 1, dataSourceIndex: 3, isLast: true, path: ['South America'], rowspan: 1, text: 'South America', type: 'D' } }, + { excelCell: { value: 11190, alignment: alignRightTopWrap }, pivotCell: { area: 'data', colspan: 1, columnPath: [2013], columnType: 'T', dataIndex: 0, dataType: 'number', rowPath: ['South America'], rowType: 'D', rowspan: 1, text: '11190' } }, + { excelCell: { value: 11190, alignment: alignRightTopWrap }, pivotCell: { area: 'data', colspan: 1, columnPath: [], columnType: 'GT', dataIndex: 0, dataType: 'number', rowPath: ['South America'], rowType: 'D', rowspan: 1, text: '11190' } } + ], [ + { excelCell: { value: 'Grand Total', alignment: alignLeftTopWrap }, pivotCell: { area: 'row', colspan: 1, isLast: true, rowspan: 1, text: 'Grand Total', type: 'GT' } }, + { excelCell: { value: 25035, alignment: alignRightTopWrap }, pivotCell: { area: 'data', colspan: 1, columnPath: [2013], columnType: 'T', dataIndex: 0, dataType: 'number', rowPath: [], rowType: 'GT', rowspan: 1, text: '25035' } }, + { excelCell: { value: 25035, alignment: alignRightTopWrap }, pivotCell: { area: 'data', colspan: 1, columnPath: [], columnType: 'GT', dataIndex: 0, dataType: 'number', rowPath: [], rowType: 'GT', rowspan: 1, text: '25035' } } + ]]; + + helper.extendExpectedCells(expectedCells, topLeft); + + exportPivotGrid(getOptions(this, pivotGrid, undefined)).then((cellRange) => { + helper.checkRowAndColumnCount({ row: 5, column: 3 }, { row: 5, column: 3 }, topLeft); + helper.checkColumnWidths(helper.toExcelWidths(helper.getAllRealColumnWidths(pivotGrid)), topLeft.column); + helper.checkCellStyle(expectedCells); + helper.checkValues(expectedCells); + helper.checkOutlineLevel([0, 0, 0, 0, 0], topLeft.row); + helper.checkAutoFilter(false, { from: topLeft, to: topLeft }, { state: 'frozen', ySplit: topLeft.row, xSplit: topLeft.column }); + helper.checkCellRange(cellRange, { row: 5, column: 3 }, topLeft); + + done(); + }); + }); + QUnit.test('Empty pivot', function(assert) { const done = assert.async(); diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.pivotGrid/dataController.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.pivotGrid/dataController.tests.js index b72f8f6da16e..7f87eac29b6f 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets.pivotGrid/dataController.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.pivotGrid/dataController.tests.js @@ -3194,7 +3194,6 @@ QUnit.module('dxPivotGrid DataController', moduleConfig, () => { assert.deepEqual(dataController.getRowsInfo(), [ [ { - 'colspan': 2, 'dataSourceIndex': 0, 'isLast': true, 'path': [ @@ -3206,7 +3205,6 @@ QUnit.module('dxPivotGrid DataController', moduleConfig, () => { ], [ { - 'colspan': 2, 'dataSourceIndex': 3, 'expanded': false, 'isLast': true, @@ -3219,9 +3217,7 @@ QUnit.module('dxPivotGrid DataController', moduleConfig, () => { ], [ { - 'colspan': 2, 'isLast': true, - 'text': undefined, 'type': 'GT' } ] diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.pivotGrid/pivotGrid.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.pivotGrid/pivotGrid.tests.js index 790eaa970857..0f83e7d10f8a 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets.pivotGrid/pivotGrid.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.pivotGrid/pivotGrid.tests.js @@ -2435,28 +2435,20 @@ QUnit.module('dxPivotGrid', { this.clock.tick(10); - // Check that only 2013 column and Grand Total columns are rendered - // Find and save the initial width of the Grand Total column from DOM const $grandTotalInitial = pivotGrid.$element().find('.dx-pivotgrid-horizontal-headers .dx-grandtotal').last(); assert.ok($grandTotalInitial.length, 'Grand Total column should exist initially in DOM'); const initialGrandTotalWidth = getWidth($grandTotalInitial); - // Replicate the issue sequence dataSource.expandAll('date'); this.clock.tick(10); - dataSource.collapseHeaderItem('column', [2013, 1]); - this.clock.tick(10); - dataSource.collapseHeaderItem('column', [2013]); this.clock.tick(10); - // Check the width of the Grand Total column after the collapse sequence const $grandTotalAfter = pivotGrid.$element().find('.dx-pivotgrid-horizontal-headers .dx-grandtotal').last(); assert.ok($grandTotalAfter.length, 'Grand Total column should exist after collapse in DOM'); const finalGrandTotalWidth = getWidth($grandTotalAfter); - // Output widths for examination assert.ok(true, `Initial Grand Total width: ${initialGrandTotalWidth}`); assert.ok(true, `Final Grand Total width: ${finalGrandTotalWidth}`); @@ -2467,6 +2459,91 @@ QUnit.module('dxPivotGrid', { ); }); + QUnit.test('Expand all should not mark hidden rows as expanded when hideEmptySummaryCells enabled with calculateSummaryValue returning null', function(assert) { + const pivotGrid = createPivotGrid({ + allowExpandAll: true, + hideEmptySummaryCells: true, + width: 600, + dataSource: { + store: [ + { region: 'North America', city: 'New York', country: 'USA', amount: 1740, date: '2013/01/06' }, + { region: 'North America', city: 'Los Angeles', country: 'USA', amount: 850, date: '2013/01/13' }, + { region: 'South America', city: 'Rio de Janeiro', country: 'BRA', amount: 5260, date: '2013/01/17' }, + { region: 'South America', city: 'Buenos Aires', country: 'ARG', amount: 2790, date: '2013/01/21' }, + { region: 'Europe', city: 'London', country: 'GBR', amount: 6175, date: '2013/01/24' }, + { region: 'Oceania', city: 'Sydney', country: 'AUS', amount: 0, date: '2013/01/01' } + ], + fields: [ + { + caption: 'Region', + dataField: 'region', + area: 'row', + expanded: false + }, + { + caption: 'City', + dataField: 'city', + area: 'row', + selector(data) { + return `${data.city} (${data.country})`; + } + }, + { + dataField: 'date', + dataType: 'date', + area: 'column', + expanded: false + }, + { + caption: 'Sales', + dataField: 'amount', + dataType: 'number', + summaryType: 'sum', + area: 'data', + calculateSummaryValue(e) { + const value = e.value(); + if(value === 0) return null; + return value; + } + } + ] + } + }); + + const dataSource = pivotGrid.getDataSource(); + + this.clock.tick(10); + + const $grandTotalInitial = pivotGrid.$element().find('.dx-pivotgrid-vertical-headers .dx-grandtotal').last(); + assert.ok($grandTotalInitial.length, 'Grand Total row should exist initially in DOM'); + const initialGrandTotalHeight = getHeight($grandTotalInitial); + + dataSource.expandAll('region'); + this.clock.tick(10); + + dataSource.collapseHeaderItem('row', ['Europe']); + this.clock.tick(10); + + dataSource.collapseHeaderItem('row', ['North America']); + this.clock.tick(10); + + dataSource.collapseHeaderItem('row', ['South America']); + this.clock.tick(10); + + const $grandTotalAfter = pivotGrid.$element().find('.dx-pivotgrid-vertical-headers .dx-grandtotal').last(); + assert.ok($grandTotalAfter.length, 'Grand Total row should exist after collapse in DOM'); + const finalGrandTotalHeight = getHeight($grandTotalAfter); + + assert.ok(true, `Initial Grand Total row height: ${initialGrandTotalHeight}`); + assert.ok(true, `Final Grand Total row height: ${finalGrandTotalHeight}`); + + assert.strictEqual( + finalGrandTotalHeight, + initialGrandTotalHeight, + 'Grand Total row height should remain the same after collapse sequence' + ); + }); + QUnit.test('Export should not include hidden columns when hideEmptySummaryCells enabled with calculateSummaryValue returning null after expand/collapse sequence', function(assert) { const pivotGrid = createPivotGrid({ allowExpandAll: true, From daf7c5558364810c47bcf8d131f634203075eb9c Mon Sep 17 00:00:00 2001 From: AlisherAmonulloev Date: Sat, 31 Jan 2026 13:05:39 +0200 Subject: [PATCH 3/6] Fix test --- .../DevExpress.ui.widgets.pivotGrid/dataController.tests.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.pivotGrid/dataController.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.pivotGrid/dataController.tests.js index 7f87eac29b6f..1f3351e5bc5f 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets.pivotGrid/dataController.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.pivotGrid/dataController.tests.js @@ -3191,6 +3191,8 @@ QUnit.module('dxPivotGrid DataController', moduleConfig, () => { ] ], 'cells info'); + // With hideEmptySummaryCells, empty child items (P1, P2 under A) are not counted in depth, + // so row depth is 1 and header info has no colspan (breadth 1). assert.deepEqual(dataController.getRowsInfo(), [ [ { From 1316ed51b70d2948c099e337415fbdcbdee79e97 Mon Sep 17 00:00:00 2001 From: AlisherAmonulloev Date: Sat, 31 Jan 2026 13:19:57 +0200 Subject: [PATCH 4/6] Fix test (attempt 2) --- .../dataController.tests.js | 24 +++++++++---------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.pivotGrid/dataController.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.pivotGrid/dataController.tests.js index 1f3351e5bc5f..71e69a90852c 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets.pivotGrid/dataController.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.pivotGrid/dataController.tests.js @@ -3191,36 +3191,34 @@ QUnit.module('dxPivotGrid DataController', moduleConfig, () => { ] ], 'cells info'); - // With hideEmptySummaryCells, empty child items (P1, P2 under A) are not counted in depth, - // so row depth is 1 and header info has no colspan (breadth 1). assert.deepEqual(dataController.getRowsInfo(), [ [ { - 'dataSourceIndex': 0, - 'isLast': true, + 'type': 'T', + 'text': '', 'path': [ 'A' ], - 'text': '', - 'type': 'T' + 'isLast': true, + 'dataSourceIndex': 0 } ], [ { - 'dataSourceIndex': 3, - 'expanded': false, - 'isLast': true, + 'type': 'D', + 'text': 'C', 'path': [ 'C' ], - 'text': 'C', - 'type': 'D' + 'isLast': true, + 'expanded': false, + 'dataSourceIndex': 3 } ], [ { - 'isLast': true, - 'type': 'GT' + 'type': 'GT', + 'isLast': true } ] ]); From e5d0e66f0605ae31eb65d8666e3ba05a90b017f2 Mon Sep 17 00:00:00 2001 From: AlisherAmonulloev Date: Sat, 31 Jan 2026 20:10:45 +0200 Subject: [PATCH 5/6] Update dataController.tests.js --- .../DevExpress.ui.widgets.pivotGrid/dataController.tests.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.pivotGrid/dataController.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.pivotGrid/dataController.tests.js index 71e69a90852c..7f86f1a72128 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets.pivotGrid/dataController.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.pivotGrid/dataController.tests.js @@ -3218,7 +3218,8 @@ QUnit.module('dxPivotGrid DataController', moduleConfig, () => { [ { 'type': 'GT', - 'isLast': true + 'isLast': true, + 'isEmpty': undefined } ] ]); From f0d9f9cfb289b5d66158fc63539f097f73924f7f Mon Sep 17 00:00:00 2001 From: AlisherAmonulloev Date: Sat, 31 Jan 2026 20:42:23 +0200 Subject: [PATCH 6/6] Update dataController.tests.js --- .../DevExpress.ui.widgets.pivotGrid/dataController.tests.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.pivotGrid/dataController.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.pivotGrid/dataController.tests.js index 7f86f1a72128..b52ef3b5aafd 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets.pivotGrid/dataController.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.pivotGrid/dataController.tests.js @@ -3218,8 +3218,8 @@ QUnit.module('dxPivotGrid DataController', moduleConfig, () => { [ { 'type': 'GT', - 'isLast': true, - 'isEmpty': undefined + 'text': undefined, + 'isLast': true } ] ]);