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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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();
Comment on lines +53 to +56
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 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.

Suggested change
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();
}

Copilot uses AI. Check for mistakes.
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');
Comment on lines +182 to +209
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 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.

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