diff --git a/.vscode/settings.json b/.vscode/settings.json index 2180436686d..5b1bd29d795 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -79,9 +79,10 @@ "cSpell.files": ["**/*", "!**/{package,settings,project,tsconfig}.json"], "git.detectSubmodulesLimit": 20, "files.exclude": { - "**/vitest.config.*.timestamp*": true, + "**/.angular/": true, + "**/.DS_Store": true, "**/.git": true, - "**/.DS_Store": true + "**/vitest.config.*.timestamp*": true }, "[scss]": { "editor.detectIndentation": false, diff --git a/documentation/ag-grid-docs/src/content/api-documentation/grid-options/properties.json b/documentation/ag-grid-docs/src/content/api-documentation/grid-options/properties.json index b0c2fa79988..a74b9e01619 100644 --- a/documentation/ag-grid-docs/src/content/api-documentation/grid-options/properties.json +++ b/documentation/ag-grid-docs/src/content/api-documentation/grid-options/properties.json @@ -1406,6 +1406,12 @@ "url": "./grouping-multiple-group-columns/#hiding-expanded-parent-rows" } }, + "groupHideColumnsUntilExpanded": { + "more": { + "name": "Hiding Group Columns Until Expanded", + "url": "./grouping-multiple-group-columns/#hiding-group-columns-until-expanded" + } + }, "groupHideParentOfSingleChild": { "more": { "name": "Remove Single Children", diff --git a/documentation/ag-grid-docs/src/content/docs/grouping-multiple-group-columns/_examples/hide-columns-until-expanded/example.spec.ts b/documentation/ag-grid-docs/src/content/docs/grouping-multiple-group-columns/_examples/hide-columns-until-expanded/example.spec.ts new file mode 100644 index 00000000000..45cb4627e93 --- /dev/null +++ b/documentation/ag-grid-docs/src/content/docs/grouping-multiple-group-columns/_examples/hide-columns-until-expanded/example.spec.ts @@ -0,0 +1,34 @@ +import { expect, test, waitForGridContent } from '@utils/grid/test-utils'; + +import { GROUP_AUTO_COLUMN_ID } from 'ag-grid-community'; + +test.agExample(import.meta, () => { + test.eachFramework('Example', async ({ page, agIdFor }) => { + const countryColId = `${GROUP_AUTO_COLUMN_ID}-country`; + const yearColId = `${GROUP_AUTO_COLUMN_ID}-year`; + const sportColId = `${GROUP_AUTO_COLUMN_ID}-sport`; + await waitForGridContent(page); + + // Initially only the country group column is visible + await expect(agIdFor.headerCell(countryColId)).toBeVisible(); + await expect(agIdFor.headerCell(yearColId)).not.toBeVisible(); + await expect(agIdFor.headerCell(sportColId)).not.toBeVisible(); + + // Expand a country group - reveals the year group column + await agIdFor.groupContracted('row-group-country-Canada', countryColId).first().click(); + await expect(agIdFor.headerCell(yearColId)).toBeVisible(); + await expect(agIdFor.headerCell(sportColId)).not.toBeVisible(); + + // Expand a year group - reveals the sport group column + await agIdFor.groupContracted('row-group-country-Canada-year-2006', yearColId).first().click(); + await expect(agIdFor.headerCell(sportColId)).toBeVisible(); + + // Collapse the year group - sport column hides again + await agIdFor.groupExpanded('row-group-country-Canada-year-2006', yearColId).first().click(); + await expect(agIdFor.headerCell(sportColId)).not.toBeVisible(); + + // Collapse the country group - year column hides again + await agIdFor.groupExpanded('row-group-country-Canada', countryColId).first().click(); + await expect(agIdFor.headerCell(yearColId)).not.toBeVisible(); + }); +}); diff --git a/documentation/ag-grid-docs/src/content/docs/grouping-multiple-group-columns/_examples/hide-columns-until-expanded/index.html b/documentation/ag-grid-docs/src/content/docs/grouping-multiple-group-columns/_examples/hide-columns-until-expanded/index.html new file mode 100644 index 00000000000..378fad58398 --- /dev/null +++ b/documentation/ag-grid-docs/src/content/docs/grouping-multiple-group-columns/_examples/hide-columns-until-expanded/index.html @@ -0,0 +1 @@ +
diff --git a/documentation/ag-grid-docs/src/content/docs/grouping-multiple-group-columns/_examples/hide-columns-until-expanded/main.ts b/documentation/ag-grid-docs/src/content/docs/grouping-multiple-group-columns/_examples/hide-columns-until-expanded/main.ts new file mode 100644 index 00000000000..f5affebfcd3 --- /dev/null +++ b/documentation/ag-grid-docs/src/content/docs/grouping-multiple-group-columns/_examples/hide-columns-until-expanded/main.ts @@ -0,0 +1,40 @@ +import type { GridApi, GridOptions } from 'ag-grid-community'; +import { ClientSideRowModelModule, ModuleRegistry, ValidationModule, createGrid } from 'ag-grid-community'; +import { RowGroupingModule } from 'ag-grid-enterprise'; + +ModuleRegistry.registerModules([ + ClientSideRowModelModule, + RowGroupingModule, + ...(process.env.NODE_ENV !== 'production' ? [ValidationModule] : []), +]); + +let gridApi: GridApi; + +const gridOptions: GridOptions = { + columnDefs: [ + { field: 'country', rowGroup: true, hide: true }, + { field: 'year', rowGroup: true, hide: true }, + { field: 'sport', rowGroup: true, hide: true }, + { field: 'athlete', minWidth: 200, aggFunc: 'count' }, + { field: 'total', aggFunc: 'sum' }, + ], + defaultColDef: { + minWidth: 150, + }, + autoGroupColumnDef: { + minWidth: 200, + }, + groupDisplayType: 'multipleColumns', + groupHideColumnsUntilExpanded: true, + groupDefaultExpanded: 0, +}; + +// setup the grid after the page has finished loading +document.addEventListener('DOMContentLoaded', function () { + const gridDiv = document.querySelector('#myGrid')!; + gridApi = createGrid(gridDiv, gridOptions); + + fetch('https://www.ag-grid.com/example-assets/olympic-winners.json') + .then((response) => response.json()) + .then((data: IOlympicData[]) => gridApi!.setGridOption('rowData', data)); +}); diff --git a/documentation/ag-grid-docs/src/content/docs/grouping-multiple-group-columns/_examples/hide-open-parents/main.ts b/documentation/ag-grid-docs/src/content/docs/grouping-multiple-group-columns/_examples/hide-open-parents/main.ts index eb688279b56..933a4ed5d19 100644 --- a/documentation/ag-grid-docs/src/content/docs/grouping-multiple-group-columns/_examples/hide-open-parents/main.ts +++ b/documentation/ag-grid-docs/src/content/docs/grouping-multiple-group-columns/_examples/hide-open-parents/main.ts @@ -1,4 +1,4 @@ -import type { GridApi, GridOptions, ValueGetterParams } from 'ag-grid-community'; +import type { GridApi, GridOptions } from 'ag-grid-community'; import { ClientSideRowModelModule, ModuleRegistry, ValidationModule, createGrid } from 'ag-grid-community'; import { ColumnMenuModule, diff --git a/documentation/ag-grid-docs/src/content/docs/grouping-multiple-group-columns/index.mdoc b/documentation/ag-grid-docs/src/content/docs/grouping-multiple-group-columns/index.mdoc index f42611d79a9..91983694572 100644 --- a/documentation/ag-grid-docs/src/content/docs/grouping-multiple-group-columns/index.mdoc +++ b/documentation/ag-grid-docs/src/content/docs/grouping-multiple-group-columns/index.mdoc @@ -188,3 +188,23 @@ const gridOptions = { groupHideOpenParents: true, } ``` + +## Hiding Group Columns Until Expanded + +By default all group columns are visible at all times. Setting `groupHideColumnsUntilExpanded` to `true` hides group columns +for levels that have not yet been reached through expansion. Only the top-level group column is initially visible; each +subsequent level's column becomes visible when at least one group at the preceding level is expanded. + +Try expanding a Country row to reveal the Year group column, then expand a Year row to reveal the Sport group column. +Collapsing all rows at a given level will hide the corresponding group column again. + +{% gridExampleRunner title="Hide Group Columns Until Expanded" name="hide-columns-until-expanded" /%} + +The example above demonstrates the following configuration: + +```{% frameworkTransform=true %} +const gridOptions = { + groupDisplayType: 'multipleColumns', + groupHideColumnsUntilExpanded: true, +} +``` diff --git a/packages/ag-grid-angular/projects/ag-grid-angular/src/lib/ag-grid-angular.component.ts b/packages/ag-grid-angular/projects/ag-grid-angular/src/lib/ag-grid-angular.component.ts index 64b10d5dbec..516ed88c07b 100644 --- a/packages/ag-grid-angular/projects/ag-grid-angular/src/lib/ag-grid-angular.component.ts +++ b/packages/ag-grid-angular/projects/ag-grid-angular/src/lib/ag-grid-angular.component.ts @@ -1421,6 +1421,14 @@ export class AgGridAngular = ColDef { const isAutoGroupCol = isColumnGroupAutoCol(col); if (showAutoGroupAndValuesOnly) { const isValueCol = valueColumns?.includes(col); return ( - isAutoGroupCol || isValueCol || + (isAutoGroupCol && (!hideEmptyAutoColGroups || col.isVisible())) || (showSelectionColumn && isColumnSelectionCol(col)) || (showRowNumbers && isRowNumberCol(col)) ); } else { - // keep col if a) it's auto-group or b) it's visible - return isAutoGroupCol || col.isVisible(); + // keep col if a) it's auto-group (and feature not managing visibility) or b) it's visible + return (isAutoGroupCol && !hideEmptyAutoColGroups) || col.isVisible(); } }); diff --git a/packages/ag-grid-community/src/entities/gridOptions.ts b/packages/ag-grid-community/src/entities/gridOptions.ts index 73d505ef795..32ed8cce14e 100644 --- a/packages/ag-grid-community/src/entities/gridOptions.ts +++ b/packages/ag-grid-community/src/entities/gridOptions.ts @@ -1495,6 +1495,15 @@ export interface GridOptions { * @agModule `RowGroupingModule` */ groupHideOpenParents?: boolean; + /** + * When using `groupDisplayType='multipleColumns'` or `groupHideOpenParents=true`, hides group columns for levels + * that have not yet been expanded. Only the top-level group column is initially + * visible; each subsequent level becomes visible when at least one group at the + * preceding level is expanded. + * @default false + * @agModule `RowGroupingModule` + */ + groupHideColumnsUntilExpanded?: boolean; /** * Set to `true` to prevent the grid from creating a '(Blanks)' group for nodes which do not belong to a group, and display the unbalanced nodes alongside group nodes. * @default false diff --git a/packages/ag-grid-community/src/gridOptionsDefault.ts b/packages/ag-grid-community/src/gridOptionsDefault.ts index 7c2272c3ab4..baff9ef4c6c 100644 --- a/packages/ag-grid-community/src/gridOptionsDefault.ts +++ b/packages/ag-grid-community/src/gridOptionsDefault.ts @@ -117,6 +117,7 @@ export const GRID_OPTION_DEFAULTS = { groupRemoveSingleChildren: false, groupRemoveLowestSingleChildren: false, groupHideOpenParents: false, + groupHideColumnsUntilExpanded: false, groupAllowUnbalanced: false, rowGroupPanelShow: 'never', suppressMakeColumnVisibleAfterUnGroup: false, diff --git a/packages/ag-grid-community/src/gridOptionsUtils.ts b/packages/ag-grid-community/src/gridOptionsUtils.ts index fba01e13446..70404c02af8 100644 --- a/packages/ag-grid-community/src/gridOptionsUtils.ts +++ b/packages/ag-grid-community/src/gridOptionsUtils.ts @@ -236,6 +236,10 @@ export function _isGroupMultiAutoColumn(gos: GridOptionsService) { return gos.get('groupDisplayType') === 'multipleColumns'; } +export function _isGroupHideColumnsUntilExpanded(gos: GridOptionsService) { + return _isGroupMultiAutoColumn(gos) && gos.get('groupHideColumnsUntilExpanded'); +} + export function _isGroupUseEntireRow(gos: GridOptionsService, pivotMode: boolean): boolean { // we never allow groupDisplayType = 'groupRows' if in pivot mode, otherwise we won't see the pivot values. if (pivotMode) { diff --git a/packages/ag-grid-community/src/main-internal.ts b/packages/ag-grid-community/src/main-internal.ts index e885d087eab..0b8e52300ce 100644 --- a/packages/ag-grid-community/src/main-internal.ts +++ b/packages/ag-grid-community/src/main-internal.ts @@ -370,6 +370,7 @@ export { _isFullWidthGroupRow, _isGetRowHeightFunction, _isGroupMultiAutoColumn, + _isGroupHideColumnsUntilExpanded, _isGroupRowsSticky, _isGroupUseEntireRow, _isLegacyMenuEnabled, diff --git a/packages/ag-grid-community/src/propertyKeys.ts b/packages/ag-grid-community/src/propertyKeys.ts index 11094d179fb..d473c08c50d 100644 --- a/packages/ag-grid-community/src/propertyKeys.ts +++ b/packages/ag-grid-community/src/propertyKeys.ts @@ -253,6 +253,7 @@ export const _BOOLEAN_GRID_OPTIONS: KeysWithType[] = [ 'embedFullWidthRows', 'suppressPaginationPanel', 'groupHideOpenParents', + 'groupHideColumnsUntilExpanded', 'groupAllowUnbalanced', 'pagination', 'paginationAutoPageSize', diff --git a/packages/ag-grid-community/src/validation/rules/gridOptionsValidations.ts b/packages/ag-grid-community/src/validation/rules/gridOptionsValidations.ts index e305ecee2cb..943390f1216 100644 --- a/packages/ag-grid-community/src/validation/rules/gridOptionsValidations.ts +++ b/packages/ag-grid-community/src/validation/rules/gridOptionsValidations.ts @@ -246,6 +246,15 @@ const GRID_OPTION_VALIDATIONS: () => Validations = () => { groupDefaultExpanded: { supportedRowModels: ['clientSide'], }, + groupHideColumnsUntilExpanded: { + supportedRowModels: ['clientSide'], + validate({ groupHideColumnsUntilExpanded, groupHideOpenParents, groupDisplayType }) { + if (groupHideColumnsUntilExpanded && !groupHideOpenParents && groupDisplayType !== 'multipleColumns') { + return "`groupHideColumnsUntilExpanded = true` requires either `groupDisplayType = 'multipleColumns'` or `groupHideOpenParents = true`"; + } + return null; + }, + }, groupHideOpenParents: { supportedRowModels: ['clientSide', 'serverSide'], dependencies: { diff --git a/packages/ag-grid-enterprise/src/rowHierarchy/autoColService.ts b/packages/ag-grid-enterprise/src/rowHierarchy/autoColService.ts index 2341163839d..1cda0dea0c1 100644 --- a/packages/ag-grid-enterprise/src/rowHierarchy/autoColService.ts +++ b/packages/ag-grid-enterprise/src/rowHierarchy/autoColService.ts @@ -5,6 +5,7 @@ import type { IColumnCollectionService, NamedBean, PropertyValueChangedEvent, + RowNode, _ColumnCollections, } from 'ag-grid-community'; import { @@ -19,6 +20,7 @@ import { _destroyColumnTree, _getColumnStateFromColDef, _isColumnsSortingCoupledToGroup, + _isGroupHideColumnsUntilExpanded, _isGroupMultiAutoColumn, _isGroupUseEntireRow, _mergeDeep, @@ -36,14 +38,30 @@ export class AutoColService extends BeanStub implements NamedBean, IColumnCollec public postConstruct(): void { this.addManagedPropertyListener('autoGroupColumnDef', this.updateColumns.bind(this)); + + this.setupGroupHideColumnsUntilExpanded(); + } + + private setupGroupHideColumnsUntilExpanded() { + const updateGroupColumnVisibility = () => this.updateGroupColumnVisibility(); + this.addManagedEventListeners({ + // modelUpdated is fired when rowGroup events are fired so we do not duplicate work by also listening to "rowGroupOpened" and "expandOrCollapseAll" + modelUpdated: updateGroupColumnVisibility, + }); + // Ensure the properties are reactive. + this.addManagedPropertyListeners( + ['groupHideColumnsUntilExpanded', 'groupDisplayType', 'groupHideOpenParents'], + updateGroupColumnVisibility + ); } public addColumns(cols: _ColumnCollections): void { - if (this.columns == null) { + const { columns } = this; + if (columns == null) { return; } - cols.list = this.columns.list.concat(cols.list); - cols.tree = this.columns.tree.concat(cols.tree); + cols.list = columns.list.concat(cols.list); + cols.tree = columns.tree.concat(cols.tree); _updateColsMap(cols); } @@ -273,6 +291,86 @@ export class AutoColService extends BeanStub implements NamedBean, IColumnCollec return res; } + private getDeepestExpandedLevel(nodes: RowNode[] | null | undefined, maxLevel: number): number { + let deepest = -1; + + if (!nodes) { + return deepest; + } + + for (const node of nodes) { + if (!node.group || !node.expanded) { + continue; + } + + if (node.level > deepest) { + deepest = node.level; + } + + if (deepest >= maxLevel) { + return deepest; + } + + // only expanded nodes recurse into their child groups; collapsed branches are skipped. + const childDeepest = this.getDeepestExpandedLevel(node.childrenAfterGroup, maxLevel); + if (childDeepest > deepest) { + deepest = childDeepest; + } + if (deepest >= maxLevel) { + return deepest; + } + } + + return deepest; + } + + private updateGroupColumnVisibility(): void { + const columns = this.columns?.list; + + if (!columns || columns.length === 0) { + return; + } + + const { gos, visibleCols, rowModel } = this.beans; + const isFeatureEnabled = _isGroupHideColumnsUntilExpanded(gos); + + let changed = false; + const setColVisible = (col: AgColumn, visible: boolean): void => { + if (visible !== col.isVisible()) { + col.setVisible(visible, 'api'); + changed = true; + } + }; + + const setAllColumnsVisible = (): void => { + for (const col of columns) { + setColVisible(col, true); + } + }; + + if (!isFeatureEnabled) { + setAllColumnsVisible(); + } else if (columns.length > 1) { + // Feature only applies when there are multiple columns to show/hide; + // the first column is always visible so a single column needs no adjustment. + const maxLevel = columns.length - 2; + const rootChildren = rowModel?.rootNode?.childrenAfterGroup; + const deepestExpandedLevel = this.getDeepestExpandedLevel(rootChildren, maxLevel); + + if (deepestExpandedLevel >= maxLevel) { + setAllColumnsVisible(); + } else { + for (let level = 0; level < columns.length - 1; level++) { + setColVisible(columns[level + 1], deepestExpandedLevel >= level); + } + } + } + + if (changed) { + visibleCols.refresh('api'); + } + } + public override destroy(): void { _destroyColumnTree(this.beans, this.columns?.tree); super.destroy(); diff --git a/packages/ag-grid-vue3/src/components/utils.ts b/packages/ag-grid-vue3/src/components/utils.ts index eec769216e0..8825a67f6cc 100644 --- a/packages/ag-grid-vue3/src/components/utils.ts +++ b/packages/ag-grid-vue3/src/components/utils.ts @@ -1244,6 +1244,14 @@ export interface Props { * @agModule `RowGroupingModule` */ groupHideOpenParents?: boolean, + /** When using `groupDisplayType='multipleColumns'` or `groupHideOpenParents=true`, hides group columns for levels + * that have not yet been expanded. Only the top-level group column is initially + * visible; each subsequent level becomes visible when at least one group at the + * preceding level is expanded. + * @default false + * @agModule `RowGroupingModule` + */ + groupHideColumnsUntilExpanded?: boolean, /** Set to `true` to prevent the grid from creating a '(Blanks)' group for nodes which do not belong to a group, and display the unbalanced nodes alongside group nodes. * @default false * @agModule `RowGroupingModule` @@ -2208,6 +2216,7 @@ export function getProps() { groupRemoveSingleChildren: undefined, groupRemoveLowestSingleChildren: undefined, groupHideOpenParents: undefined, + groupHideColumnsUntilExpanded: undefined, groupAllowUnbalanced: undefined, rowGroupPanelShow: undefined, groupRowRenderer: undefined, diff --git a/testing/behavioural/src/grouping-data/grouping-show-columns-when-expanded.test.ts b/testing/behavioural/src/grouping-data/grouping-show-columns-when-expanded.test.ts new file mode 100644 index 00000000000..8697c5c42e5 --- /dev/null +++ b/testing/behavioural/src/grouping-data/grouping-show-columns-when-expanded.test.ts @@ -0,0 +1,726 @@ +import type { GridApi } from 'ag-grid-community'; +import { ClientSideRowModelModule, QuickFilterModule } from 'ag-grid-community'; +import { PivotModule, RowGroupingModule } from 'ag-grid-enterprise'; + +import { TestGridsManager, cachedJSONObjects } from '../test-utils'; + +function getVisibleAutoGroupColIds(api: GridApi): string[] { + return api + .getAllDisplayedColumns() + .filter((col) => col.getColId().startsWith('ag-Grid-AutoColumn')) + .map((col) => col.getColId()); +} + +describe('ag-grid groupHideColumnsUntilExpanded', () => { + const gridsManager = new TestGridsManager({ + modules: [ClientSideRowModelModule, RowGroupingModule, QuickFilterModule], + }); + + beforeEach(() => { + gridsManager.reset(); + }); + + afterEach(() => { + gridsManager.reset(); + }); + + const twoLevelRowData = cachedJSONObjects.array([ + { id: '1', country: 'Ireland', year: '2020', athlete: 'John Smith', gold: 1 }, + { id: '2', country: 'Ireland', year: '2021', athlete: 'Jane Doe', gold: 2 }, + { id: '3', country: 'Italy', year: '2020', athlete: 'Mario Rossi', gold: 3 }, + { id: '4', country: 'France', year: '2021', athlete: 'Jean Dupont', gold: 1 }, + ]); + + const threeLevelRowData = cachedJSONObjects.array([ + { id: '1', country: 'Ireland', year: '2020', sport: 'Sailing', athlete: 'John Smith', gold: 1 }, + { id: '2', country: 'Ireland', year: '2020', sport: 'Soccer', athlete: 'Jane Doe', gold: 2 }, + { id: '3', country: 'Ireland', year: '2021', sport: 'Soccer', athlete: 'Bob Johnson', gold: 3 }, + { id: '4', country: 'Italy', year: '2020', sport: 'Soccer', athlete: 'Mario Rossi', gold: 4 }, + ]); + + const fourLevelRowData = cachedJSONObjects.array([ + { id: '1', country: 'Ireland', year: '2020', sport: 'Sailing', athlete: 'John Smith', gold: 1 }, + { id: '2', country: 'Ireland', year: '2020', sport: 'Soccer', athlete: 'Jane Doe', gold: 2 }, + { id: '3', country: 'Ireland', year: '2021', sport: 'Soccer', athlete: 'Bob Johnson', gold: 3 }, + { id: '4', country: 'Italy', year: '2020', sport: 'Soccer', athlete: 'Mario Rossi', gold: 4 }, + ]); + + test('default (false) - all auto columns visible when collapsed', async () => { + const api = gridsManager.createGrid('myGrid', { + columnDefs: [ + { field: 'country', rowGroup: true, hide: true }, + { field: 'year', rowGroup: true, hide: true }, + { field: 'athlete' }, + { field: 'gold' }, + ], + groupDisplayType: 'multipleColumns', + groupDefaultExpanded: 0, + rowData: twoLevelRowData, + getRowId: (params) => params.data.id, + }); + + // All auto group columns visible even though nothing is expanded + expect(getVisibleAutoGroupColIds(api)).toEqual(['ag-Grid-AutoColumn-country', 'ag-Grid-AutoColumn-year']); + }); + + test('default (false) - all auto columns remain visible after expand and collapse', async () => { + const api = gridsManager.createGrid('myGrid', { + columnDefs: [ + { field: 'country', rowGroup: true, hide: true }, + { field: 'year', rowGroup: true, hide: true }, + { field: 'athlete' }, + { field: 'gold' }, + ], + groupDisplayType: 'multipleColumns', + groupDefaultExpanded: 0, + rowData: twoLevelRowData, + getRowId: (params) => params.data.id, + }); + + expect(getVisibleAutoGroupColIds(api)).toEqual(['ag-Grid-AutoColumn-country', 'ag-Grid-AutoColumn-year']); + + api.setRowNodeExpanded(api.getRowNode('row-group-country-Ireland')!, true, false, true); + + expect(getVisibleAutoGroupColIds(api)).toEqual(['ag-Grid-AutoColumn-country', 'ag-Grid-AutoColumn-year']); + + api.collapseAll(); + + expect(getVisibleAutoGroupColIds(api)).toEqual(['ag-Grid-AutoColumn-country', 'ag-Grid-AutoColumn-year']); + }); + + test('explicit false - all auto columns visible with 3-level grouping', async () => { + const api = gridsManager.createGrid('myGrid', { + columnDefs: [ + { field: 'country', rowGroup: true, hide: true }, + { field: 'year', rowGroup: true, hide: true }, + { field: 'sport', rowGroup: true, hide: true }, + { field: 'athlete' }, + { field: 'gold' }, + ], + groupDisplayType: 'multipleColumns', + groupHideColumnsUntilExpanded: false, + groupDefaultExpanded: 0, + rowData: threeLevelRowData, + getRowId: (params) => params.data.id, + }); + + // All 3 auto group columns visible even with nothing expanded + expect(getVisibleAutoGroupColIds(api)).toEqual([ + 'ag-Grid-AutoColumn-country', + 'ag-Grid-AutoColumn-year', + 'ag-Grid-AutoColumn-sport', + ]); + }); + + test('all collapsed - only level 0 auto column visible', async () => { + const api = gridsManager.createGrid('myGrid', { + columnDefs: [ + { field: 'country', rowGroup: true, hide: true }, + { field: 'year', rowGroup: true, hide: true }, + { field: 'athlete' }, + { field: 'gold' }, + ], + groupDisplayType: 'multipleColumns', + groupHideColumnsUntilExpanded: true, + groupDefaultExpanded: 0, + rowData: twoLevelRowData, + getRowId: (params) => params.data.id, + }); + + const visibleCols = getVisibleAutoGroupColIds(api); + expect(visibleCols).toEqual(['ag-Grid-AutoColumn-country']); + }); + + test('expand level 0 - level 1 auto column appears', async () => { + const api = gridsManager.createGrid('myGrid', { + columnDefs: [ + { field: 'country', rowGroup: true, hide: true }, + { field: 'year', rowGroup: true, hide: true }, + { field: 'athlete' }, + { field: 'gold' }, + ], + groupDisplayType: 'multipleColumns', + groupHideColumnsUntilExpanded: true, + groupDefaultExpanded: 0, + rowData: twoLevelRowData, + getRowId: (params) => params.data.id, + }); + + // Initially only level 0 + expect(getVisibleAutoGroupColIds(api)).toEqual(['ag-Grid-AutoColumn-country']); + + // Expand Ireland + api.setRowNodeExpanded(api.getRowNode('row-group-country-Ireland')!, true, false, true); + + // Now both levels should be visible + expect(getVisibleAutoGroupColIds(api)).toEqual(['ag-Grid-AutoColumn-country', 'ag-Grid-AutoColumn-year']); + }); + + test('3-level grouping - expand level 1 shows level 2 auto column', async () => { + const api = gridsManager.createGrid('myGrid', { + columnDefs: [ + { field: 'country', rowGroup: true, hide: true }, + { field: 'year', rowGroup: true, hide: true }, + { field: 'sport', rowGroup: true, hide: true }, + { field: 'athlete' }, + { field: 'gold' }, + ], + groupDisplayType: 'multipleColumns', + groupHideColumnsUntilExpanded: true, + groupDefaultExpanded: 0, + rowData: threeLevelRowData, + getRowId: (params) => params.data.id, + }); + + // Initially only level 0 + expect(getVisibleAutoGroupColIds(api)).toEqual(['ag-Grid-AutoColumn-country']); + + // Expand Ireland (level 0) + api.setRowNodeExpanded(api.getRowNode('row-group-country-Ireland')!, true, false, true); + + expect(getVisibleAutoGroupColIds(api)).toEqual(['ag-Grid-AutoColumn-country', 'ag-Grid-AutoColumn-year']); + + // Expand Ireland > 2020 (level 1) + api.setRowNodeExpanded(api.getRowNode('row-group-country-Ireland-year-2020')!, true, false, true); + + expect(getVisibleAutoGroupColIds(api)).toEqual([ + 'ag-Grid-AutoColumn-country', + 'ag-Grid-AutoColumn-year', + 'ag-Grid-AutoColumn-sport', + ]); + }); + + test('4-level grouping - each expansion reveals the next auto column', async () => { + const api = gridsManager.createGrid('myGrid', { + columnDefs: [ + { field: 'country', rowGroup: true, hide: true }, + { field: 'year', rowGroup: true, hide: true }, + { field: 'sport', rowGroup: true, hide: true }, + { field: 'athlete', rowGroup: true, hide: true }, + { field: 'gold' }, + ], + groupDisplayType: 'multipleColumns', + groupHideColumnsUntilExpanded: true, + groupDefaultExpanded: 0, + rowData: fourLevelRowData, + getRowId: (params) => params.data.id, + }); + + // Initially only level 0 + expect(getVisibleAutoGroupColIds(api)).toEqual(['ag-Grid-AutoColumn-country']); + + // Expand Ireland (level 0) + api.setRowNodeExpanded(api.getRowNode('row-group-country-Ireland')!, true, false, true); + expect(getVisibleAutoGroupColIds(api)).toEqual(['ag-Grid-AutoColumn-country', 'ag-Grid-AutoColumn-year']); + + // Expand Ireland > 2020 (level 1) + api.setRowNodeExpanded(api.getRowNode('row-group-country-Ireland-year-2020')!, true, false, true); + expect(getVisibleAutoGroupColIds(api)).toEqual([ + 'ag-Grid-AutoColumn-country', + 'ag-Grid-AutoColumn-year', + 'ag-Grid-AutoColumn-sport', + ]); + + // Expand Ireland > 2020 > Sailing (level 2) + api.setRowNodeExpanded(api.getRowNode('row-group-country-Ireland-year-2020-sport-Sailing')!, true, false, true); + expect(getVisibleAutoGroupColIds(api)).toEqual([ + 'ag-Grid-AutoColumn-country', + 'ag-Grid-AutoColumn-year', + 'ag-Grid-AutoColumn-sport', + 'ag-Grid-AutoColumn-athlete', + ]); + + // Collapse Ireland > 2020 > Sailing (level 2) - athlete column should hide + api.setRowNodeExpanded( + api.getRowNode('row-group-country-Ireland-year-2020-sport-Sailing')!, + false, + false, + true + ); + expect(getVisibleAutoGroupColIds(api)).toEqual([ + 'ag-Grid-AutoColumn-country', + 'ag-Grid-AutoColumn-year', + 'ag-Grid-AutoColumn-sport', + ]); + }); + + test('expand level 0 - level 1 auto column appears, filter out row group column still visible', async () => { + const api = gridsManager.createGrid('myGrid', { + columnDefs: [ + { field: 'country', rowGroup: true, hide: true }, + { field: 'year', rowGroup: true, hide: true }, + { field: 'athlete' }, + { field: 'gold' }, + ], + groupDisplayType: 'multipleColumns', + groupHideColumnsUntilExpanded: true, + groupDefaultExpanded: 0, + rowData: twoLevelRowData, + getRowId: (params) => params.data.id, + }); + + // Initially only level 0 + expect(getVisibleAutoGroupColIds(api)).toEqual(['ag-Grid-AutoColumn-country']); + + // Expand Ireland + api.setRowNodeExpanded(api.getRowNode('row-group-country-Ireland')!, true, false, true); + + // Now both levels should be visible + expect(getVisibleAutoGroupColIds(api)).toEqual(['ag-Grid-AutoColumn-country', 'ag-Grid-AutoColumn-year']); + + api.setGridOption('quickFilterText', 'France'); + + // Filter hides Ireland rows - only France group row is displayed + expect(api.getDisplayedRowCount()).toBe(1); + expect(api.getDisplayedRowAtIndex(0)!.key).toBe('France'); + + // Year column remains visible even though the expanded Ireland group is now filtered out + expect(getVisibleAutoGroupColIds(api)).toEqual(['ag-Grid-AutoColumn-country', 'ag-Grid-AutoColumn-year']); + }); + + test('collapse all level 0 - hides level 1+', async () => { + const api = gridsManager.createGrid('myGrid', { + columnDefs: [ + { field: 'country', rowGroup: true, hide: true }, + { field: 'year', rowGroup: true, hide: true }, + { field: 'athlete' }, + { field: 'gold' }, + ], + groupDisplayType: 'multipleColumns', + groupHideColumnsUntilExpanded: true, + groupDefaultExpanded: 0, + rowData: twoLevelRowData, + getRowId: (params) => params.data.id, + }); + + // Expand Ireland + api.setRowNodeExpanded(api.getRowNode('row-group-country-Ireland')!, true, false, true); + + expect(getVisibleAutoGroupColIds(api)).toEqual(['ag-Grid-AutoColumn-country', 'ag-Grid-AutoColumn-year']); + + // Collapse Ireland + api.setRowNodeExpanded(api.getRowNode('row-group-country-Ireland')!, false, false, true); + + expect(getVisibleAutoGroupColIds(api)).toEqual(['ag-Grid-AutoColumn-country']); + }); + + test('expandAll - all auto columns visible', async () => { + const api = gridsManager.createGrid('myGrid', { + columnDefs: [ + { field: 'country', rowGroup: true, hide: true }, + { field: 'year', rowGroup: true, hide: true }, + { field: 'athlete' }, + { field: 'gold' }, + ], + groupDisplayType: 'multipleColumns', + groupHideColumnsUntilExpanded: true, + groupDefaultExpanded: 0, + rowData: twoLevelRowData, + getRowId: (params) => params.data.id, + }); + + expect(getVisibleAutoGroupColIds(api)).toEqual(['ag-Grid-AutoColumn-country']); + + api.expandAll(); + + expect(getVisibleAutoGroupColIds(api)).toEqual(['ag-Grid-AutoColumn-country', 'ag-Grid-AutoColumn-year']); + }); + + test('collapseAll - only level 0 visible', async () => { + const api = gridsManager.createGrid('myGrid', { + columnDefs: [ + { field: 'country', rowGroup: true, hide: true }, + { field: 'year', rowGroup: true, hide: true }, + { field: 'athlete' }, + { field: 'gold' }, + ], + groupDisplayType: 'multipleColumns', + groupHideColumnsUntilExpanded: true, + groupDefaultExpanded: -1, + rowData: twoLevelRowData, + getRowId: (params) => params.data.id, + }); + + // All expanded initially + expect(getVisibleAutoGroupColIds(api)).toEqual(['ag-Grid-AutoColumn-country', 'ag-Grid-AutoColumn-year']); + + api.collapseAll(); + + expect(getVisibleAutoGroupColIds(api)).toEqual(['ag-Grid-AutoColumn-country']); + }); + + test('groupDefaultExpanded: 1 - levels 0 and 1 visible on initial load', async () => { + const api = gridsManager.createGrid('myGrid', { + columnDefs: [ + { field: 'country', rowGroup: true, hide: true }, + { field: 'year', rowGroup: true, hide: true }, + { field: 'athlete' }, + { field: 'gold' }, + ], + groupDisplayType: 'multipleColumns', + groupHideColumnsUntilExpanded: true, + groupDefaultExpanded: 1, + rowData: twoLevelRowData, + getRowId: (params) => params.data.id, + }); + + expect(getVisibleAutoGroupColIds(api)).toEqual(['ag-Grid-AutoColumn-country', 'ag-Grid-AutoColumn-year']); + }); + + test('groupDefaultExpanded: -1 - all visible on initial load', async () => { + const api = gridsManager.createGrid('myGrid', { + columnDefs: [ + { field: 'country', rowGroup: true, hide: true }, + { field: 'year', rowGroup: true, hide: true }, + { field: 'sport', rowGroup: true, hide: true }, + { field: 'athlete' }, + { field: 'gold' }, + ], + groupDisplayType: 'multipleColumns', + groupHideColumnsUntilExpanded: true, + groupDefaultExpanded: -1, + rowData: threeLevelRowData, + getRowId: (params) => params.data.id, + }); + + expect(getVisibleAutoGroupColIds(api)).toEqual([ + 'ag-Grid-AutoColumn-country', + 'ag-Grid-AutoColumn-year', + 'ag-Grid-AutoColumn-sport', + ]); + }); + + test('runtime toggle - turning option on hides unexpanded columns', async () => { + const api = gridsManager.createGrid('myGrid', { + columnDefs: [ + { field: 'country', rowGroup: true, hide: true }, + { field: 'year', rowGroup: true, hide: true }, + { field: 'athlete' }, + { field: 'gold' }, + ], + groupDisplayType: 'multipleColumns', + groupHideColumnsUntilExpanded: false, + groupDefaultExpanded: 0, + rowData: twoLevelRowData, + getRowId: (params) => params.data.id, + }); + + // Both visible by default when feature is off + expect(getVisibleAutoGroupColIds(api)).toEqual(['ag-Grid-AutoColumn-country', 'ag-Grid-AutoColumn-year']); + + // Enable feature + api.updateGridOptions({ groupHideColumnsUntilExpanded: true }); + + // Now only level 0 should be visible + expect(getVisibleAutoGroupColIds(api)).toEqual(['ag-Grid-AutoColumn-country']); + }); + + test('runtime toggle - turning option off restores all columns', async () => { + const api = gridsManager.createGrid('myGrid', { + columnDefs: [ + { field: 'country', rowGroup: true, hide: true }, + { field: 'year', rowGroup: true, hide: true }, + { field: 'athlete' }, + { field: 'gold' }, + ], + groupDisplayType: 'multipleColumns', + groupHideColumnsUntilExpanded: true, + groupDefaultExpanded: 0, + rowData: twoLevelRowData, + getRowId: (params) => params.data.id, + }); + + // Only level 0 visible + expect(getVisibleAutoGroupColIds(api)).toEqual(['ag-Grid-AutoColumn-country']); + + // Disable feature + api.updateGridOptions({ groupHideColumnsUntilExpanded: false }); + + // Both should be visible again + expect(getVisibleAutoGroupColIds(api)).toEqual(['ag-Grid-AutoColumn-country', 'ag-Grid-AutoColumn-year']); + }); + + test('runtime groupDisplayType change to multipleColumns - feature activates', async () => { + const api = gridsManager.createGrid('myGrid', { + columnDefs: [ + { field: 'country', rowGroup: true, hide: true }, + { field: 'year', rowGroup: true, hide: true }, + { field: 'athlete' }, + { field: 'gold' }, + ], + groupDisplayType: 'singleColumn', + groupHideColumnsUntilExpanded: true, + groupDefaultExpanded: 0, + rowData: twoLevelRowData, + getRowId: (params) => params.data.id, + }); + + // In singleColumn mode, feature has no effect - one auto col always visible + expect(getVisibleAutoGroupColIds(api)).toEqual(['ag-Grid-AutoColumn']); + + // Switch to multipleColumns at runtime + api.updateGridOptions({ groupDisplayType: 'multipleColumns' }); + + // Feature now active: only level 0 visible (nothing expanded) + expect(getVisibleAutoGroupColIds(api)).toEqual(['ag-Grid-AutoColumn-country']); + + // Expand Ireland to reveal level 1 + api.setRowNodeExpanded(api.getRowNode('row-group-country-Ireland')!, true, false, true); + + expect(getVisibleAutoGroupColIds(api)).toEqual(['ag-Grid-AutoColumn-country', 'ag-Grid-AutoColumn-year']); + }); + + test('runtime groupDisplayType change from multipleColumns - resets to singleColumn', async () => { + const api = gridsManager.createGrid('myGrid', { + columnDefs: [ + { field: 'country', rowGroup: true, hide: true }, + { field: 'year', rowGroup: true, hide: true }, + { field: 'athlete' }, + { field: 'gold' }, + ], + groupDisplayType: 'multipleColumns', + groupHideColumnsUntilExpanded: true, + groupDefaultExpanded: 0, + rowData: twoLevelRowData, + getRowId: (params) => params.data.id, + }); + + // Feature active: only level 0 visible + expect(getVisibleAutoGroupColIds(api)).toEqual(['ag-Grid-AutoColumn-country']); + + // Switch back to singleColumn at runtime + api.updateGridOptions({ groupDisplayType: 'singleColumn' }); + + // Feature no longer active: single auto col visible + expect(getVisibleAutoGroupColIds(api)).toEqual(['ag-Grid-AutoColumn']); + }); + + test('runtime groupHideOpenParents true - feature activates with multipleColumns behaviour', async () => { + const api = gridsManager.createGrid('myGrid', { + columnDefs: [ + { field: 'country', rowGroup: true, hide: true }, + { field: 'year', rowGroup: true, hide: true }, + { field: 'athlete' }, + { field: 'gold' }, + ], + groupHideOpenParents: false, + groupHideColumnsUntilExpanded: true, + groupDefaultExpanded: 0, + rowData: twoLevelRowData, + getRowId: (params) => params.data.id, + }); + + // Without groupHideOpenParents, singleColumn mode (default) - feature has no effect + expect(getVisibleAutoGroupColIds(api)).toEqual(['ag-Grid-AutoColumn']); + + // Enable groupHideOpenParents at runtime (implies multipleColumns) + api.updateGridOptions({ groupHideOpenParents: true }); + + // Feature now active: only level 0 visible (nothing expanded) + expect(getVisibleAutoGroupColIds(api)).toEqual(['ag-Grid-AutoColumn-country']); + + // Expand Ireland to reveal level 1 + api.setRowNodeExpanded(api.getRowNode('row-group-country-Ireland')!, true, false, true); + + expect(getVisibleAutoGroupColIds(api)).toEqual(['ag-Grid-AutoColumn-country', 'ag-Grid-AutoColumn-year']); + }); + + test('runtime groupHideOpenParents false - feature deactivates', async () => { + const api = gridsManager.createGrid('myGrid', { + columnDefs: [ + { field: 'country', rowGroup: true, hide: true }, + { field: 'year', rowGroup: true, hide: true }, + { field: 'athlete' }, + { field: 'gold' }, + ], + groupHideOpenParents: true, + groupHideColumnsUntilExpanded: true, + groupDefaultExpanded: 0, + rowData: twoLevelRowData, + getRowId: (params) => params.data.id, + }); + + // Feature active via groupHideOpenParents: only level 0 visible + expect(getVisibleAutoGroupColIds(api)).toEqual(['ag-Grid-AutoColumn-country']); + + // Disable groupHideOpenParents at runtime + api.updateGridOptions({ groupHideOpenParents: false }); + + // Without multipleColumns mode, feature deactivates - single auto col visible + expect(getVisibleAutoGroupColIds(api)).toEqual(['ag-Grid-AutoColumn']); + }); + + test('with groupHideOpenParents - feature works (forces multipleColumns mode)', async () => { + const api = gridsManager.createGrid('myGrid', { + columnDefs: [ + { field: 'country', rowGroup: true, hide: true }, + { field: 'year', rowGroup: true, hide: true }, + { field: 'athlete' }, + { field: 'gold' }, + ], + groupHideOpenParents: true, + groupHideColumnsUntilExpanded: true, + groupDefaultExpanded: 0, + rowData: twoLevelRowData, + getRowId: (params) => params.data.id, + }); + + // Only level 0 visible + expect(getVisibleAutoGroupColIds(api)).toEqual(['ag-Grid-AutoColumn-country']); + + // Expand Ireland + api.setRowNodeExpanded(api.getRowNode('row-group-country-Ireland')!, true, false, true); + + // Now both levels should be visible + expect(getVisibleAutoGroupColIds(api)).toEqual(['ag-Grid-AutoColumn-country', 'ag-Grid-AutoColumn-year']); + }); + + test('has no effect with singleColumn display type', async () => { + const api = gridsManager.createGrid('myGrid', { + columnDefs: [ + { field: 'country', rowGroup: true, hide: true }, + { field: 'year', rowGroup: true, hide: true }, + { field: 'athlete' }, + { field: 'gold' }, + ], + groupDisplayType: 'singleColumn', + groupHideColumnsUntilExpanded: true, + groupDefaultExpanded: 0, + rowData: twoLevelRowData, + getRowId: (params) => params.data.id, + }); + + // Single auto group column should always be visible + expect(getVisibleAutoGroupColIds(api)).toEqual(['ag-Grid-AutoColumn']); + }); + + test('has no effect with groupRows display type', async () => { + const api = gridsManager.createGrid('myGrid', { + columnDefs: [ + { field: 'country', rowGroup: true, hide: true }, + { field: 'year', rowGroup: true, hide: true }, + { field: 'athlete' }, + { field: 'gold' }, + ], + groupDisplayType: 'groupRows', + groupHideColumnsUntilExpanded: true, + groupDefaultExpanded: 0, + rowData: twoLevelRowData, + getRowId: (params) => params.data.id, + }); + + // No auto group columns in groupRows mode + expect(getVisibleAutoGroupColIds(api)).toEqual([]); + }); +}); + +describe('ag-grid groupHideColumnsUntilExpanded with pivot mode', () => { + const gridsManager = new TestGridsManager({ + modules: [ClientSideRowModelModule, RowGroupingModule, PivotModule], + }); + + beforeEach(() => { + gridsManager.reset(); + }); + + afterEach(() => { + gridsManager.reset(); + }); + + const pivotRowData = cachedJSONObjects.array([ + { id: '1', country: 'Ireland', year: '2020', gold: 1 }, + { id: '2', country: 'Ireland', year: '2021', gold: 2 }, + { id: '3', country: 'Italy', year: '2020', gold: 3 }, + { id: '4', country: 'France', year: '2021', gold: 1 }, + ]); + + function getVisibleNonAutoColIds(api: GridApi): string[] { + return api + .getAllDisplayedColumns() + .filter((col) => !col.getColId().startsWith('ag-Grid-AutoColumn')) + .map((col) => col.getColId()); + } + + test('pivot mode with active pivot result - auto group column visible (multipleColumns)', async () => { + const api = gridsManager.createGrid('myGrid', { + columnDefs: [ + { field: 'country', rowGroup: true, hide: true }, + { field: 'sport', rowGroup: true, hide: true }, + { field: 'year', pivot: true, hide: true }, + { field: 'gold', aggFunc: 'sum', hide: true }, + ], + pivotMode: true, + groupHideColumnsUntilExpanded: true, + groupDisplayType: 'multipleColumns', + groupDefaultExpanded: 0, + rowData: pivotRowData, + getRowId: (params) => params.data.id, + }); + + // In pivot mode with results, single auto group column should be visible + expect(getVisibleAutoGroupColIds(api)).toEqual(['ag-Grid-AutoColumn-country']); + }); + + test('pivot mode with active pivot result - auto group column visible (groupHideOpenParents)', async () => { + const api = gridsManager.createGrid('myGrid', { + columnDefs: [ + { field: 'country', rowGroup: true, hide: true }, + { field: 'sport', rowGroup: true, hide: true }, + { field: 'year', pivot: true, hide: true }, + { field: 'gold', aggFunc: 'sum', hide: true }, + ], + pivotMode: true, + groupHideColumnsUntilExpanded: true, + groupHideOpenParents: true, + groupDefaultExpanded: 0, + rowData: pivotRowData, + getRowId: (params) => params.data.id, + }); + + // In pivot mode with results, single auto group column should be visible + expect(getVisibleAutoGroupColIds(api)).toEqual(['ag-Grid-AutoColumn-country']); + }); + + test('pivot mode without pivot result - does not leak regular columns', async () => { + const api = gridsManager.createGrid('myGrid', { + columnDefs: [ + { field: 'country', rowGroup: true }, + { field: 'year', rowGroup: true }, + { field: 'gold', aggFunc: 'sum' }, + ], + pivotMode: true, + groupDisplayType: 'multipleColumns', + groupHideColumnsUntilExpanded: true, + groupDefaultExpanded: 0, + rowData: pivotRowData, + getRowId: (params) => params.data.id, + }); + + // In pivot mode without pivot columns, only auto group + value columns should be shown + // Regular columns like 'country' and 'year' must NOT leak through + const nonAutoCols = getVisibleNonAutoColIds(api); + expect(nonAutoCols).toEqual(['gold']); + }); + + test('pivot mode without pivot result and feature off - auto group always visible', async () => { + const api = gridsManager.createGrid('myGrid', { + columnDefs: [ + { field: 'country', rowGroup: true }, + { field: 'year', rowGroup: true }, + { field: 'gold', aggFunc: 'sum' }, + ], + pivotMode: true, + groupDisplayType: 'multipleColumns', + groupHideColumnsUntilExpanded: false, + groupDefaultExpanded: 0, + rowData: pivotRowData, + getRowId: (params) => params.data.id, + }); + + // Auto group column always visible when feature is off + expect(getVisibleAutoGroupColIds(api)).toEqual(['ag-Grid-AutoColumn-country', 'ag-Grid-AutoColumn-year']); + // Only value columns alongside auto group + const nonAutoCols = getVisibleNonAutoColIds(api); + expect(nonAutoCols).toEqual(['gold']); + }); +});