From bb6e00e9ed69ea535cec04620df9a8ca50813539 Mon Sep 17 00:00:00 2001 From: Stephen Cooper Date: Tue, 17 Mar 2026 12:24:51 +0000 Subject: [PATCH 1/3] AG-1190: Maintain row group expansion state across column changes and add resetRowGroupExpansion API (#13232) * First pass at CSRM * Support user collapsed rows * Cleanup * Add pivot tests for CSRM * stash SSRM changes * Add reset function * Add resetRowGroupExpansion to docs * Small optimisation * Relax typing * State and SSRM improved support * Add tests for SSRM grouping state * Add another test case * Review changes * more reviews * small cleanup * small cleanup * state bug fix. * Handle missing test case for expandAll * Share common code and optimise expandAll loop * Add more tests for current behaviour * Improve doc links * Fix lint * Merge branch 'latest' into AG-1190-maintain-expansion-state * AG-1190 Maintain expansion flag updates (#13304) * Stash changes * expanded property * expanded property * expanded property --------- Co-authored-by: Salvatore Previti * Review changes * Split out SSRM maintain-expansion-state for separate review Revert SSRM-specific changes (serverSideRowModel, lazyStore, defaultStrategy, preserveAndResetExpand logic) to latest, keeping only the shared interface stubs (isExpanded, resetExpansion, updateAllNodes returning bool) needed by the CSRM branch. Full SSRM implementation preserved on AG-1190/ssrm-maintain-expansion-state branch. * fix lint * re-use code * Revert changes that are not required * reduce PR changes * Cleanup previous strategy * review changes * Safer handling of SSRM undefined case --------- Co-authored-by: Salvatore Previti --- .../api-documentation/grid-api/api.json | 20 +- .../grid-options/properties.json | 14 +- .../reset-group-expansion/example.spec.ts | 34 + .../reset-group-expansion/index.html | 8 + .../_examples/reset-group-expansion/main.ts | 64 + .../reset-group-expansion/styles.css | 10 + .../docs/grouping-opening-groups/index.mdoc | 10 +- packages/ag-grid-community/src/api/gridApi.ts | 11 +- .../src/api/gridApiFunctions.ts | 1 + .../src/api/rowModelSharedApi.ts | 4 + .../src/api/sharedApiModule.ts | 10 +- .../clientSideNodeManager.ts | 2 +- .../ag-grid-community/src/context/context.ts | 8 +- .../ag-grid-community/src/entities/rowNode.ts | 19 +- .../src/interfaces/iExpansionService.ts | 7 +- .../src/misc/state/stateService.ts | 70 +- .../src/rendering/row/rowCtrl.ts | 2 +- .../src/selection/selectionService.ts | 2 +- .../src/valueService/valueService.ts | 19 +- .../src/masterDetail/masterDetailService.ts | 15 +- .../groupStrategy/groupStrategy.ts | 160 ++- .../src/rowGrouping/rowGroupingUtils.ts | 1 - .../src/rowHierarchy/baseExpansionService.ts | 27 +- .../clientSideExpansionService.ts | 35 +- .../src/rowHierarchy/flattenUtils.ts | 27 +- .../rendering/groupCellRendererCtrl.ts | 4 +- .../src/rowHierarchy/rowHierarchyUtils.ts | 28 - .../src/rowHierarchy/stickyRowFeature.ts | 2 +- .../serverSideRowModel/blocks/blockUtils.ts | 4 +- .../services/serverSideExpansionService.ts | 36 +- .../src/treeData/treeGroupStrategy.ts | 38 +- .../cell-editing/benchmarks/getvalue.bench.ts | 62 + .../managed-row-group-drag-edit-basic.test.ts | 15 +- .../src/grid-state/grid-state.test.ts | 207 ++- .../grouping-change-column.test.ts | 1258 ++++++++++++++++- .../src/row-data/row-node-expanded.test.ts | 46 + .../server-side-expansion-service.test.ts | 2 + .../src/test-utils/gridRows/gridRows.ts | 13 +- .../rows-validation/gridRowsValidator.ts | 1 - 39 files changed, 2061 insertions(+), 235 deletions(-) create mode 100644 documentation/ag-grid-docs/src/content/docs/grouping-opening-groups/_examples/reset-group-expansion/example.spec.ts create mode 100644 documentation/ag-grid-docs/src/content/docs/grouping-opening-groups/_examples/reset-group-expansion/index.html create mode 100644 documentation/ag-grid-docs/src/content/docs/grouping-opening-groups/_examples/reset-group-expansion/main.ts create mode 100644 documentation/ag-grid-docs/src/content/docs/grouping-opening-groups/_examples/reset-group-expansion/styles.css create mode 100644 testing/behavioural/src/cell-editing/benchmarks/getvalue.bench.ts create mode 100644 testing/behavioural/src/row-data/row-node-expanded.test.ts diff --git a/documentation/ag-grid-docs/src/content/api-documentation/grid-api/api.json b/documentation/ag-grid-docs/src/content/api-documentation/grid-api/api.json index 914a3bd29af..cc7fcc31d45 100644 --- a/documentation/ag-grid-docs/src/content/api-documentation/grid-api/api.json +++ b/documentation/ag-grid-docs/src/content/api-documentation/grid-api/api.json @@ -662,8 +662,24 @@ "displayName": "Row Expand and Collapse", "description": "See [Row Grouping](./grouping-opening-groups/), [Master Detail](./master-detail/) or [Tree Data](./tree-data-opening-groups/) for more information" }, - "expandAll": {}, - "collapseAll": {}, + "expandAll": { + "more": { + "name": "Expanding Groups", + "url": "./grouping-opening-groups/#api" + } + }, + "collapseAll": { + "more": { + "name": "Expanding Groups", + "url": "./grouping-opening-groups/#api" + } + }, + "resetRowGroupExpansion": { + "more": { + "name": "Reset Group Expansion", + "url": "./grouping-opening-groups/#reset-group-expansion" + } + }, "setRowNodeExpanded": { "more": { "name": "Expand to a Row", 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 d78e3f4bd04..57a60cd22d9 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 @@ -1731,8 +1731,18 @@ "url": "./server-side-model-pivoting/#supplying-pivot-result-fields-simple" } }, - "serverSideSortAllLevels": {}, - "serverSideEnableClientSideSort": {}, + "serverSideSortAllLevels": { + "more": { + "name": "Sorting All Levels", + "url": "./server-side-model-sorting/#sorting-all-levels" + } + }, + "serverSideEnableClientSideSort": { + "more": { + "name": "Client-side Sorting", + "url": "./server-side-model-sorting/#client-side-sorting" + } + }, "serverSideOnlyRefreshFilteredGroups": { "more": { "name": "Filters", diff --git a/documentation/ag-grid-docs/src/content/docs/grouping-opening-groups/_examples/reset-group-expansion/example.spec.ts b/documentation/ag-grid-docs/src/content/docs/grouping-opening-groups/_examples/reset-group-expansion/example.spec.ts new file mode 100644 index 00000000000..20e1e009d24 --- /dev/null +++ b/documentation/ag-grid-docs/src/content/docs/grouping-opening-groups/_examples/reset-group-expansion/example.spec.ts @@ -0,0 +1,34 @@ +import { expect, test } from '@utils/grid/test-utils'; + +test.agExample(import.meta, () => { + test.eachFramework('Example', async ({ page, agIdFor }) => { + const australiaId = 'row-group-country-Australia'; + const australia2004Id = 'row-group-country-Australia-year-2004'; + const unitedStatesId = 'row-group-country-United States'; + + // Initial state: isGroupOpenByDefault expands Australia > 2004 + await expect(agIdFor.autoGroupExpanded(australiaId)).toBeVisible(); + await expect(agIdFor.autoGroupExpanded(australia2004Id)).toBeVisible(); + await expect(agIdFor.autoGroupContracted(unitedStatesId)).toBeVisible(); + + // Expand All — United States should now be expanded + await page.getByRole('button', { name: 'Expand All' }).click(); + await expect(agIdFor.autoGroupExpanded(unitedStatesId)).toBeVisible(); + + // Reset to Defaults — back to isGroupOpenByDefault state + await page.getByRole('button', { name: 'Reset to Defaults' }).click(); + await expect(agIdFor.autoGroupExpanded(australiaId)).toBeVisible(); + await expect(agIdFor.autoGroupExpanded(australia2004Id)).toBeVisible(); + await expect(agIdFor.autoGroupContracted(unitedStatesId)).toBeVisible(); + + // Collapse All — everything collapsed + await page.getByRole('button', { name: 'Collapse All' }).click(); + await expect(agIdFor.autoGroupContracted(australiaId)).toBeVisible(); + + // Reset to Defaults — Australia > 2004 expanded again + await page.getByRole('button', { name: 'Reset to Defaults' }).click(); + await expect(agIdFor.autoGroupExpanded(australiaId)).toBeVisible(); + await expect(agIdFor.autoGroupExpanded(australia2004Id)).toBeVisible(); + await expect(agIdFor.autoGroupContracted(unitedStatesId)).toBeVisible(); + }); +}); diff --git a/documentation/ag-grid-docs/src/content/docs/grouping-opening-groups/_examples/reset-group-expansion/index.html b/documentation/ag-grid-docs/src/content/docs/grouping-opening-groups/_examples/reset-group-expansion/index.html new file mode 100644 index 00000000000..1bd6b185e1f --- /dev/null +++ b/documentation/ag-grid-docs/src/content/docs/grouping-opening-groups/_examples/reset-group-expansion/index.html @@ -0,0 +1,8 @@ +
+
+ + + +
+
+
diff --git a/documentation/ag-grid-docs/src/content/docs/grouping-opening-groups/_examples/reset-group-expansion/main.ts b/documentation/ag-grid-docs/src/content/docs/grouping-opening-groups/_examples/reset-group-expansion/main.ts new file mode 100644 index 00000000000..22017c70ac5 --- /dev/null +++ b/documentation/ag-grid-docs/src/content/docs/grouping-opening-groups/_examples/reset-group-expansion/main.ts @@ -0,0 +1,64 @@ +import type { GridApi, GridOptions, IsGroupOpenByDefaultParams } from 'ag-grid-community'; +import { + ClientSideRowModelApiModule, + ClientSideRowModelModule, + ModuleRegistry, + RowApiModule, + ValidationModule, + createGrid, +} from 'ag-grid-community'; +import { RowGroupingModule } from 'ag-grid-enterprise'; + +ModuleRegistry.registerModules([ + RowApiModule, + ClientSideRowModelModule, + ClientSideRowModelApiModule, + RowGroupingModule, + ...(process.env.NODE_ENV !== 'production' ? [ValidationModule] : []), +]); + +let gridApi: GridApi; + +const gridOptions: GridOptions = { + columnDefs: [ + { field: 'country', rowGroup: true }, + { field: 'year', rowGroup: true }, + { field: 'sport' }, + { field: 'athlete' }, + { field: 'total' }, + ], + defaultColDef: { + flex: 1, + minWidth: 100, + }, + autoGroupColumnDef: { + minWidth: 200, + }, + isGroupOpenByDefault: (params: IsGroupOpenByDefaultParams) => { + const route = params.rowNode.getRoute(); + const destPath = ['Australia', '2004']; + return !!route?.every((item, idx) => destPath[idx] === item); + }, +}; + +function onBtExpandAll() { + gridApi!.expandAll(); +} + +function onBtCollapseAll() { + gridApi!.collapseAll(); +} + +function onBtResetExpansion() { + gridApi!.resetRowGroupExpansion(); +} + +// 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-opening-groups/_examples/reset-group-expansion/styles.css b/documentation/ag-grid-docs/src/content/docs/grouping-opening-groups/_examples/reset-group-expansion/styles.css new file mode 100644 index 00000000000..072f3617e55 --- /dev/null +++ b/documentation/ag-grid-docs/src/content/docs/grouping-opening-groups/_examples/reset-group-expansion/styles.css @@ -0,0 +1,10 @@ +.example-wrapper { + display: flex; + flex-direction: column; + height: 100%; +} + +#myGrid { + flex: 1 1 0px; + width: 100%; +} diff --git a/documentation/ag-grid-docs/src/content/docs/grouping-opening-groups/index.mdoc b/documentation/ag-grid-docs/src/content/docs/grouping-opening-groups/index.mdoc index 3ce3a88ba68..947bf5b7d55 100644 --- a/documentation/ag-grid-docs/src/content/docs/grouping-opening-groups/index.mdoc +++ b/documentation/ag-grid-docs/src/content/docs/grouping-opening-groups/index.mdoc @@ -75,7 +75,7 @@ The row group expansion state can be saved and restored as part of [Grid State]( {% /note %} The grid exposes API methods to expand or collapse groups programmatically. -{% apiDocumentation source="grid-api/api.json" section="rowExpansion" names=["setRowNodeExpanded", "expandAll", "collapseAll"] /%} +{% apiDocumentation source="grid-api/api.json" section="rowExpansion" names=["setRowNodeExpanded", "expandAll", "collapseAll", "resetRowGroupExpansion"] /%} ### Expand Row Ancestors @@ -93,3 +93,11 @@ const expandToRow = () => { } } ``` + +### Reset Group Expansion + +After users have expanded or collapsed groups, `resetRowGroupExpansion()` discards all overrides and re-evaluates each group against the configured defaults (`groupDefaultExpanded` or `isGroupOpenByDefault`). + +In the example below, the `isGroupOpenByDefault` callback expands the `Australia > 2004` path by default. Try expanding or collapsing groups, then click **Reset to Defaults** to restore the original expansion state. + +{% gridExampleRunner title="Reset Group Expansion" name="reset-group-expansion" /%} diff --git a/packages/ag-grid-community/src/api/gridApi.ts b/packages/ag-grid-community/src/api/gridApi.ts index f5b6d21ecde..90ad0dbddb3 100644 --- a/packages/ag-grid-community/src/api/gridApi.ts +++ b/packages/ag-grid-community/src/api/gridApi.ts @@ -529,6 +529,15 @@ export interface _CsrmSsrmSharedGridApi { * @agModule `ClientSideRowModelApiModule / ServerSideRowModelApiModule` */ collapseAll(): void; + + /** + * Reset all group expansion to defaults, as determined by `groupDefaultExpanded`, + * `isGroupOpenByDefault`, or `isServerSideGroupOpenByDefault`. + * Any user-initiated expand/collapse overrides are discarded. + * + * @agModule `ClientSideRowModelApiModule / ServerSideRowModelApiModule` + */ + resetRowGroupExpansion(): void; } export interface _RowModelSharedApi { @@ -631,7 +640,7 @@ export interface _ColumnResizeApi { } export interface _ColumnMoveApi { - /** Moves the column at `fromIdex` to `toIndex`. The column is first removed, then added at the `toIndex` location, thus index locations will change to the right of the column after the removal. */ + /** Moves the column at `fromIndex` to `toIndex`. The column is first removed, then added at the `toIndex` location, thus index locations will change to the right of the column after the removal. */ moveColumnByIndex(fromIndex: number, toIndex: number): void; /** Moves columns to `toIndex`. The columns are first removed, then added at the `toIndex` location, thus index locations will change to the right of the column after the removal. */ diff --git a/packages/ag-grid-community/src/api/gridApiFunctions.ts b/packages/ag-grid-community/src/api/gridApiFunctions.ts index 49f56ac938b..afd445844a6 100644 --- a/packages/ag-grid-community/src/api/gridApiFunctions.ts +++ b/packages/ag-grid-community/src/api/gridApiFunctions.ts @@ -287,6 +287,7 @@ export const gridApiFunctionsMap: Record = ...mod<_CsrmSsrmSharedGridApi>('CsrmSsrmSharedApi', { expandAll: 0, collapseAll: 0, + resetRowGroupExpansion: 0, }), ...mod<_SsrmInfiniteSharedGridApi>('SsrmInfiniteSharedApi', { setRowCount: 0, diff --git a/packages/ag-grid-community/src/api/rowModelSharedApi.ts b/packages/ag-grid-community/src/api/rowModelSharedApi.ts index 87950571ed2..c012dbf703d 100644 --- a/packages/ag-grid-community/src/api/rowModelSharedApi.ts +++ b/packages/ag-grid-community/src/api/rowModelSharedApi.ts @@ -22,3 +22,7 @@ export function resetRowHeights(beans: BeanCollection) { } beans.rowModel?.resetRowHeights(); } + +export function resetRowGroupExpansion(beans: BeanCollection) { + beans.expansionSvc?.resetExpansion(); +} diff --git a/packages/ag-grid-community/src/api/sharedApiModule.ts b/packages/ag-grid-community/src/api/sharedApiModule.ts index aa067bd639a..25239071127 100644 --- a/packages/ag-grid-community/src/api/sharedApiModule.ts +++ b/packages/ag-grid-community/src/api/sharedApiModule.ts @@ -1,7 +1,13 @@ import type { _ModuleWithApi } from '../interfaces/iModule'; import { VERSION } from '../version'; import type { _CsrmSsrmSharedGridApi, _RowModelSharedApi, _SsrmInfiniteSharedGridApi } from './gridApi'; -import { collapseAll, expandAll, onRowHeightChanged, resetRowHeights } from './rowModelSharedApi'; +import { + collapseAll, + expandAll, + onRowHeightChanged, + resetRowGroupExpansion, + resetRowHeights, +} from './rowModelSharedApi'; import { getCacheBlockState, isLastRowIndexKnown, setRowCount } from './ssrmInfiniteSharedApi'; // these modules are not used in core, but are shared between multiple other modules @@ -12,7 +18,7 @@ import { getCacheBlockState, isLastRowIndexKnown, setRowCount } from './ssrmInfi export const CsrmSsrmSharedApiModule: _ModuleWithApi<_CsrmSsrmSharedGridApi> = { moduleName: 'CsrmSsrmSharedApi', version: VERSION, - apiFunctions: { expandAll, collapseAll }, + apiFunctions: { expandAll, collapseAll, resetRowGroupExpansion }, }; /** diff --git a/packages/ag-grid-community/src/clientSideRowModel/clientSideNodeManager.ts b/packages/ag-grid-community/src/clientSideRowModel/clientSideNodeManager.ts index adc4c0ca83f..5fbfc35ec1a 100644 --- a/packages/ag-grid-community/src/clientSideRowModel/clientSideNodeManager.ts +++ b/packages/ag-grid-community/src/clientSideRowModel/clientSideNodeManager.ts @@ -338,7 +338,6 @@ export class ClientSideNodeManager extends BeanStub { node.parent = this.rootNode; node.level = level; node.group = false; - node.expanded = false; if (sourceRowIndex != null) { node.sourceRowIndex = sourceRowIndex; } @@ -413,6 +412,7 @@ const adjustAddIndexForDataPath = (allLeafs: RowNode[], addIndex: const initRootNode = (rootNode: RowNode): RowNode => { rootNode.group = true; rootNode.level = -1; + rootNode._expanded = true; rootNode.id = 'ROOT_NODE_ID'; if (rootNode._leafs?.length !== 0) { rootNode._leafs = []; diff --git a/packages/ag-grid-community/src/context/context.ts b/packages/ag-grid-community/src/context/context.ts index 3764ad5ca7b..b6e754ebb6b 100644 --- a/packages/ag-grid-community/src/context/context.ts +++ b/packages/ag-grid-community/src/context/context.ts @@ -60,7 +60,11 @@ import type { AgGridCommon } from '../interfaces/iCommon'; import type { IContextMenuService } from '../interfaces/iContextMenu'; import type { ICsvCreator } from '../interfaces/iCsvCreator'; import type { IExcelCreator } from '../interfaces/iExcelCreator'; -import type { IExpansionService } from '../interfaces/iExpansionService'; +import type { + IExpansionService, + RowGroupBulkExpansionState, + RowGroupExpansionState, +} from '../interfaces/iExpansionService'; import type { IFindService } from '../interfaces/iFind'; import type { IFooterService } from '../interfaces/iFooterService'; import type { IFrameworkOverrides } from '../interfaces/iFrameworkOverrides'; @@ -334,7 +338,7 @@ interface CoreBeanCollection pageBounds: PageBoundsService; apiFunctionSvc: ApiFunctionService; gridDestroySvc: GridDestroyService; - expansionSvc?: IExpansionService; + expansionSvc?: IExpansionService; sideBar?: ISideBarService; ssrmTxnManager?: IServerSideTransactionManager; aggFuncSvc?: IAggFuncService; diff --git a/packages/ag-grid-community/src/entities/rowNode.ts b/packages/ag-grid-community/src/entities/rowNode.ts index f4b8848209a..64c34a75ec4 100644 --- a/packages/ag-grid-community/src/entities/rowNode.ts +++ b/packages/ag-grid-community/src/entities/rowNode.ts @@ -244,8 +244,23 @@ export class RowNode /** Server Side Row Model Only - the children are in an infinite cache. */ public childStore: IServerSideStore | null; - /** `true` if group is expanded, otherwise `false`. */ - public expanded: boolean; + /** + * Backing field for `expanded` property. + * - `true`/`false`: explicit expansion state. + * - `null`: triggers lazy evaluation — in CSRM, SSRM, getter resolves the default on first access and caches it. + * - `undefined`: uninitialized, means false. + */ + public _expanded: boolean | null | undefined = undefined; + + /** `true` if group or master row is expanded. */ + public get expanded(): boolean { + const expansionSvc = this.beans.expansionSvc; + return expansionSvc ? expansionSvc.isExpanded(this) : this.level === -1 ? true : !!this._expanded; + } + + public set expanded(value: boolean) { + this._expanded = value; + } /** If using footers, reference to the footer node for this group. */ public sibling: RowNode; diff --git a/packages/ag-grid-community/src/interfaces/iExpansionService.ts b/packages/ag-grid-community/src/interfaces/iExpansionService.ts index 623f3e9c3ce..e5f341355d2 100644 --- a/packages/ag-grid-community/src/interfaces/iExpansionService.ts +++ b/packages/ag-grid-community/src/interfaces/iExpansionService.ts @@ -26,9 +26,7 @@ export interface RowGroupBulkExpansionState { } /** @internal AG_GRID_INTERNAL - Not for public use. Can change / be removed at any time. */ -export interface IExpansionService< - T extends RowGroupExpansionState | RowGroupBulkExpansionState = RowGroupExpansionState, -> { +export interface IExpansionService { addExpandedCss(classes: string[], rowNode: RowNode): void; getRowExpandedListeners(rowCtrl: RowCtrl): { @@ -40,6 +38,7 @@ export interface IExpansionService< getExpansionState(): T; expandAll(value: boolean): void; + resetExpansion(): void; onGroupExpandedOrCollapsed(): void; @@ -47,5 +46,7 @@ export interface IExpansionService< isExpandable(rowNode: RowNode): boolean; + isExpanded(rowNode: RowNode): boolean; + setDetailsExpansionState(detailGridApi: GridApi): void; } diff --git a/packages/ag-grid-community/src/misc/state/stateService.ts b/packages/ag-grid-community/src/misc/state/stateService.ts index 9230e0413d7..cdd850ff602 100644 --- a/packages/ag-grid-community/src/misc/state/stateService.ts +++ b/packages/ag-grid-community/src/misc/state/stateService.ts @@ -49,19 +49,7 @@ export class StateService extends BeanStub implements NamedBean { ); // If user is doing a manual expand all node by node, we don't want to process one at a time. // EVENT_ROW_GROUP_OPENED is already async, so no impact of making the state async here. - private readonly onRowGroupOpenedDebounced = _debounce( - this, - () => { - if (this.beans.gos.get('ssrmExpandAllAffectsAllRows')) { - this.updateCachedState('ssrmRowGroupExpansion', this.getRowGroupExpansionState()); - this.updateCachedState('rowGroupExpansion', undefined); - } else { - this.updateCachedState('rowGroupExpansion', this.getRowGroupExpansionState() as RowGroupExpansionState); - this.updateCachedState('ssrmRowGroupExpansion', undefined); - } - }, - 0 - ); + private readonly onRowGroupOpenedDebounced = _debounce(this, () => this.updateGroupExpansionState(), 0); // similar to row expansion, want to debounce. However, selection is synchronous, so need to mark as stale in case `getState` is called. private readonly onRowSelectedDebounced = _debounce( this, @@ -225,7 +213,15 @@ export class StateService extends BeanStub implements NamedBean { columnPivotModeChanged: onUpdate('pivot'), columnRowGroupChanged: onUpdate('rowGroup'), sortChanged: onUpdate('sort'), - newColumnsLoaded: this.updateColumnAndGroupState.bind(this), + newColumnsLoaded: ({ source }) => { + this.updateColumnAndGroupState(); + // When setGridOption('columnDefs') changes row groups, columnRowGroupChanged fires + // while changeEventsDispatching=true, so CSRM defers its re-group to newColumnsLoaded. + // Re-read expansion state here so the cache reflects the post-re-group node IDs. + if (source !== 'gridInitializing' && this.isClientSideRowModel) { + this.onRowGroupOpenedDebounced(); + } + }, columnGroupOpened: () => this.updateCachedState('columnGroup', this.getColumnGroupState()), }); } @@ -249,7 +245,10 @@ export class StateService extends BeanStub implements NamedBean { if (shouldSetState('filter', filterState)) { this.setFilterState(filterState); } - if (shouldSetState('rowGroupExpansion', rowGroupExpansionState)) { + if ( + shouldSetState('rowGroupExpansion', rowGroupExpansionState) || + shouldSetState('ssrmRowGroupExpansion', ssrmRowGroupExpansion) + ) { this.setRowGroupExpansionState(ssrmRowGroupExpansion, rowGroupExpansionState, source); } if (shouldSetState('rowSelection', rowSelectionState)) { @@ -264,13 +263,7 @@ export class StateService extends BeanStub implements NamedBean { const updateCachedState = this.updateCachedState.bind(this); updateCachedState('filter', this.getFilterState()); - if (this.beans.gos.get('ssrmExpandAllAffectsAllRows')) { - updateCachedState('ssrmRowGroupExpansion', this.getRowGroupExpansionState()); - updateCachedState('rowGroupExpansion', undefined); - } else { - updateCachedState('rowGroupExpansion', this.getRowGroupExpansionState() as RowGroupExpansionState); - updateCachedState('ssrmRowGroupExpansion', undefined); - } + this.updateGroupExpansionState(); updateCachedState('rowSelection', this.getRowSelectionState()); updateCachedState('pagination', this.getPaginationState()); @@ -282,13 +275,7 @@ export class StateService extends BeanStub implements NamedBean { const updateCachedState = this.updateCachedState.bind(this); const updateRowGroupExpansionState = () => { this.updateRowGroupExpansionStateTimer = 0; - if (this.beans.gos.get('ssrmExpandAllAffectsAllRows')) { - updateCachedState('ssrmRowGroupExpansion', this.getRowGroupExpansionState()); - updateCachedState('rowGroupExpansion', undefined); - } else { - updateCachedState('rowGroupExpansion', this.getRowGroupExpansionState() as RowGroupExpansionState); - updateCachedState('ssrmRowGroupExpansion', undefined); - } + this.updateGroupExpansionState(); }; const updateFilterState = () => updateCachedState('filter', this.getFilterState()); @@ -835,13 +822,16 @@ export class StateService extends BeanStub implements NamedBean { this.beans.selectionSvc?.setSelectionState(rowSelectionState, source, source === 'api'); } - private getRowGroupExpansionState(): RowGroupExpansionState | RowGroupBulkExpansionState | undefined { - const { expansionSvc } = this.beans; - if (!expansionSvc) { - return undefined; - } + private updateGroupExpansionState(): void { + const { expansionSvc, gos } = this.beans; + const state = expansionSvc?.getExpansionState(); + const ssrmExpandAllAffectsAllRows = gos.get('ssrmExpandAllAffectsAllRows'); - return expansionSvc.getExpansionState(); + this.updateCachedState('ssrmRowGroupExpansion', ssrmExpandAllAffectsAllRows ? state : undefined); + this.updateCachedState( + 'rowGroupExpansion', + ssrmExpandAllAffectsAllRows ? undefined : (state as RowGroupExpansionState) + ); } private getRowPinningState(): RowPinningState | undefined { @@ -862,13 +852,9 @@ export class StateService extends BeanStub implements NamedBean { rowGroupExpansionState: RowGroupExpansionState | undefined, source: 'gridInitializing' | 'api' ): void { - const expansionSvc = this.beans.expansionSvc; - if (!expansionSvc) { - return; - } - - const newExpansionState = rowGroupExpansionState ?? { expandedRowGroupIds: [], collapsedRowGroupIds: [] }; - expansionSvc.setExpansionState(newExpansionState, source); + const state = ssrmRowGroupExpansionState ?? + rowGroupExpansionState ?? { expandedRowGroupIds: [], collapsedRowGroupIds: [] }; + this.beans.expansionSvc?.setExpansionState(state, source); } private updateColumnState(features: (keyof GridState)[]): void { diff --git a/packages/ag-grid-community/src/rendering/row/rowCtrl.ts b/packages/ag-grid-community/src/rendering/row/rowCtrl.ts index 2eb30ebf778..3d2515d6937 100644 --- a/packages/ag-grid-community/src/rendering/row/rowCtrl.ts +++ b/packages/ag-grid-community/src/rendering/row/rowCtrl.ts @@ -287,7 +287,7 @@ export class RowCtrl extends BeanStub { this.executeSlideAndFadeAnimations(gui); if (this.rowNode.group) { - _setAriaExpanded(gui.element, this.rowNode.expanded == true); + _setAriaExpanded(gui.element, !!this.rowNode.expanded); } this.setRowCompRowId(comp); diff --git a/packages/ag-grid-community/src/selection/selectionService.ts b/packages/ag-grid-community/src/selection/selectionService.ts index ffa82b11849..4b565c4035f 100644 --- a/packages/ag-grid-community/src/selection/selectionService.ts +++ b/packages/ag-grid-community/src/selection/selectionService.ts @@ -543,7 +543,7 @@ export class SelectionService extends BaseSelectionService implements NamedBean, return; } - if (!node.expanded && !node.footer) { + if (!node.footer && !node.expanded) { // even with groupSelectsChildren, do this recursively as only the filtered children // are considered as the current page const recursivelyAddChildren = (child: RowNode) => { diff --git a/packages/ag-grid-community/src/valueService/valueService.ts b/packages/ag-grid-community/src/valueService/valueService.ts index fa334c4c450..bbb257c8a5a 100644 --- a/packages/ag-grid-community/src/valueService/valueService.ts +++ b/packages/ag-grid-community/src/valueService/valueService.ts @@ -235,19 +235,22 @@ export class ValueService extends BeanStub implements NamedBean { // in the header when the group is open. // Result is: isOpenedGroup && !groupShowsAggData - // Check isOpenedGroup conditions: node.group && node.expanded && !node.footer && !isPivotLeaf - if (!node.group || !node.expanded || node.footer) { + // Check isOpenedGroup conditions: node.group && !node.footer && !isPivotLeaf && node.expanded + // The root node (level -1) is always expanded but should not suppress its agg data display. + if (!node.group || node.footer || node.level === -1) { + return false; + } + // groupShowsAggData = this.gos.get('groupSuppressBlankHeader') || !node.sibling + // We return true only if !groupShowsAggData, i.e., !groupSuppressBlankHeader && node.sibling + if (!node.sibling || this.gos.get('groupSuppressBlankHeader')) { return false; } // When in pivot mode, leafGroups cannot be expanded if (node.leafGroup && this.colModel.isPivotMode()) { - return false; // isPivotLeaf - not an opened group + return false; } - - // isOpenedGroup is true. Now check if groupShowsAggData. - // groupShowsAggData = this.gos.get('groupSuppressBlankHeader') || !node.sibling - // We return true only if !groupShowsAggData, i.e., !groupSuppressBlankHeader && node.sibling - return !!node.sibling && !this.gos.get('groupSuppressBlankHeader'); + // node.expanded (getter with side effects) evaluated last + return !!node.expanded; } private resolveValue( diff --git a/packages/ag-grid-enterprise/src/masterDetail/masterDetailService.ts b/packages/ag-grid-enterprise/src/masterDetail/masterDetailService.ts index b3b79bf3127..888963f3784 100644 --- a/packages/ag-grid-enterprise/src/masterDetail/masterDetailService.ts +++ b/packages/ag-grid-enterprise/src/masterDetail/masterDetailService.ts @@ -18,8 +18,6 @@ import { _observeResize, } from 'ag-grid-community'; -import { _getRowDefaultExpanded } from '../rowHierarchy/rowHierarchyUtils'; - export class MasterDetailService extends BeanStub implements NamedBean, IMasterDetailService { beanName: BeanName = 'masterDetailSvc' as const; @@ -96,15 +94,14 @@ export class MasterDetailService extends BeanStub implements NamedBean, IMasterD } } - const beans = this.beans; if (!treeData) { // Note that with treeData the initialization of the expansed state is delegated to treeGroupStrategy - if (newMaster && created) { - const level = beans.rowGroupColsSvc?.columns.length ?? 0; - row.expanded = _getRowDefaultExpanded(beans, row, level, false); - } else if (!newMaster && oldMaster) { - // if changing AWAY from master, then un-expand, otherwise next time it's shown it is expanded again - row.expanded = false; + if ( + (newMaster && created) || + // if changing AWAY from master, forget current state + (!newMaster && oldMaster) + ) { + row._expanded ??= null; } } diff --git a/packages/ag-grid-enterprise/src/rowGrouping/groupStrategy/groupStrategy.ts b/packages/ag-grid-enterprise/src/rowGrouping/groupStrategy/groupStrategy.ts index 2e9ff4eb2ef..c5437ce9213 100644 --- a/packages/ag-grid-enterprise/src/rowGrouping/groupStrategy/groupStrategy.ts +++ b/packages/ag-grid-enterprise/src/rowGrouping/groupStrategy/groupStrategy.ts @@ -1,15 +1,14 @@ import type { AgColumn, ChangedPath, - GridOptions, IRowNode, + InitialGroupOrderComparator, RefreshModelParams, _ChangedRowNodes, } from 'ag-grid-community'; import { BeanStub, RowNode, _csrmFirstLeaf, _forEachChangedGroupDepthFirst, _warn } from 'ag-grid-community'; import type { IRowGroupingStrategy } from '../../rowHierarchy/rowHierarchyUtils'; -import { _getRowDefaultExpanded } from '../../rowHierarchy/rowHierarchyUtils'; import { setRowNodeGroup } from '../rowGroupingUtils'; import type { GroupColumn } from './groupColumns'; import { groupColumnsChanged, makeGroupColumns } from './groupColumns'; @@ -82,8 +81,9 @@ export class GroupStrategy extends BeanStub implements IRowGroupingStrategy { } public execute(rootNode: RowNode, params: RefreshModelParams): void { - const changedPath = params.changedPath; - if (this.initRefresh(params)) { + const changedPath = params.changedPath!; + const refreshResult = this.initRefresh(params); + if (refreshResult !== 'skip') { const changedRowNodes = params.changedRowNodes; if (changedRowNodes) { this.handleDeltaUpdate(rootNode, changedPath, changedRowNodes, !!params.animate); @@ -137,11 +137,11 @@ export class GroupStrategy extends BeanStub implements IRowGroupingStrategy { }); } - private initRefresh(params: RefreshModelParams): boolean { + private initRefresh(params: RefreshModelParams): 'skip' | 'refresh' | 'groupColsChanged' { const { rowGroupColsSvc, colModel, gos } = this.beans; - const pivotMode = colModel.isPivotMode(); - this.pivotMode = pivotMode; - this.groupEmpty = pivotMode || !gos.get('groupAllowUnbalanced'); + + this.pivotMode = colModel.isPivotMode(); + this.groupEmpty = this.pivotMode || !gos.get('groupAllowUnbalanced'); const cols = rowGroupColsSvc?.columns; const groupCols = this.groupCols; const afterColumnsChanged = params.afterColumnsChanged; @@ -149,16 +149,19 @@ export class GroupStrategy extends BeanStub implements IRowGroupingStrategy { this.checkGroupCols = false; if (groupCols && !groupColumnsChanged(groupCols, cols)) { if (afterColumnsChanged) { - return false; // no change to grouping + return 'skip'; // no change to grouping } } else { - // Group columns changed. - params.animate = false; // if grouping columns change, we don't animate the regrouping - makeGroupColumns(cols, this.groupCols); + params.animate = false; + // If the top-level group column changed, every existing node ID will differ after + // rebuild, so no group nodes can be reused. Check before makeGroupColumns overwrites groupCols. + const topLevelChanged = groupCols[0]?.col.getId() !== cols?.[0]?.getId(); + makeGroupColumns(cols, groupCols); + return topLevelChanged ? 'refresh' : 'groupColsChanged'; } } - return true; + return 'refresh'; } private handleDeltaUpdate( @@ -223,7 +226,7 @@ export class GroupStrategy extends BeanStub implements IRowGroupingStrategy { } private orderGroups(rootNode: RowNode): void { - const initialGroupOrderComparator: GridOptions['initialGroupOrderComparator'] = + const initialGroupOrderComparator: InitialGroupOrderComparator | undefined = this.gos.getCallback('initialGroupOrderComparator'); if (!initialGroupOrderComparator) { return; @@ -408,10 +411,16 @@ export class GroupStrategy extends BeanStub implements IRowGroupingStrategy { } private shotgunResetEverything(rootNode: RowNode): void { - // groups are about to get disposed, so need to deselect any that are selected - this.beans.selectionSvc?.filterFromSelection?.((node) => !node.group); + const groupsById = this.nonLeafsById; + + // Mark all existing group nodes as stale by nulling childrenAfterGroup. + // Nodes that are reused during insertion will have childrenAfterGroup reset to []. + // Nodes still null after insertion are stale and will be destroyed. + for (const node of groupsById.values()) { + node.childrenAfterGroup = null; + node.childrenMapped = null; + } - this.nonLeafsById.clear(); // because we are not creating the root node each time, we have the logic // here to change leafGroup once. rootNode.leafGroup = !this.groupCols?.length; @@ -431,11 +440,37 @@ export class GroupStrategy extends BeanStub implements IRowGroupingStrategy { for (let i = 0, len = allLeafs.length; i < len; ++i) { this.insertOneNode(rootNode, allLeafs[i]); } + + // Destroy stale group nodes that were not reused during insertion + this.destroyStaleGroups(groupsById); + } + + /** Remove and destroy group nodes that were not reused (still have childrenAfterGroup === null) */ + private destroyStaleGroups(groupsById: Map): void { + const selectionSvc = this.beans.selectionSvc; + let nodesToDeselect: RowNode[] | undefined; + for (const [id, node] of groupsById) { + if (node.childrenAfterGroup !== null) { + continue; + } + if (selectionSvc && node.isSelected()) { + (nodesToDeselect ??= []).push(node); + } + groupsById.delete(id); + node._destroy(false); + } + if (nodesToDeselect) { + selectionSvc!.setNodesSelected({ + nodes: nodesToDeselect, + newValue: false, + source: 'rowGroupChanged', + }); + } } private insertOneNode(rootNode: RowNode, childNode: RowNode): void { let parentGroup = rootNode; - const { beans, pivotMode, groupCols, groupEmpty } = this; + const { beans, groupCols, groupEmpty } = this; const valueSvc = beans.valueSvc; if (!groupCols) { return; @@ -460,7 +495,7 @@ export class GroupStrategy extends BeanStub implements IRowGroupingStrategy { const isLeafLevel = nextLevel >= len - 1; const newGroup = this.createGroup(parentGroup, groupCol, key, nextLevel, isLeafLevel, childNode); this.addToParent(newGroup, parentGroup); - this.setExpandedInitialValue(pivotMode, newGroup); + parentGroup = newGroup; } if (!parentGroup.group) { @@ -485,37 +520,62 @@ export class GroupStrategy extends BeanStub implements IRowGroupingStrategy { const id = (parent.level >= 0 ? parent.id! + '-' : 'row-group-') + (col.getColId() + '-' + key); const groupsById = this.nonLeafsById; - let groupNode = groupsById.get(id); - if (groupNode !== undefined) { - return groupNode; // already exists - } - - groupNode = new RowNode(this.beans); - groupNode.group = true; - groupNode.parent = parent; - groupNode.field = groupCol.field ?? null; - groupNode.rowGroupColumn = col; - - groupNode.key = key; - groupNode.id = id; - - groupNode.level = level; - groupNode.leafGroup = isLeafLevel; + let node = groupsById.get(id); + let singleUse = true; + + if (node) { + if (node.childrenAfterGroup !== null) { + // Already active — just ensure lazy expansion state. + node._expanded ??= null; + return node; + } + singleUse = false; + // Reused existing group node from a shotgun reset. + // Reset children but preserve selection and expansion state. + invalidateAllLeafChildren(node); + } else { + // Brand new group node. + node = new RowNode(this.beans); + node.group = true; + node.key = key; + node.id = id; + node.aggData = null; + groupsById.set(id, node); + } + + const applyValuesToNode = (n: RowNode) => { + n.childrenAfterGroup = children; + n.childrenMapped = mapped; + n.parent = parent; + n.level = level; + n.rowGroupIndex = level; + n.leafGroup = isLeafLevel; + }; - groupNode.rowGroupIndex = level; - groupNode.childrenAfterGroup = []; - groupNode.childrenMapped = {}; + // Shared setup for both reused and new nodes. + const children: RowNode[] = []; + const mapped = {}; + applyValuesToNode(node); + node.field = groupCol.field ?? null; + node.rowGroupColumn = col; + node.groupValue = this.beans.valueSvc.getValue(col, leafNode, 'data'); + // null triggers lazy default resolution in the expanded getter. + node._expanded ??= null; - groupsById.set(id, groupNode); + if (singleUse) { + node.setAllChildrenCount(0); + node.updateHasChildren(); + return node; + } - groupNode.groupValue = leafNode && this.beans.valueSvc.getValue(col, leafNode, 'data'); + const sibling = node.sibling; + if (sibling) { + applyValuesToNode(sibling); + } - // why is this done here? we are not updating the children count as we go, - // i suspect this is updated in the filter stage - groupNode.setAllChildrenCount(0); - groupNode.updateHasChildren(); + node.dispatchRowEvent('hasChildrenChanged'); - return groupNode; + return node; } private getChildrenMappedKey(key: string, rowGroupColumn: AgColumn | null): string { @@ -523,16 +583,6 @@ export class GroupStrategy extends BeanStub implements IRowGroupingStrategy { return rowGroupColumn ? rowGroupColumn.getId() + '-' + key : key; } - private setExpandedInitialValue(pivotMode: boolean, groupNode: RowNode): void { - // if pivoting the leaf group is never expanded as we do not show leaf rows - if (pivotMode && groupNode.leafGroup) { - groupNode.expanded = false; - return; - } - - groupNode.expanded = _getRowDefaultExpanded(this.beans, groupNode, groupNode.level); - } - public onShowRowGroupColsSetChanged(): void { const { rowModel, valueSvc } = this.beans; for (const groupNode of this.nonLeafsById.values()) { diff --git a/packages/ag-grid-enterprise/src/rowGrouping/rowGroupingUtils.ts b/packages/ag-grid-enterprise/src/rowGrouping/rowGroupingUtils.ts index c3f726fa68b..cc2c51c44ee 100644 --- a/packages/ag-grid-enterprise/src/rowGrouping/rowGroupingUtils.ts +++ b/packages/ag-grid-enterprise/src/rowGrouping/rowGroupingUtils.ts @@ -34,7 +34,6 @@ export function setRowNodeGroup(rowNode: RowNode, beans: BeanCollection, group: // if we used to be a group, and no longer, then close the node if (rowNode.group && !group) { - rowNode.expanded = false; // Clear stale aggData when demoting from group to leaf. const colModel = beans.colModel; setAggData(rowNode, null, colModel); diff --git a/packages/ag-grid-enterprise/src/rowHierarchy/baseExpansionService.ts b/packages/ag-grid-enterprise/src/rowHierarchy/baseExpansionService.ts index fe7450534f4..e79df2a55fa 100644 --- a/packages/ag-grid-enterprise/src/rowHierarchy/baseExpansionService.ts +++ b/packages/ag-grid-enterprise/src/rowHierarchy/baseExpansionService.ts @@ -1,5 +1,5 @@ -import type { RowCtrl, RowGroupOpenedEvent, RowNode } from 'ag-grid-community'; -import { BeanStub, _createGlobalRowEvent, _setAriaExpanded } from 'ag-grid-community'; +import type { IsGroupOpenByDefaultParams, RowCtrl, RowGroupOpenedEvent, RowNode } from 'ag-grid-community'; +import { BeanStub, _addGridCommonParams, _createGlobalRowEvent, _setAriaExpanded } from 'ag-grid-community'; export abstract class BaseExpansionService extends BeanStub { protected abstract dispatchExpandedEvent(event: RowGroupOpenedEvent, forceSync?: boolean): void; @@ -28,7 +28,7 @@ export abstract class BaseExpansionService extends BeanStub { return; } - rowNode.expanded = expanded; + rowNode._expanded = expanded; rowNode.dispatchRowEvent('expandedChanged'); @@ -37,6 +37,27 @@ export abstract class BaseExpansionService extends BeanStub { this.dispatchExpandedEvent(event, forceSync); } + public defaultExpanded(rowNode: RowNode): boolean { + const beans = this.beans; + const gos = beans.gos; + const level = rowNode.level ?? 0; + // see AG-11476 isGroupOpenByDefault callback doesn't apply to master/detail grid + // We call isGroupOpenByDefault only for group nodes and not for master/detail leafs + const isGroupOpenByDefault = rowNode.group && gos.get('isGroupOpenByDefault'); + if (!isGroupOpenByDefault) { + const groupDefaultExpanded = gos.get('groupDefaultExpanded'); + return groupDefaultExpanded === -1 || level < groupDefaultExpanded; + } + const params = _addGridCommonParams(gos, { + rowNode, + field: rowNode.field!, + key: rowNode.key!, + level, + rowGroupColumn: rowNode.rowGroupColumn!, + }); + return !!isGroupOpenByDefault(params); + } + public isExpandable(rowNode: RowNode): boolean { if (rowNode.footer) { return false; diff --git a/packages/ag-grid-enterprise/src/rowHierarchy/clientSideExpansionService.ts b/packages/ag-grid-enterprise/src/rowHierarchy/clientSideExpansionService.ts index 008205f68e3..6e59a84a033 100644 --- a/packages/ag-grid-enterprise/src/rowHierarchy/clientSideExpansionService.ts +++ b/packages/ag-grid-enterprise/src/rowHierarchy/clientSideExpansionService.ts @@ -35,7 +35,7 @@ export class ClientSideExpansionService return; } - node.expanded = rowIdsToExpandSet.has(id); + node._expanded = rowIdsToExpandSet.has(id); }); this.onGroupExpandedOrCollapsed(); } @@ -62,6 +62,37 @@ export class ClientSideExpansionService return this.getInternalExpansionState(); } + public isExpanded(rowNode: RowNode): boolean { + // Footer nodes use their own _expanded backing field directly (copied from group at creation time). + // This preserves the snapshot semantics from when expanded was a plain field. + if (rowNode.footer) { + return !!rowNode._expanded; + } + if (!(rowNode.group || rowNode.master) || (rowNode.leafGroup && this.beans.colModel.isPivotMode())) { + return false; // Not expandable, so always return false + } + let value = rowNode._expanded; + if (value === null) { + // Lazy resolution of the default expansion state via the enterprise expansion service. + value = this.defaultExpanded(rowNode) ?? false; + rowNode._expanded = value; + } + return !!value; + } + + public resetExpansion(): void { + const { rowModel } = this.beans; + + rowModel.forEachNode((node) => { + if (!node.group && !node.master) { + return; + } + node._expanded = null; // null triggers lazy default resolution in the expanded getter + }); + + this.onGroupExpandedOrCollapsed(); + } + public expandAll(expand: boolean): void { const { gos, rowModel, colModel, eventSvc } = this.beans; const usingTreeData = gos.get('treeData'); @@ -73,7 +104,7 @@ export class ClientSideExpansionService } for (const rowNode of rowNodes) { const actionRow = () => { - rowNode.expanded = expand; + rowNode._expanded = expand; recursiveExpandOrCollapse(rowNode.childrenAfterGroup); }; diff --git a/packages/ag-grid-enterprise/src/rowHierarchy/flattenUtils.ts b/packages/ag-grid-enterprise/src/rowHierarchy/flattenUtils.ts index 97e6e832ad5..635d6db9bb5 100644 --- a/packages/ag-grid-enterprise/src/rowHierarchy/flattenUtils.ts +++ b/packages/ag-grid-enterprise/src/rowHierarchy/flattenUtils.ts @@ -57,19 +57,30 @@ export function _shouldRowBeRendered( isRemovedSingleChildrenGroup: boolean, isRemovedLowestSingleChildrenGroup: boolean | undefined ): boolean { - const isSkippedLeafNode = skipLeafNodes && !isParent; + if (skipLeafNodes && !isParent) { + return false; + } + if (isRemovedSingleChildrenGroup || isRemovedLowestSingleChildrenGroup) { + return false; + } // hide open parents means when group is open, we don't show it. we also need to make sure the // group is expandable in the first place (as leaf groups are not expandable if pivot mode is on). // the UI will never allow expanding leaf groups, however the user might via the API (or menu option 'expand all row groups') + if (!details.hideOpenParents) { + return true; + } + + if (rowNode.master || rowNode.level === -1) { + return true; + } + const neverAllowToExpand = skipLeafNodes && rowNode.leafGroup; - const isHiddenOpenParent = details.hideOpenParents && rowNode.expanded && !rowNode.master && !neverAllowToExpand; + // rowNode.expanded evaluated LAST because it has side effects + if (!neverAllowToExpand && rowNode.expanded) { + return false; + } - return ( - !isSkippedLeafNode && - !isHiddenOpenParent && - !isRemovedSingleChildrenGroup && - !isRemovedLowestSingleChildrenGroup - ); + return true; } diff --git a/packages/ag-grid-enterprise/src/rowHierarchy/rendering/groupCellRendererCtrl.ts b/packages/ag-grid-enterprise/src/rowHierarchy/rendering/groupCellRendererCtrl.ts index 4a4f34f0c9a..a598125044d 100644 --- a/packages/ag-grid-enterprise/src/rowHierarchy/rendering/groupCellRendererCtrl.ts +++ b/packages/ag-grid-enterprise/src/rowHierarchy/rendering/groupCellRendererCtrl.ts @@ -208,11 +208,11 @@ export class GroupCellRendererCtrl extends BeanStub implements IGroupCellRendere return; } - const expanded = this.displayedNode.expanded; + const expanded = !!this.displayedNode.expanded; comp.setExpandedDisplayed(expanded); comp.setContractedDisplayed(!expanded); - _setAriaExpanded(eGridCell, !!this.displayedNode.expanded); + _setAriaExpanded(eGridCell, expanded); }; const onExpandableChanged = () => { diff --git a/packages/ag-grid-enterprise/src/rowHierarchy/rowHierarchyUtils.ts b/packages/ag-grid-enterprise/src/rowHierarchy/rowHierarchyUtils.ts index b9b32ed24c8..bfd90cba121 100644 --- a/packages/ag-grid-enterprise/src/rowHierarchy/rowHierarchyUtils.ts +++ b/packages/ag-grid-enterprise/src/rowHierarchy/rowHierarchyUtils.ts @@ -1,9 +1,7 @@ import type { Bean, - BeanCollection, GridOptions, GridOptionsService, - IRowNode, NestedDataGetter, RefreshModelParams, RowNode, @@ -60,29 +58,3 @@ export function _isHiddenParent(node: RowNode, ancestor: RowNode, gos: GridOptio } return currentNode === ancestor; } - -export const _getRowDefaultExpanded = ( - beans: BeanCollection, - rowNode: IRowNode, - level: number, - group = rowNode.group -): boolean => { - const gos = beans.gos; - // see AG-11476 isGroupOpenByDefault callback doesn't apply to master/detail grid - // We call isGroupOpenByDefault only for group nodes and not for master/detail leafs - const isGroupOpenByDefault = group && gos.get('isGroupOpenByDefault'); - if (!isGroupOpenByDefault) { - const groupDefaultExpanded = gos.get('groupDefaultExpanded'); - return groupDefaultExpanded === -1 || level < groupDefaultExpanded; - } - const params = { - api: beans.gridApi, - context: beans.gridOptions.context, - rowNode, - field: rowNode.field!, - key: rowNode.key!, - level, - rowGroupColumn: rowNode.rowGroupColumn!, - }; - return isGroupOpenByDefault(params) == true; -}; diff --git a/packages/ag-grid-enterprise/src/rowHierarchy/stickyRowFeature.ts b/packages/ag-grid-enterprise/src/rowHierarchy/stickyRowFeature.ts index 36a2b4be146..abb22d53e1b 100644 --- a/packages/ag-grid-enterprise/src/rowHierarchy/stickyRowFeature.ts +++ b/packages/ag-grid-enterprise/src/rowHierarchy/stickyRowFeature.ts @@ -174,7 +174,7 @@ export class StickyRowFeature extends BeanStub implements IStickyRowFeature { return false; } const alreadySticking = newStickyRows.has(row); - return !alreadySticking && row.expanded; + return !alreadySticking && !!row.expanded; } return false; diff --git a/packages/ag-grid-enterprise/src/serverSideRowModel/blocks/blockUtils.ts b/packages/ag-grid-enterprise/src/serverSideRowModel/blocks/blockUtils.ts index 7fa476a084e..678533008d2 100644 --- a/packages/ag-grid-enterprise/src/serverSideRowModel/blocks/blockUtils.ts +++ b/packages/ag-grid-enterprise/src/serverSideRowModel/blocks/blockUtils.ts @@ -404,11 +404,11 @@ export class BlockUtils extends BeanStub implements NamedBean { public checkOpenByDefault(rowNode: RowNode): void { const expanded = !!this.expansionSvc?.isNodeExpanded(rowNode); - const oldExpanded = rowNode.expanded; + const oldExpanded = rowNode._expanded; if (!!oldExpanded !== expanded) { rowNode.setExpanded(expanded); } else if (oldExpanded === undefined) { - rowNode.expanded = expanded; // Initial state, don't fire event + rowNode._expanded = expanded; // Initial state, don't fire event } } } diff --git a/packages/ag-grid-enterprise/src/serverSideRowModel/services/serverSideExpansionService.ts b/packages/ag-grid-enterprise/src/serverSideRowModel/services/serverSideExpansionService.ts index 9a63122de4e..c347d5e0c78 100644 --- a/packages/ag-grid-enterprise/src/serverSideRowModel/services/serverSideExpansionService.ts +++ b/packages/ag-grid-enterprise/src/serverSideRowModel/services/serverSideExpansionService.ts @@ -37,9 +37,14 @@ export class ServerSideExpansionService this.storeFactory = beans.ssrmStoreFactory as StoreFactory; } + private setStrategy(strategy: ExpandStrategy | ExpandAllStrategy): void { + this.destroyBean(this.strategy as any); + this.strategy = this.createManagedBean(strategy); + } + public postConstruct(): void { const setDefaultExpand = () => { - this.strategy = this.createManagedBean(new ExpandStrategy()); + this.setStrategy(new ExpandStrategy()); }; this.addManagedEventListeners({ @@ -53,7 +58,7 @@ export class ServerSideExpansionService // reset strategy if explicitly disabled, otherwise state is fine to remain until new // select all value is set/removed if (!p.currentValue) { - this.strategy = this.createManagedBean(new ExpandStrategy()); + this.setStrategy(new ExpandStrategy()); this.updateAllNodes(); this.dispatchStateUpdatedEvent(); } @@ -67,9 +72,7 @@ export class ServerSideExpansionService const isExpandAllStrategy = this.isExpandAllStrategy(this.strategy); if (isExpandAllState !== isExpandAllStrategy) { - this.strategy = isExpandAllState - ? this.createManagedBean(new ExpandAllStrategy()) - : this.createManagedBean(new ExpandStrategy()); + this.setStrategy(isExpandAllState ? new ExpandAllStrategy() : new ExpandStrategy()); } this.strategy.setExpandedState(state as any); // cast to any, as we know the type is correct due to the previous assertion this.dispatchStateUpdatedEvent(); @@ -89,6 +92,19 @@ export class ServerSideExpansionService }); } + public isExpanded(rowNode: RowNode): boolean { + let value = rowNode._expanded; + if (value === null) { + value = this.defaultExpanded(rowNode); + rowNode._expanded = value; + } + // This could be returning undefined which is currently + // handled via coercion in SSRM. When fixing this to always + // return a boolean must validate that the undefined state is + // correctly handled. + return value as boolean; + } + public isNodeExpanded(node: RowNode): boolean { return this.strategy.isRowExpanded(node); } @@ -100,12 +116,20 @@ export class ServerSideExpansionService this.updateExpandedState(node); } + public resetExpansion(): void { + this.setStrategy(new ExpandStrategy()); + this.updateAllNodes(); + this.dispatchStateUpdatedEvent(); + } + public expandAll(expanded: boolean): void { const ssrmExpandAllAffectsAllRows = this.beans.gos.get('ssrmExpandAllAffectsAllRows'); // if allowed, swap to expand all strategy const shouldUseExpandAllStrategy = !this.isExpandAllStrategy(this.strategy) && ssrmExpandAllAffectsAllRows; - this.strategy = shouldUseExpandAllStrategy ? new ExpandAllStrategy() : this.strategy; + if (shouldUseExpandAllStrategy) { + this.setStrategy(new ExpandAllStrategy()); + } this.strategy.expandAll(expanded); this.updateAllNodes(); this.dispatchStateUpdatedEvent(); diff --git a/packages/ag-grid-enterprise/src/treeData/treeGroupStrategy.ts b/packages/ag-grid-enterprise/src/treeData/treeGroupStrategy.ts index 238cec67dab..44c0c8702f2 100644 --- a/packages/ag-grid-enterprise/src/treeData/treeGroupStrategy.ts +++ b/packages/ag-grid-enterprise/src/treeData/treeGroupStrategy.ts @@ -9,7 +9,6 @@ import { BeanStub, RowNode, _removeFromArray, _warn } from 'ag-grid-community'; import { setRowNodeGroup } from '../rowGrouping/rowGroupingUtils'; import type { IRowGroupingStrategy } from '../rowHierarchy/rowHierarchyUtils'; -import { _getRowDefaultExpanded } from '../rowHierarchy/rowHierarchyUtils'; import { fieldGetter } from './fieldAccess'; // The approach used here avoids complex incremental updates by using linear passes and a final traversal. @@ -20,7 +19,7 @@ import { fieldGetter } from './fieldAccess'; // This guarantees correct parent-child relationships without requiring sorting or post-processing. // // No new arrays are allocated for childrenAfterGroup — existing arrays are reused. -// The treeNodeFlags field encodes temporary state, child counters, and expanded status. +// The treeNodeFlags field encodes temporary state and child counters. // The treeParent field tracks hierarchy changes and supports re-parenting (e.g., drag-and-drop). // Setting a node treeParent to a desired node and then executing grouping without full reload will generate a valid tree. // @@ -34,9 +33,6 @@ const FLAG_CHANGED = 0x40000000; /** Toggling this flag is used to mark a filler node as used or already processed */ const FLAG_MARKED_FILLER = 0x20000000; -/** This is the only flag that stays in the node, to indicate that the expanded state was initialized */ -const FLAG_EXPANDED_INITIALIZED = 0x10000000; - /** Mask used to keep track of the number of children in a node */ const MASK_CHILDREN_LEN = 0x0fffffff; // This equates to 268,435,455 maximum children per parent, more than enough @@ -219,8 +215,8 @@ export class TreeGroupStrategy extends BeanStub implements IRowGrou } if (parentChanged && oldParent) { - if (oldParent.destroyed && maybeExpandFromRemovedParent(parent, oldParent)) { - parentFlags |= FLAG_EXPANDED_INITIALIZED; + if (oldParent.destroyed) { + maybeExpandFromRemovedParent(parent, oldParent); } oldParent.treeNodeFlags |= FLAG_CHANGED; } @@ -360,7 +356,7 @@ export class TreeGroupStrategy extends BeanStub implements IRowGrou const len = children?.length ?? 0; let flags = row.treeNodeFlags; - row.treeNodeFlags = flags & FLAG_EXPANDED_INITIALIZED; + row.treeNodeFlags = 0; row.level = level; // Update group state and children markers @@ -376,21 +372,16 @@ export class TreeGroupStrategy extends BeanStub implements IRowGrou const canBeExpanded = len !== 0 || row.master; if (!canBeExpanded) { - if (row.expanded) { - row.expanded = false; - } - if ((flags & FLAG_EXPANDED_INITIALIZED) !== 0) { - row.treeNodeFlags &= ~FLAG_EXPANDED_INITIALIZED; - } - } else if ((flags & FLAG_EXPANDED_INITIALIZED) === 0) { - row.treeNodeFlags |= FLAG_EXPANDED_INITIALIZED; - row.expanded = _getRowDefaultExpanded(this.beans, row, level); // Initialize the expanded state + row._expanded = undefined; + } else { + // null triggers lazy default resolution in the expanded getter + row._expanded ??= null; } if (collapsed && row.rowIndex !== null) { row.clearRowTopAndRowIndex(); // Mark row hidden if collapsed } - collapsed ||= row.expanded === false; + collapsed ||= !row.expanded; ++level; // Increment level as it is passed down to children flags &= FLAG_CHILDREN_CHANGED; @@ -861,19 +852,16 @@ type DuplicatePathsMap = Map[]>; /** * If parent is a new filler node, copy the expanded flag from old removed parent. - * Returns true if the expanded flag was copied. */ -const maybeExpandFromRemovedParent = (parent: RowNode, oldParent: RowNode): boolean => { +const maybeExpandFromRemovedParent = (parent: RowNode, oldParent: RowNode): void => { if ( - (oldParent.treeNodeFlags & FLAG_EXPANDED_INITIALIZED) !== 0 && - (parent.treeNodeFlags & FLAG_EXPANDED_INITIALIZED) === 0 && + oldParent._expanded !== undefined && + parent._expanded === undefined && parent.treeParent !== null && parent.sourceRowIndex < 0 ) { - parent.expanded = oldParent.expanded; - return true; + parent._expanded = oldParent._expanded; } - return false; }; const updateNodeKey = (node: RowNode, key: string): void => { diff --git a/testing/behavioural/src/cell-editing/benchmarks/getvalue.bench.ts b/testing/behavioural/src/cell-editing/benchmarks/getvalue.bench.ts new file mode 100644 index 00000000000..c5d177d685c --- /dev/null +++ b/testing/behavioural/src/cell-editing/benchmarks/getvalue.bench.ts @@ -0,0 +1,62 @@ +import { bench, describe } from 'vitest'; + +import type { GridApi, IRowNode } from 'ag-grid-community'; +import { ClientSideRowModelModule, RowApiModule } from 'ag-grid-community'; + +import { SimplePRNG, TestGridsManager } from '../../test-utils'; + +interface IData { + name: string; + id: string; +} + +function buildRandomData(numberOfRows: number): IData[] { + const prng = new SimplePRNG(0x12345678); + const result = new Array(numberOfRows); + for (let i = 0; i < numberOfRows; i++) { + result[i] = { name: prng.nextString(10), id: i.toString() }; + } + return result; +} + +describe('getValue profiling', () => { + const rowCount = 2000; + + const gridsManager = new TestGridsManager({ + benchmark: true, + modules: [ClientSideRowModelModule, RowApiModule], + }); + + const baseRowData = buildRandomData(rowCount); + const api: GridApi = gridsManager.createGrid('G', { + columnDefs: [{ field: 'name' }], + rowData: baseRowData, + getRowId: ({ data }) => data.id, + }); + + const rowNodes: IRowNode[] = []; + api.forEachNode((n) => rowNodes.push(n)); + + bench(`getDataValue`, () => { + for (let i = 0; i < rowCount; ++i) { + rowNodes[i].getDataValue('name'); + } + }); + + bench(`getCellValue`, () => { + for (let i = 0; i < rowCount; ++i) { + api.getCellValue({ rowNode: rowNodes[i], colKey: 'name', useFormatter: false }); + } + }); + + bench(`direct data.name `, () => { + let sum = 0; + for (let i = 0; i < rowCount; ++i) { + const val = (rowNodes[i] as any).data.name; + if (val) { + sum++; + } + } + return sum as any; + }); +}); diff --git a/testing/behavioural/src/drag-n-drop/grouping/managed-row-group-drag-edit-basic.test.ts b/testing/behavioural/src/drag-n-drop/grouping/managed-row-group-drag-edit-basic.test.ts index ff578733817..ed213eb369b 100644 --- a/testing/behavioural/src/drag-n-drop/grouping/managed-row-group-drag-edit-basic.test.ts +++ b/testing/behavioural/src/drag-n-drop/grouping/managed-row-group-drag-edit-basic.test.ts @@ -90,13 +90,14 @@ describe('drag refreshAfterGroupEdit multi-step interactions', () => { `; const assertIntermediateStep = async (expectedParentId: string, snapshot: string, label: string) => { - await asyncSetTimeout(0); - const rowDragMoveEvents = dispatcher.rowDragMoveEvents; - expect(rowDragMoveEvents.some((event) => event.rowsDrop?.newParent?.id === expectedParentId)).toBe(true); - const latestRowsDrop = rowDragMoveEvents[rowDragMoveEvents.length - 1]?.rowsDrop; - expect(latestRowsDrop?.allowed).toBe(true); - expect(latestRowsDrop?.moved).toBe(true); await waitFor(async () => { + const rowDragMoveEvents = dispatcher.rowDragMoveEvents; + expect(rowDragMoveEvents.some((event) => event.rowsDrop?.newParent?.id === expectedParentId)).toBe( + true + ); + const latestRowsDrop = rowDragMoveEvents[rowDragMoveEvents.length - 1]?.rowsDrop; + expect(latestRowsDrop?.allowed).toBe(true); + expect(latestRowsDrop?.moved).toBe(true); const intermediateRows = new GridRows(api, label); await intermediateRows.check(snapshot); const draggedRowElement = getRowHtmlElement(api, '2'); @@ -109,10 +110,8 @@ describe('drag refreshAfterGroupEdit multi-step interactions', () => { await dispatcher.start('2'); await waitFor(() => expect(dispatcher.getDragGhostLabel()).toBe('A2')); await dispatcher.move('3', { yOffsetPercent: 0.4 }); - await asyncSetTimeout(15); await assertIntermediateStep('row-group-group-B', intermediateHoverLeaf, 'after hover over group B leaf'); await dispatcher.move('row-group-group-C', { center: true }); - await asyncSetTimeout(8); await assertIntermediateStep('row-group-group-C', intermediateHoverLeaf, 'after hover over group C group node'); await dispatcher.move('6', { yOffsetPercent: 0.9 }); await dispatcher.finish(); diff --git a/testing/behavioural/src/grid-state/grid-state.test.ts b/testing/behavioural/src/grid-state/grid-state.test.ts index 08d8626058f..739f072bb90 100644 --- a/testing/behavioural/src/grid-state/grid-state.test.ts +++ b/testing/behavioural/src/grid-state/grid-state.test.ts @@ -1,7 +1,7 @@ -import type { GridState } from 'ag-grid-community'; +import type { GridState, IServerSideDatasource, IServerSideGetRowsParams } from 'ag-grid-community'; import { AllEnterpriseModule } from 'ag-grid-enterprise'; -import { TestGridsManager, asyncSetTimeout } from '../test-utils'; +import { TestGridsManager, asyncSetTimeout, waitForNoLoadingRows } from '../test-utils'; describe('StateService - Grid State Management', () => { const gridsManager = new TestGridsManager({ @@ -257,26 +257,30 @@ describe('StateService - Grid State Management', () => { expect(api.getState().rowSelection).toEqual(['0', '2']); }); - test('should capture row group expansion state - TODO FIX', async () => { + test('CSRM: getState captures rowGroupExpansion, setState restores it', async () => { const columnDefs = [ { field: 'id', hide: false }, { field: 'sport', hide: false, rowGroup: true }, { field: 'name', hide: false }, { field: 'age', hide: false }, ]; - const api = gridsManager.createGrid('myGrid', { + const api = gridsManager.createGrid('source', { columnDefs, rowData: defaultRowData, + getRowId: ({ data }) => data.id, }); + // Initially all groups collapsed expect(api.getState().rowGroupExpansion).toEqual({ collapsedRowGroupIds: [], expandedRowGroupIds: [], }); + expect(api.getState().ssrmRowGroupExpansion).toBeUndefined(); api.expandAll(); - expect(api.getState().rowGroupExpansion).toEqual({ + const savedState = api.getState(); + expect(savedState.rowGroupExpansion).toEqual({ collapsedRowGroupIds: [], expandedRowGroupIds: [ 'row-group-sport-Football', @@ -286,6 +290,199 @@ describe('StateService - Grid State Management', () => { 'row-group-sport-Swimming', ], }); + expect(savedState.ssrmRowGroupExpansion).toBeUndefined(); + + // Restore into a fresh grid + const api2 = gridsManager.createGrid('target', { + columnDefs, + rowData: defaultRowData, + getRowId: ({ data }) => data.id, + initialState: savedState, + }); + + expect(api2.getState().rowGroupExpansion).toEqual(savedState.rowGroupExpansion); + }); + + test('SSRM: getState captures rowGroupExpansion, setState restores it', async () => { + const datasource: IServerSideDatasource = { + getRows({ request, success }: IServerSideGetRowsParams) { + if (request.groupKeys.length === 0) { + success({ + rowData: [ + { id: 'ie', country: 'Ireland' }, + { id: 'fr', country: 'France' }, + ], + }); + } else { + success({ + rowData: [{ id: `${request.groupKeys[0]}-leaf`, country: request.groupKeys[0], medals: 5 }], + }); + } + }, + }; + + const api = gridsManager.createGrid('ssrmSource', { + columnDefs: [{ field: 'country', rowGroup: true, hide: true }, { field: 'medals' }], + rowModelType: 'serverSide', + serverSideDatasource: datasource, + getRowId: ({ data }) => data.id, + }); + + await waitForNoLoadingRows(api); + + // Without ssrmExpandAllAffectsAllRows, SSRM uses rowGroupExpansion (same as CSRM) + expect(api.getState().rowGroupExpansion).toEqual({ expandedRowGroupIds: [], collapsedRowGroupIds: [] }); + expect(api.getState().ssrmRowGroupExpansion).toBeUndefined(); + + // Expand Ireland + api.setRowNodeExpanded(api.getRowNode('ie')!, true); + await waitForNoLoadingRows(api); + + const savedState = api.getState(); + expect(savedState.rowGroupExpansion).toEqual({ + expandedRowGroupIds: ['ie'], + collapsedRowGroupIds: [], + }); + expect(savedState.ssrmRowGroupExpansion).toBeUndefined(); + + // Collapse Ireland + api.setRowNodeExpanded(api.getRowNode('ie')!, false); + expect(api.getRowNode('ie')!.expanded).toBe(false); + + // Restore the saved state + api.setState(savedState); + await waitForNoLoadingRows(api); + + expect(api.getRowNode('ie')!.expanded).toBe(true); + expect(api.getState().rowGroupExpansion).toEqual(savedState.rowGroupExpansion); + }); + + test('SSRM expandAll strategy: getState captures RowGroupBulkExpansionState, setState restores it', async () => { + const datasource: IServerSideDatasource = { + getRows({ request, success }: IServerSideGetRowsParams) { + if (request.groupKeys.length === 0) { + success({ + rowData: [ + { id: 'ie', key: 'Ireland', country: 'Ireland', group: true }, + { id: 'fr', key: 'France', country: 'France', group: true }, + ], + }); + } else { + success({ + rowData: [{ id: `${request.groupKeys[0]}-leaf`, country: request.groupKeys[0], medals: 1 }], + }); + } + }, + }; + + const gridOpts = { + columnDefs: [{ field: 'country', rowGroup: true, hide: true }, { field: 'medals' }], + rowModelType: 'serverSide' as const, + serverSideDatasource: datasource, + getRowId: ({ data }: any) => data.id, + ssrmExpandAllAffectsAllRows: true, + }; + + const api = gridsManager.createGrid('ssrmBulkSource', gridOpts); + await waitForNoLoadingRows(api); + + // Expand all — switches to ExpandAllStrategy + api.expandAll(); + await waitForNoLoadingRows(api); + + // Collapse France individually — recorded as an exception in ExpandAllStrategy + api.setRowNodeExpanded(api.getRowNode('fr')!, false); + await asyncSetTimeout(0); // allow debounced state update to flush + + const savedState = api.getState(); + expect(savedState.ssrmRowGroupExpansion).toEqual({ + expandAll: true, + invertedRowGroupIds: ['fr'], + }); + expect(savedState.rowGroupExpansion).toBeUndefined(); + + // Collapse all to reset state + api.collapseAll(); + expect(api.getRowNode('ie')!.expanded).toBe(false); + expect(api.getRowNode('fr')!.expanded).toBe(false); + + // Restore saved state — previously broken (ssrmRowGroupExpansion was ignored) + api.setState(savedState); + await waitForNoLoadingRows(api); + + // Ireland should be expanded (expandAll: true), France collapsed (in invertedRowGroupIds) + expect(api.getRowNode('ie')!.expanded).toBe(true); + expect(api.getRowNode('fr')!.expanded).toBe(false); + }); + + test('SSRM expandAll strategy: initialState with ssrmRowGroupExpansion restores on grid creation', async () => { + // Tests the gridInitializing path: ssrmRowGroupExpansion must be checked (not just + // rowGroupExpansion) when deciding whether to call setRowGroupExpansionState. + const datasource: IServerSideDatasource = { + getRows({ request, success }: IServerSideGetRowsParams) { + if (request.groupKeys.length === 0) { + success({ + rowData: [ + { id: 'ie', country: 'Ireland' }, + { id: 'fr', country: 'France' }, + ], + }); + } else { + success({ + rowData: [{ id: `${request.groupKeys[0]}-leaf`, country: request.groupKeys[0], medals: 1 }], + }); + } + }, + }; + + const gridOpts = { + columnDefs: [{ field: 'country', rowGroup: true, hide: true }, { field: 'medals' }], + rowModelType: 'serverSide' as const, + serverSideDatasource: datasource, + getRowId: ({ data }: any) => data.id, + ssrmExpandAllAffectsAllRows: true, + initialState: { + ssrmRowGroupExpansion: { expandAll: true, invertedRowGroupIds: ['fr'] }, + }, + }; + + const api = gridsManager.createGrid('ssrmBulkInit', gridOpts); + await waitForNoLoadingRows(api); + + // Ireland should be expanded (expandAll: true), France collapsed (in invertedRowGroupIds) + expect(api.getRowNode('ie')!.expanded).toBe(true); + expect(api.getRowNode('fr')!.expanded).toBe(false); + }); + + test('CSRM: getState reflects new node IDs after group column change via setGridOption', async () => { + // columnRowGroupChanged fires while changeEventsDispatching=true (before CSRM re-groups), + // so expansion state must be refreshed on newColumnsLoaded instead. + const api = gridsManager.createGrid('csrmRegroup', { + columnDefs: [ + { field: 'sport', rowGroup: true, hide: true }, + { field: 'name', rowGroup: false, hide: true }, + { field: 'age' }, + ], + rowData: defaultRowData, + getRowId: ({ data }) => data.id, + }); + + // expandAll uses the synchronous onGroupExpandedOrCollapsed path + api.expandAll(); + await asyncSetTimeout(0); + expect(api.getState().rowGroupExpansion?.expandedRowGroupIds).toContain('row-group-sport-Football'); + + // Switch grouping to name — all sport-based IDs are gone + api.setGridOption('columnDefs', [ + { field: 'sport', rowGroup: false, hide: true }, + { field: 'name', rowGroup: true, hide: true }, + { field: 'age' }, + ]); + await asyncSetTimeout(0); + + // Cached state must reflect the new name-based IDs, not stale sport-based IDs + const expansion = api.getState().rowGroupExpansion; + expect(expansion?.expandedRowGroupIds).not.toContain('row-group-sport-Football'); }); test('should capture pagination state', async () => { diff --git a/testing/behavioural/src/grouping-data/grouping-change-column.test.ts b/testing/behavioural/src/grouping-data/grouping-change-column.test.ts index b78bfd3ddfb..f0c5fbf3cc5 100644 --- a/testing/behavioural/src/grouping-data/grouping-change-column.test.ts +++ b/testing/behavioural/src/grouping-data/grouping-change-column.test.ts @@ -1,12 +1,12 @@ import type { GridOptions } from 'ag-grid-community'; -import { ClientSideRowModelModule } from 'ag-grid-community'; -import { RowGroupingModule } from 'ag-grid-enterprise'; +import { ClientSideRowModelModule, GridStateModule, RowSelectionModule } from 'ag-grid-community'; +import { PivotModule, RowGroupingModule } from 'ag-grid-enterprise'; -import { GridRows, TestGridsManager } from '../test-utils'; +import { GridRows, TestGridsManager, asyncSetTimeout } from '../test-utils'; describe('ag-grid grouping simple data', () => { const gridsManager = new TestGridsManager({ - modules: [ClientSideRowModelModule, RowGroupingModule], + modules: [ClientSideRowModelModule, GridStateModule, RowGroupingModule], }); beforeEach(() => { @@ -56,4 +56,1254 @@ describe('ag-grid grouping simple data', () => { · └── LEAF id:0 1:"bob" `); }); + + test('expanding groups then adding a deeper group column preserves expansion state', async () => { + const rowData = [ + { id: '1', country: 'Ireland', year: 2020 }, + { id: '2', country: 'Ireland', year: 2021 }, + { id: '3', country: 'France', year: 2020 }, + ]; + + const api = gridsManager.createGrid('myGrid', { + columnDefs: [{ field: 'country', rowGroup: true, hide: true }, { field: 'year' }], + rowData, + getRowId: (params) => params.data.id, + }); + + api.setRowNodeExpanded(api.getRowNode('row-group-country-Ireland')!, true, false, true); + + await new GridRows(api, 'Ireland expanded').check(` + ROOT id:ROOT_NODE_ID + ├─┬ LEAF_GROUP id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" + │ ├── LEAF id:1 country:"Ireland" year:2020 + │ └── LEAF id:2 country:"Ireland" year:2021 + └─┬ LEAF_GROUP collapsed id:row-group-country-France ag-Grid-AutoColumn:"France" + · └── LEAF hidden id:3 country:"France" year:2020 + `); + + // Flush the debounced state update (rowExpansionStateChanged is debounced with 0ms) + await asyncSetTimeout(0); + + // Expansion state debounce has settled — Ireland is in the expanded set + expect(api.getState().rowGroupExpansion).toEqual({ + expandedRowGroupIds: ['row-group-country-Ireland'], + collapsedRowGroupIds: [], + }); + + // Add year as a second grouping column + api.setGridOption('columnDefs', [ + { field: 'country', rowGroup: true, hide: true }, + { field: 'year', rowGroup: true, hide: true }, + ]); + + // Ireland should remain expanded; new year sub-groups follow groupDefaultExpanded (collapsed by default) + await new GridRows(api, 'after adding year as group column').check(` + ROOT id:ROOT_NODE_ID + ├─┬ filler id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" + │ ├─┬ LEAF_GROUP collapsed id:row-group-country-Ireland-year-2020 ag-Grid-AutoColumn:2020 + │ │ └── LEAF hidden id:1 country:"Ireland" year:2020 + │ └─┬ LEAF_GROUP collapsed id:row-group-country-Ireland-year-2021 ag-Grid-AutoColumn:2021 + │ · └── LEAF hidden id:2 country:"Ireland" year:2021 + └─┬ filler collapsed id:row-group-country-France ag-Grid-AutoColumn:"France" + · └─┬ LEAF_GROUP collapsed hidden id:row-group-country-France-year-2020 ag-Grid-AutoColumn:2020 + · · └── LEAF hidden id:3 country:"France" year:2020 + `); + + // Flush the debounced state update + await asyncSetTimeout(0); + + // Ireland's ID is unchanged so it remains in the expanded set after the column change + expect(api.getState().rowGroupExpansion).toEqual({ + expandedRowGroupIds: ['row-group-country-Ireland'], + collapsedRowGroupIds: [], + }); + }); + + test('expanding groups then removing the deepest group column preserves expansion state', async () => { + const rowData = [ + { id: '1', country: 'Ireland', year: 2020 }, + { id: '2', country: 'Ireland', year: 2021 }, + { id: '3', country: 'France', year: 2020 }, + ]; + + const api = gridsManager.createGrid('myGrid', { + columnDefs: [ + { field: 'country', rowGroup: true, hide: true }, + { field: 'year', rowGroup: true, hide: true }, + ], + rowData, + getRowId: (params) => params.data.id, + }); + + api.setRowNodeExpanded(api.getRowNode('row-group-country-Ireland')!, true, false, true); + api.setRowNodeExpanded(api.getRowNode('row-group-country-Ireland-year-2020')!, true, false, true); + + await new GridRows(api, 'Ireland and Ireland/2020 expanded').check(` + ROOT id:ROOT_NODE_ID + ├─┬ filler id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" + │ ├─┬ LEAF_GROUP id:row-group-country-Ireland-year-2020 ag-Grid-AutoColumn:2020 + │ │ └── LEAF id:1 country:"Ireland" year:2020 + │ └─┬ LEAF_GROUP collapsed id:row-group-country-Ireland-year-2021 ag-Grid-AutoColumn:2021 + │ · └── LEAF hidden id:2 country:"Ireland" year:2021 + └─┬ filler collapsed id:row-group-country-France ag-Grid-AutoColumn:"France" + · └─┬ LEAF_GROUP collapsed hidden id:row-group-country-France-year-2020 ag-Grid-AutoColumn:2020 + · · └── LEAF hidden id:3 country:"France" year:2020 + `); + + // Flush the debounced state update + await asyncSetTimeout(0); + + // Expansion state debounce has settled — both expanded nodes are in the set + expect(api.getState().rowGroupExpansion).toEqual({ + expandedRowGroupIds: ['row-group-country-Ireland', 'row-group-country-Ireland-year-2020'], + collapsedRowGroupIds: [], + }); + + // Remove year from grouping - only country remains + api.setGridOption('columnDefs', [ + { field: 'country', rowGroup: true, hide: true }, + { field: 'year', rowGroup: false }, + ]); + + // Ireland should remain expanded; France should remain collapsed + await new GridRows(api, 'after removing year group column').check(` + ROOT id:ROOT_NODE_ID + ├─┬ LEAF_GROUP id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" + │ ├── LEAF id:1 country:"Ireland" year:2020 + │ └── LEAF id:2 country:"Ireland" year:2021 + └─┬ LEAF_GROUP collapsed id:row-group-country-France ag-Grid-AutoColumn:"France" + · └── LEAF hidden id:3 country:"France" year:2020 + `); + + // Flush the debounced state update + await asyncSetTimeout(0); + + // Year sub-group nodes are gone — only Ireland remains in the expanded set + expect(api.getState().rowGroupExpansion).toEqual({ + expandedRowGroupIds: ['row-group-country-Ireland'], + collapsedRowGroupIds: [], + }); + }); + + test('removing the top group column resets all expansion to default', async () => { + const rowData = [ + { id: '1', country: 'Ireland', year: 2020 }, + { id: '2', country: 'Ireland', year: 2021 }, + { id: '3', country: 'France', year: 2020 }, + ]; + + const api = gridsManager.createGrid('myGrid', { + columnDefs: [ + { field: 'country', rowGroup: true, hide: true }, + { field: 'year', rowGroup: true, hide: true }, + ], + rowData, + getRowId: (params) => params.data.id, + }); + + api.setRowNodeExpanded(api.getRowNode('row-group-country-Ireland')!, true, false, true); + api.setRowNodeExpanded(api.getRowNode('row-group-country-Ireland-year-2020')!, true, false, true); + + await new GridRows(api, 'Ireland and Ireland/2020 expanded').check(` + ROOT id:ROOT_NODE_ID + ├─┬ filler id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" + │ ├─┬ LEAF_GROUP id:row-group-country-Ireland-year-2020 ag-Grid-AutoColumn:2020 + │ │ └── LEAF id:1 country:"Ireland" year:2020 + │ └─┬ LEAF_GROUP collapsed id:row-group-country-Ireland-year-2021 ag-Grid-AutoColumn:2021 + │ · └── LEAF hidden id:2 country:"Ireland" year:2021 + └─┬ filler collapsed id:row-group-country-France ag-Grid-AutoColumn:"France" + · └─┬ LEAF_GROUP collapsed hidden id:row-group-country-France-year-2020 ag-Grid-AutoColumn:2020 + · · └── LEAF hidden id:3 country:"France" year:2020 + `); + + // Flush the debounced state update + await asyncSetTimeout(0); + + // Expansion state debounce has settled — both expanded nodes are in the set + expect(api.getState().rowGroupExpansion).toEqual({ + expandedRowGroupIds: ['row-group-country-Ireland', 'row-group-country-Ireland-year-2020'], + collapsedRowGroupIds: [], + }); + + // Remove country (top level) from grouping — only year remains + api.setGridOption('columnDefs', [ + { field: 'country', rowGroup: false }, + { field: 'year', rowGroup: true, hide: true }, + ]); + + // All year-level IDs change (old: row-group-country-Ireland-year-2020, new: row-group-year-2020) + // so no saved IDs match — all groups fall back to groupDefaultExpanded (collapsed) + await new GridRows(api, 'after removing top group column').check(` + ROOT id:ROOT_NODE_ID + ├─┬ LEAF_GROUP collapsed id:row-group-year-2020 ag-Grid-AutoColumn:2020 + │ ├── LEAF hidden id:1 country:"Ireland" year:2020 + │ └── LEAF hidden id:3 country:"France" year:2020 + └─┬ LEAF_GROUP collapsed id:row-group-year-2021 ag-Grid-AutoColumn:2021 + · └── LEAF hidden id:2 country:"Ireland" year:2021 + `); + + // Flush the debounced state update + await asyncSetTimeout(0); + + // All year-level IDs changed so none matched — state resets to empty + expect(api.getState().rowGroupExpansion).toEqual({ + expandedRowGroupIds: [], + collapsedRowGroupIds: [], + }); + }); + + test('removing a middle group column does not preserve deeper level expansion state', async () => { + const rowData = [ + { id: '1', country: 'Ireland', year: 2020, sport: 'Football' }, + { id: '2', country: 'Ireland', year: 2020, sport: 'Rugby' }, + { id: '3', country: 'Ireland', year: 2021, sport: 'Football' }, + { id: '4', country: 'France', year: 2020, sport: 'Football' }, + ]; + + const api = gridsManager.createGrid('myGrid', { + columnDefs: [ + { field: 'country', rowGroup: true, hide: true }, + { field: 'year', rowGroup: true, hide: true }, + { field: 'sport', rowGroup: true, hide: true }, + ], + rowData, + getRowId: (params) => params.data.id, + }); + + // Expand Ireland → 2020 → Football (all three levels) + api.setRowNodeExpanded(api.getRowNode('row-group-country-Ireland')!, true, false, true); + api.setRowNodeExpanded(api.getRowNode('row-group-country-Ireland-year-2020')!, true, false, true); + api.setRowNodeExpanded( + api.getRowNode('row-group-country-Ireland-year-2020-sport-Football')!, + true, + false, + true + ); + + await new GridRows(api, 'three levels expanded').check(` + ROOT id:ROOT_NODE_ID + ├─┬ filler id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" + │ ├─┬ filler id:row-group-country-Ireland-year-2020 ag-Grid-AutoColumn:2020 + │ │ ├─┬ LEAF_GROUP id:row-group-country-Ireland-year-2020-sport-Football ag-Grid-AutoColumn:"Football" + │ │ │ └── LEAF id:1 country:"Ireland" year:2020 sport:"Football" + │ │ └─┬ LEAF_GROUP collapsed id:row-group-country-Ireland-year-2020-sport-Rugby ag-Grid-AutoColumn:"Rugby" + │ │ · └── LEAF hidden id:2 country:"Ireland" year:2020 sport:"Rugby" + │ └─┬ filler collapsed id:row-group-country-Ireland-year-2021 ag-Grid-AutoColumn:2021 + │ · └─┬ LEAF_GROUP collapsed hidden id:row-group-country-Ireland-year-2021-sport-Football ag-Grid-AutoColumn:"Football" + │ · · └── LEAF hidden id:3 country:"Ireland" year:2021 sport:"Football" + └─┬ filler collapsed id:row-group-country-France ag-Grid-AutoColumn:"France" + · └─┬ filler collapsed hidden id:row-group-country-France-year-2020 ag-Grid-AutoColumn:2020 + · · └─┬ LEAF_GROUP collapsed hidden id:row-group-country-France-year-2020-sport-Football ag-Grid-AutoColumn:"Football" + · · · └── LEAF hidden id:4 country:"France" year:2020 sport:"Football" + `); + + // Remove the middle group column (year) + api.setGridOption('columnDefs', [ + { field: 'country', rowGroup: true, hide: true }, + { field: 'year', rowGroup: false }, + { field: 'sport', rowGroup: true, hide: true }, + ]); + + // Country-level expansion IS preserved (Ireland expanded, France collapsed). + // Sport-level expansion is NOT preserved because IDs encode the full ancestor path: + // before: row-group-country-Ireland-year-2020-sport-Football + // after: row-group-country-Ireland-sport-Football + // The IDs no longer match, so sport groups fall back to groupDefaultExpanded (collapsed). + await new GridRows(api, 'after removing middle group column').check(` + ROOT id:ROOT_NODE_ID + ├─┬ filler id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" + │ ├─┬ LEAF_GROUP collapsed id:row-group-country-Ireland-sport-Football ag-Grid-AutoColumn:"Football" + │ │ ├── LEAF hidden id:1 country:"Ireland" year:2020 sport:"Football" + │ │ └── LEAF hidden id:3 country:"Ireland" year:2021 sport:"Football" + │ └─┬ LEAF_GROUP collapsed id:row-group-country-Ireland-sport-Rugby ag-Grid-AutoColumn:"Rugby" + │ · └── LEAF hidden id:2 country:"Ireland" year:2020 sport:"Rugby" + └─┬ filler collapsed id:row-group-country-France ag-Grid-AutoColumn:"France" + · └─┬ LEAF_GROUP collapsed hidden id:row-group-country-France-sport-Football ag-Grid-AutoColumn:"Football" + · · └── LEAF hidden id:4 country:"France" year:2020 sport:"Football" + `); + }); + + test('swapping 2nd and 3rd group columns resets expansion at those levels', async () => { + const rowData = [ + { id: '1', country: 'Ireland', year: 2020, sport: 'Football' }, + { id: '2', country: 'Ireland', year: 2020, sport: 'Rugby' }, + { id: '3', country: 'Ireland', year: 2021, sport: 'Football' }, + { id: '4', country: 'France', year: 2020, sport: 'Football' }, + ]; + + const api = gridsManager.createGrid('myGrid', { + columnDefs: [ + { field: 'country', rowGroupIndex: 0, hide: true }, + { field: 'year', rowGroupIndex: 1, hide: true }, + { field: 'sport', rowGroupIndex: 2, hide: true }, + ], + rowData, + getRowId: (params) => params.data.id, + }); + + // Expand Ireland → 2020 → Football + api.setRowNodeExpanded(api.getRowNode('row-group-country-Ireland')!, true, false, true); + api.setRowNodeExpanded(api.getRowNode('row-group-country-Ireland-year-2020')!, true, false, true); + api.setRowNodeExpanded( + api.getRowNode('row-group-country-Ireland-year-2020-sport-Football')!, + true, + false, + true + ); + + await new GridRows(api, 'three levels expanded').check(` + ROOT id:ROOT_NODE_ID + ├─┬ filler id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" + │ ├─┬ filler id:row-group-country-Ireland-year-2020 ag-Grid-AutoColumn:2020 + │ │ ├─┬ LEAF_GROUP id:row-group-country-Ireland-year-2020-sport-Football ag-Grid-AutoColumn:"Football" + │ │ │ └── LEAF id:1 country:"Ireland" year:2020 sport:"Football" + │ │ └─┬ LEAF_GROUP collapsed id:row-group-country-Ireland-year-2020-sport-Rugby ag-Grid-AutoColumn:"Rugby" + │ │ · └── LEAF hidden id:2 country:"Ireland" year:2020 sport:"Rugby" + │ └─┬ filler collapsed id:row-group-country-Ireland-year-2021 ag-Grid-AutoColumn:2021 + │ · └─┬ LEAF_GROUP collapsed hidden id:row-group-country-Ireland-year-2021-sport-Football ag-Grid-AutoColumn:"Football" + │ · · └── LEAF hidden id:3 country:"Ireland" year:2021 sport:"Football" + └─┬ filler collapsed id:row-group-country-France ag-Grid-AutoColumn:"France" + · └─┬ filler collapsed hidden id:row-group-country-France-year-2020 ag-Grid-AutoColumn:2020 + · · └─┬ LEAF_GROUP collapsed hidden id:row-group-country-France-year-2020-sport-Football ag-Grid-AutoColumn:"Football" + · · · └── LEAF hidden id:4 country:"France" year:2020 sport:"Football" + `); + + // Swap year and sport column order + api.setGridOption('columnDefs', [ + { field: 'country', rowGroupIndex: 0, hide: true }, + { field: 'year', rowGroupIndex: 2, hide: true }, + { field: 'sport', rowGroupIndex: 1, hide: true }, + ]); + + // Country-level expansion is preserved (Ireland expanded, France collapsed). + // Levels 2 and 3 reset because IDs change when column order changes: + // old: row-group-country-Ireland-year-2020-sport-Football + // new: row-group-country-Ireland-sport-Football-year-2020 + await new GridRows(api, 'after swapping year and sport').check(` + ROOT id:ROOT_NODE_ID + ├─┬ filler id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" + │ ├─┬ filler collapsed id:row-group-country-Ireland-sport-Football ag-Grid-AutoColumn:"Football" + │ │ ├─┬ LEAF_GROUP collapsed hidden id:row-group-country-Ireland-sport-Football-year-2020 ag-Grid-AutoColumn:2020 + │ │ │ └── LEAF hidden id:1 country:"Ireland" year:2020 sport:"Football" + │ │ └─┬ LEAF_GROUP collapsed hidden id:row-group-country-Ireland-sport-Football-year-2021 ag-Grid-AutoColumn:2021 + │ │ · └── LEAF hidden id:3 country:"Ireland" year:2021 sport:"Football" + │ └─┬ filler collapsed id:row-group-country-Ireland-sport-Rugby ag-Grid-AutoColumn:"Rugby" + │ · └─┬ LEAF_GROUP collapsed hidden id:row-group-country-Ireland-sport-Rugby-year-2020 ag-Grid-AutoColumn:2020 + │ · · └── LEAF hidden id:2 country:"Ireland" year:2020 sport:"Rugby" + └─┬ filler collapsed id:row-group-country-France ag-Grid-AutoColumn:"France" + · └─┬ filler collapsed hidden id:row-group-country-France-sport-Football ag-Grid-AutoColumn:"Football" + · · └─┬ LEAF_GROUP collapsed hidden id:row-group-country-France-sport-Football-year-2020 ag-Grid-AutoColumn:2020 + · · · └── LEAF hidden id:4 country:"France" year:2020 sport:"Football" + `); + }); + + test('swapping group columns with isGroupOpenByDefault expands via callback at reset levels', async () => { + const rowData = [ + { id: '1', country: 'Ireland', year: 2020, sport: 'Football' }, + { id: '2', country: 'Ireland', year: 2020, sport: 'Rugby' }, + { id: '3', country: 'Ireland', year: 2021, sport: 'Football' }, + { id: '4', country: 'France', year: 2020, sport: 'Football' }, + ]; + + const api = gridsManager.createGrid('myGrid', { + columnDefs: [ + { field: 'country', rowGroupIndex: 0, hide: true }, + { field: 'year', rowGroupIndex: 1, hide: true }, + { field: 'sport', rowGroupIndex: 2, hide: true }, + ], + rowData, + getRowId: (params) => params.data.id, + // Expand sport="Football" groups regardless of level + isGroupOpenByDefault: (params) => params.field === 'sport' && params.key === 'Football', + }); + + // Initially: sport=Football groups are expanded, others collapsed. + // Manually expand Ireland and Ireland/2020 as well. + api.setRowNodeExpanded(api.getRowNode('row-group-country-Ireland')!, true, false, true); + api.setRowNodeExpanded(api.getRowNode('row-group-country-Ireland-year-2020')!, true, false, true); + + await new GridRows(api, 'initial state').check(` + ROOT id:ROOT_NODE_ID + ├─┬ filler id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" + │ ├─┬ filler id:row-group-country-Ireland-year-2020 ag-Grid-AutoColumn:2020 + │ │ ├─┬ LEAF_GROUP id:row-group-country-Ireland-year-2020-sport-Football ag-Grid-AutoColumn:"Football" + │ │ │ └── LEAF id:1 country:"Ireland" year:2020 sport:"Football" + │ │ └─┬ LEAF_GROUP collapsed id:row-group-country-Ireland-year-2020-sport-Rugby ag-Grid-AutoColumn:"Rugby" + │ │ · └── LEAF hidden id:2 country:"Ireland" year:2020 sport:"Rugby" + │ └─┬ filler collapsed id:row-group-country-Ireland-year-2021 ag-Grid-AutoColumn:2021 + │ · └─┬ LEAF_GROUP hidden id:row-group-country-Ireland-year-2021-sport-Football ag-Grid-AutoColumn:"Football" + │ · · └── LEAF hidden id:3 country:"Ireland" year:2021 sport:"Football" + └─┬ filler collapsed id:row-group-country-France ag-Grid-AutoColumn:"France" + · └─┬ filler collapsed hidden id:row-group-country-France-year-2020 ag-Grid-AutoColumn:2020 + · · └─┬ LEAF_GROUP hidden id:row-group-country-France-year-2020-sport-Football ag-Grid-AutoColumn:"Football" + · · · └── LEAF hidden id:4 country:"France" year:2020 sport:"Football" + `); + + // Swap year and sport column order + api.setGridOption('columnDefs', [ + { field: 'country', rowGroupIndex: 0, hide: true }, + { field: 'year', rowGroupIndex: 2, hide: true }, + { field: 'sport', rowGroupIndex: 1, hide: true }, + ]); + + // Country-level expansion is preserved (Ireland expanded, France collapsed). + // Levels 2 and 3 IDs change, so saved IDs don't match — but isGroupOpenByDefault + // fires for sport="Football" nodes, expanding them at their new level. + await new GridRows(api, 'after swap with isGroupOpenByDefault').check(` + ROOT id:ROOT_NODE_ID + ├─┬ filler id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" + │ ├─┬ filler id:row-group-country-Ireland-sport-Football ag-Grid-AutoColumn:"Football" + │ │ ├─┬ LEAF_GROUP collapsed id:row-group-country-Ireland-sport-Football-year-2020 ag-Grid-AutoColumn:2020 + │ │ │ └── LEAF hidden id:1 country:"Ireland" year:2020 sport:"Football" + │ │ └─┬ LEAF_GROUP collapsed id:row-group-country-Ireland-sport-Football-year-2021 ag-Grid-AutoColumn:2021 + │ │ · └── LEAF hidden id:3 country:"Ireland" year:2021 sport:"Football" + │ └─┬ filler collapsed id:row-group-country-Ireland-sport-Rugby ag-Grid-AutoColumn:"Rugby" + │ · └─┬ LEAF_GROUP collapsed hidden id:row-group-country-Ireland-sport-Rugby-year-2020 ag-Grid-AutoColumn:2020 + │ · · └── LEAF hidden id:2 country:"Ireland" year:2020 sport:"Rugby" + └─┬ filler collapsed id:row-group-country-France ag-Grid-AutoColumn:"France" + · └─┬ filler hidden id:row-group-country-France-sport-Football ag-Grid-AutoColumn:"Football" + · · └─┬ LEAF_GROUP collapsed hidden id:row-group-country-France-sport-Football-year-2020 ag-Grid-AutoColumn:2020 + · · · └── LEAF hidden id:4 country:"France" year:2020 sport:"Football" + `); + }); + + test('groupDefaultExpanded -1 with added group column keeps all groups expanded', async () => { + const rowData = [ + { id: '1', country: 'Ireland', year: 2020 }, + { id: '2', country: 'Ireland', year: 2021 }, + { id: '3', country: 'France', year: 2020 }, + ]; + + const api = gridsManager.createGrid('myGrid', { + columnDefs: [{ field: 'country', rowGroup: true, hide: true }, { field: 'year' }], + groupDefaultExpanded: -1, + rowData, + getRowId: (params) => params.data.id, + }); + + await new GridRows(api, 'initial - all countries expanded').check(` + ROOT id:ROOT_NODE_ID + ├─┬ LEAF_GROUP id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" + │ ├── LEAF id:1 country:"Ireland" year:2020 + │ └── LEAF id:2 country:"Ireland" year:2021 + └─┬ LEAF_GROUP id:row-group-country-France ag-Grid-AutoColumn:"France" + · └── LEAF id:3 country:"France" year:2020 + `); + + // Add year as a second grouping column + api.setGridOption('columnDefs', [ + { field: 'country', rowGroup: true, hide: true }, + { field: 'year', rowGroup: true, hide: true }, + ]); + + // All country groups should remain expanded; new year sub-groups should also be expanded + // because groupDefaultExpanded: -1 applies to newly created group nodes + await new GridRows(api, 'after adding year as group column').check(` + ROOT id:ROOT_NODE_ID + ├─┬ filler id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" + │ ├─┬ LEAF_GROUP id:row-group-country-Ireland-year-2020 ag-Grid-AutoColumn:2020 + │ │ └── LEAF id:1 country:"Ireland" year:2020 + │ └─┬ LEAF_GROUP id:row-group-country-Ireland-year-2021 ag-Grid-AutoColumn:2021 + │ · └── LEAF id:2 country:"Ireland" year:2021 + └─┬ filler id:row-group-country-France ag-Grid-AutoColumn:"France" + · └─┬ LEAF_GROUP id:row-group-country-France-year-2020 ag-Grid-AutoColumn:2020 + · · └── LEAF id:3 country:"France" year:2020 + `); + }); + + test('adding a group column above existing resets all expansion to default', async () => { + const rowData = [ + { id: '1', country: 'Ireland', year: 2020 }, + { id: '2', country: 'Ireland', year: 2021 }, + { id: '3', country: 'France', year: 2020 }, + ]; + + const api = gridsManager.createGrid('myGrid', { + columnDefs: [{ field: 'country' }, { field: 'year', rowGroupIndex: 0, hide: true }], + rowData, + getRowId: (params) => params.data.id, + }); + + api.setRowNodeExpanded(api.getRowNode('row-group-year-2020')!, true, false, true); + + await new GridRows(api, 'year 2020 expanded').check(` + ROOT id:ROOT_NODE_ID + ├─┬ LEAF_GROUP id:row-group-year-2020 ag-Grid-AutoColumn:2020 + │ ├── LEAF id:1 country:"Ireland" year:2020 + │ └── LEAF id:3 country:"France" year:2020 + └─┬ LEAF_GROUP collapsed id:row-group-year-2021 ag-Grid-AutoColumn:2021 + · └── LEAF hidden id:2 country:"Ireland" year:2021 + `); + + // Add country as a new top-level group column above year + api.setGridOption('columnDefs', [ + { field: 'country', rowGroupIndex: 0, hide: true }, + { field: 'year', rowGroupIndex: 1, hide: true }, + ]); + + // Year-level IDs change (old: row-group-year-2020, new: row-group-country-Ireland-year-2020) + // so no saved IDs match — all groups fall back to groupDefaultExpanded (collapsed) + await new GridRows(api, 'after adding country above year').check(` + ROOT id:ROOT_NODE_ID + ├─┬ filler collapsed id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" + │ ├─┬ LEAF_GROUP collapsed hidden id:row-group-country-Ireland-year-2020 ag-Grid-AutoColumn:2020 + │ │ └── LEAF hidden id:1 country:"Ireland" year:2020 + │ └─┬ LEAF_GROUP collapsed hidden id:row-group-country-Ireland-year-2021 ag-Grid-AutoColumn:2021 + │ · └── LEAF hidden id:2 country:"Ireland" year:2021 + └─┬ filler collapsed id:row-group-country-France ag-Grid-AutoColumn:"France" + · └─┬ LEAF_GROUP collapsed hidden id:row-group-country-France-year-2020 ag-Grid-AutoColumn:2020 + · · └── LEAF hidden id:3 country:"France" year:2020 + `); + }); + + test('adding a group column in the middle does not preserve deeper level expansion state', async () => { + const rowData = [ + { id: '1', country: 'Ireland', year: 2020, sport: 'Football' }, + { id: '2', country: 'Ireland', year: 2020, sport: 'Rugby' }, + { id: '3', country: 'Ireland', year: 2021, sport: 'Football' }, + { id: '4', country: 'France', year: 2020, sport: 'Football' }, + ]; + + const api = gridsManager.createGrid('myGrid', { + columnDefs: [ + { field: 'country', rowGroupIndex: 0, hide: true }, + { field: 'year' }, + { field: 'sport', rowGroupIndex: 1, hide: true }, + ], + rowData, + getRowId: (params) => params.data.id, + }); + + api.setRowNodeExpanded(api.getRowNode('row-group-country-Ireland')!, true, false, true); + api.setRowNodeExpanded(api.getRowNode('row-group-country-Ireland-sport-Football')!, true, false, true); + + await new GridRows(api, 'Ireland and Ireland/Football expanded').check(` + ROOT id:ROOT_NODE_ID + ├─┬ filler id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" + │ ├─┬ LEAF_GROUP id:row-group-country-Ireland-sport-Football ag-Grid-AutoColumn:"Football" + │ │ ├── LEAF id:1 country:"Ireland" year:2020 sport:"Football" + │ │ └── LEAF id:3 country:"Ireland" year:2021 sport:"Football" + │ └─┬ LEAF_GROUP collapsed id:row-group-country-Ireland-sport-Rugby ag-Grid-AutoColumn:"Rugby" + │ · └── LEAF hidden id:2 country:"Ireland" year:2020 sport:"Rugby" + └─┬ filler collapsed id:row-group-country-France ag-Grid-AutoColumn:"France" + · └─┬ LEAF_GROUP collapsed hidden id:row-group-country-France-sport-Football ag-Grid-AutoColumn:"Football" + · · └── LEAF hidden id:4 country:"France" year:2020 sport:"Football" + `); + + // Add year as a middle group column between country and sport + api.setGridOption('columnDefs', [ + { field: 'country', rowGroupIndex: 0, hide: true }, + { field: 'year', rowGroupIndex: 1, hide: true }, + { field: 'sport', rowGroupIndex: 2, hide: true }, + ]); + + // Country-level expansion is preserved (Ireland expanded, France collapsed). + // Sport-level IDs change (old: row-group-country-Ireland-sport-Football, + // new: row-group-country-Ireland-year-2020-sport-Football) so sport expansion resets. + await new GridRows(api, 'after adding year in the middle').check(` + ROOT id:ROOT_NODE_ID + ├─┬ filler id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" + │ ├─┬ filler collapsed id:row-group-country-Ireland-year-2020 ag-Grid-AutoColumn:2020 + │ │ ├─┬ LEAF_GROUP collapsed hidden id:row-group-country-Ireland-year-2020-sport-Football ag-Grid-AutoColumn:"Football" + │ │ │ └── LEAF hidden id:1 country:"Ireland" year:2020 sport:"Football" + │ │ └─┬ LEAF_GROUP collapsed hidden id:row-group-country-Ireland-year-2020-sport-Rugby ag-Grid-AutoColumn:"Rugby" + │ │ · └── LEAF hidden id:2 country:"Ireland" year:2020 sport:"Rugby" + │ └─┬ filler collapsed id:row-group-country-Ireland-year-2021 ag-Grid-AutoColumn:2021 + │ · └─┬ LEAF_GROUP collapsed hidden id:row-group-country-Ireland-year-2021-sport-Football ag-Grid-AutoColumn:"Football" + │ · · └── LEAF hidden id:3 country:"Ireland" year:2021 sport:"Football" + └─┬ filler collapsed id:row-group-country-France ag-Grid-AutoColumn:"France" + · └─┬ filler collapsed hidden id:row-group-country-France-year-2020 ag-Grid-AutoColumn:2020 + · · └─┬ LEAF_GROUP collapsed hidden id:row-group-country-France-year-2020-sport-Football ag-Grid-AutoColumn:"Football" + · · · └── LEAF hidden id:4 country:"France" year:2020 sport:"Football" + `); + }); + + test('groupDefaultExpanded -1 with explicit collapse preserves collapsed state on column change', async () => { + const rowData = [ + { id: '1', country: 'Ireland', year: 2020 }, + { id: '2', country: 'Ireland', year: 2021 }, + { id: '3', country: 'France', year: 2020 }, + ]; + + const api = gridsManager.createGrid('myGrid', { + columnDefs: [{ field: 'country', rowGroup: true, hide: true }, { field: 'year' }], + groupDefaultExpanded: -1, + rowData, + getRowId: (params) => params.data.id, + }); + + await new GridRows(api, 'initial - all expanded').check(` + ROOT id:ROOT_NODE_ID + ├─┬ LEAF_GROUP id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" + │ ├── LEAF id:1 country:"Ireland" year:2020 + │ └── LEAF id:2 country:"Ireland" year:2021 + └─┬ LEAF_GROUP id:row-group-country-France ag-Grid-AutoColumn:"France" + · └── LEAF id:3 country:"France" year:2020 + `); + + // Explicitly collapse France + api.setRowNodeExpanded(api.getRowNode('row-group-country-France')!, false, false, true); + + await new GridRows(api, 'France collapsed').check(` + ROOT id:ROOT_NODE_ID + ├─┬ LEAF_GROUP id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" + │ ├── LEAF id:1 country:"Ireland" year:2020 + │ └── LEAF id:2 country:"Ireland" year:2021 + └─┬ LEAF_GROUP collapsed id:row-group-country-France ag-Grid-AutoColumn:"France" + · └── LEAF hidden id:3 country:"France" year:2020 + `); + + // Add year as a second grouping column + api.setGridOption('columnDefs', [ + { field: 'country', rowGroup: true, hide: true }, + { field: 'year', rowGroup: true, hide: true }, + ]); + + // Ireland stays expanded (saved expanded ID match). France stays collapsed (saved collapsed ID match). + // New year sub-groups follow groupDefaultExpanded: -1 (expanded). + await new GridRows(api, 'after adding year — France stays collapsed').check(` + ROOT id:ROOT_NODE_ID + ├─┬ filler id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" + │ ├─┬ LEAF_GROUP id:row-group-country-Ireland-year-2020 ag-Grid-AutoColumn:2020 + │ │ └── LEAF id:1 country:"Ireland" year:2020 + │ └─┬ LEAF_GROUP id:row-group-country-Ireland-year-2021 ag-Grid-AutoColumn:2021 + │ · └── LEAF id:2 country:"Ireland" year:2021 + └─┬ filler collapsed id:row-group-country-France ag-Grid-AutoColumn:"France" + · └─┬ LEAF_GROUP hidden id:row-group-country-France-year-2020 ag-Grid-AutoColumn:2020 + · · └── LEAF hidden id:3 country:"France" year:2020 + `); + }); + + describe('resetRowGroupExpansion', () => { + test('resets all groups back to collapsed (default groupDefaultExpanded: 0)', async () => { + const rowData = [ + { id: '1', country: 'Ireland', year: 2020 }, + { id: '2', country: 'Ireland', year: 2021 }, + { id: '3', country: 'France', year: 2020 }, + ]; + + const api = gridsManager.createGrid('myGrid', { + columnDefs: [ + { field: 'country', rowGroup: true, hide: true }, + { field: 'year', rowGroup: true, hide: true }, + ], + rowData, + getRowId: (params) => params.data.id, + }); + + // Expand Ireland and Ireland/2020 + api.setRowNodeExpanded(api.getRowNode('row-group-country-Ireland')!, true, false, true); + api.setRowNodeExpanded(api.getRowNode('row-group-country-Ireland-year-2020')!, true, false, true); + + await new GridRows(api, 'Ireland and Ireland/2020 expanded').check(` + ROOT id:ROOT_NODE_ID + ├─┬ filler id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" + │ ├─┬ LEAF_GROUP id:row-group-country-Ireland-year-2020 ag-Grid-AutoColumn:2020 + │ │ └── LEAF id:1 country:"Ireland" year:2020 + │ └─┬ LEAF_GROUP collapsed id:row-group-country-Ireland-year-2021 ag-Grid-AutoColumn:2021 + │ · └── LEAF hidden id:2 country:"Ireland" year:2021 + └─┬ filler collapsed id:row-group-country-France ag-Grid-AutoColumn:"France" + · └─┬ LEAF_GROUP collapsed hidden id:row-group-country-France-year-2020 ag-Grid-AutoColumn:2020 + · · └── LEAF hidden id:3 country:"France" year:2020 + `); + + // Flush the debounced state update + await asyncSetTimeout(0); + + // Expansion state debounce has settled — both expanded nodes are in the set + expect(api.getState().rowGroupExpansion).toEqual({ + expandedRowGroupIds: ['row-group-country-Ireland', 'row-group-country-Ireland-year-2020'], + collapsedRowGroupIds: [], + }); + + api.resetRowGroupExpansion(); + + // All groups should be collapsed (groupDefaultExpanded defaults to 0) + await new GridRows(api, 'after resetRowGroupExpansion').check(` + ROOT id:ROOT_NODE_ID + ├─┬ filler collapsed id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" + │ ├─┬ LEAF_GROUP collapsed hidden id:row-group-country-Ireland-year-2020 ag-Grid-AutoColumn:2020 + │ │ └── LEAF hidden id:1 country:"Ireland" year:2020 + │ └─┬ LEAF_GROUP collapsed hidden id:row-group-country-Ireland-year-2021 ag-Grid-AutoColumn:2021 + │ · └── LEAF hidden id:2 country:"Ireland" year:2021 + └─┬ filler collapsed id:row-group-country-France ag-Grid-AutoColumn:"France" + · └─┬ LEAF_GROUP collapsed hidden id:row-group-country-France-year-2020 ag-Grid-AutoColumn:2020 + · · └── LEAF hidden id:3 country:"France" year:2020 + `); + + // Flush the debounced state update + await asyncSetTimeout(0); + + // Expansion state debounce settled — reset returns state to empty + expect(api.getState().rowGroupExpansion).toEqual({ + expandedRowGroupIds: [], + collapsedRowGroupIds: [], + }); + }); + + test('resets to groupDefaultExpanded: -1 (all expanded)', async () => { + const rowData = [ + { id: '1', country: 'Ireland', year: 2020 }, + { id: '2', country: 'Ireland', year: 2021 }, + { id: '3', country: 'France', year: 2020 }, + ]; + + const api = gridsManager.createGrid('myGrid', { + columnDefs: [ + { field: 'country', rowGroup: true, hide: true }, + { field: 'year', rowGroup: true, hide: true }, + ], + groupDefaultExpanded: -1, + rowData, + getRowId: (params) => params.data.id, + }); + + // Collapse France and Ireland/2020 + api.setRowNodeExpanded(api.getRowNode('row-group-country-France')!, false, false, true); + api.setRowNodeExpanded(api.getRowNode('row-group-country-Ireland-year-2020')!, false, false, true); + + await new GridRows(api, 'France collapsed, Ireland/2020 collapsed').check(` + ROOT id:ROOT_NODE_ID + ├─┬ filler id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" + │ ├─┬ LEAF_GROUP collapsed id:row-group-country-Ireland-year-2020 ag-Grid-AutoColumn:2020 + │ │ └── LEAF hidden id:1 country:"Ireland" year:2020 + │ └─┬ LEAF_GROUP id:row-group-country-Ireland-year-2021 ag-Grid-AutoColumn:2021 + │ · └── LEAF id:2 country:"Ireland" year:2021 + └─┬ filler collapsed id:row-group-country-France ag-Grid-AutoColumn:"France" + · └─┬ LEAF_GROUP hidden id:row-group-country-France-year-2020 ag-Grid-AutoColumn:2020 + · · └── LEAF hidden id:3 country:"France" year:2020 + `); + + api.resetRowGroupExpansion(); + + // All groups should be expanded (groupDefaultExpanded: -1) + await new GridRows(api, 'after resetRowGroupExpansion — all expanded').check(` + ROOT id:ROOT_NODE_ID + ├─┬ filler id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" + │ ├─┬ LEAF_GROUP id:row-group-country-Ireland-year-2020 ag-Grid-AutoColumn:2020 + │ │ └── LEAF id:1 country:"Ireland" year:2020 + │ └─┬ LEAF_GROUP id:row-group-country-Ireland-year-2021 ag-Grid-AutoColumn:2021 + │ · └── LEAF id:2 country:"Ireland" year:2021 + └─┬ filler id:row-group-country-France ag-Grid-AutoColumn:"France" + · └─┬ LEAF_GROUP id:row-group-country-France-year-2020 ag-Grid-AutoColumn:2020 + · · └── LEAF id:3 country:"France" year:2020 + `); + }); + + test('resets to groupDefaultExpanded: 1 (first level expanded only)', async () => { + const rowData = [ + { id: '1', country: 'Ireland', year: 2020 }, + { id: '2', country: 'Ireland', year: 2021 }, + { id: '3', country: 'France', year: 2020 }, + ]; + + const api = gridsManager.createGrid('myGrid', { + columnDefs: [ + { field: 'country', rowGroup: true, hide: true }, + { field: 'year', rowGroup: true, hide: true }, + ], + groupDefaultExpanded: 1, + rowData, + getRowId: (params) => params.data.id, + }); + + // Collapse Ireland and expand Ireland/2020 (overrides both levels) + api.setRowNodeExpanded(api.getRowNode('row-group-country-Ireland')!, false, false, true); + api.setRowNodeExpanded(api.getRowNode('row-group-country-Ireland-year-2020')!, true, false, true); + + await new GridRows(api, 'Ireland collapsed, Ireland/2020 expanded (override)').check(` + ROOT id:ROOT_NODE_ID + ├─┬ filler collapsed id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" + │ ├─┬ LEAF_GROUP hidden id:row-group-country-Ireland-year-2020 ag-Grid-AutoColumn:2020 + │ │ └── LEAF hidden id:1 country:"Ireland" year:2020 + │ └─┬ LEAF_GROUP collapsed hidden id:row-group-country-Ireland-year-2021 ag-Grid-AutoColumn:2021 + │ · └── LEAF hidden id:2 country:"Ireland" year:2021 + └─┬ filler id:row-group-country-France ag-Grid-AutoColumn:"France" + · └─┬ LEAF_GROUP collapsed id:row-group-country-France-year-2020 ag-Grid-AutoColumn:2020 + · · └── LEAF hidden id:3 country:"France" year:2020 + `); + + api.resetRowGroupExpansion(); + + // Level 0 expanded, level 1 collapsed (groupDefaultExpanded: 1) + await new GridRows(api, 'after resetRowGroupExpansion — level 0 expanded, level 1 collapsed').check(` + ROOT id:ROOT_NODE_ID + ├─┬ filler id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" + │ ├─┬ LEAF_GROUP collapsed id:row-group-country-Ireland-year-2020 ag-Grid-AutoColumn:2020 + │ │ └── LEAF hidden id:1 country:"Ireland" year:2020 + │ └─┬ LEAF_GROUP collapsed id:row-group-country-Ireland-year-2021 ag-Grid-AutoColumn:2021 + │ · └── LEAF hidden id:2 country:"Ireland" year:2021 + └─┬ filler id:row-group-country-France ag-Grid-AutoColumn:"France" + · └─┬ LEAF_GROUP collapsed id:row-group-country-France-year-2020 ag-Grid-AutoColumn:2020 + · · └── LEAF hidden id:3 country:"France" year:2020 + `); + }); + + test('re-evaluates isGroupOpenByDefault callback', async () => { + const rowData = [ + { id: '1', country: 'Ireland', year: 2020 }, + { id: '2', country: 'Ireland', year: 2021 }, + { id: '3', country: 'France', year: 2020 }, + ]; + + const api = gridsManager.createGrid('myGrid', { + columnDefs: [ + { field: 'country', rowGroup: true, hide: true }, + { field: 'year', rowGroup: true, hide: true }, + ], + rowData, + getRowId: (params) => params.data.id, + // Only Ireland should be expanded by default + isGroupOpenByDefault: (params) => params.key === 'Ireland', + }); + + await new GridRows(api, 'initial — Ireland expanded via callback').check(` + ROOT id:ROOT_NODE_ID + ├─┬ filler id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" + │ ├─┬ LEAF_GROUP collapsed id:row-group-country-Ireland-year-2020 ag-Grid-AutoColumn:2020 + │ │ └── LEAF hidden id:1 country:"Ireland" year:2020 + │ └─┬ LEAF_GROUP collapsed id:row-group-country-Ireland-year-2021 ag-Grid-AutoColumn:2021 + │ · └── LEAF hidden id:2 country:"Ireland" year:2021 + └─┬ filler collapsed id:row-group-country-France ag-Grid-AutoColumn:"France" + · └─┬ LEAF_GROUP collapsed hidden id:row-group-country-France-year-2020 ag-Grid-AutoColumn:2020 + · · └── LEAF hidden id:3 country:"France" year:2020 + `); + + // Override: collapse Ireland, expand France and Ireland/2020 + api.setRowNodeExpanded(api.getRowNode('row-group-country-Ireland')!, false, false, true); + api.setRowNodeExpanded(api.getRowNode('row-group-country-France')!, true, false, true); + api.setRowNodeExpanded(api.getRowNode('row-group-country-Ireland-year-2020')!, true, false, true); + + await new GridRows(api, 'user overrides applied').check(` + ROOT id:ROOT_NODE_ID + ├─┬ filler collapsed id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" + │ ├─┬ LEAF_GROUP hidden id:row-group-country-Ireland-year-2020 ag-Grid-AutoColumn:2020 + │ │ └── LEAF hidden id:1 country:"Ireland" year:2020 + │ └─┬ LEAF_GROUP collapsed hidden id:row-group-country-Ireland-year-2021 ag-Grid-AutoColumn:2021 + │ · └── LEAF hidden id:2 country:"Ireland" year:2021 + └─┬ filler id:row-group-country-France ag-Grid-AutoColumn:"France" + · └─┬ LEAF_GROUP collapsed id:row-group-country-France-year-2020 ag-Grid-AutoColumn:2020 + · · └── LEAF hidden id:3 country:"France" year:2020 + `); + + api.resetRowGroupExpansion(); + + // Callback re-evaluated: Ireland expanded, France collapsed (back to initial state) + await new GridRows(api, 'after resetRowGroupExpansion — callback re-evaluated').check(` + ROOT id:ROOT_NODE_ID + ├─┬ filler id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" + │ ├─┬ LEAF_GROUP collapsed id:row-group-country-Ireland-year-2020 ag-Grid-AutoColumn:2020 + │ │ └── LEAF hidden id:1 country:"Ireland" year:2020 + │ └─┬ LEAF_GROUP collapsed id:row-group-country-Ireland-year-2021 ag-Grid-AutoColumn:2021 + │ · └── LEAF hidden id:2 country:"Ireland" year:2021 + └─┬ filler collapsed id:row-group-country-France ag-Grid-AutoColumn:"France" + · └─┬ LEAF_GROUP collapsed hidden id:row-group-country-France-year-2020 ag-Grid-AutoColumn:2020 + · · └── LEAF hidden id:3 country:"France" year:2020 + `); + }); + + test('resets preserved expansion state after column change', async () => { + const rowData = [ + { id: '1', country: 'Ireland', year: 2020 }, + { id: '2', country: 'Ireland', year: 2021 }, + { id: '3', country: 'France', year: 2020 }, + ]; + + const api = gridsManager.createGrid('myGrid', { + columnDefs: [{ field: 'country', rowGroup: true, hide: true }, { field: 'year' }], + rowData, + getRowId: (params) => params.data.id, + }); + + // Expand Ireland + api.setRowNodeExpanded(api.getRowNode('row-group-country-Ireland')!, true, false, true); + + // Add year as group column — Ireland stays expanded (preserved) + api.setGridOption('columnDefs', [ + { field: 'country', rowGroup: true, hide: true }, + { field: 'year', rowGroup: true, hide: true }, + ]); + + // columnRowGroupChanged updates state synchronously — Ireland's ID is unchanged so it remains expanded + expect(api.getState().rowGroupExpansion).toEqual({ + expandedRowGroupIds: ['row-group-country-Ireland'], + collapsedRowGroupIds: [], + }); + + await new GridRows(api, 'Ireland preserved expanded after column change').check(` + ROOT id:ROOT_NODE_ID + ├─┬ filler id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" + │ ├─┬ LEAF_GROUP collapsed id:row-group-country-Ireland-year-2020 ag-Grid-AutoColumn:2020 + │ │ └── LEAF hidden id:1 country:"Ireland" year:2020 + │ └─┬ LEAF_GROUP collapsed id:row-group-country-Ireland-year-2021 ag-Grid-AutoColumn:2021 + │ · └── LEAF hidden id:2 country:"Ireland" year:2021 + └─┬ filler collapsed id:row-group-country-France ag-Grid-AutoColumn:"France" + · └─┬ LEAF_GROUP collapsed hidden id:row-group-country-France-year-2020 ag-Grid-AutoColumn:2020 + · · └── LEAF hidden id:3 country:"France" year:2020 + `); + + api.resetRowGroupExpansion(); + + // Reset clears the preserved expansion — all groups return to default (collapsed) + await new GridRows(api, 'after resetRowGroupExpansion — all collapsed').check(` + ROOT id:ROOT_NODE_ID + ├─┬ filler collapsed id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" + │ ├─┬ LEAF_GROUP collapsed hidden id:row-group-country-Ireland-year-2020 ag-Grid-AutoColumn:2020 + │ │ └── LEAF hidden id:1 country:"Ireland" year:2020 + │ └─┬ LEAF_GROUP collapsed hidden id:row-group-country-Ireland-year-2021 ag-Grid-AutoColumn:2021 + │ · └── LEAF hidden id:2 country:"Ireland" year:2021 + └─┬ filler collapsed id:row-group-country-France ag-Grid-AutoColumn:"France" + · └─┬ LEAF_GROUP collapsed hidden id:row-group-country-France-year-2020 ag-Grid-AutoColumn:2020 + · · └── LEAF hidden id:3 country:"France" year:2020 + `); + + // Flush the debounced state update + await asyncSetTimeout(0); + + // Expansion state debounce settled — reset returns state to empty + expect(api.getState().rowGroupExpansion).toEqual({ + expandedRowGroupIds: [], + collapsedRowGroupIds: [], + }); + }); + }); + + describe('pivot mode', () => { + const pivotGridsManager = new TestGridsManager({ + modules: [ClientSideRowModelModule, GridStateModule, RowGroupingModule, PivotModule], + }); + + beforeEach(() => { + pivotGridsManager.reset(); + }); + + afterEach(() => { + pivotGridsManager.reset(); + }); + + test('pivot mode without active pivot — add deeper group column preserves non-leaf expansion, leaf groups forced collapsed', async () => { + const rowData = [ + { id: '1', country: 'Ireland', year: 2020, sport: 'Football' }, + { id: '2', country: 'Ireland', year: 2021, sport: 'Rugby' }, + { id: '3', country: 'France', year: 2020, sport: 'Football' }, + ]; + + const api = pivotGridsManager.createGrid('myGrid', { + columnDefs: [ + { field: 'country', rowGroup: true, hide: true }, + { field: 'year', rowGroup: true, hide: true }, + ], + pivotMode: true, + rowData, + getRowId: (params) => params.data.id, + }); + + // Country is non-leaf (filler), year is LEAF_GROUP → forced collapsed in pivot mode + await new GridRows(api, 'initial — all collapsed in pivot mode').check(` + ROOT id:ROOT_NODE_ID + ├─┬ filler collapsed id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" + │ ├─┬ LEAF_GROUP collapsed hidden id:row-group-country-Ireland-year-2020 ag-Grid-AutoColumn:2020 + │ │ └── LEAF hidden id:1 country:"Ireland" year:2020 + │ └─┬ LEAF_GROUP collapsed hidden id:row-group-country-Ireland-year-2021 ag-Grid-AutoColumn:2021 + │ · └── LEAF hidden id:2 country:"Ireland" year:2021 + └─┬ filler collapsed id:row-group-country-France ag-Grid-AutoColumn:"France" + · └─┬ LEAF_GROUP collapsed hidden id:row-group-country-France-year-2020 ag-Grid-AutoColumn:2020 + · · └── LEAF hidden id:3 country:"France" year:2020 + `); + + // Expand Ireland (non-leaf group) + api.setRowNodeExpanded(api.getRowNode('row-group-country-Ireland')!, true, false, true); + + await new GridRows(api, 'Ireland expanded, year leaf groups forced collapsed').check(` + ROOT id:ROOT_NODE_ID + ├─┬ filler id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" + │ ├─┬ LEAF_GROUP collapsed id:row-group-country-Ireland-year-2020 ag-Grid-AutoColumn:2020 + │ │ └── LEAF hidden id:1 country:"Ireland" year:2020 + │ └─┬ LEAF_GROUP collapsed id:row-group-country-Ireland-year-2021 ag-Grid-AutoColumn:2021 + │ · └── LEAF hidden id:2 country:"Ireland" year:2021 + └─┬ filler collapsed id:row-group-country-France ag-Grid-AutoColumn:"France" + · └─┬ LEAF_GROUP collapsed hidden id:row-group-country-France-year-2020 ag-Grid-AutoColumn:2020 + · · └── LEAF hidden id:3 country:"France" year:2020 + `); + + // Add sport as 3rd group column + api.setGridOption('columnDefs', [ + { field: 'country', rowGroup: true, hide: true }, + { field: 'year', rowGroup: true, hide: true }, + { field: 'sport', rowGroup: true, hide: true }, + ]); + + // Ireland preserved expanded (non-leaf, snapshot match). + // Year now non-leaf → stays collapsed (snapshot match). + // Sport is new leaf group → forced collapsed in pivot mode. + await new GridRows(api, 'after adding sport — leaf groups forced collapsed').check(` + ROOT id:ROOT_NODE_ID + ├─┬ filler id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" + │ ├─┬ filler collapsed id:row-group-country-Ireland-year-2020 ag-Grid-AutoColumn:2020 + │ │ └─┬ LEAF_GROUP collapsed hidden id:row-group-country-Ireland-year-2020-sport-Football ag-Grid-AutoColumn:"Football" + │ │ · └── LEAF hidden id:1 country:"Ireland" year:2020 sport:"Football" + │ └─┬ filler collapsed id:row-group-country-Ireland-year-2021 ag-Grid-AutoColumn:2021 + │ · └─┬ LEAF_GROUP collapsed hidden id:row-group-country-Ireland-year-2021-sport-Rugby ag-Grid-AutoColumn:"Rugby" + │ · · └── LEAF hidden id:2 country:"Ireland" year:2021 sport:"Rugby" + └─┬ filler collapsed id:row-group-country-France ag-Grid-AutoColumn:"France" + · └─┬ filler collapsed hidden id:row-group-country-France-year-2020 ag-Grid-AutoColumn:2020 + · · └─┬ LEAF_GROUP collapsed hidden id:row-group-country-France-year-2020-sport-Football ag-Grid-AutoColumn:"Football" + · · · └── LEAF hidden id:3 country:"France" year:2020 sport:"Football" + `); + }); + + test('pivot mode with active pivot — add deeper group column preserves non-leaf expansion, leaf groups forced collapsed', async () => { + const rowData = [ + { id: '1', country: 'Ireland', year: 2020, sport: 'Football', medals: 2 }, + { id: '2', country: 'Ireland', year: 2021, sport: 'Rugby', medals: 3 }, + { id: '3', country: 'France', year: 2020, sport: 'Football', medals: 4 }, + ]; + + const gridRowsOptions = { + forcedColumns: ['ag-Grid-AutoColumn', 'pivot_sport_Football_medals', 'pivot_sport_Rugby_medals'], + }; + + const api = pivotGridsManager.createGrid('myGrid', { + columnDefs: [ + { field: 'country', rowGroup: true, hide: true }, + { field: 'sport', pivot: true, hide: true }, + { field: 'medals', aggFunc: 'sum', hide: true }, + { field: 'year' }, + ], + pivotMode: true, + rowData, + getRowId: (params) => params.data.id, + }); + + // Country is LEAF_GROUP → forced collapsed in pivot mode + // Secondary pivot columns created from sport values + await new GridRows(api, 'initial — leaf groups forced collapsed with pivot', gridRowsOptions).check(` + ROOT id:ROOT_NODE_ID pivot_sport_Football_medals:6 pivot_sport_Rugby_medals:3 + ├─┬ LEAF_GROUP collapsed id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" pivot_sport_Football_medals:2 pivot_sport_Rugby_medals:3 + │ ├── LEAF hidden id:1 pivot_sport_Football_medals:2 pivot_sport_Rugby_medals:2 + │ └── LEAF hidden id:2 pivot_sport_Football_medals:3 pivot_sport_Rugby_medals:3 + └─┬ LEAF_GROUP collapsed id:row-group-country-France ag-Grid-AutoColumn:"France" pivot_sport_Football_medals:4 pivot_sport_Rugby_medals:null + · └── LEAF hidden id:3 pivot_sport_Football_medals:4 pivot_sport_Rugby_medals:4 + `); + + // Expand Ireland (leaf group in pivot — user override) + api.setRowNodeExpanded(api.getRowNode('row-group-country-Ireland')!, true, false, true); + + // Add year as 2nd group column — country becomes non-leaf + api.setGridOption('columnDefs', [ + { field: 'country', rowGroup: true, hide: true }, + { field: 'year', rowGroup: true, hide: true }, + { field: 'sport', pivot: true, hide: true }, + { field: 'medals', aggFunc: 'sum', hide: true }, + ]); + + // Ireland preserved expanded (non-leaf now, snapshot match). + // Year groups are new leaf groups → forced collapsed in pivot mode. + await new GridRows(api, 'after adding year — non-leaf preserved, leaf collapsed', gridRowsOptions).check(` + ROOT id:ROOT_NODE_ID pivot_sport_Football_medals:6 pivot_sport_Rugby_medals:3 + ├─┬ filler id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" pivot_sport_Football_medals:2 pivot_sport_Rugby_medals:3 + │ ├─┬ LEAF_GROUP collapsed id:row-group-country-Ireland-year-2020 ag-Grid-AutoColumn:2020 pivot_sport_Football_medals:2 pivot_sport_Rugby_medals:null + │ │ └── LEAF hidden id:1 pivot_sport_Football_medals:2 pivot_sport_Rugby_medals:2 + │ └─┬ LEAF_GROUP collapsed id:row-group-country-Ireland-year-2021 ag-Grid-AutoColumn:2021 pivot_sport_Football_medals:null pivot_sport_Rugby_medals:3 + │ · └── LEAF hidden id:2 pivot_sport_Football_medals:3 pivot_sport_Rugby_medals:3 + └─┬ filler collapsed id:row-group-country-France ag-Grid-AutoColumn:"France" pivot_sport_Football_medals:4 pivot_sport_Rugby_medals:null + · └─┬ LEAF_GROUP collapsed hidden id:row-group-country-France-year-2020 ag-Grid-AutoColumn:2020 pivot_sport_Football_medals:4 pivot_sport_Rugby_medals:null + · · └── LEAF hidden id:3 pivot_sport_Football_medals:4 pivot_sport_Rugby_medals:4 + `); + }); + + test('resetRowGroupExpansion in pivot mode forces leaf groups collapsed, re-evaluates non-leaf defaults', async () => { + const rowData = [ + { id: '1', country: 'Ireland', year: 2020, sport: 'Football' }, + { id: '2', country: 'Ireland', year: 2021, sport: 'Rugby' }, + { id: '3', country: 'France', year: 2020, sport: 'Football' }, + ]; + + const api = pivotGridsManager.createGrid('myGrid', { + columnDefs: [ + { field: 'country', rowGroup: true, hide: true }, + { field: 'year', rowGroup: true, hide: true }, + ], + pivotMode: true, + groupDefaultExpanded: -1, + rowData, + getRowId: (params) => params.data.id, + }); + + // groupDefaultExpanded: -1 expands non-leaf groups; leaf groups forced collapsed in pivot + await new GridRows(api, 'initial — non-leaf expanded, leaf forced collapsed').check(` + ROOT id:ROOT_NODE_ID + ├─┬ filler id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" + │ ├─┬ LEAF_GROUP collapsed id:row-group-country-Ireland-year-2020 ag-Grid-AutoColumn:2020 + │ │ └── LEAF hidden id:1 country:"Ireland" year:2020 + │ └─┬ LEAF_GROUP collapsed id:row-group-country-Ireland-year-2021 ag-Grid-AutoColumn:2021 + │ · └── LEAF hidden id:2 country:"Ireland" year:2021 + └─┬ filler id:row-group-country-France ag-Grid-AutoColumn:"France" + · └─┬ LEAF_GROUP collapsed id:row-group-country-France-year-2020 ag-Grid-AutoColumn:2020 + · · └── LEAF hidden id:3 country:"France" year:2020 + `); + + // Collapse Ireland, expand year leaf groups (user overrides) + api.setRowNodeExpanded(api.getRowNode('row-group-country-Ireland')!, false, false, true); + api.setRowNodeExpanded(api.getRowNode('row-group-country-Ireland-year-2020')!, true, false, true); + + // Leaf groups in pivot mode are never expanded (getter returns false), + // so setRowNodeExpanded(true) has no visible effect on leaf groups. + await new GridRows(api, 'user overrides: Ireland collapsed, year 2020 expanded').check(` + ROOT id:ROOT_NODE_ID + ├─┬ filler collapsed id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" + │ ├─┬ LEAF_GROUP collapsed hidden id:row-group-country-Ireland-year-2020 ag-Grid-AutoColumn:2020 + │ │ └── LEAF hidden id:1 country:"Ireland" year:2020 + │ └─┬ LEAF_GROUP collapsed hidden id:row-group-country-Ireland-year-2021 ag-Grid-AutoColumn:2021 + │ · └── LEAF hidden id:2 country:"Ireland" year:2021 + └─┬ filler id:row-group-country-France ag-Grid-AutoColumn:"France" + · └─┬ LEAF_GROUP collapsed id:row-group-country-France-year-2020 ag-Grid-AutoColumn:2020 + · · └── LEAF hidden id:3 country:"France" year:2020 + `); + + api.resetRowGroupExpansion(); + + // Non-leaf groups re-evaluated against groupDefaultExpanded: -1 → expanded. + // Leaf groups forced collapsed in pivot mode regardless. + await new GridRows(api, 'after reset — non-leaf expanded, leaf forced collapsed').check(` + ROOT id:ROOT_NODE_ID + ├─┬ filler id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" + │ ├─┬ LEAF_GROUP collapsed id:row-group-country-Ireland-year-2020 ag-Grid-AutoColumn:2020 + │ │ └── LEAF hidden id:1 country:"Ireland" year:2020 + │ └─┬ LEAF_GROUP collapsed id:row-group-country-Ireland-year-2021 ag-Grid-AutoColumn:2021 + │ · └── LEAF hidden id:2 country:"Ireland" year:2021 + └─┬ filler id:row-group-country-France ag-Grid-AutoColumn:"France" + · └─┬ LEAF_GROUP collapsed id:row-group-country-France-year-2020 ag-Grid-AutoColumn:2020 + · · └── LEAF hidden id:3 country:"France" year:2020 + `); + }); + }); + + describe('selection during group column changes', () => { + const gridsManagerWithSelection = new TestGridsManager({ + modules: [ClientSideRowModelModule, GridStateModule, RowSelectionModule, RowGroupingModule], + }); + + beforeEach(() => { + gridsManagerWithSelection.reset(); + }); + + afterEach(() => { + gridsManagerWithSelection.reset(); + }); + + test('group and leaf selections are preserved when adding a deeper group column', async () => { + const rowData = [ + { id: '1', country: 'Ireland', year: 2020 }, + { id: '2', country: 'Ireland', year: 2021 }, + { id: '3', country: 'France', year: 2020 }, + { id: '4', country: 'France', year: 2021 }, + ]; + + const api = gridsManagerWithSelection.createGrid('myGrid', { + columnDefs: [{ field: 'country', rowGroup: true, hide: true }, { field: 'year' }], + rowSelection: { mode: 'multiRow' }, + groupDefaultExpanded: -1, + rowData, + getRowId: (params) => params.data.id, + }); + + // Select a group node and some leaf nodes + api.setNodesSelected({ + nodes: [api.getRowNode('row-group-country-Ireland')!, api.getRowNode('1')!, api.getRowNode('3')!], + newValue: true, + }); + + await new GridRows(api, 'initial selection').check(` + ROOT id:ROOT_NODE_ID + ├─┬ LEAF_GROUP selected id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" + │ ├── LEAF selected id:1 country:"Ireland" year:2020 + │ └── LEAF id:2 country:"Ireland" year:2021 + └─┬ LEAF_GROUP id:row-group-country-France ag-Grid-AutoColumn:"France" + · ├── LEAF selected id:3 country:"France" year:2020 + · └── LEAF id:4 country:"France" year:2021 + `); + + // Add year as a second grouping column — triggers shotgunResetEverything + api.setGridOption('columnDefs', [ + { field: 'country', rowGroup: true, hide: true }, + { field: 'year', rowGroup: true, hide: true }, + ]); + + // Leaf selections (id:1, id:3) and the Ireland group selection are preserved + // because group nodes with the same ID are reused during the regroup. + await new GridRows(api, 'after adding year group column').check(` + ROOT id:ROOT_NODE_ID + ├─┬ filler selected id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" + │ ├─┬ LEAF_GROUP id:row-group-country-Ireland-year-2020 ag-Grid-AutoColumn:2020 + │ │ └── LEAF selected id:1 country:"Ireland" year:2020 + │ └─┬ LEAF_GROUP id:row-group-country-Ireland-year-2021 ag-Grid-AutoColumn:2021 + │ · └── LEAF id:2 country:"Ireland" year:2021 + └─┬ filler id:row-group-country-France ag-Grid-AutoColumn:"France" + · ├─┬ LEAF_GROUP id:row-group-country-France-year-2020 ag-Grid-AutoColumn:2020 + · │ └── LEAF selected id:3 country:"France" year:2020 + · └─┬ LEAF_GROUP id:row-group-country-France-year-2021 ag-Grid-AutoColumn:2021 + · · └── LEAF id:4 country:"France" year:2021 + `); + }); + + test('group selections are preserved for reused nodes but lost for destroyed nodes when removing a group column', async () => { + const rowData = [ + { id: '1', country: 'Ireland', year: 2020 }, + { id: '2', country: 'Ireland', year: 2021 }, + { id: '3', country: 'France', year: 2020 }, + ]; + + const api = gridsManagerWithSelection.createGrid('myGrid', { + columnDefs: [ + { field: 'country', rowGroup: true, hide: true }, + { field: 'year', rowGroup: true, hide: true }, + ], + rowSelection: { mode: 'multiRow' }, + groupDefaultExpanded: -1, + rowData, + getRowId: (params) => params.data.id, + }); + + // Select group nodes and leaf nodes + api.setNodesSelected({ + nodes: [ + api.getRowNode('row-group-country-Ireland')!, + api.getRowNode('row-group-country-Ireland-year-2020')!, + api.getRowNode('1')!, + api.getRowNode('3')!, + ], + newValue: true, + }); + + await new GridRows(api, 'initial selection').check(` + ROOT id:ROOT_NODE_ID + ├─┬ filler selected id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" + │ ├─┬ LEAF_GROUP selected id:row-group-country-Ireland-year-2020 ag-Grid-AutoColumn:2020 + │ │ └── LEAF selected id:1 country:"Ireland" year:2020 + │ └─┬ LEAF_GROUP id:row-group-country-Ireland-year-2021 ag-Grid-AutoColumn:2021 + │ · └── LEAF id:2 country:"Ireland" year:2021 + └─┬ filler id:row-group-country-France ag-Grid-AutoColumn:"France" + · └─┬ LEAF_GROUP id:row-group-country-France-year-2020 ag-Grid-AutoColumn:2020 + · · └── LEAF selected id:3 country:"France" year:2020 + `); + + // Remove year from grouping + api.setGridOption('columnDefs', [ + { field: 'country', rowGroup: true, hide: true }, + { field: 'year', rowGroup: false }, + ]); + + // Leaf selections preserved. Ireland group selection preserved (same ID reused). + // Year sub-group selections lost (those nodes are destroyed). + await new GridRows(api, 'after removing year group column').check(` + ROOT id:ROOT_NODE_ID + ├─┬ LEAF_GROUP selected id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" + │ ├── LEAF selected id:1 country:"Ireland" year:2020 + │ └── LEAF id:2 country:"Ireland" year:2021 + └─┬ LEAF_GROUP id:row-group-country-France ag-Grid-AutoColumn:"France" + · └── LEAF selected id:3 country:"France" year:2020 + `); + }); + }); }); diff --git a/testing/behavioural/src/row-data/row-node-expanded.test.ts b/testing/behavioural/src/row-data/row-node-expanded.test.ts new file mode 100644 index 00000000000..3b739643c50 --- /dev/null +++ b/testing/behavioural/src/row-data/row-node-expanded.test.ts @@ -0,0 +1,46 @@ +import type { RowNode } from 'ag-grid-community'; +import { ClientSideRowModelModule } from 'ag-grid-community'; + +import { GridRows, TestGridsManager } from '../test-utils'; + +describe('RowNode.expanded getter without expansionSvc', () => { + const gridsManager = new TestGridsManager({ + modules: [ClientSideRowModelModule], + }); + + afterEach(() => { + gridsManager.reset(); + }); + + test('root node expanded returns true without expansionSvc', async () => { + const api = gridsManager.createGrid('G', { + columnDefs: [{ field: 'name' }], + rowData: [{ name: 'Alice' }, { name: 'Bob' }], + }); + + const gridRows = new GridRows(api, 'root node expanded'); + const rootNode = gridRows.rootRowNode!; + + expect(rootNode).toBeDefined(); + expect(rootNode.id).toBe('ROOT_NODE_ID'); + expect(rootNode.level).toBe(-1); + expect(rootNode._expanded).toBe(true); + expect(rootNode.expanded).toBe(true); + }); + + test('leaf nodes expanded returns false without expansionSvc', async () => { + const api = gridsManager.createGrid('G', { + columnDefs: [{ field: 'name' }], + rowData: [{ name: 'Alice' }], + getRowId: ({ data }) => data.name, + }); + + const leafNode = api.getRowNode('Alice')!; + + expect(leafNode).toBeDefined(); + expect(leafNode.group).toBe(false); + expect((leafNode as RowNode)._expanded).toBeUndefined(); + // Without expansionSvc, the getter falls back to `_expanded as boolean` which is undefined (falsy) + expect(leafNode.expanded).toEqual(false); + }); +}); diff --git a/testing/behavioural/src/services/server-side-expansion-service.test.ts b/testing/behavioural/src/services/server-side-expansion-service.test.ts index 1bdc8131652..c8f3612606c 100644 --- a/testing/behavioural/src/services/server-side-expansion-service.test.ts +++ b/testing/behavioural/src/services/server-side-expansion-service.test.ts @@ -30,7 +30,9 @@ describe('ServerSideExpansionService', () => { expansionService['serverSideRowModel'] = beans.serverSideRowModel as any; expansionService['eventSvc'] = beans.eventSvc; expansionService['beans'] = beans; + expansionService['createBean'] = (bean: any) => bean; expansionService['createManagedBean'] = (bean: any) => bean; + expansionService['destroyBean'] = () => undefined; expansionService['addManagedEventListeners'] = () => []; expansionService['addManagedPropertyListener'] = () => () => null; expansionService.postConstruct(); diff --git a/testing/behavioural/src/test-utils/gridRows/gridRows.ts b/testing/behavioural/src/test-utils/gridRows/gridRows.ts index abbcab8b766..2c023ae50f8 100644 --- a/testing/behavioural/src/test-utils/gridRows/gridRows.ts +++ b/testing/behavioural/src/test-utils/gridRows/gridRows.ts @@ -265,6 +265,12 @@ export class GridRows { attempt.loadErrors(); lastError = attempt.#tryCheck(diagramSnapshot); if (!lastError) { + if (i > 0) { + console.error( + `GridRows flaky check detected for "${this.label}" — passed only after retrying with delays. ` + + `Add \`await asyncSetTimeout(N)\` before this check to avoid intermittent failures.` + ); + } return this; } if (i < retryDelays.length) { @@ -273,13 +279,6 @@ export class GridRows { } } - if (attempt !== this) { - console.error( - `GridRows flaky check detected for "${this.label}" — passed only after retrying with delays. ` + - `Add \`await asyncSetTimeout(N)\` before this check to avoid intermittent failures.` - ); - } - addDiagramToError(lastError, attempt.makeDiagram(false), this.label); Error.captureStackTrace(lastError, this.check); throw lastError; diff --git a/testing/behavioural/src/test-utils/gridRows/rows-validation/gridRowsValidator.ts b/testing/behavioural/src/test-utils/gridRows/rows-validation/gridRowsValidator.ts index 61e95617f07..e74698e1464 100644 --- a/testing/behavioural/src/test-utils/gridRows/rows-validation/gridRowsValidator.ts +++ b/testing/behavioural/src/test-utils/gridRows/rows-validation/gridRowsValidator.ts @@ -54,7 +54,6 @@ export class GridRowsValidator { const rowErrors = this.errors.get(root); rowErrors.expectValueEqual('id', root.id, csrm ? 'ROOT_NODE_ID' : undefined); rowErrors.expectValueEqual('level', root.level, -1); - rowErrors.expectValueEqual('expanded', root.expanded, undefined); rowErrors.add(!!root.key && 'Root node has key ' + root.key); rowErrors.add(root.destroyed && 'Root node is destroyed'); rowErrors.add(root.rowIndex !== null && 'Root node has rowIndex ' + root.rowIndex); From 71e7899f14be57a28e67e52576514373376bc077 Mon Sep 17 00:00:00 2001 From: AgGitDeployment Date: Tue, 17 Mar 2026 12:42:40 +0000 Subject: [PATCH 2/3] Merge from latest. --- documentation/ag-grid-docs/package.json | 12 +-- packages/ag-grid-community/package.json | 2 +- packages/ag-grid-enterprise/package.json | 8 +- testing/accessibility/package.json | 4 +- testing/module-size-angular/package.json | 4 +- testing/module-size/package.json | 4 +- yarn.lock | 113 +++++++++-------------- 7 files changed, 61 insertions(+), 86 deletions(-) diff --git a/documentation/ag-grid-docs/package.json b/documentation/ag-grid-docs/package.json index 1f3ecdf6a23..4fffbe5a322 100644 --- a/documentation/ag-grid-docs/package.json +++ b/documentation/ag-grid-docs/package.json @@ -53,12 +53,12 @@ "@types/react": "^18.2.47", "@types/react-dom": "^18.2.18", "@pqina/flip": "^1.8.4", - "ag-charts-angular": "13.1.0-beta.20260316", - "ag-charts-community": "13.1.0-beta.20260316", - "ag-charts-enterprise": "13.1.0-beta.20260316", - "ag-charts-types": "13.1.0-beta.20260316", - "ag-charts-react": "13.1.0-beta.20260316", - "ag-charts-vue3": "13.1.0-beta.20260316", + "ag-charts-angular": "13.1.0-beta.20260317", + "ag-charts-community": "13.1.0-beta.20260317", + "ag-charts-enterprise": "13.1.0-beta.20260317", + "ag-charts-types": "13.1.0-beta.20260317", + "ag-charts-react": "13.1.0-beta.20260317", + "ag-charts-vue3": "13.1.0-beta.20260317", "ag-grid-angular": "35.1.0-beta.20260316.108", "ag-grid-community": "35.1.0-beta.20260316.108", "ag-grid-enterprise": "35.1.0-beta.20260316.108", diff --git a/packages/ag-grid-community/package.json b/packages/ag-grid-community/package.json index 35c21f8d2e9..c07725d8d57 100644 --- a/packages/ag-grid-community/package.json +++ b/packages/ag-grid-community/package.json @@ -119,7 +119,7 @@ ], "homepage": "https://www.ag-grid.com/", "dependencies": { - "ag-charts-types": "13.1.0-beta.20260316" + "ag-charts-types": "13.1.0-beta.20260317" }, "devDependencies": { "web-streams-polyfill": "^3.3.2", diff --git a/packages/ag-grid-enterprise/package.json b/packages/ag-grid-enterprise/package.json index 40265090674..6819efb89d4 100644 --- a/packages/ag-grid-enterprise/package.json +++ b/packages/ag-grid-enterprise/package.json @@ -116,12 +116,12 @@ "ag-grid-community": "35.1.0-beta.20260316.108" }, "optionalDependencies": { - "ag-charts-community": "13.1.0-beta.20260316", - "ag-charts-enterprise": "13.1.0-beta.20260316" + "ag-charts-community": "13.1.0-beta.20260317", + "ag-charts-enterprise": "13.1.0-beta.20260317" }, "devDependencies": { - "ag-charts-community": "13.1.0-beta.20260316", - "ag-charts-enterprise": "13.1.0-beta.20260316", + "ag-charts-community": "13.1.0-beta.20260317", + "ag-charts-enterprise": "13.1.0-beta.20260317", "@types/jest": "^29.5.0", "jest": "^29.5.0", "jest-environment-jsdom": "^29.7.0", diff --git a/testing/accessibility/package.json b/testing/accessibility/package.json index d7d11a7ec0e..3c1f7971309 100644 --- a/testing/accessibility/package.json +++ b/testing/accessibility/package.json @@ -21,8 +21,8 @@ "ag-grid-angular": "35.1.0-beta.20260316.108", "ag-grid-community": "35.1.0-beta.20260316.108", "ag-grid-enterprise": "35.1.0-beta.20260316.108", - "ag-charts-community": "13.1.0-beta.20260316", - "ag-charts-enterprise": "13.1.0-beta.20260316", + "ag-charts-community": "13.1.0-beta.20260317", + "ag-charts-enterprise": "13.1.0-beta.20260317", "rxjs": "~7.8.0", "tslib": "^2.3.0", "zone.js": "~0.15.0" diff --git a/testing/module-size-angular/package.json b/testing/module-size-angular/package.json index 374be546b35..56d69648790 100644 --- a/testing/module-size-angular/package.json +++ b/testing/module-size-angular/package.json @@ -23,8 +23,8 @@ "ag-grid-angular": "35.1.0-beta.20260316.108", "ag-grid-community": "35.1.0-beta.20260316.108", "ag-grid-enterprise": "35.1.0-beta.20260316.108", - "ag-charts-community": "13.1.0-beta.20260316", - "ag-charts-enterprise": "13.1.0-beta.20260316", + "ag-charts-community": "13.1.0-beta.20260317", + "ag-charts-enterprise": "13.1.0-beta.20260317", "rxjs": "~7.8.0", "tslib": "^2.3.0", "zone.js": "~0.15.0" diff --git a/testing/module-size/package.json b/testing/module-size/package.json index bace13437cd..c6f01bdae9d 100644 --- a/testing/module-size/package.json +++ b/testing/module-size/package.json @@ -17,8 +17,8 @@ "ag-grid-react": "35.1.0-beta.20260316.108", "ag-grid-community": "35.1.0-beta.20260316.108", "ag-grid-enterprise": "35.1.0-beta.20260316.108", - "ag-charts-community": "13.1.0-beta.20260316", - "ag-charts-enterprise": "13.1.0-beta.20260316", + "ag-charts-community": "13.1.0-beta.20260317", + "ag-charts-enterprise": "13.1.0-beta.20260317", "ag-shared": "0.0.1", "react": "^18.3.1", "react-dom": "^18.3.1" diff --git a/yarn.lock b/yarn.lock index 12c88bf1e3e..089c86a27c5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9186,61 +9186,61 @@ adm-zip@^0.5.10: resolved "http://52.50.158.57:4873/adm-zip/-/adm-zip-0.5.16.tgz#0b5e4c779f07dedea5805cdccb1147071d94a909" integrity sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ== -ag-charts-angular@13.1.0-beta.20260316: - version "13.1.0-beta.20260316" - resolved "https://registry.ag-grid.com/ag-charts-angular/-/ag-charts-angular-13.1.0-beta.20260316.tgz#9b12a7fde20fb890f91d04c707a43f172bbbd036" - integrity sha512-kmaMfU69ABo+1jCc2dRNjvfbtJCPuyV1+HeIg6idrZcI9a+w2rKz+wuhkjKbNHszshEVANpZ2czlrhRl1fFGAg== +ag-charts-angular@13.1.0-beta.20260317: + version "13.1.0-beta.20260317" + resolved "https://registry.ag-grid.com/ag-charts-angular/-/ag-charts-angular-13.1.0-beta.20260317.tgz#849515c4450b0d6813d3c0c3aaf8a62031e1812e" + integrity sha512-168ttJyl3Yd8jSZKslgJj9al2AouE9jkYdyJNC+lYjWPwUA3WghH87VJ6YwXvPqkwQAzOdeFyuDe87AiTC1aow== dependencies: - ag-charts-community "13.1.0-beta.20260316" + ag-charts-community "13.1.0-beta.20260317" tslib "^2.3.0" -ag-charts-community@13.1.0-beta.20260316: - version "13.1.0-beta.20260316" - resolved "https://registry.ag-grid.com/ag-charts-community/-/ag-charts-community-13.1.0-beta.20260316.tgz#e0da49c8d7e707ed9eca10b705723864a9cd8cc5" - integrity sha512-4NjGJCSEbLgIVQmVFJGsNCE6d+6Okn3HM+ynR03+m/V8Ut9FupoS8KDW6HCRIhR+neHMgyB8Mekv2eIwaRD1UA== +ag-charts-community@13.1.0-beta.20260317: + version "13.1.0-beta.20260317" + resolved "https://registry.ag-grid.com/ag-charts-community/-/ag-charts-community-13.1.0-beta.20260317.tgz#0d641b4d3f670b5af98d9b19dde0e0825c3f09f9" + integrity sha512-UufsabNwr8rIecxqpOkhRjP4lvoK7EKrCWGHNIhVmuW8Nmd2WvS4F2e9cema13jey4g8MsZhj+eXeuFGzE2K9g== dependencies: - ag-charts-core "13.1.0-beta.20260316" - ag-charts-locale "13.1.0-beta.20260316" - ag-charts-types "13.1.0-beta.20260316" + ag-charts-core "13.1.0-beta.20260317" + ag-charts-locale "13.1.0-beta.20260317" + ag-charts-types "13.1.0-beta.20260317" -ag-charts-core@13.1.0-beta.20260316: - version "13.1.0-beta.20260316" - resolved "https://registry.ag-grid.com/ag-charts-core/-/ag-charts-core-13.1.0-beta.20260316.tgz#4b4998f683f0cf5e25dcd25ca5b7ae07ce4c5305" - integrity sha512-mjxNwFOq7djJTg7j/VoUIOY+s0a+iCEErwsjSsmr7ZFL2wgfM97v3vqPLp8HR3rS4lfawkx5qtrNK1Dola4EWw== +ag-charts-core@13.1.0-beta.20260317: + version "13.1.0-beta.20260317" + resolved "https://registry.ag-grid.com/ag-charts-core/-/ag-charts-core-13.1.0-beta.20260317.tgz#bc88a29d8b546c587acbbef0aac7222bdb1a0ce2" + integrity sha512-rE17H78NfyfiS2MUP2QuwWwfQDy7K+VY1uIUaHZN36Lqzg6EjIGZslXyVafSCKL2PzgbrueDzN9aaAjhpUideg== dependencies: - ag-charts-types "13.1.0-beta.20260316" + ag-charts-types "13.1.0-beta.20260317" -ag-charts-enterprise@13.1.0-beta.20260316: - version "13.1.0-beta.20260316" - resolved "https://registry.ag-grid.com/ag-charts-enterprise/-/ag-charts-enterprise-13.1.0-beta.20260316.tgz#9f1a714ede7e90fc4496ee3f56bfd2729cc460fe" - integrity sha512-fPoxS9Dg4KBRpSm8o4mfHcOMkRUxS39hLCaJZDV773kyWf9filvmss7nNwNgfrCr7aGbvhR996m4wY2Jrqiq/g== +ag-charts-enterprise@13.1.0-beta.20260317: + version "13.1.0-beta.20260317" + resolved "https://registry.ag-grid.com/ag-charts-enterprise/-/ag-charts-enterprise-13.1.0-beta.20260317.tgz#04c6ce46c3fecfb274714256a200e0437a225904" + integrity sha512-EKwOnIxOllFsCDZLrumoc9sq2vfI3XY2tTtL0CGvtCCTPOBKeRaGwX3VXSal17KfMPs34v/AhPhJDXvWaI1zjA== dependencies: - ag-charts-community "13.1.0-beta.20260316" - ag-charts-core "13.1.0-beta.20260316" + ag-charts-community "13.1.0-beta.20260317" + ag-charts-core "13.1.0-beta.20260317" -ag-charts-locale@13.1.0-beta.20260316: - version "13.1.0-beta.20260316" - resolved "https://registry.ag-grid.com/ag-charts-locale/-/ag-charts-locale-13.1.0-beta.20260316.tgz#f96ed40b04adbc75db68d07369181629973845d0" - integrity sha512-i1zYD/RWuhVK+Ffd8QbuGVMnfbIZ2p9i/7JGwMYnhPNxht305MFZbIXCh1wiCJqaQYGe6kiOJJ0zn9p/nLqGvg== +ag-charts-locale@13.1.0-beta.20260317: + version "13.1.0-beta.20260317" + resolved "https://registry.ag-grid.com/ag-charts-locale/-/ag-charts-locale-13.1.0-beta.20260317.tgz#24e78e0b69dd42bcf598bd3b3e3efb0d90f8678f" + integrity sha512-sSdIwX2T/vY0+/6ESgxGIOq9f5LLRDQHYCQad2vqqE/zfRS01Lx968Gqlvy5fhQU0L2drivxz3mvxHIK4b7dMw== -ag-charts-react@13.1.0-beta.20260316: - version "13.1.0-beta.20260316" - resolved "https://registry.ag-grid.com/ag-charts-react/-/ag-charts-react-13.1.0-beta.20260316.tgz#44e864cb9a3eebbdd8dd7033931a39bb9b6340dd" - integrity sha512-wTv7M+9obU1NECIpEqQ1OBpXr4AaiSrGO9s761FYRCS1q4IkYxAXYZNCRhdEcbTlzzUcOhbXChAR95aJwcD/oA== +ag-charts-react@13.1.0-beta.20260317: + version "13.1.0-beta.20260317" + resolved "https://registry.ag-grid.com/ag-charts-react/-/ag-charts-react-13.1.0-beta.20260317.tgz#617d002caa0398588bb18f75310b906bd3a936ca" + integrity sha512-Chbg20g8ds+MwoCXyHDh7ZnLcECuGsg8tOY79yux09NRjFA177aZxMG85QyJ9wKMUo4M5N8FRQEsvcyhSGJe9w== dependencies: - ag-charts-community "13.1.0-beta.20260316" + ag-charts-community "13.1.0-beta.20260317" -ag-charts-types@13.1.0-beta.20260316: - version "13.1.0-beta.20260316" - resolved "https://registry.ag-grid.com/ag-charts-types/-/ag-charts-types-13.1.0-beta.20260316.tgz#d360bd47bc21ff977333f7eee49e1fe8197a483d" - integrity sha512-W44kNoCmpWLv9h66RtcAOGdGl746l+kM5YtgJoLs0Faq+eBy0b9woiqxQV20LeEfVaTwcWHp1zroHE5zlzeDEg== +ag-charts-types@13.1.0-beta.20260317: + version "13.1.0-beta.20260317" + resolved "https://registry.ag-grid.com/ag-charts-types/-/ag-charts-types-13.1.0-beta.20260317.tgz#34862f2c558db6dc4500e08215db834c53d6fc84" + integrity sha512-EcDingQ3VDuRtOMKsUsGNejCRvWE91VXswuAwg6z4WnAJxeFaU5nQuLoYSVE63kkgX83Ciw1epq57AKFJWJNVg== -ag-charts-vue3@13.1.0-beta.20260316: - version "13.1.0-beta.20260316" - resolved "https://registry.ag-grid.com/ag-charts-vue3/-/ag-charts-vue3-13.1.0-beta.20260316.tgz#9550c8a3b80ad5cfe305ba1bbdfc559b9b53742f" - integrity sha512-Lbem16r/o+eN25eIgQUabla1jqx46thWvsp+4+Zk9MLP6bjhvY36vl4aS3FMjGUJ6JyL5f82G8nFz3ref78t5g== +ag-charts-vue3@13.1.0-beta.20260317: + version "13.1.0-beta.20260317" + resolved "https://registry.ag-grid.com/ag-charts-vue3/-/ag-charts-vue3-13.1.0-beta.20260317.tgz#41401eb165ebf9ee3082fdee4e093ebced31cb04" + integrity sha512-prBELHLc3CtVRvmfp0fGA+1yb4nG8aYzhYSFXO7IfRD7QjyUD04J2yLUJI1mLzBuSAkWw7GqHk8XXOymjlJxnA== dependencies: - ag-charts-community "13.1.0-beta.20260316" + ag-charts-community "13.1.0-beta.20260317" agent-base@6, agent-base@^6.0.2: version "6.0.2" @@ -23029,16 +23029,7 @@ string-length@^4.0.1: char-regex "^1.0.2" strip-ansi "^6.0.0" -"string-width-cjs@npm:string-width@^4.2.0": - version "4.2.3" - resolved "http://52.50.158.57:4873/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "http://52.50.158.57:4873/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -23143,7 +23134,7 @@ stringify-entities@^4.0.0: character-entities-html4 "^2.0.0" character-entities-legacy "^3.0.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "http://52.50.158.57:4873/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -23157,13 +23148,6 @@ strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: dependencies: ansi-regex "^4.1.0" -strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "http://52.50.158.57:4873/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - strip-ansi@^7.0.1, strip-ansi@^7.1.0: version "7.1.2" resolved "http://52.50.158.57:4873/strip-ansi/-/strip-ansi-7.1.2.tgz#132875abde678c7ea8d691533f2e7e22bb744dba" @@ -25633,7 +25617,7 @@ wordwrap@^1.0.0: resolved "http://52.50.158.57:4873/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: version "7.0.0" resolved "http://52.50.158.57:4873/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -25669,15 +25653,6 @@ wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "http://52.50.158.57:4873/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - wrap-ansi@^9.0.0: version "9.0.2" resolved "http://52.50.158.57:4873/wrap-ansi/-/wrap-ansi-9.0.2.tgz#956832dea9494306e6d209eb871643bb873d7c98" From 689865df243b917e7f02381bcdc8dd5ab75eb117 Mon Sep 17 00:00:00 2001 From: AgGitDeployment Date: Tue, 17 Mar 2026 13:09:09 +0000 Subject: [PATCH 3/3] Merge from latest. --- .env | 4 ++-- community-modules/locale/package.json | 2 +- community-modules/styles/package.json | 2 +- documentation/ag-grid-docs/package.json | 12 ++++++------ documentation/update-algolia-indices/package.json | 2 +- package.json | 2 +- packages/ag-grid-angular/package.json | 6 +++--- .../projects/ag-grid-angular/package.json | 4 ++-- packages/ag-grid-community/package.json | 2 +- packages/ag-grid-community/src/version.ts | 2 +- packages/ag-grid-enterprise/package.json | 4 ++-- packages/ag-grid-enterprise/src/version.ts | 2 +- packages/ag-grid-react/package.json | 6 +++--- packages/ag-grid-vue3/package.json | 4 ++-- .../package.json | 2 +- plugins/ag-grid-generate-example-files/package.json | 4 ++-- plugins/ag-grid-task-autogen/package.json | 2 +- testing/accessibility/package.json | 8 ++++---- testing/angular-tests/package.json | 6 +++--- testing/behavioural/package.json | 8 ++++---- testing/behavioural/src/version.ts | 2 +- testing/csp/package.json | 2 +- testing/module-size-angular/package.json | 8 ++++---- testing/module-size/package.json | 8 ++++---- testing/public-recipes/e2e/package.json | 4 ++-- testing/vue3-tests/package.json | 8 ++++---- 26 files changed, 58 insertions(+), 58 deletions(-) diff --git a/.env b/.env index 6330f63e9ba..85421b83698 100644 --- a/.env +++ b/.env @@ -1,6 +1,6 @@ # Production Build -BUILD_GRID_VERSION=35.1.0-beta.20260316.108 -BUILD_CHARTS_VERSION=13.1.0-beta.20260316 +BUILD_GRID_VERSION=35.1.0-beta.20260317.135 +BUILD_CHARTS_VERSION=13.1.0-beta.20260317 ENV=local NX_BATCH_MODE=true NX_ADD_PLUGINS=false diff --git a/community-modules/locale/package.json b/community-modules/locale/package.json index 10c23da8bbd..46e8b6b86a7 100644 --- a/community-modules/locale/package.json +++ b/community-modules/locale/package.json @@ -1,6 +1,6 @@ { "name": "@ag-grid-community/locale", - "version": "35.1.0-beta.20260316.108", + "version": "35.1.0-beta.20260317.135", "description": "Localisation Module for AG Grid, providing translations in 31 languages.", "main": "./dist/package/main.cjs.js", "types": "./dist/types/src/main.d.ts", diff --git a/community-modules/styles/package.json b/community-modules/styles/package.json index 887d6b5b8cf..ae6060f5592 100644 --- a/community-modules/styles/package.json +++ b/community-modules/styles/package.json @@ -1,6 +1,6 @@ { "name": "@ag-grid-community/styles", - "version": "35.1.0-beta.20260316.108", + "version": "35.1.0-beta.20260317.135", "description": "AG Grid Styles and Themes", "main": "_index.scss", "files": [ diff --git a/documentation/ag-grid-docs/package.json b/documentation/ag-grid-docs/package.json index 4fffbe5a322..7c7fc94dbe3 100644 --- a/documentation/ag-grid-docs/package.json +++ b/documentation/ag-grid-docs/package.json @@ -2,7 +2,7 @@ "name": "ag-grid-docs", "description": "Documentation for AG Grid", "type": "module", - "version": "35.1.0-beta.20260316.108", + "version": "35.1.0-beta.20260317.135", "repository": { "type": "git", "url": "https://github.com/ag-grid/ag-grid.git" @@ -59,11 +59,11 @@ "ag-charts-types": "13.1.0-beta.20260317", "ag-charts-react": "13.1.0-beta.20260317", "ag-charts-vue3": "13.1.0-beta.20260317", - "ag-grid-angular": "35.1.0-beta.20260316.108", - "ag-grid-community": "35.1.0-beta.20260316.108", - "ag-grid-enterprise": "35.1.0-beta.20260316.108", - "ag-grid-react": "35.1.0-beta.20260316.108", - "ag-grid-vue3": "35.1.0-beta.20260316.108", + "ag-grid-angular": "35.1.0-beta.20260317.135", + "ag-grid-community": "35.1.0-beta.20260317.135", + "ag-grid-enterprise": "35.1.0-beta.20260317.135", + "ag-grid-react": "35.1.0-beta.20260317.135", + "ag-grid-vue3": "35.1.0-beta.20260317.135", "algoliasearch": "^4.18.0", "astro": "5.16.6", "cheerio": "^1.0.0", diff --git a/documentation/update-algolia-indices/package.json b/documentation/update-algolia-indices/package.json index 16a665039e5..d6896934039 100644 --- a/documentation/update-algolia-indices/package.json +++ b/documentation/update-algolia-indices/package.json @@ -1,6 +1,6 @@ { "name": "update-algolia-indices", - "version": "35.1.0-beta.20260316.108", + "version": "35.1.0-beta.20260317.135", "description": "Update algolia indices", "main": "src/index.ts", "type": "module", diff --git a/package.json b/package.json index ceab2c959a3..893196768a6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ag-grid", - "version": "35.1.0-beta.20260316.108", + "version": "35.1.0-beta.20260317.135", "license": "MIT", "scripts": { "compressVideo": "tsx external/ag-website-shared/scripts/compress-video", diff --git a/packages/ag-grid-angular/package.json b/packages/ag-grid-angular/package.json index 9308fb4c7c8..4cdf1133ee0 100644 --- a/packages/ag-grid-angular/package.json +++ b/packages/ag-grid-angular/package.json @@ -1,6 +1,6 @@ { "name": "ag-grid-angular", - "version": "35.1.0-beta.20260316.108", + "version": "35.1.0-beta.20260317.135", "description": "AG Grid Angular Component", "scripts": { "clean": "rimraf dist", @@ -15,7 +15,7 @@ "module": "./dist/ag-grid-angular/fesm2022/ag-grid-angular.mjs", "typings": "./dist/ag-grid-angular/index.d.ts", "dependencies": { - "ag-grid-community": "35.1.0-beta.20260316.108", + "ag-grid-community": "35.1.0-beta.20260317.135", "@angular/animations": "^18.0.7", "@angular/common": "^18.0.7", "@angular/compiler": "^18.0.7", @@ -27,7 +27,7 @@ "zone.js": "~0.15.1" }, "devDependencies": { - "ag-grid-community": "35.1.0-beta.20260316.108", + "ag-grid-community": "35.1.0-beta.20260317.135", "@angular-devkit/build-angular": "^18.0.7", "@angular/cli": "^18.0.7", "@angular/forms": "^18.0.7", diff --git a/packages/ag-grid-angular/projects/ag-grid-angular/package.json b/packages/ag-grid-angular/projects/ag-grid-angular/package.json index 221f2b96f12..17271e576c8 100644 --- a/packages/ag-grid-angular/projects/ag-grid-angular/package.json +++ b/packages/ag-grid-angular/projects/ag-grid-angular/package.json @@ -1,6 +1,6 @@ { "name": "ag-grid-angular", - "version": "35.1.0-beta.20260316.108", + "version": "35.1.0-beta.20260317.135", "description": "AG Grid Angular Component", "license": "MIT", "peerDependencies": { @@ -8,7 +8,7 @@ "@angular/core": ">= 18.0.0" }, "dependencies": { - "ag-grid-community": "35.1.0-beta.20260316.108", + "ag-grid-community": "35.1.0-beta.20260317.135", "tslib": "^2.3.0" }, "repository": { diff --git a/packages/ag-grid-community/package.json b/packages/ag-grid-community/package.json index c07725d8d57..a5d40612bb5 100644 --- a/packages/ag-grid-community/package.json +++ b/packages/ag-grid-community/package.json @@ -1,6 +1,6 @@ { "name": "ag-grid-community", - "version": "35.1.0-beta.20260316.108", + "version": "35.1.0-beta.20260317.135", "description": "Advanced Data Grid / Data Table supporting Javascript / Typescript / React / Angular / Vue", "main": "./dist/package/main.cjs.js", "types": "./dist/types/src/main.d.ts", diff --git a/packages/ag-grid-community/src/version.ts b/packages/ag-grid-community/src/version.ts index 353202c0311..63095b37ec2 100644 --- a/packages/ag-grid-community/src/version.ts +++ b/packages/ag-grid-community/src/version.ts @@ -1,2 +1,2 @@ // DO NOT UPDATE MANUALLY: Generated from script during build time -export const VERSION = '35.1.0-beta.20260316.108'; +export const VERSION = '35.1.0-beta.20260317.135'; diff --git a/packages/ag-grid-enterprise/package.json b/packages/ag-grid-enterprise/package.json index 6819efb89d4..b739aadeff5 100644 --- a/packages/ag-grid-enterprise/package.json +++ b/packages/ag-grid-enterprise/package.json @@ -1,6 +1,6 @@ { "name": "ag-grid-enterprise", - "version": "35.1.0-beta.20260316.108", + "version": "35.1.0-beta.20260317.135", "description": "Advanced Data Grid / Data Table supporting Javascript / Typescript / React / Angular / Vue", "main": "./dist/package/main.cjs.js", "types": "./dist/types/src/main.d.ts", @@ -113,7 +113,7 @@ ], "homepage": "https://www.ag-grid.com/", "dependencies": { - "ag-grid-community": "35.1.0-beta.20260316.108" + "ag-grid-community": "35.1.0-beta.20260317.135" }, "optionalDependencies": { "ag-charts-community": "13.1.0-beta.20260317", diff --git a/packages/ag-grid-enterprise/src/version.ts b/packages/ag-grid-enterprise/src/version.ts index 353202c0311..63095b37ec2 100644 --- a/packages/ag-grid-enterprise/src/version.ts +++ b/packages/ag-grid-enterprise/src/version.ts @@ -1,2 +1,2 @@ // DO NOT UPDATE MANUALLY: Generated from script during build time -export const VERSION = '35.1.0-beta.20260316.108'; +export const VERSION = '35.1.0-beta.20260317.135'; diff --git a/packages/ag-grid-react/package.json b/packages/ag-grid-react/package.json index 5613bf37d3c..9b415d56318 100644 --- a/packages/ag-grid-react/package.json +++ b/packages/ag-grid-react/package.json @@ -1,6 +1,6 @@ { "name": "ag-grid-react", - "version": "35.1.0-beta.20260316.108", + "version": "35.1.0-beta.20260317.135", "description": "AG Grid React Component", "main": "./dist/package/index.cjs.js", "types": "./dist/types/src/index.d.ts", @@ -31,7 +31,7 @@ "devDependencies": { "@babel/runtime": "^7.27.1", "prop-types": "^15.6.2", - "ag-grid-community": "35.1.0-beta.20260316.108", + "ag-grid-community": "35.1.0-beta.20260317.135", "@babel/plugin-proposal-throw-expressions": "^7.27.1", "@babel/preset-typescript": "^7.27.1", "@types/react": "~18.3.26", @@ -44,7 +44,7 @@ }, "dependencies": { "prop-types": "^15.8.1", - "ag-grid-community": "35.1.0-beta.20260316.108" + "ag-grid-community": "35.1.0-beta.20260317.135" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", diff --git a/packages/ag-grid-vue3/package.json b/packages/ag-grid-vue3/package.json index 357c12fb599..7c2ea3b95a9 100644 --- a/packages/ag-grid-vue3/package.json +++ b/packages/ag-grid-vue3/package.json @@ -1,7 +1,7 @@ { "name": "ag-grid-vue3", "description": "AG Grid Vue 3 Component", - "version": "35.1.0-beta.20260316.108", + "version": "35.1.0-beta.20260317.135", "author": "Sean Landsman ", "license": "MIT", "files": [ @@ -44,7 +44,7 @@ "build-only:watch": "vite build --watch" }, "dependencies": { - "ag-grid-community": "35.1.0-beta.20260316.108" + "ag-grid-community": "35.1.0-beta.20260317.135" }, "devDependencies": { "vue": "^3.5.0", diff --git a/plugins/ag-grid-generate-code-reference-files/package.json b/plugins/ag-grid-generate-code-reference-files/package.json index 7c4cd65b021..c16a71faac6 100644 --- a/plugins/ag-grid-generate-code-reference-files/package.json +++ b/plugins/ag-grid-generate-code-reference-files/package.json @@ -1,6 +1,6 @@ { "name": "ag-grid-generate-code-reference-files", - "version": "35.1.0-beta.20260316.108", + "version": "35.1.0-beta.20260317.135", "private": true, "dependencies": { "ag-shared": "0.0.1", diff --git a/plugins/ag-grid-generate-example-files/package.json b/plugins/ag-grid-generate-example-files/package.json index 922c0c95128..7f5aa6d9910 100644 --- a/plugins/ag-grid-generate-example-files/package.json +++ b/plugins/ag-grid-generate-example-files/package.json @@ -1,10 +1,10 @@ { "name": "ag-grid-generate-example-files", - "version": "35.1.0-beta.20260316.108", + "version": "35.1.0-beta.20260317.135", "private": true, "dependencies": { "ag-shared": "0.0.1", - "ag-grid-community": "35.1.0-beta.20260316.108", + "ag-grid-community": "35.1.0-beta.20260317.135", "glob": "8.0.3", "typescript": "~5.4.5", "cheerio": "^1.0.0", diff --git a/plugins/ag-grid-task-autogen/package.json b/plugins/ag-grid-task-autogen/package.json index e66597440d7..eda247f9e81 100644 --- a/plugins/ag-grid-task-autogen/package.json +++ b/plugins/ag-grid-task-autogen/package.json @@ -1,6 +1,6 @@ { "name": "ag-grid-task-autogen", - "version": "35.1.0-beta.20260316.108", + "version": "35.1.0-beta.20260317.135", "private": true, "dependencies": { "@nx/devkit": "20.3.1", diff --git a/testing/accessibility/package.json b/testing/accessibility/package.json index 3c1f7971309..3de5a2db2a3 100644 --- a/testing/accessibility/package.json +++ b/testing/accessibility/package.json @@ -1,6 +1,6 @@ { "name": "ag-grid-accessibility", - "version": "35.1.0-beta.20260316.108", + "version": "35.1.0-beta.20260317.135", "scripts": { "download-examples": "curl --retry 5 -retry-all-errors https://grid-staging.ag-grid.com/debug/all-examples.json > ./all-examples.json", "download-examples-local": "curl https://localhost:4610/debug/all-examples.json > ./all-examples.json", @@ -18,9 +18,9 @@ "@angular/platform-browser": "^19.0.0", "@angular/platform-browser-dynamic": "^19.0.0", "@angular/router": "^19.0.0", - "ag-grid-angular": "35.1.0-beta.20260316.108", - "ag-grid-community": "35.1.0-beta.20260316.108", - "ag-grid-enterprise": "35.1.0-beta.20260316.108", + "ag-grid-angular": "35.1.0-beta.20260317.135", + "ag-grid-community": "35.1.0-beta.20260317.135", + "ag-grid-enterprise": "35.1.0-beta.20260317.135", "ag-charts-community": "13.1.0-beta.20260317", "ag-charts-enterprise": "13.1.0-beta.20260317", "rxjs": "~7.8.0", diff --git a/testing/angular-tests/package.json b/testing/angular-tests/package.json index eb0f36b9f42..c9cbaae421b 100644 --- a/testing/angular-tests/package.json +++ b/testing/angular-tests/package.json @@ -1,6 +1,6 @@ { "name": "ag-grid-angular-tests", - "version": "35.1.0-beta.20260316.108", + "version": "35.1.0-beta.20260317.135", "private": true, "scripts": { "test:e2e": "jest --no-cache" @@ -11,8 +11,8 @@ "@angular/core": "^21.0.0", "@angular/platform-browser": "^21.0.0", "@angular/platform-browser-dynamic": "^21.0.0", - "ag-grid-angular": "35.1.0-beta.20260316.108", - "ag-grid-community": "35.1.0-beta.20260316.108", + "ag-grid-angular": "35.1.0-beta.20260317.135", + "ag-grid-community": "35.1.0-beta.20260317.135", "rxjs": "~7.8.0", "tslib": "^2.3.0", "zone.js": "~0.15.0" diff --git a/testing/behavioural/package.json b/testing/behavioural/package.json index 020a72b1876..cd4c6009fbe 100644 --- a/testing/behavioural/package.json +++ b/testing/behavioural/package.json @@ -1,6 +1,6 @@ { "name": "ag-behavioural-testing", - "version": "35.1.0-beta.20260316.108", + "version": "35.1.0-beta.20260317.135", "private": true, "description": "Behavioural unit testing for ag-Grid", "dependencies": { @@ -8,9 +8,9 @@ }, "type": "module", "devDependencies": { - "ag-grid-community": "35.1.0-beta.20260316.108", - "ag-grid-enterprise": "35.1.0-beta.20260316.108", - "ag-grid-react": "35.1.0-beta.20260316.108", + "ag-grid-community": "35.1.0-beta.20260317.135", + "ag-grid-enterprise": "35.1.0-beta.20260317.135", + "ag-grid-react": "35.1.0-beta.20260317.135", "@types/react": "^18.3.23", "@types/react-dom": "^18.3.7", "@testing-library/dom": "^10.4.0", diff --git a/testing/behavioural/src/version.ts b/testing/behavioural/src/version.ts index 353202c0311..63095b37ec2 100644 --- a/testing/behavioural/src/version.ts +++ b/testing/behavioural/src/version.ts @@ -1,2 +1,2 @@ // DO NOT UPDATE MANUALLY: Generated from script during build time -export const VERSION = '35.1.0-beta.20260316.108'; +export const VERSION = '35.1.0-beta.20260317.135'; diff --git a/testing/csp/package.json b/testing/csp/package.json index ff01dea0b76..e0e485a8a83 100644 --- a/testing/csp/package.json +++ b/testing/csp/package.json @@ -1,6 +1,6 @@ { "name": "ag-grid-csp", - "version": "35.1.0-beta.20260316.108", + "version": "35.1.0-beta.20260317.135", "description": "CSP testing for AG Grid", "main": "index.js", "scripts": {}, diff --git a/testing/module-size-angular/package.json b/testing/module-size-angular/package.json index 56d69648790..3db61c92dc2 100644 --- a/testing/module-size-angular/package.json +++ b/testing/module-size-angular/package.json @@ -1,6 +1,6 @@ { "name": "ag-grid-module-size-angular", - "version": "35.1.0-beta.20260316.108", + "version": "35.1.0-beta.20260317.135", "scripts": { "ng": "ng", "start": "ng serve", @@ -20,9 +20,9 @@ "@angular/platform-browser": "^19.0.0", "@angular/platform-browser-dynamic": "^19.0.0", "@angular/router": "^19.0.0", - "ag-grid-angular": "35.1.0-beta.20260316.108", - "ag-grid-community": "35.1.0-beta.20260316.108", - "ag-grid-enterprise": "35.1.0-beta.20260316.108", + "ag-grid-angular": "35.1.0-beta.20260317.135", + "ag-grid-community": "35.1.0-beta.20260317.135", + "ag-grid-enterprise": "35.1.0-beta.20260317.135", "ag-charts-community": "13.1.0-beta.20260317", "ag-charts-enterprise": "13.1.0-beta.20260317", "rxjs": "~7.8.0", diff --git a/testing/module-size/package.json b/testing/module-size/package.json index c6f01bdae9d..927574276eb 100644 --- a/testing/module-size/package.json +++ b/testing/module-size/package.json @@ -1,7 +1,7 @@ { "name": "ag-grid-module-size", "private": true, - "version": "35.1.0-beta.20260316.108", + "version": "35.1.0-beta.20260317.135", "scripts": { "dev": "vite", "cp-app": "cp ./src/App_Src.tsx ./src/App_AUTO.tsx", @@ -14,9 +14,9 @@ "test:e2e": "run-s \"module-combinations -- {1}\" module-validate --" }, "dependencies": { - "ag-grid-react": "35.1.0-beta.20260316.108", - "ag-grid-community": "35.1.0-beta.20260316.108", - "ag-grid-enterprise": "35.1.0-beta.20260316.108", + "ag-grid-react": "35.1.0-beta.20260317.135", + "ag-grid-community": "35.1.0-beta.20260317.135", + "ag-grid-enterprise": "35.1.0-beta.20260317.135", "ag-charts-community": "13.1.0-beta.20260317", "ag-charts-enterprise": "13.1.0-beta.20260317", "ag-shared": "0.0.1", diff --git a/testing/public-recipes/e2e/package.json b/testing/public-recipes/e2e/package.json index f0381527730..dfbe9b74e19 100644 --- a/testing/public-recipes/e2e/package.json +++ b/testing/public-recipes/e2e/package.json @@ -1,12 +1,12 @@ { "name": "ag-grid-public-e2e-testing-recipes", - "version": "35.1.0-beta.20260316.108", + "version": "35.1.0-beta.20260317.135", "description": "Public E2E testing recipes for AG Grid", "main": "index.js", "scripts": {}, "license": "MIT", "devDependencies": { - "ag-grid-community": "35.1.0-beta.20260316.108", + "ag-grid-community": "35.1.0-beta.20260317.135", "playwright": "^1.56.0", "@playwright/test": "^1.56.0", "@types/node": "^24.0.3" diff --git a/testing/vue3-tests/package.json b/testing/vue3-tests/package.json index a39251e6fd6..5838b27da1c 100644 --- a/testing/vue3-tests/package.json +++ b/testing/vue3-tests/package.json @@ -1,7 +1,7 @@ { "name": "ag-grid-vue3-tests", "private": true, - "version": "35.1.0-beta.20260316.108", + "version": "35.1.0-beta.20260317.135", "type": "module", "scripts": { "dev": "vite", @@ -15,9 +15,9 @@ "dependencies": { "vue": "^3.5.0", "vue-router": "^4.5.0", - "ag-grid-community": "35.1.0-beta.20260316.108", - "ag-grid-enterprise": "35.1.0-beta.20260316.108", - "ag-grid-vue3": "35.1.0-beta.20260316.108", + "ag-grid-community": "35.1.0-beta.20260317.135", + "ag-grid-enterprise": "35.1.0-beta.20260317.135", + "ag-grid-vue3": "35.1.0-beta.20260317.135", "decimal.js": "^10.4.3" }, "devDependencies": {