Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Comment on lines +171 to +177
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isEmpty can be an array (set in summary display mode). With the current logic, an empty array ([]) will skip the isEmpty?.length branch, remain truthy, and then be treated as “empty” later, causing this callback to return and potentially undercount header depth. Consider normalizing to an explicit boolean, e.g. const isFullyEmpty = Array.isArray(item.isEmpty) ? item.isEmpty.length > 0 && item.isEmpty.every(Boolean) : !!item.isEmpty; and use that for the emptiness check.

Suggested change
let { isEmpty } = item;
if (isEmpty?.length) {
isEmpty = item.isEmpty.filter((v) => v).length === isEmpty.length;
}
if (item && isEmpty) {
const isEmptyValue = item?.isEmpty;
const isFullyEmpty = Array.isArray(isEmptyValue)
? isEmptyValue.length > 0 && isEmptyValue.every(Boolean)
: !!isEmptyValue;
if (item && isFullyEmpty) {

Copilot uses AI. Check for mistakes.
return;
}

depth = math.max(depth, items.length);
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test name mentions “mark hidden columns as expanded”, but the assertions validate Grand Total column width stability. Please align the test name (or add an assertion that actually checks expanded state / rendered columns) so the intent and coverage are clear.

Suggested change
QUnit.test('Expand all should not mark hidden columns as expanded when hideEmptySummaryCells enabled with calculateSummaryValue returning null', function(assert) {
QUnit.test('Expand all should not change Grand Total column width when hideEmptySummaryCells enabled with calculateSummaryValue returning null', function(assert) {

Copilot uses AI. Check for mistakes.
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}`);

Comment on lines +2459 to +2462
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These assert.ok(true, ...) calls act as debug logging and add unconditional passing assertions, which can hide real failures and add noise to test output. Please remove them (or replace with a meaningful assertion on the expected values).

Suggested change
// Output widths for examination
assert.ok(true, `Initial Grand Total width: ${initialGrandTotalWidth}`);
assert.ok(true, `Final Grand Total width: ${finalGrandTotalWidth}`);

Copilot uses AI. Check for mistakes.
assert.strictEqual(
finalGrandTotalWidth,
initialGrandTotalWidth,
Comment on lines +2463 to +2465
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comparing DOM widths with assert.strictEqual is likely to be flaky across browsers/font rendering/subpixel rounding. Since this file already uses assert.roughEqual for width-related checks, consider switching this to a tolerant comparison (e.g., roughEqual with a small epsilon) to reduce intermittent failures.

Suggested change
assert.strictEqual(
finalGrandTotalWidth,
initialGrandTotalWidth,
assert.roughEqual(
finalGrandTotalWidth,
initialGrandTotalWidth,
1,

Copilot uses AI. Check for mistakes.
'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}`);
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

assert.ok(true, ...) here is unconditional and acts like debug logging rather than validating behavior. Please remove it or replace it with an assertion that checks an expected property (e.g., an explicit expected column count).

Suggested change
assert.ok(true, `Initial export column count: ${initialColumnCount}`);
assert.ok(initialColumnCount > 0, `Initial export column count should be greater than 0, actual: ${initialColumnCount}`);

Copilot uses AI. Check for mistakes.

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}`);

Comment on lines +2556 to +2558
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

assert.ok(true, ...) here is unconditional and acts like debug logging rather than validating behavior. Please remove it or replace it with an assertion that checks an expected property (e.g., an explicit expected column count).

Copilot uses AI. Check for mistakes.
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');
Expand Down
Loading