From ddd13b1e64218d5de038455bacd1ccfc015b0bf0 Mon Sep 17 00:00:00 2001 From: Andrey Dolzhikov <16618553+Raushen@users.noreply.github.com> Date: Fri, 19 Jun 2026 12:51:58 +0300 Subject: [PATCH 1/3] T1330038: TypeError - DataGrid does not accept groupInterval as an array (#34063) --- .../src/ui/card-view/nested/column-header-filter.ts | 4 ++-- .../src/ui/card-view/nested/header-filter.ts | 4 ++-- .../src/ui/data-grid/nested/column-header-filter.ts | 4 ++-- .../src/ui/data-grid/nested/header-filter.ts | 4 ++-- .../src/ui/gantt/nested/column-header-filter.ts | 4 ++-- .../devextreme-angular/src/ui/gantt/nested/header-filter.ts | 4 ++-- .../src/ui/nested/base/column-header-filter.ts | 4 ++-- .../src/ui/tree-list/nested/column-header-filter.ts | 4 ++-- .../src/ui/tree-list/nested/header-filter.ts | 4 ++-- packages/devextreme-react/src/card-view.ts | 4 ++-- packages/devextreme-react/src/data-grid.ts | 4 ++-- packages/devextreme-react/src/gantt.ts | 4 ++-- packages/devextreme-react/src/tree-list.ts | 4 ++-- packages/devextreme-vue/src/card-view.ts | 4 ++-- packages/devextreme-vue/src/data-grid.ts | 4 ++-- packages/devextreme-vue/src/gantt.ts | 4 ++-- packages/devextreme-vue/src/tree-list.ts | 4 ++-- packages/devextreme/js/common/grids.d.ts | 2 +- packages/devextreme/ts/dx.all.d.ts | 6 +++++- 19 files changed, 40 insertions(+), 36 deletions(-) diff --git a/packages/devextreme-angular/src/ui/card-view/nested/column-header-filter.ts b/packages/devextreme-angular/src/ui/card-view/nested/column-header-filter.ts index 5a341d95359f..351a9e662be4 100644 --- a/packages/devextreme-angular/src/ui/card-view/nested/column-header-filter.ts +++ b/packages/devextreme-angular/src/ui/card-view/nested/column-header-filter.ts @@ -60,10 +60,10 @@ export class DxoCardViewColumnHeaderFilterComponent extends NestedOption impleme } @Input() - get groupInterval(): HeaderFilterGroupInterval | number | undefined { + get groupInterval(): Array | HeaderFilterGroupInterval | number | undefined { return this._getOption('groupInterval'); } - set groupInterval(value: HeaderFilterGroupInterval | number | undefined) { + set groupInterval(value: Array | HeaderFilterGroupInterval | number | undefined) { this._setOption('groupInterval', value); } diff --git a/packages/devextreme-angular/src/ui/card-view/nested/header-filter.ts b/packages/devextreme-angular/src/ui/card-view/nested/header-filter.ts index dc63380e62b8..165e47c35b75 100644 --- a/packages/devextreme-angular/src/ui/card-view/nested/header-filter.ts +++ b/packages/devextreme-angular/src/ui/card-view/nested/header-filter.ts @@ -60,10 +60,10 @@ export class DxoCardViewHeaderFilterComponent extends NestedOption implements On } @Input() - get groupInterval(): HeaderFilterGroupInterval | number | undefined { + get groupInterval(): Array | HeaderFilterGroupInterval | number | undefined { return this._getOption('groupInterval'); } - set groupInterval(value: HeaderFilterGroupInterval | number | undefined) { + set groupInterval(value: Array | HeaderFilterGroupInterval | number | undefined) { this._setOption('groupInterval', value); } diff --git a/packages/devextreme-angular/src/ui/data-grid/nested/column-header-filter.ts b/packages/devextreme-angular/src/ui/data-grid/nested/column-header-filter.ts index ecc1d0e0b238..e1110f855d7d 100644 --- a/packages/devextreme-angular/src/ui/data-grid/nested/column-header-filter.ts +++ b/packages/devextreme-angular/src/ui/data-grid/nested/column-header-filter.ts @@ -60,10 +60,10 @@ export class DxoDataGridColumnHeaderFilterComponent extends NestedOption impleme } @Input() - get groupInterval(): HeaderFilterGroupInterval | number | undefined { + get groupInterval(): Array | HeaderFilterGroupInterval | number | undefined { return this._getOption('groupInterval'); } - set groupInterval(value: HeaderFilterGroupInterval | number | undefined) { + set groupInterval(value: Array | HeaderFilterGroupInterval | number | undefined) { this._setOption('groupInterval', value); } diff --git a/packages/devextreme-angular/src/ui/data-grid/nested/header-filter.ts b/packages/devextreme-angular/src/ui/data-grid/nested/header-filter.ts index f5d9aecdbf79..8f1085f2ecb8 100644 --- a/packages/devextreme-angular/src/ui/data-grid/nested/header-filter.ts +++ b/packages/devextreme-angular/src/ui/data-grid/nested/header-filter.ts @@ -60,10 +60,10 @@ export class DxoDataGridHeaderFilterComponent extends NestedOption implements On } @Input() - get groupInterval(): HeaderFilterGroupInterval | number | undefined { + get groupInterval(): Array | HeaderFilterGroupInterval | number | undefined { return this._getOption('groupInterval'); } - set groupInterval(value: HeaderFilterGroupInterval | number | undefined) { + set groupInterval(value: Array | HeaderFilterGroupInterval | number | undefined) { this._setOption('groupInterval', value); } diff --git a/packages/devextreme-angular/src/ui/gantt/nested/column-header-filter.ts b/packages/devextreme-angular/src/ui/gantt/nested/column-header-filter.ts index 070a671c153e..520fdb9da704 100644 --- a/packages/devextreme-angular/src/ui/gantt/nested/column-header-filter.ts +++ b/packages/devextreme-angular/src/ui/gantt/nested/column-header-filter.ts @@ -60,10 +60,10 @@ export class DxoGanttColumnHeaderFilterComponent extends NestedOption implements } @Input() - get groupInterval(): HeaderFilterGroupInterval | number | undefined { + get groupInterval(): Array | HeaderFilterGroupInterval | number | undefined { return this._getOption('groupInterval'); } - set groupInterval(value: HeaderFilterGroupInterval | number | undefined) { + set groupInterval(value: Array | HeaderFilterGroupInterval | number | undefined) { this._setOption('groupInterval', value); } diff --git a/packages/devextreme-angular/src/ui/gantt/nested/header-filter.ts b/packages/devextreme-angular/src/ui/gantt/nested/header-filter.ts index e95acff9947e..7bef9bbf537b 100644 --- a/packages/devextreme-angular/src/ui/gantt/nested/header-filter.ts +++ b/packages/devextreme-angular/src/ui/gantt/nested/header-filter.ts @@ -61,10 +61,10 @@ export class DxoGanttHeaderFilterComponent extends NestedOption implements OnDes } @Input() - get groupInterval(): HeaderFilterGroupInterval | number | undefined { + get groupInterval(): Array | HeaderFilterGroupInterval | number | undefined { return this._getOption('groupInterval'); } - set groupInterval(value: HeaderFilterGroupInterval | number | undefined) { + set groupInterval(value: Array | HeaderFilterGroupInterval | number | undefined) { this._setOption('groupInterval', value); } diff --git a/packages/devextreme-angular/src/ui/nested/base/column-header-filter.ts b/packages/devextreme-angular/src/ui/nested/base/column-header-filter.ts index 88a1d1a0bc7b..eb8ae00abc91 100644 --- a/packages/devextreme-angular/src/ui/nested/base/column-header-filter.ts +++ b/packages/devextreme-angular/src/ui/nested/base/column-header-filter.ts @@ -36,10 +36,10 @@ export abstract class DxoColumnHeaderFilter extends NestedOption { this._setOption('dataSource', value); } - get groupInterval(): HeaderFilterGroupInterval | number | undefined { + get groupInterval(): HeaderFilterGroupInterval | number | undefined | Array { return this._getOption('groupInterval'); } - set groupInterval(value: HeaderFilterGroupInterval | number | undefined) { + set groupInterval(value: HeaderFilterGroupInterval | number | undefined | Array) { this._setOption('groupInterval', value); } diff --git a/packages/devextreme-angular/src/ui/tree-list/nested/column-header-filter.ts b/packages/devextreme-angular/src/ui/tree-list/nested/column-header-filter.ts index c3aa3e2ff005..6c769a3a86c8 100644 --- a/packages/devextreme-angular/src/ui/tree-list/nested/column-header-filter.ts +++ b/packages/devextreme-angular/src/ui/tree-list/nested/column-header-filter.ts @@ -60,10 +60,10 @@ export class DxoTreeListColumnHeaderFilterComponent extends NestedOption impleme } @Input() - get groupInterval(): HeaderFilterGroupInterval | number | undefined { + get groupInterval(): Array | HeaderFilterGroupInterval | number | undefined { return this._getOption('groupInterval'); } - set groupInterval(value: HeaderFilterGroupInterval | number | undefined) { + set groupInterval(value: Array | HeaderFilterGroupInterval | number | undefined) { this._setOption('groupInterval', value); } diff --git a/packages/devextreme-angular/src/ui/tree-list/nested/header-filter.ts b/packages/devextreme-angular/src/ui/tree-list/nested/header-filter.ts index 46ad14509289..a1214534e4a7 100644 --- a/packages/devextreme-angular/src/ui/tree-list/nested/header-filter.ts +++ b/packages/devextreme-angular/src/ui/tree-list/nested/header-filter.ts @@ -60,10 +60,10 @@ export class DxoTreeListHeaderFilterComponent extends NestedOption implements On } @Input() - get groupInterval(): HeaderFilterGroupInterval | number | undefined { + get groupInterval(): Array | HeaderFilterGroupInterval | number | undefined { return this._getOption('groupInterval'); } - set groupInterval(value: HeaderFilterGroupInterval | number | undefined) { + set groupInterval(value: Array | HeaderFilterGroupInterval | number | undefined) { this._setOption('groupInterval', value); } diff --git a/packages/devextreme-react/src/card-view.ts b/packages/devextreme-react/src/card-view.ts index 7a6a000ee342..89e3307b5df1 100644 --- a/packages/devextreme-react/src/card-view.ts +++ b/packages/devextreme-react/src/card-view.ts @@ -796,7 +796,7 @@ type IColumnHeaderFilterProps = React.PropsWithChildren<{ allowSearch?: boolean; allowSelectAll?: boolean; dataSource?: Array | DataSourceOptions | ((options: { component: Record, dataSource: DataSourceOptions | null }) => void) | null | Store | undefined; - groupInterval?: HeaderFilterGroupInterval | number | undefined; + groupInterval?: Array | HeaderFilterGroupInterval | number | undefined; height?: number | string | undefined; search?: ColumnHeaderFilterSearchConfig; searchMode?: SearchMode; @@ -1532,7 +1532,7 @@ type IHeaderFilterProps = React.PropsWithChildren<{ allowSearch?: boolean; allowSelectAll?: boolean; dataSource?: Array | DataSourceOptions | ((options: { component: Record, dataSource: DataSourceOptions | null }) => void) | null | Store | undefined; - groupInterval?: HeaderFilterGroupInterval | number | undefined; + groupInterval?: Array | HeaderFilterGroupInterval | number | undefined; height?: number | string | undefined; search?: ColumnHeaderFilterSearchConfig | HeaderFilterSearchConfig; searchMode?: SearchMode; diff --git a/packages/devextreme-react/src/data-grid.ts b/packages/devextreme-react/src/data-grid.ts index 246426a1713e..3ed261af8fea 100644 --- a/packages/devextreme-react/src/data-grid.ts +++ b/packages/devextreme-react/src/data-grid.ts @@ -686,7 +686,7 @@ type IColumnHeaderFilterProps = React.PropsWithChildren<{ allowSearch?: boolean; allowSelectAll?: boolean; dataSource?: Array | DataSourceOptions | ((options: { component: Record, dataSource: DataSourceOptions | null }) => void) | null | Store | undefined; - groupInterval?: HeaderFilterGroupInterval | number | undefined; + groupInterval?: Array | HeaderFilterGroupInterval | number | undefined; height?: number | string | undefined; search?: ColumnHeaderFilterSearchConfig; searchMode?: SearchMode; @@ -1799,7 +1799,7 @@ type IHeaderFilterProps = React.PropsWithChildren<{ allowSearch?: boolean; allowSelectAll?: boolean; dataSource?: Array | DataSourceOptions | ((options: { component: Record, dataSource: DataSourceOptions | null }) => void) | null | Store | undefined; - groupInterval?: HeaderFilterGroupInterval | number | undefined; + groupInterval?: Array | HeaderFilterGroupInterval | number | undefined; height?: number | string | undefined; search?: ColumnHeaderFilterSearchConfig | HeaderFilterSearchConfig; searchMode?: SearchMode; diff --git a/packages/devextreme-react/src/gantt.ts b/packages/devextreme-react/src/gantt.ts index bc15ed6dc93c..2195514b360b 100644 --- a/packages/devextreme-react/src/gantt.ts +++ b/packages/devextreme-react/src/gantt.ts @@ -241,7 +241,7 @@ type IColumnHeaderFilterProps = React.PropsWithChildren<{ allowSearch?: boolean; allowSelectAll?: boolean; dataSource?: Array | DataSourceOptions | ((options: { component: Record, dataSource: DataSourceOptions | null }) => void) | null | Store | undefined; - groupInterval?: HeaderFilterGroupInterval | number | undefined; + groupInterval?: Array | HeaderFilterGroupInterval | number | undefined; height?: number | string | undefined; search?: ColumnHeaderFilterSearchConfig; searchMode?: SearchMode; @@ -541,7 +541,7 @@ type IHeaderFilterProps = React.PropsWithChildren<{ allowSearch?: boolean; allowSelectAll?: boolean; dataSource?: Array | DataSourceOptions | ((options: { component: Record, dataSource: DataSourceOptions | null }) => void) | null | Store | undefined; - groupInterval?: HeaderFilterGroupInterval | number | undefined; + groupInterval?: Array | HeaderFilterGroupInterval | number | undefined; height?: number | string | undefined; search?: ColumnHeaderFilterSearchConfig | HeaderFilterSearchConfig; searchMode?: SearchMode; diff --git a/packages/devextreme-react/src/tree-list.ts b/packages/devextreme-react/src/tree-list.ts index 7e1f51eac838..207def6c77f9 100644 --- a/packages/devextreme-react/src/tree-list.ts +++ b/packages/devextreme-react/src/tree-list.ts @@ -642,7 +642,7 @@ type IColumnHeaderFilterProps = React.PropsWithChildren<{ allowSearch?: boolean; allowSelectAll?: boolean; dataSource?: Array | DataSourceOptions | ((options: { component: Record, dataSource: DataSourceOptions | null }) => void) | null | Store | undefined; - groupInterval?: HeaderFilterGroupInterval | number | undefined; + groupInterval?: Array | HeaderFilterGroupInterval | number | undefined; height?: number | string | undefined; search?: ColumnHeaderFilterSearchConfig; searchMode?: SearchMode; @@ -1502,7 +1502,7 @@ type IHeaderFilterProps = React.PropsWithChildren<{ allowSearch?: boolean; allowSelectAll?: boolean; dataSource?: Array | DataSourceOptions | ((options: { component: Record, dataSource: DataSourceOptions | null }) => void) | null | Store | undefined; - groupInterval?: HeaderFilterGroupInterval | number | undefined; + groupInterval?: Array | HeaderFilterGroupInterval | number | undefined; height?: number | string | undefined; search?: ColumnHeaderFilterSearchConfig | HeaderFilterSearchConfig; searchMode?: SearchMode; diff --git a/packages/devextreme-vue/src/card-view.ts b/packages/devextreme-vue/src/card-view.ts index e713d7269b8b..6b19d05842f7 100644 --- a/packages/devextreme-vue/src/card-view.ts +++ b/packages/devextreme-vue/src/card-view.ts @@ -1153,7 +1153,7 @@ const DxColumnHeaderFilterConfig = { allowSearch: Boolean, allowSelectAll: Boolean, dataSource: [Array, Object, Function] as PropType | DataSourceOptions | (((options: { component: Record, dataSource: DataSourceOptions | null }) => void)) | null | Store | Record>, - groupInterval: [String, Number] as PropType, + groupInterval: [Array, String, Number] as PropType<(Array) | HeaderFilterGroupInterval | number>, height: [Number, String], search: Object as PropType>, searchMode: String as PropType, @@ -1957,7 +1957,7 @@ const DxHeaderFilterConfig = { allowSearch: Boolean, allowSelectAll: Boolean, dataSource: [Array, Object, Function] as PropType | DataSourceOptions | (((options: { component: Record, dataSource: DataSourceOptions | null }) => void)) | null | Store | Record>, - groupInterval: [String, Number] as PropType, + groupInterval: [Array, String, Number] as PropType<(Array) | HeaderFilterGroupInterval | number>, height: [Number, String], search: Object as PropType>, searchMode: String as PropType, diff --git a/packages/devextreme-vue/src/data-grid.ts b/packages/devextreme-vue/src/data-grid.ts index 202d0cdcdded..440d3eabdd15 100644 --- a/packages/devextreme-vue/src/data-grid.ts +++ b/packages/devextreme-vue/src/data-grid.ts @@ -1116,7 +1116,7 @@ const DxColumnHeaderFilterConfig = { allowSearch: Boolean, allowSelectAll: Boolean, dataSource: [Array, Object, Function] as PropType | DataSourceOptions | (((options: { component: Record, dataSource: DataSourceOptions | null }) => void)) | null | Store | Record>, - groupInterval: [String, Number] as PropType, + groupInterval: [Array, String, Number] as PropType<(Array) | HeaderFilterGroupInterval | number>, height: [Number, String], search: Object as PropType>, searchMode: String as PropType, @@ -2317,7 +2317,7 @@ const DxHeaderFilterConfig = { allowSearch: Boolean, allowSelectAll: Boolean, dataSource: [Array, Object, Function] as PropType | DataSourceOptions | (((options: { component: Record, dataSource: DataSourceOptions | null }) => void)) | null | Store | Record>, - groupInterval: [String, Number] as PropType, + groupInterval: [Array, String, Number] as PropType<(Array) | HeaderFilterGroupInterval | number>, height: [Number, String], search: Object as PropType>, searchMode: String as PropType, diff --git a/packages/devextreme-vue/src/gantt.ts b/packages/devextreme-vue/src/gantt.ts index 1b8c150a57c5..c386da0bdae5 100644 --- a/packages/devextreme-vue/src/gantt.ts +++ b/packages/devextreme-vue/src/gantt.ts @@ -454,7 +454,7 @@ const DxColumnHeaderFilterConfig = { allowSearch: Boolean, allowSelectAll: Boolean, dataSource: [Array, Object, Function] as PropType | DataSourceOptions | (((options: { component: Record, dataSource: DataSourceOptions | null }) => void)) | null | Store | Record>, - groupInterval: [String, Number] as PropType, + groupInterval: [Array, String, Number] as PropType<(Array) | HeaderFilterGroupInterval | number>, height: [Number, String], search: Object as PropType>, searchMode: String as PropType, @@ -796,7 +796,7 @@ const DxHeaderFilterConfig = { allowSearch: Boolean, allowSelectAll: Boolean, dataSource: [Array, Object, Function] as PropType | DataSourceOptions | (((options: { component: Record, dataSource: DataSourceOptions | null }) => void)) | null | Store | Record>, - groupInterval: [String, Number] as PropType, + groupInterval: [Array, String, Number] as PropType<(Array) | HeaderFilterGroupInterval | number>, height: [Number, String], search: Object as PropType>, searchMode: String as PropType, diff --git a/packages/devextreme-vue/src/tree-list.ts b/packages/devextreme-vue/src/tree-list.ts index 8e1d5d19a4c3..3896d5e77832 100644 --- a/packages/devextreme-vue/src/tree-list.ts +++ b/packages/devextreme-vue/src/tree-list.ts @@ -1091,7 +1091,7 @@ const DxColumnHeaderFilterConfig = { allowSearch: Boolean, allowSelectAll: Boolean, dataSource: [Array, Object, Function] as PropType | DataSourceOptions | (((options: { component: Record, dataSource: DataSourceOptions | null }) => void)) | null | Store | Record>, - groupInterval: [String, Number] as PropType, + groupInterval: [Array, String, Number] as PropType<(Array) | HeaderFilterGroupInterval | number>, height: [Number, String], search: Object as PropType>, searchMode: String as PropType, @@ -2022,7 +2022,7 @@ const DxHeaderFilterConfig = { allowSearch: Boolean, allowSelectAll: Boolean, dataSource: [Array, Object, Function] as PropType | DataSourceOptions | (((options: { component: Record, dataSource: DataSourceOptions | null }) => void)) | null | Store | Record>, - groupInterval: [String, Number] as PropType, + groupInterval: [Array, String, Number] as PropType<(Array) | HeaderFilterGroupInterval | number>, height: [Number, String], search: Object as PropType>, searchMode: String as PropType, diff --git a/packages/devextreme/js/common/grids.d.ts b/packages/devextreme/js/common/grids.d.ts index 6a5e532b68cd..8a664ea6fbcd 100644 --- a/packages/devextreme/js/common/grids.d.ts +++ b/packages/devextreme/js/common/grids.d.ts @@ -727,7 +727,7 @@ export type ColumnHeaderFilter = { * @docid * @default undefined */ - groupInterval?: HeaderFilterGroupInterval | number | undefined; + groupInterval?: HeaderFilterGroupInterval | number | Array | undefined; /** * @docid * @default undefined diff --git a/packages/devextreme/ts/dx.all.d.ts b/packages/devextreme/ts/dx.all.d.ts index 42768d9e0192..38634fc3ffb8 100644 --- a/packages/devextreme/ts/dx.all.d.ts +++ b/packages/devextreme/ts/dx.all.d.ts @@ -4884,7 +4884,11 @@ declare module DevExpress.common.grids { /** * [descr:ColumnHeaderFilter.groupInterval] */ - groupInterval?: HeaderFilterGroupInterval | number | undefined; + groupInterval?: + | HeaderFilterGroupInterval + | number + | Array + | undefined; /** * [descr:ColumnHeaderFilter.height] */ From f1521ee69fa324f1a90c2248f0240cc64cc83953 Mon Sep 17 00:00:00 2001 From: Andrey Dolzhikov <16618553+Raushen@users.noreply.github.com> Date: Fri, 19 Jun 2026 15:01:39 +0300 Subject: [PATCH 2/3] [TESTS] T1330038: TypeError - DataGrid does not accept groupInterval as an array (#34064) --- .../__mock__/model/filter_builder.ts | 2 +- .../__mock__/model/column_chooser.ts | 2 +- .../__tests__/__mock__/model/grid_core.ts | 7 + .../__tests__/__mock__/model/header_filter.ts | 8 + .../header_filter.integration.test.ts | 188 ++++++++++++++++++ .../header_filter/__tests__/utils.test.ts | 156 +++++++++++++++ .../ui/__tests__/__mock__/model/tree_view.ts | 52 ----- .../__mock__/model/tree_view/node.ts | 61 ++++++ .../__mock__/model/tree_view/tree_view.ts | 64 ++++++ .../__mock__/model/tree_view/types.ts | 4 + 10 files changed, 490 insertions(+), 54 deletions(-) create mode 100644 packages/devextreme/js/__internal/grids/grid_core/header_filter/__tests__/header_filter.integration.test.ts create mode 100644 packages/devextreme/js/__internal/grids/grid_core/header_filter/__tests__/utils.test.ts delete mode 100644 packages/devextreme/js/__internal/ui/__tests__/__mock__/model/tree_view.ts create mode 100644 packages/devextreme/js/__internal/ui/__tests__/__mock__/model/tree_view/node.ts create mode 100644 packages/devextreme/js/__internal/ui/__tests__/__mock__/model/tree_view/tree_view.ts create mode 100644 packages/devextreme/js/__internal/ui/__tests__/__mock__/model/tree_view/types.ts diff --git a/packages/devextreme/js/__internal/filter_builder/__tests__/__mock__/model/filter_builder.ts b/packages/devextreme/js/__internal/filter_builder/__tests__/__mock__/model/filter_builder.ts index 15cf4fa7f481..7f92b50f77fe 100644 --- a/packages/devextreme/js/__internal/filter_builder/__tests__/__mock__/model/filter_builder.ts +++ b/packages/devextreme/js/__internal/filter_builder/__tests__/__mock__/model/filter_builder.ts @@ -1,5 +1,5 @@ import { PopupModel } from '@ts/ui/__tests__/__mock__/model/popup'; -import { TreeViewModel } from '@ts/ui/__tests__/__mock__/model/tree_view'; +import { TreeViewModel } from '@ts/ui/__tests__/__mock__/model/tree_view/tree_view'; const CLASSES = { filterBuilder: 'dx-filterbuilder', diff --git a/packages/devextreme/js/__internal/grids/grid_core/__tests__/__mock__/model/column_chooser.ts b/packages/devextreme/js/__internal/grids/grid_core/__tests__/__mock__/model/column_chooser.ts index 891a990f2986..d1c127a6a8a1 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/__tests__/__mock__/model/column_chooser.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/__tests__/__mock__/model/column_chooser.ts @@ -1,5 +1,5 @@ import { PopupModel } from '@ts/ui/__tests__/__mock__/model/popup'; -import { TreeViewModel } from '@ts/ui/__tests__/__mock__/model/tree_view'; +import { TreeViewModel } from '@ts/ui/__tests__/__mock__/model/tree_view/tree_view'; export class ColumnChooserModel extends PopupModel { private readonly columnChooserListClass: string; diff --git a/packages/devextreme/js/__internal/grids/grid_core/__tests__/__mock__/model/grid_core.ts b/packages/devextreme/js/__internal/grids/grid_core/__tests__/__mock__/model/grid_core.ts index 44d61cc2f967..d04caedf2a44 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/__tests__/__mock__/model/grid_core.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/__tests__/__mock__/model/grid_core.ts @@ -12,6 +12,7 @@ import { ColumnChooserModel } from './column_chooser'; import { ConfirmationDialogModel } from './confirmation_dialog'; import { EditFormModel } from './edit_form'; import { FilterPanelModel } from './filter_panel'; +import { HeaderFilterModel } from './header_filter'; import { DataRowModel } from './row/data_row'; import { FilterRowModel } from './row/filter_row'; import { GroupRowModel } from './row/group_row'; @@ -73,6 +74,12 @@ export abstract class GridCoreModel { return $(Array.from(this.getHeaderCells()).find((el) => $(el).text().includes(text))); } + public openHeaderFilter(columnIndex: number): HeaderFilterModel { + (this.getHeaderCellFilter(columnIndex).get(0) as HTMLElement).click(); + + return new HeaderFilterModel(); + } + public getDataRows(): NodeListOf { return this.root.querySelectorAll(`.${SELECTORS.dataRowClass}`); } diff --git a/packages/devextreme/js/__internal/grids/grid_core/__tests__/__mock__/model/header_filter.ts b/packages/devextreme/js/__internal/grids/grid_core/__tests__/__mock__/model/header_filter.ts index a1a8bfda6b4f..ee9a4792a5db 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/__tests__/__mock__/model/header_filter.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/__tests__/__mock__/model/header_filter.ts @@ -1,8 +1,10 @@ import { PopupModel } from '@ts/ui/__tests__/__mock__/model/popup'; +import { TreeViewModel } from '@ts/ui/__tests__/__mock__/model/tree_view/tree_view'; const CLASSES = { headerFilterMenu: 'dx-header-filter-menu', listItemContent: 'dx-list-item-content', + treeView: 'dx-treeview', }; const SELECTORS = { @@ -15,6 +17,12 @@ export class HeaderFilterModel extends PopupModel { return popup?.querySelector(SELECTORS.okButton) as HTMLElement; } + public getTreeView(): TreeViewModel { + const popup = this.getPopupWrapper(); + const treeViewElement = popup?.querySelector(`.${CLASSES.treeView}`) as HTMLElement; + return new TreeViewModel(treeViewElement); + } + public getListItems(): HTMLElement[] { const popup = this.getPopupWrapper(); const listItems = popup?.querySelectorAll(`.${CLASSES.listItemContent}`); diff --git a/packages/devextreme/js/__internal/grids/grid_core/header_filter/__tests__/header_filter.integration.test.ts b/packages/devextreme/js/__internal/grids/grid_core/header_filter/__tests__/header_filter.integration.test.ts new file mode 100644 index 000000000000..74d991489907 --- /dev/null +++ b/packages/devextreme/js/__internal/grids/grid_core/header_filter/__tests__/header_filter.integration.test.ts @@ -0,0 +1,188 @@ +import { + afterEach, beforeEach, describe, expect, it, +} from '@jest/globals'; + +import { + afterTest, beforeTest, createDataGrid, flushAsync, +} from '../../__tests__/__mock__/helpers/utils'; + +describe('headerFilter.groupInterval as array - tree popup', () => { + beforeEach(beforeTest); + afterEach(afterTest); + + describe('number[] - automatic numeric buckets', () => { + it('builds a tree of nested buckets and filters by the selected one', async () => { + const { instance, component } = await createDataGrid({ + dataSource: [ + { id: 1, price: 5 }, + { id: 2, price: 15 }, + { id: 3, price: 115 }, + ], + headerFilter: { visible: true }, + columns: [ + { + dataField: 'price', + dataType: 'number', + headerFilter: { groupInterval: [100, 10] }, + }, + ], + }); + + const headerFilter = component.openHeaderFilter(0); + + await flushAsync(); + await flushAsync(); + const treeView = headerFilter.getTreeView(); + treeView.expandAll(); + + expect(treeView.getStructure()).toEqual([ + { + text: '0 - 100', + children: [ + { text: '0 - 10', children: [] }, + { text: '10 - 20', children: [] }, + ], + }, + { + text: '100 - 200', + children: [{ text: '110 - 120', children: [] }], + }, + ]); + + treeView.selectItemByText('0 - 100'); + await flushAsync(); + headerFilter.getOKButton().click(); + await flushAsync(); + + expect([...(instance.getCombinedFilter(true) as unknown[])]).toEqual([ + ['price', '>=', 0], 'and', ['price', '<', 100], + ]); + expect(instance.getVisibleRows().map((row) => row.data.price)).toEqual([5, 15]); + }); + }); + + describe('string[] - hierarchical dataSource', () => { + const createGrid = (): ReturnType => createDataGrid({ + dataSource: [ + { + id: 1, city: 'Munich', state: 'Bavaria', country: 'Germany', + }, + { + id: 2, city: 'Nuremberg', state: 'Bavaria', country: 'Germany', + }, + { + id: 3, city: 'Berlin', state: 'Berlin', country: 'Germany', + }, + { + id: 4, city: 'Lyon', state: 'Rhone', country: 'France', + }, + ], + headerFilter: { visible: true }, + columns: [ + { + dataField: 'city', + dataType: 'string', + headerFilter: { + groupInterval: ['country', 'state', 'city'], + dataSource: [ + { + text: 'Germany', + value: 'Germany', + items: [ + { + text: 'Bavaria', + value: 'Bavaria', + items: [ + { text: 'Munich', value: 'Munich' }, + { text: 'Nuremberg', value: 'Nuremberg' }, + ], + }, + { text: 'Berlin', value: 'Berlin', items: [{ text: 'Berlin', value: 'Berlin' }] }, + ], + }, + { + text: 'France', + value: 'France', + items: [{ text: 'Rhone', value: 'Rhone', items: [{ text: 'Lyon', value: 'Lyon' }] }], + }, + ], + }, + calculateFilterExpression(filterValue, selectedFilterOperation, target) { + if (target === 'headerFilter' && filterValue) { + const operation = selectedFilterOperation ?? '='; + return [ + [['country', operation, filterValue], 'or', ['state', operation, filterValue]], + 'or', + ['city', operation, filterValue], + ]; + } + + return this.defaultCalculateFilterExpression?.( + filterValue, + selectedFilterOperation, + target, + ) ?? ''; + }, + }, + { dataField: 'country', dataType: 'string' }, + ], + }); + + it('builds the hierarchy tree from the dataSource', async () => { + const { component } = await createGrid(); + + const headerFilter = component.openHeaderFilter(0); + + await flushAsync(); + await flushAsync(); + const treeView = headerFilter.getTreeView(); + treeView.expandAll(); + + expect(treeView.getStructure()).toEqual([ + { + text: 'Germany', + children: [ + { + text: 'Bavaria', + children: [ + { text: 'Munich', children: [] }, + { text: 'Nuremberg', children: [] }, + ], + }, + { text: 'Berlin', children: [{ text: 'Berlin', children: [] }] }, + ], + }, + { + text: 'France', + children: [{ text: 'Rhone', children: [{ text: 'Lyon', children: [] }] }], + }, + ]); + }); + + it.each([ + { node: 'Germany', cities: ['Munich', 'Nuremberg', 'Berlin'] }, + { node: 'Bavaria', cities: ['Munich', 'Nuremberg'] }, + { node: 'Munich', cities: ['Munich'] }, + ])('filters by the selected "$node" node', async ({ node, cities }) => { + const { instance, component } = await createGrid(); + + const headerFilter = component.openHeaderFilter(0); + + await flushAsync(); + await flushAsync(); + const treeView = headerFilter.getTreeView(); + treeView.expandAll(); + treeView.selectItemByText(node); + await flushAsync(); + headerFilter.getOKButton().click(); + await flushAsync(); + + expect([...(instance.getCombinedFilter(true) as unknown[])]).toEqual([ + [['country', '=', node], 'or', ['state', '=', node]], + 'or', + ['city', '=', node], + ]); + expect(instance.getVisibleRows().map((row) => row.data.city)).toEqual(cities); + }); + }); +}); diff --git a/packages/devextreme/js/__internal/grids/grid_core/header_filter/__tests__/utils.test.ts b/packages/devextreme/js/__internal/grids/grid_core/header_filter/__tests__/utils.test.ts new file mode 100644 index 000000000000..8beb0d1be88f --- /dev/null +++ b/packages/devextreme/js/__internal/grids/grid_core/header_filter/__tests__/utils.test.ts @@ -0,0 +1,156 @@ +import { describe, expect, it } from '@jest/globals'; +import filterUtils from '@js/ui/shared/filtering'; + +import gridCoreUtils from '../../m_utils'; +import { getFormatOptions } from '../m_header_filter'; + +describe('getGroupInterval', () => { + describe('headerFilter.groupInterval normalization', () => { + it.each([ + { + caseName: 'scalar number', dataType: 'number', groupInterval: 100, expected: [100], + }, + { + caseName: 'number[]', dataType: 'number', groupInterval: [100, 10], expected: [100, 10], + }, + { + caseName: 'three-level number[]', dataType: 'number', groupInterval: [100000, 10000, 1000], expected: [100000, 10000, 1000], + }, + { + caseName: 'string[] (hierarchy field names)', dataType: 'string', groupInterval: ['Country', 'State', 'City'], expected: ['Country', 'State', 'City'], + }, + { + caseName: 'unset', dataType: 'number', groupInterval: undefined, expected: undefined, + }, + { + caseName: 'date, default', dataType: 'date', groupInterval: undefined, expected: ['year', 'month', 'day'], + }, + { + caseName: 'datetime, default', dataType: 'datetime', groupInterval: undefined, expected: ['year', 'month', 'day', 'hour', 'minute'], + }, + { + caseName: 'date, "year"', dataType: 'date', groupInterval: 'year', expected: ['year'], + }, + { + caseName: 'date, "month"', dataType: 'date', groupInterval: 'month', expected: ['year', 'month'], + }, + { + caseName: 'date, "quarter"', dataType: 'date', groupInterval: 'quarter', expected: ['year', 'quarter'], + }, + { + caseName: 'datetime, "hour"', dataType: 'datetime', groupInterval: 'hour', expected: ['year', 'month', 'day', 'hour'], + }, + { + caseName: 'datetime, "second"', dataType: 'datetime', groupInterval: 'second', expected: ['year', 'month', 'day', 'hour', 'minute', 'second'], + }, + { + caseName: 'date, null', dataType: 'date', groupInterval: null, expected: undefined, + }, + { + caseName: 'date, ["quarter", "second"]', dataType: 'date', groupInterval: ['quarter', 'second'], expected: ['year', 'month', 'day'], + }, + { + caseName: 'date, [10, 20]', dataType: 'date', groupInterval: [10, 20], expected: ['year', 'month', 'day'], + }, + { + caseName: 'date, ["a", "b"]', dataType: 'date', groupInterval: ['a', 'b'], expected: ['year', 'month', 'day'], + }, + { + caseName: 'datetime, ["quarter", "second"]', dataType: 'datetime', groupInterval: ['quarter', 'second'], expected: ['year', 'month', 'day', 'hour', 'minute'], + }, + ])('normalizes $caseName into $expected', ({ dataType, groupInterval, expected }) => { + const column = { dataType, headerFilter: { groupInterval } }; + + expect(filterUtils.getGroupInterval(column)).toEqual(expected); + }); + }); +}); + +describe('getHeaderFilterGroupParameters', () => { + it('builds one remote group parameter per level, expanding all but the last', () => { + const column = { + dataField: 'Price', dataType: 'number', headerFilter: { groupInterval: [100, 10] }, + }; + + expect(gridCoreUtils.getHeaderFilterGroupParameters(column, true)).toEqual([ + { selector: 'Price', groupInterval: 100, isExpanded: true }, + { selector: 'Price', groupInterval: 10, isExpanded: false }, + ]); + }); + + it('supports an arbitrary number of levels', () => { + const column = { + dataField: 'Population', + dataType: 'number', + headerFilter: { groupInterval: [100000, 10000, 1000] }, + }; + + expect(gridCoreUtils.getHeaderFilterGroupParameters(column, true)).toEqual([ + { selector: 'Population', groupInterval: 100000, isExpanded: true }, + { selector: 'Population', groupInterval: 10000, isExpanded: true }, + { selector: 'Population', groupInterval: 1000, isExpanded: false }, + ]); + }); + + it('builds a local interval selector per level that buckets the cell value', () => { + const column = { + dataField: 'Price', + dataType: 'number', + headerFilter: { groupInterval: [100, 10] }, + calculateCellValue: (data) => data.Price, + }; + + const params = gridCoreUtils.getHeaderFilterGroupParameters(column); + + expect(params).toHaveLength(2); + // 753 -> floor(753 / 100) * 100 on the outer level, floor(753 / 10) * 10 on the inner level + expect(params[0]({ Price: 753 })).toBe(700); + expect(params[1]({ Price: 753 })).toBe(750); + }); +}); + +describe('getFormatOptions', () => { + it('selects the interval of the requested level for a number[]', () => { + const column = { dataType: 'number', headerFilter: { groupInterval: [100, 10] } }; + + expect(getFormatOptions(700, column, 0).groupInterval).toBe(100); + expect(getFormatOptions(750, column, 1).groupInterval).toBe(10); + }); + + it('formats a numeric bucket as a " - " range', () => { + const column = { dataType: 'number', headerFilter: { groupInterval: [100, 10] } }; + + expect(getFormatOptions(700, column, 0).getDisplayFormat()).toBe('700 - 800'); + expect(getFormatOptions(750, column, 1).getDisplayFormat()).toBe('750 - 760'); + }); + + it('selects the hierarchy level name for a string[]', () => { + const column = { dataType: 'string', headerFilter: { groupInterval: ['Country', 'State', 'City'] } }; + + expect(getFormatOptions('USA', column, 0).groupInterval).toBe('Country'); + expect(getFormatOptions('Arkansas', column, 1).groupInterval).toBe('State'); + expect(getFormatOptions('Bentonville', column, 2).groupInterval).toBe('City'); + }); + + it('selects the date interval and its format per level for a date column', () => { + const column = { dataType: 'date', headerFilter: { groupInterval: 'month' } }; + + const yearLevel = getFormatOptions(2024, column, 0); + expect(yearLevel.groupInterval).toBe('year'); + expect(yearLevel.format(2024)).toBe('2024'); + + const monthLevel = getFormatOptions(3, column, 1); + expect(monthLevel.groupInterval).toBe('month'); + expect(monthLevel.format(3)).toBe('March'); + }); + + it('selects the date interval and its format per level for a datetime column', () => { + const column = { dataType: 'datetime', headerFilter: { groupInterval: 'hour' } }; + + expect(getFormatOptions(2024, column, 0).groupInterval).toBe('year'); + + const hourLevel = getFormatOptions(14, column, 3); + expect(hourLevel.groupInterval).toBe('hour'); + expect(hourLevel.format(14)).toBe('14'); + }); +}); diff --git a/packages/devextreme/js/__internal/ui/__tests__/__mock__/model/tree_view.ts b/packages/devextreme/js/__internal/ui/__tests__/__mock__/model/tree_view.ts deleted file mode 100644 index 06b9e8d77d60..000000000000 --- a/packages/devextreme/js/__internal/ui/__tests__/__mock__/model/tree_view.ts +++ /dev/null @@ -1,52 +0,0 @@ -import TreeView from '@js/ui/tree_view'; - -import { CheckBoxModel } from './checkbox'; -import { TextBoxModel } from './textbox'; - -const CLASSES = { - treeView: 'dx-treeview', - searchBox: 'dx-treeview-search', - node: 'dx-treeview-node', - item: 'dx-treeview-item', - checkbox: 'dx-checkbox', -}; - -export class TreeViewModel { - constructor(protected readonly root: HTMLElement) {} - - public getInstance(): TreeView { - return TreeView.getInstance(this.root) as TreeView; - } - - private getSearchBox(): TextBoxModel { - 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 | null { - return this.root?.querySelectorAll(`.${CLASSES.node}`) ?? null; - } - - public getNodeByText(text: string): HTMLElement | null { - const nodes = this.getNodes(); - if (!nodes) return null; - - const foundNode = Array.from(nodes).find((node) => { - const itemElement = node.querySelector(`.${CLASSES.item}`); - const nodeText = itemElement?.textContent; - return nodeText?.includes(text); - }) ?? null; - - return foundNode; - } - - public getCheckboxByText(text: string): CheckBoxModel | null { - const node = this.getNodeByText(text); - const checkboxElement = node?.querySelector(`.${CLASSES.checkbox}`) as HTMLElement; - return checkboxElement ? new CheckBoxModel(checkboxElement) : null; - } -} diff --git a/packages/devextreme/js/__internal/ui/__tests__/__mock__/model/tree_view/node.ts b/packages/devextreme/js/__internal/ui/__tests__/__mock__/model/tree_view/node.ts new file mode 100644 index 000000000000..cef048be4574 --- /dev/null +++ b/packages/devextreme/js/__internal/ui/__tests__/__mock__/model/tree_view/node.ts @@ -0,0 +1,61 @@ +import { CheckBoxModel } from '../checkbox'; +import type { TreeViewNodeStructure } from './types'; + +const CLASSES = { + node: 'dx-treeview-node', + item: 'dx-treeview-item', + itemContent: 'dx-treeview-item-content', + nodeContainer: 'dx-treeview-node-container', + toggle: 'dx-treeview-toggle-item-visibility', + checkbox: 'dx-checkbox', +}; + +const SELECTORS = { + collapsed: '[aria-expanded="false"]', +}; + +export class NodeModel { + constructor(private readonly root: HTMLElement) {} + + public static fromContainer(container: Element | null): NodeModel[] { + if (!container) return []; + + return Array.from(container.children) + .filter((element) => element.classList.contains(CLASSES.node)) + .map((element) => new NodeModel(element as HTMLElement)); + } + + public getElement(): HTMLElement { + return this.root; + } + + public getText(): string { + return this.root.querySelector(`:scope > .${CLASSES.item} > .${CLASSES.itemContent}`)?.textContent ?? ''; + } + + public isCollapsed(): boolean { + return this.root.matches(SELECTORS.collapsed); + } + + public expand(): void { + if (this.isCollapsed()) { + this.root.querySelector(`.${CLASSES.toggle}`)?.click(); + } + } + + public getCheckBox(): CheckBoxModel | null { + const checkbox = this.root.querySelector(`.${CLASSES.checkbox}`); + return checkbox ? new CheckBoxModel(checkbox) : null; + } + + public getChildren(): NodeModel[] { + return NodeModel.fromContainer(this.root.querySelector(`:scope > .${CLASSES.nodeContainer}`)); + } + + public getStructure(): TreeViewNodeStructure { + return { + text: this.getText(), + children: this.getChildren().map((child) => child.getStructure()), + }; + } +} diff --git a/packages/devextreme/js/__internal/ui/__tests__/__mock__/model/tree_view/tree_view.ts b/packages/devextreme/js/__internal/ui/__tests__/__mock__/model/tree_view/tree_view.ts new file mode 100644 index 000000000000..cbbed7c4b7b8 --- /dev/null +++ b/packages/devextreme/js/__internal/ui/__tests__/__mock__/model/tree_view/tree_view.ts @@ -0,0 +1,64 @@ +import TreeView from '@js/ui/tree_view'; + +import type { CheckBoxModel } from '../checkbox'; +import { TextBoxModel } from '../textbox'; +import { NodeModel } from './node'; +import type { TreeViewNodeStructure } from './types'; + +const CLASSES = { + treeView: 'dx-treeview', + searchBox: 'dx-treeview-search', + node: 'dx-treeview-node', + nodeContainer: 'dx-treeview-node-container', +}; + +export class TreeViewModel { + constructor(protected readonly root: HTMLElement) {} + + public getInstance(): TreeView { + return TreeView.getInstance(this.root) as TreeView; + } + + public getStructure(): TreeViewNodeStructure[] { + return this.getRootNodes().map((node) => node.getStructure()); + } + + public expandAll(): void { + const MAX_DEPTH = 10; + for (let depth = 0; depth < MAX_DEPTH; depth += 1) { + const collapsedNodes = this.getNodes().filter((node) => node.isCollapsed()); + + if (!collapsedNodes.length) return; + collapsedNodes.forEach((node) => node.expand()); + } + } + + public setSearchValue(value: string): void { + this.getSearchBox().setValue(value); + } + + public selectItemByText(text: string): void { + this.getNodeByText(text)?.getCheckBox()?.toggle(); + } + + public getNodeByText(text: string): NodeModel | null { + return this.getNodes().find((node) => node.getText().includes(text)) ?? null; + } + + public getCheckboxByText(text: string): CheckBoxModel | null { + return this.getNodeByText(text)?.getCheckBox() ?? null; + } + + private getSearchBox(): TextBoxModel { + return new TextBoxModel(this.root.querySelector(`.${CLASSES.searchBox}`) as HTMLElement); + } + + private getRootNodes(): NodeModel[] { + return NodeModel.fromContainer(this.root.querySelector(`.${CLASSES.nodeContainer}`)); + } + + private getNodes(): NodeModel[] { + return Array.from(this.root.querySelectorAll(`.${CLASSES.node}`)) + .map((element) => new NodeModel(element as HTMLElement)); + } +} diff --git a/packages/devextreme/js/__internal/ui/__tests__/__mock__/model/tree_view/types.ts b/packages/devextreme/js/__internal/ui/__tests__/__mock__/model/tree_view/types.ts new file mode 100644 index 000000000000..478aeab01e93 --- /dev/null +++ b/packages/devextreme/js/__internal/ui/__tests__/__mock__/model/tree_view/types.ts @@ -0,0 +1,4 @@ +export interface TreeViewNodeStructure { + text: string; + children: TreeViewNodeStructure[]; +} From 20c2ab4ac093de46486c1723ac4b136f9b75af60 Mon Sep 17 00:00:00 2001 From: Raushen Date: Fri, 19 Jun 2026 15:23:33 +0300 Subject: [PATCH 3/3] Remove unsupported tests --- .../header_filter/__tests__/utils.test.ts | 22 ------------------- 1 file changed, 22 deletions(-) diff --git a/packages/devextreme/js/__internal/grids/grid_core/header_filter/__tests__/utils.test.ts b/packages/devextreme/js/__internal/grids/grid_core/header_filter/__tests__/utils.test.ts index 8beb0d1be88f..5f91fdb6d668 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/header_filter/__tests__/utils.test.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/header_filter/__tests__/utils.test.ts @@ -131,26 +131,4 @@ describe('getFormatOptions', () => { expect(getFormatOptions('Arkansas', column, 1).groupInterval).toBe('State'); expect(getFormatOptions('Bentonville', column, 2).groupInterval).toBe('City'); }); - - it('selects the date interval and its format per level for a date column', () => { - const column = { dataType: 'date', headerFilter: { groupInterval: 'month' } }; - - const yearLevel = getFormatOptions(2024, column, 0); - expect(yearLevel.groupInterval).toBe('year'); - expect(yearLevel.format(2024)).toBe('2024'); - - const monthLevel = getFormatOptions(3, column, 1); - expect(monthLevel.groupInterval).toBe('month'); - expect(monthLevel.format(3)).toBe('March'); - }); - - it('selects the date interval and its format per level for a datetime column', () => { - const column = { dataType: 'datetime', headerFilter: { groupInterval: 'hour' } }; - - expect(getFormatOptions(2024, column, 0).groupInterval).toBe('year'); - - const hourLevel = getFormatOptions(14, column, 3); - expect(hourLevel.groupInterval).toBe('hour'); - expect(hourLevel.format(14)).toBe('14'); - }); });