Skip to content
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { TreeViewModel } from '@ts/ui/__tests__/__mock__/model/tree_view';

const CLASSES = {
columnChooser: 'dx-datagrid-column-chooser',
columnChooserList: 'dx-datagrid-column-chooser-list',
popupWrapper: 'dx-popup-wrapper',
};

export class ColumnChooserModel {
constructor(protected readonly root: HTMLElement) {}

private getPopupWrapper(): HTMLElement | null {
return document.body.querySelector(`.${CLASSES.popupWrapper}.${CLASSES.columnChooser}`);
}

private getOverlay(): HTMLElement | null {
const wrapper = this.getPopupWrapper();
return wrapper?.querySelector('.dx-overlay-content') ?? null;
}

private getTreeView(): TreeViewModel | null {
const overlay = this.getOverlay();
if (!overlay) return null;

const treeViewElement = overlay.querySelector(`.${CLASSES.columnChooserList}`) as HTMLElement;
return treeViewElement ? new TreeViewModel(treeViewElement) : null;
}

public isVisible(): boolean {
return this.getOverlay() !== null;
}

public searchColumn(text: string): void {
const treeView = this.getTreeView();
treeView?.setSearchValue(text);
}

public toggleColumn(columnText: string): void {
const treeView = this.getTreeView();
const checkBox = treeView?.getCheckboxByText(columnText);
checkBox?.toggle();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { AIPromptEditorModel } from './ai_prompt_editor';
import { AIHeaderCellModel } from './cell/ai_header_cell';
import { DataCellModel } from './cell/data_cell';
import { HeaderCellModel } from './cell/header_cell';
import { ColumnChooserModel } from './column_chooser';
import { EditFormModel } from './edit_form';
import { DataRowModel } from './row/data_row';

Expand Down Expand Up @@ -134,5 +135,9 @@ export abstract class GridCoreModel<TInstance extends GridBase = GridBase> {
return new EditFormModel(this.root.querySelector(`.${this.addWidgetPrefix(SELECTORS.editForm)}`));
}

public getColumnChooser(): ColumnChooserModel {
return new ColumnChooserModel(this.root);
}

public abstract getInstance(): TInstance;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,307 @@
import {
afterEach, beforeEach, describe, expect, it, jest,
} from '@jest/globals';
import type { dxElementWrapper } from '@js/core/renderer';
import $ from '@js/core/renderer';
import type { Properties as DataGridProperties } from '@js/ui/data_grid';
import DataGrid from '@js/ui/data_grid';
import errors from '@js/ui/widget/ui.errors';
import { DataGridModel } from '@ts/grids/data_grid/__tests__/__mock__/model/data_grid';

const SELECTORS = {
gridContainer: '#gridContainer',
};

const GRID_CONTAINER_ID = 'gridContainer';

const createDataGrid = async (
options: DataGridProperties = {},
): Promise<{
$container: dxElementWrapper;
component: DataGridModel;
instance: DataGrid;
}> => new Promise((resolve) => {
const $container = $('<div>')
.attr('id', GRID_CONTAINER_ID)
.appendTo(document.body);

const dataGridOptions: DataGridProperties = {
keyExpr: 'id',
...options,
};

const instance = new DataGrid($container.get(0) as HTMLDivElement, dataGridOptions);
const component = new DataGridModel($container.get(0) as HTMLElement);

jest.runAllTimers();

resolve({
$container,
component,
instance,
});
});

const beforeTest = (): void => {
jest.useFakeTimers();
jest.spyOn(errors, 'log').mockImplementation(jest.fn());
jest.spyOn(errors, 'Error').mockImplementation(() => ({}));
};

const afterTest = (): void => {
const $container = $(SELECTORS.gridContainer);
const dataGrid = ($container as any).dxDataGrid('instance') as DataGrid;

dataGrid.dispose();
$container.remove();
jest.clearAllMocks();
jest.useRealTimers();
};

describe('Bugs', () => {
beforeEach(beforeTest);
afterEach(afterTest);

describe('T1311329 - DataGrid - Column chooser hides a banded column on using search and recursive selection', () => {
it('should not hide banded column when using search (two levels)', async () => {
const { instance, component } = await createDataGrid({
dataSource: [
{
id: 1,
name: 'Name 1',
value: 10,
phone: 'Banded 1',
email: 'Banded 2',
skype: 'Banded 3',
},
],
columnChooser: {
enabled: true,
search: {
enabled: true,
},
mode: 'select',
selection: {
recursive: true,
selectByClick: true,
allowSelectAll: true,
},
},
columns: [
{ dataField: 'id', caption: 'ID' },
{ dataField: 'name', caption: 'Name' },
{ dataField: 'value', caption: 'Value' },
{
caption: 'Contacts',
columns: [
{
dataField: 'phone',
visible: false,
},
{
dataField: 'email',
},
{
dataField: 'skype',
},
],
},
],
});

let visibleColumnsLevel0 = instance.getVisibleColumns(0);
let visibleColumnsLevel1 = instance.getVisibleColumns(1);

expect(visibleColumnsLevel0.find((col) => col.caption === 'Contacts')).toBeDefined();
expect(visibleColumnsLevel1.find((col) => col.dataField === 'phone')).toBeUndefined();
expect(visibleColumnsLevel1.find((col) => col.dataField === 'email')).toBeDefined();
expect(visibleColumnsLevel1.find((col) => col.dataField === 'skype')).toBeDefined();
expect(visibleColumnsLevel0.find((col) => col.dataField === 'name')).toBeDefined();

instance.showColumnChooser();
jest.runAllTimers();

const columnChooser = component.getColumnChooser();
expect(columnChooser.isVisible()).toBe(true);

columnChooser.searchColumn('n');
jest.runAllTimers();

columnChooser.toggleColumn('Name');
jest.runAllTimers();

visibleColumnsLevel0 = instance.getVisibleColumns(0);
visibleColumnsLevel1 = instance.getVisibleColumns(1);

expect(visibleColumnsLevel0.find((col) => col.caption === 'Contacts')).toBeDefined();
expect(visibleColumnsLevel1.find((col) => col.dataField === 'phone')).toBeUndefined();
expect(visibleColumnsLevel1.find((col) => col.dataField === 'email')).toBeDefined();
expect(visibleColumnsLevel1.find((col) => col.dataField === 'skype')).toBeDefined();
expect(visibleColumnsLevel0.find((col) => col.dataField === 'name')).toBeUndefined();
});

it('should not hide banded column when using search (three levels)', async () => {
const { instance, component } = await createDataGrid({
dataSource: [],
columnChooser: {
enabled: true,
search: {
enabled: true,
},
mode: 'select',
selection: {
recursive: true,
selectByClick: true,
allowSelectAll: true,
},
},
columns: [
{
caption: 'band_level1',
columns: [
{
caption: 'band_level2',
columns: [
{
dataField: 'data1_level3',
visible: false,
},
{
dataField: 'data2_level3',
},
],
},
{
dataField: 'data1_level2',
},
{
dataField: 'data2_level2',
},
],
},
{
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');
jest.runAllTimers();

visibleColumnsLevel0 = instance.getVisibleColumns(0);
visibleColumnsLevel1 = instance.getVisibleColumns(1);
visibleColumnsLevel2 = instance.getVisibleColumns(2);

expect(visibleColumnsLevel0.find((col) => col.caption === 'band_level1')).toBeDefined();
expect(visibleColumnsLevel0.find((col) => col.dataField === 'data1_level1')).toBeUndefined();
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();
});

it('should hide banded column by click', async () => {
const { instance, component } = await createDataGrid({
dataSource: [
{
id: 1,
name: 'Name 1',
value: 10,
phone: 'Banded 1',
email: 'Banded 2',
skype: 'Banded 3',
},
],
columnChooser: {
enabled: true,
search: {
enabled: true,
},
mode: 'select',
selection: {
recursive: true,
selectByClick: true,
allowSelectAll: true,
},
},
columns: [
{ dataField: 'id', caption: 'ID' },
{ dataField: 'name', caption: 'Name' },
{ dataField: 'value', caption: 'Value' },
{
caption: 'Contacts',
columns: [
{
dataField: 'phone',
visible: false,
},
{
dataField: 'email',
},
{
dataField: 'skype',
},
],
},
],
});
let visibleColumnsLevel0 = instance.getVisibleColumns(0);
let visibleColumnsLevel1 = instance.getVisibleColumns(1);

expect(visibleColumnsLevel0.find((col) => col.caption === 'Contacts')).toBeDefined();
expect(visibleColumnsLevel1.find((col) => col.dataField === 'phone')).toBeUndefined();
expect(visibleColumnsLevel1.find((col) => col.dataField === 'email')).toBeDefined();
expect(visibleColumnsLevel1.find((col) => col.dataField === 'skype')).toBeDefined();

instance.showColumnChooser();
jest.runAllTimers();

const columnChooser = component.getColumnChooser();
expect(columnChooser.isVisible()).toBe(true);

columnChooser.toggleColumn('Contacts');
jest.runAllTimers();

visibleColumnsLevel0 = instance.getVisibleColumns(0);
visibleColumnsLevel1 = instance.getVisibleColumns(1);

expect(visibleColumnsLevel0.find((col) => col.caption === 'Contacts')).toBeDefined();
Copy link

Copilot AI Jan 16, 2026

Choose a reason for hiding this comment

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

The test uses .toBeDefined() which passes even when find() returns undefined. This assertion will always pass because find() always returns a value (either an object or undefined), and undefined.toBeDefined() is true. Use .toBeTruthy() or check the result with .not.toBeUndefined() for the intended behavior.

Copilot uses AI. Check for mistakes.
expect(visibleColumnsLevel1.find((col) => col.dataField === 'phone')).toBeDefined();
expect(visibleColumnsLevel1.find((col) => col.dataField === 'email')).toBeDefined();
expect(visibleColumnsLevel1.find((col) => col.dataField === 'skype')).toBeDefined();

columnChooser.toggleColumn('Contacts');
jest.runAllTimers();

visibleColumnsLevel0 = instance.getVisibleColumns(0);
visibleColumnsLevel1 = instance.getVisibleColumns(1);

expect(visibleColumnsLevel0.find((col) => col.caption === 'Contacts')).toBeUndefined();
expect(visibleColumnsLevel1.find((col) => col.dataField === 'phone')).toBeUndefined();
expect(visibleColumnsLevel1.find((col) => col.dataField === 'email')).toBeUndefined();
expect(visibleColumnsLevel1.find((col) => col.dataField === 'skype')).toBeUndefined();
});
});
});
Loading
Loading