-
Notifications
You must be signed in to change notification settings - Fork 664
T1311329 - DataGrid - Column chooser hides a banded column on using search and recursive selection #32186
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: 26_1
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR fixes a bug (T1311329) where the DataGrid column chooser incorrectly hides a banded (parent) column when using search functionality with recursive selection enabled. The issue occurred because column visibility was being updated without considering the relationship between banded columns and their children.
Changes:
- Refactored column visibility update logic to handle banded columns correctly by processing them in sorted order (non-band columns first, then band columns)
- Added integration tests to verify the fix for both the search scenario and direct toggling of banded columns
- Created reusable test model classes (TreeViewModel, TextBoxModel, CheckBoxModel, ColumnChooserModel) to support the new tests
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/devextreme/js/__internal/grids/grid_core/column_chooser/m_column_chooser.ts | Refactored column visibility logic to correctly handle banded columns by sorting nodes and determining visibility based on child column states |
| packages/devextreme/js/__internal/grids/grid_core/column_chooser/tests/column_chooser.integration.test.ts | Added comprehensive integration tests for the banded column visibility fix |
| packages/devextreme/js/__internal/grids/grid_core/tests/mock/model/grid_core.ts | Added getColumnChooser method to support column chooser testing |
| packages/devextreme/js/__internal/grids/grid_core/tests/mock/model/column_chooser.ts | Created model class for interacting with column chooser in tests |
| packages/devextreme/js/__internal/ui/tests/mock/model/tree_view.ts | Created reusable model for TreeView interactions in tests |
| packages/devextreme/js/__internal/ui/tests/mock/model/textbox.ts | Created reusable model for TextBox interactions in tests |
| packages/devextreme/js/__internal/ui/tests/mock/model/checkbox.ts | Created reusable model for CheckBox interactions in tests |
packages/devextreme/js/__internal/grids/grid_core/column_chooser/m_column_chooser.ts
Outdated
Show resolved
Hide resolved
packages/devextreme/js/__internal/grids/grid_core/column_chooser/m_column_chooser.ts
Show resolved
Hide resolved
packages/devextreme/js/__internal/ui/__tests__/__mock__/model/tree_view.ts
Show resolved
Hide resolved
packages/devextreme/js/__internal/grids/grid_core/__tests__/__mock__/model/column_chooser.ts
Show resolved
Hide resolved
...me/js/__internal/grids/grid_core/column_chooser/__tests__/column_chooser.integration.test.ts
Show resolved
Hide resolved
packages/devextreme/js/__internal/grids/grid_core/column_chooser/m_column_chooser.ts
Outdated
Show resolved
Hide resolved
packages/devextreme/js/__internal/grids/grid_core/column_chooser/m_column_chooser.ts
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.
packages/devextreme/js/__internal/grids/grid_core/column_chooser/m_column_chooser.ts
Show resolved
Hide resolved
packages/devextreme/js/__internal/grids/grid_core/column_chooser/m_column_chooser.ts
Outdated
Show resolved
Hide resolved
packages/devextreme/js/__internal/ui/__tests__/__mock__/model/tree_view.ts
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.
packages/devextreme/js/__internal/grids/grid_core/column_chooser/m_column_chooser.ts
Outdated
Show resolved
Hide resolved
packages/devextreme/js/__internal/ui/__tests__/__mock__/model/tree_view.ts
Show resolved
Hide resolved
...me/js/__internal/grids/grid_core/column_chooser/__tests__/column_chooser.integration.test.ts
Show resolved
Hide resolved
packages/devextreme/js/__internal/grids/grid_core/column_chooser/m_column_chooser.ts
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 7 out of 7 changed files in this pull request and generated 6 comments.
| const dataGrid = ($container as any).dxDataGrid('instance') as DataGrid; | ||
|
|
||
| dataGrid.dispose(); | ||
| $container.remove(); |
Copilot
AI
Jan 16, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The cleanup function assumes a DataGrid instance always exists, which may not be true if a test fails early or during grid creation. Add a null check before calling dispose to prevent errors during cleanup. Consider checking if the container exists and has a DataGrid instance before attempting disposal.
| const dataGrid = ($container as any).dxDataGrid('instance') as DataGrid; | |
| dataGrid.dispose(); | |
| $container.remove(); | |
| if ($container.length) { | |
| const hasDxDataGrid = typeof ($container as any).dxDataGrid === 'function'; | |
| const dataGrid = hasDxDataGrid | |
| ? ($container as any).dxDataGrid('instance') as DataGrid | undefined | |
| : undefined; | |
| if (dataGrid) { | |
| dataGrid.dispose(); | |
| } | |
| $container.remove(); | |
| } |
| return new TextBoxModel(this.root?.querySelector(`.${CLASSES.searchBox}`) as HTMLElement); | ||
| } | ||
|
|
||
| public setSearchValue(value: string): void { | ||
| const searchBox = this.getSearchBox(); | ||
| searchBox.setValue(value); | ||
| } | ||
|
|
||
| private getNodes(): NodeListOf<HTMLElement> | null { | ||
| return this.root?.querySelectorAll(`.${CLASSES.node}`) ?? null; |
Copilot
AI
Jan 16, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The root parameter is defined as non-nullable (protected readonly root: HTMLElement), but the code uses optional chaining (this.root?.querySelector). Since root cannot be null or undefined, the optional chaining is unnecessary and can be simplified to this.root.querySelector.
| return result; | ||
| }, flatNodesArray); | ||
|
|
||
| // Band columns should be updated after regular columns |
Copilot
AI
Jan 16, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comment "Band columns should be updated after regular columns" explains why the array is reversed, but the implementation may be incomplete. When reversing a flattened tree, parent nodes will be processed before their children. However, in a multi-level banded column hierarchy (3+ levels), you need to ensure that the deepest children are processed first, then their parents, then grandparents, etc. The current implementation reverses the depth-first traversal order, which should work correctly. Consider adding a more detailed comment explaining that reversing a depth-first traversal ensures children are processed before parents.
| // Band columns should be updated after regular columns | |
| // NOTE: | |
| // getNodeLevelPairsRecursive performs a depth-first traversal that flattens the | |
| // banded column tree in "parent before children" order. Reversing this flat list | |
| // produces an update order where the deepest child columns are processed first, | |
| // then their parents, then grandparents, etc. | |
| // | |
| // This guarantees that regular (leaf) columns are updated before band columns, | |
| // even in multi-level banded hierarchies (3+ levels), so that parent band | |
| // visibility is computed after all of its descendants. |
| } | ||
|
|
||
| public setValue(value: string): void { | ||
| this.getInstance()?.option('value', value); |
Copilot
AI
Jan 16, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The setValue method uses the widget API (getInstance().option('value', value)) which may not trigger all the same events as user input. For consistency with the existing TextAreaModel pattern, consider also dispatching an 'input' event to ensure all change handlers are triggered. This is important for testing components that rely on DOM events rather than widget option changes.
| this.getInstance()?.option('value', value); | |
| this.getInstance()?.option('value', value); | |
| const inputElement = this.root.querySelector<HTMLInputElement>('.dx-texteditor-input, input'); | |
| if (inputElement) { | |
| const event = new Event('input', { bubbles: true, cancelable: true }); | |
| inputElement.dispatchEvent(event); | |
| } |
| { | ||
| dataField: 'data1_level1', | ||
| }, | ||
| ], | ||
| }); | ||
|
|
||
| let visibleColumnsLevel0 = instance.getVisibleColumns(0); | ||
| let visibleColumnsLevel1 = instance.getVisibleColumns(1); | ||
| let visibleColumnsLevel2 = instance.getVisibleColumns(2); | ||
|
|
||
| expect(visibleColumnsLevel0.find((col) => col.caption === 'band_level1')).toBeDefined(); | ||
| expect(visibleColumnsLevel0.find((col) => col.dataField === 'data1_level1')).toBeDefined(); | ||
| expect(visibleColumnsLevel1.find((col) => col.dataField === 'data1_level2')).toBeDefined(); | ||
| expect(visibleColumnsLevel1.find((col) => col.dataField === 'data2_level2')).toBeDefined(); | ||
| expect(visibleColumnsLevel1.find((col) => col.caption === 'band_level2')).toBeDefined(); | ||
| expect(visibleColumnsLevel2.find((col) => col.dataField === 'data1_level3')).toBeUndefined(); | ||
| expect(visibleColumnsLevel2.find((col) => col.dataField === 'data2_level3')).toBeDefined(); | ||
|
|
||
| instance.showColumnChooser(); | ||
| jest.runAllTimers(); | ||
|
|
||
| const columnChooser = component.getColumnChooser(); | ||
| expect(columnChooser.isVisible()).toBe(true); | ||
|
|
||
| columnChooser.searchColumn('1'); | ||
| jest.runAllTimers(); | ||
|
|
||
| columnChooser.toggleColumn('Data 1 level 1'); |
Copilot
AI
Jan 16, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The test relies on DataGrid's auto-generated caption for the column with dataField: 'data1_level1'. While DataGrid does auto-generate captions from dataFields (converting underscores to spaces and capitalizing), explicitly setting a caption would make the test more readable and less fragile to potential changes in the caption generation logic. Consider adding an explicit caption property to make the test's intent clearer.
0b2e291 to
ceb624a
Compare
| } | ||
|
|
||
| private _getSortedFlatNodes(nodes: NodeInternal[]): NodeInternal[] { | ||
| const getNodeLevelPairsRecursive = ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this utility not returns node-level pairs any more, let's rename it to smth like flattenNodesDepthFirst to show the order is important
also method _getSortedFlatNodes has incorrect name too - we do no sort any more
No description provided.