diff --git a/e2e/testcafe-devextreme/tests/dataGrid/common/fixedColumns/etalons/T1317623-expand-columns-with-band-columns (fluent.blue.light).png b/e2e/testcafe-devextreme/tests/dataGrid/common/fixedColumns/etalons/T1317623-expand-columns-with-band-columns (fluent.blue.light).png new file mode 100644 index 000000000000..bdf57d5b899b Binary files /dev/null and b/e2e/testcafe-devextreme/tests/dataGrid/common/fixedColumns/etalons/T1317623-expand-columns-with-band-columns (fluent.blue.light).png differ diff --git a/e2e/testcafe-devextreme/tests/dataGrid/common/fixedColumns/etalons/T1317623-horizontal-scroll-with-fixed-band-columns (fluent.blue.light).png b/e2e/testcafe-devextreme/tests/dataGrid/common/fixedColumns/etalons/T1317623-horizontal-scroll-with-fixed-band-columns (fluent.blue.light).png new file mode 100644 index 000000000000..a2ec21a333cd Binary files /dev/null and b/e2e/testcafe-devextreme/tests/dataGrid/common/fixedColumns/etalons/T1317623-horizontal-scroll-with-fixed-band-columns (fluent.blue.light).png differ diff --git a/e2e/testcafe-devextreme/tests/dataGrid/common/fixedColumns/visual.ts b/e2e/testcafe-devextreme/tests/dataGrid/common/fixedColumns/visual.ts index 306c59a6c76d..344aed1eacac 100644 --- a/e2e/testcafe-devextreme/tests/dataGrid/common/fixedColumns/visual.ts +++ b/e2e/testcafe-devextreme/tests/dataGrid/common/fixedColumns/visual.ts @@ -314,3 +314,73 @@ test('The grid layout should be correct after unfixing a column via the context { dataField: 'State' }, ], })); + +// T1317623 +test('Expand columns headers offsets should be correct with fixed band columns and fixed command columns (T1317623)', async (t) => { + const dataGrid = new DataGrid(DATA_GRID_SELECTOR); + const { takeScreenshot, compareResults } = createScreenshotsComparer(t); + + await t.expect(dataGrid.isReady()).ok(); + + await testScreenshot(t, takeScreenshot, 'T1317623-expand-columns-with-band-columns.png', { element: dataGrid.element }); + + await dataGrid.scrollTo(t, { x: 5000 }); + + await testScreenshot(t, takeScreenshot, 'T1317623-horizontal-scroll-with-fixed-band-columns.png', { element: dataGrid.element }); + + await t + .expect(compareResults.isValid()) + .ok(compareResults.errorMessages()); +}).before(async () => createWidget('dxDataGrid', { + dataSource: [ + { + ID: 1, + CompanyName: 'Super Mart of the West', + Address: '702 SW 8th Street', + City: 'Bentonville', + State: 'Arkansas', + Zipcode: 72716, + Phone: '(800) 555-2797', + Fax: '(800) 555-2171', + }, + { + ID: 2, + CompanyName: 'K&S Music', + Address: '1000 Nicllet Mall', + City: 'Minneapolis', + State: 'Minnesota', + Zipcode: 55403, + Phone: '(612) 304-6073', + Fax: '(612) 304-6074', + }, + ], + keyExpr: 'ID', + width: '100%', + showBorders: true, + columnWidth: 200, + columnFixing: { enabled: true }, + selection: { mode: 'multiple' }, + grouping: { autoExpandAll: true }, + masterDetail: { + enabled: true, + }, + columns: [ + { + caption: 'Company Info', + fixed: true, + fixedPosition: 'left', + columns: [ + { dataField: 'CompanyName', groupIndex: 1, showWhenGrouped: true }, + { dataField: 'Phone' }, + { dataField: 'Fax' }, + ], + }, + 'City', + { + dataField: 'State', + groupIndex: 0, + }, + 'Address', + 'Zipcode', + ], +})); diff --git a/packages/devextreme/js/__internal/grids/data_grid/keyboard_navigation/m_column_keyboard_navigation_mixin.ts b/packages/devextreme/js/__internal/grids/data_grid/keyboard_navigation/m_column_keyboard_navigation_mixin.ts index b814293b8540..0a04c139361f 100644 --- a/packages/devextreme/js/__internal/grids/data_grid/keyboard_navigation/m_column_keyboard_navigation_mixin.ts +++ b/packages/devextreme/js/__internal/grids/data_grid/keyboard_navigation/m_column_keyboard_navigation_mixin.ts @@ -1,6 +1,6 @@ import { isCommandKeyPressed } from '@js/common/core/events/utils'; import { isDefined } from '@js/core/utils/type'; -import type { Column } from '@ts/grids/grid_core/columns_controller/m_columns_controller'; +import type { Column } from '@ts/grids/grid_core/columns_controller/types'; import type { FocusedCellPosition } from '@ts/grids/grid_core/keyboard_navigation/const'; import { KEY_CODES } from '@ts/grids/grid_core/keyboard_navigation/const'; import type { ColumnKeyboardNavigationController } from '@ts/grids/grid_core/keyboard_navigation/m_column_keyboard_navigation_core'; diff --git a/packages/devextreme/js/__internal/grids/data_grid/keyboard_navigation/m_group_panel_keyboard_navigation.ts b/packages/devextreme/js/__internal/grids/data_grid/keyboard_navigation/m_group_panel_keyboard_navigation.ts index b4ae24f34f2a..c5af64ed3ebc 100644 --- a/packages/devextreme/js/__internal/grids/data_grid/keyboard_navigation/m_group_panel_keyboard_navigation.ts +++ b/packages/devextreme/js/__internal/grids/data_grid/keyboard_navigation/m_group_panel_keyboard_navigation.ts @@ -5,7 +5,7 @@ import { } from '@js/common/core/events/utils/index'; import $ from '@js/core/renderer'; import { hiddenFocus } from '@js/ui/shared/accessibility'; -import type { Column } from '@ts/grids/grid_core/columns_controller/m_columns_controller'; +import type { Column } from '@ts/grids/grid_core/columns_controller/types'; import { Direction } from '@ts/grids/grid_core/keyboard_navigation/const'; import { ColumnKeyboardNavigationController } from '@ts/grids/grid_core/keyboard_navigation/m_column_keyboard_navigation_core'; import type { Views } from '@ts/grids/grid_core/m_types'; diff --git a/packages/devextreme/js/__internal/grids/data_grid/keyboard_navigation/m_headers_keyboard_navigation.ts b/packages/devextreme/js/__internal/grids/data_grid/keyboard_navigation/m_headers_keyboard_navigation.ts index d4d757fb7ec1..47748edce42e 100644 --- a/packages/devextreme/js/__internal/grids/data_grid/keyboard_navigation/m_headers_keyboard_navigation.ts +++ b/packages/devextreme/js/__internal/grids/data_grid/keyboard_navigation/m_headers_keyboard_navigation.ts @@ -1,7 +1,7 @@ import { isCommandKeyPressed } from '@js/common/core/events/utils'; import $ from '@js/core/renderer'; import { isDefined } from '@js/core/utils/type'; -import type { Column } from '@ts/grids/grid_core/columns_controller/m_columns_controller'; +import type { Column } from '@ts/grids/grid_core/columns_controller/types'; import { KEY_CODES } from '@ts/grids/grid_core/keyboard_navigation/const'; import type { HeadersKeyboardNavigationController } from '@ts/grids/grid_core/keyboard_navigation/m_headers_keyboard_navigation'; import { headersKeyboardNavigationModule } from '@ts/grids/grid_core/keyboard_navigation/m_headers_keyboard_navigation'; diff --git a/packages/devextreme/js/__internal/grids/data_grid/summary/utils.ts b/packages/devextreme/js/__internal/grids/data_grid/summary/utils.ts index 88662edc0fce..a6065e3b565e 100644 --- a/packages/devextreme/js/__internal/grids/data_grid/summary/utils.ts +++ b/packages/devextreme/js/__internal/grids/data_grid/summary/utils.ts @@ -1,5 +1,5 @@ import { isDefined } from '@js/core/utils/type'; -import type { Column } from '@ts/grids/grid_core/columns_controller/m_columns_controller'; +import type { Column } from '@ts/grids/grid_core/columns_controller/types'; export function getSummaryCellIndex( column: Column, diff --git a/packages/devextreme/js/__internal/grids/grid_core/adaptivity/m_adaptivity.ts b/packages/devextreme/js/__internal/grids/grid_core/adaptivity/m_adaptivity.ts index ec350d4f3ccf..254218475ba8 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/adaptivity/m_adaptivity.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/adaptivity/m_adaptivity.ts @@ -16,11 +16,12 @@ import { getWidth } from '@js/core/utils/size'; import { isDefined, isString } from '@js/core/utils/type'; import Form from '@js/ui/form'; import { isMaterial } from '@js/ui/themes'; +import type { Column } from '@ts/grids/grid_core/columns_controller/types'; import type { ResizingController } from '@ts/grids/grid_core/views/m_grid_view'; import type { ExportController } from '../../data_grid/export/m_export'; import { AI_COLUMN_NAME } from '../ai_column/const'; -import type { Column, ColumnsController } from '../columns_controller/m_columns_controller'; +import type { ColumnsController } from '../columns_controller/m_columns_controller'; import type { ColumnsResizerViewController, DraggingHeaderViewController } from '../columns_resizing_reordering/m_columns_resizing_reordering'; import type { DataController } from '../data_controller/m_data_controller'; import type { EditingController } from '../editing/m_editing'; diff --git a/packages/devextreme/js/__internal/grids/grid_core/adaptivity/utils.ts b/packages/devextreme/js/__internal/grids/grid_core/adaptivity/utils.ts index 57b26320ce84..a03d6024efb6 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/adaptivity/utils.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/adaptivity/utils.ts @@ -1,6 +1,5 @@ import { isDefined } from '@js/core/utils/type'; - -import type { Column } from '../columns_controller/m_columns_controller'; +import type { Column } from '@ts/grids/grid_core/columns_controller/types'; const HIDEABLE_COMMAND_COLUMNS = ['ai']; diff --git a/packages/devextreme/js/__internal/grids/grid_core/ai_column/controllers/m_ai_column_controller.ts b/packages/devextreme/js/__internal/grids/grid_core/ai_column/controllers/m_ai_column_controller.ts index 79bfea4ac143..0157a4227a33 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/ai_column/controllers/m_ai_column_controller.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/ai_column/controllers/m_ai_column_controller.ts @@ -1,8 +1,9 @@ import type { DataChange } from '@js/common/grids'; import type { Callback } from '@js/core/utils/callbacks'; import { isDefined } from '@ts/core/utils/m_type'; +import type { Column } from '@ts/grids/grid_core/columns_controller/types'; -import type { Column, ColumnsController } from '../../columns_controller/m_columns_controller'; +import type { ColumnsController } from '../../columns_controller/m_columns_controller'; import type { DataController, HandleDataChangedArguments, UserData } from '../../data_controller/m_data_controller'; import { Controller } from '../../m_modules'; import gridCoreUtils from '../../m_utils'; diff --git a/packages/devextreme/js/__internal/grids/grid_core/ai_column/controllers/m_ai_prompt_editor_view_controller.ts b/packages/devextreme/js/__internal/grids/grid_core/ai_column/controllers/m_ai_prompt_editor_view_controller.ts index de8052547a2e..917232df34c0 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/ai_column/controllers/m_ai_prompt_editor_view_controller.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/ai_column/controllers/m_ai_prompt_editor_view_controller.ts @@ -1,4 +1,5 @@ -import type { Column } from '../../columns_controller/m_columns_controller'; +import type { Column } from '@ts/grids/grid_core/columns_controller/types'; + import { ViewController } from '../../m_modules'; import type { AIPromptEditorView } from '../views/m_ai_prompt_editor_view'; diff --git a/packages/devextreme/js/__internal/grids/grid_core/ai_column/utils.test.ts b/packages/devextreme/js/__internal/grids/grid_core/ai_column/utils.test.ts index 6cb2bdd42a92..d50f38d9a60e 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/ai_column/utils.test.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/ai_column/utils.test.ts @@ -1,8 +1,8 @@ import { describe, expect, it, } from '@jest/globals'; +import type { Column } from '@ts/grids/grid_core/columns_controller/types'; -import type { Column } from '../columns_controller/m_columns_controller'; import type { Item } from '../data_controller/m_data_controller'; import { getDataFromRowItems, diff --git a/packages/devextreme/js/__internal/grids/grid_core/ai_column/utils.ts b/packages/devextreme/js/__internal/grids/grid_core/ai_column/utils.ts index f862f70b6506..ce5ffb741080 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/ai_column/utils.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/ai_column/utils.ts @@ -1,6 +1,6 @@ import { isDefined } from '@ts/core/utils/m_type'; +import type { Column } from '@ts/grids/grid_core/columns_controller/types'; -import type { Column } from '../columns_controller/m_columns_controller'; import type { Item, UserData } from '../data_controller/m_data_controller'; import { AI_COLUMN_NAME, CLASSES } from './const'; diff --git a/packages/devextreme/js/__internal/grids/grid_core/ai_column/views/m_ai_column_view.ts b/packages/devextreme/js/__internal/grids/grid_core/ai_column/views/m_ai_column_view.ts index 1ef7d9b84edb..54d4a0983e35 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/ai_column/views/m_ai_column_view.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/ai_column/views/m_ai_column_view.ts @@ -3,9 +3,9 @@ import type { dxElementWrapper } from '@js/core/renderer'; import $ from '@js/core/renderer'; import type { Item, ItemClickEvent, Properties as DropDownProperties } from '@js/ui/drop_down_button'; import DropDownButton from '@js/ui/drop_down_button'; +import type { Column } from '@ts/grids/grid_core/columns_controller/types'; import type { ColumnHeadersView } from '../../column_headers/m_column_headers'; -import type { Column } from '../../columns_controller/m_columns_controller'; import type { ColumnsResizerViewController } from '../../columns_resizing_reordering/m_columns_resizing_reordering'; import type { ModuleType } from '../../m_types'; import { AI_COLUMN_NAME, CLASSES, ICON_NAMES } from '../const'; diff --git a/packages/devextreme/js/__internal/grids/grid_core/ai_column/views/m_ai_prompt_editor_view.test.ts b/packages/devextreme/js/__internal/grids/grid_core/ai_column/views/m_ai_prompt_editor_view.test.ts index 57f4972ab726..545a6e9038f3 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/ai_column/views/m_ai_prompt_editor_view.test.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/ai_column/views/m_ai_prompt_editor_view.test.ts @@ -13,9 +13,9 @@ import $ from '@js/core/renderer'; import Callbacks from '@js/core/utils/callbacks'; import wrapInstanceWithMocks from '@ts/grids/grid_core/__tests__/__mock__/helpers/wrapInstance'; import { AIPromptEditorModel } from '@ts/grids/grid_core/__tests__/__mock__/model/ai_prompt_editor'; +import type { Column } from '@ts/grids/grid_core/columns_controller/types'; import { AIPromptEditor } from '../../ai_prompt_editor/ai_prompt_editor'; -import type { Column } from '../../columns_controller/m_columns_controller'; import { AIPromptEditorView } from './m_ai_prompt_editor_view'; jest.mock('../../ai_prompt_editor/ai_prompt_editor', (): any => { diff --git a/packages/devextreme/js/__internal/grids/grid_core/ai_column/views/m_ai_prompt_editor_view.ts b/packages/devextreme/js/__internal/grids/grid_core/ai_column/views/m_ai_prompt_editor_view.ts index 1ff42da2aac7..831cb6f106da 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/ai_column/views/m_ai_prompt_editor_view.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/ai_column/views/m_ai_prompt_editor_view.ts @@ -1,9 +1,10 @@ import $ from '@js/core/renderer'; import domAdapter from '@ts/core/m_dom_adapter'; +import type { Column } from '@ts/grids/grid_core/columns_controller/types'; import { AIPromptEditor } from '../../ai_prompt_editor/ai_prompt_editor'; import type { AIPromptEditorOptions } from '../../ai_prompt_editor/types'; -import type { Column, ColumnsController } from '../../columns_controller/m_columns_controller'; +import type { ColumnsController } from '../../columns_controller/m_columns_controller'; import { getColumnHeaderCellSelector } from '../../columns_controller/m_columns_controller_utils'; import { View } from '../../m_modules'; import { AI_COLUMN_NAME } from '../const'; diff --git a/packages/devextreme/js/__internal/grids/grid_core/column_headers/m_column_headers.ts b/packages/devextreme/js/__internal/grids/grid_core/column_headers/m_column_headers.ts index 71174c016b90..ac89dd174d34 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/column_headers/m_column_headers.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/column_headers/m_column_headers.ts @@ -7,11 +7,11 @@ import { Deferred } from '@js/core/utils/deferred'; import { extend } from '@js/core/utils/extend'; import { getHeight } from '@js/core/utils/size'; import { isDefined } from '@js/core/utils/type'; +import type { Column } from '@ts/grids/grid_core/columns_controller/types'; import { ColumnContextMenuMixin } from '@ts/grids/grid_core/context_menu/m_column_context_menu_mixin'; import type { HeaderFilterController } from '@ts/grids/grid_core/header_filter/m_header_filter'; import type { HeaderPanel } from '@ts/grids/grid_core/header_panel/m_header_panel'; -import type { Column } from '../columns_controller/m_columns_controller'; import { CLASSES as REORDERING_CLASSES } from '../columns_resizing_reordering/const'; import type { HeadersKeyboardNavigationController } from '../keyboard_navigation/m_headers_keyboard_navigation'; import { registerKeyboardAction } from '../m_accessibility'; diff --git a/packages/devextreme/js/__internal/grids/grid_core/columns_controller/m_columns_controller.test.ts b/packages/devextreme/js/__internal/grids/grid_core/columns_controller/m_columns_controller.test.ts index 234ef1a94e38..126b03a7fb28 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/columns_controller/m_columns_controller.test.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/columns_controller/m_columns_controller.test.ts @@ -62,4 +62,211 @@ describe('Column Controller', () => { }); }); }); + + describe('Expand columns in band columns layout', () => { + it('detail expand column header should have rowspan equal to header row count when band columns are used', async () => { + const { instance } = await createDataGrid({ + dataSource, + columns: [ + { caption: 'Band column 1', columns: ['id', 'name'] }, + { dataField: 'name', caption: 'Column 3', name: 'Column3' }, + ], + masterDetail: { + enabled: true, + }, + }); + + const columnsController = instance.getController('columns'); + const rowCount = columnsController.getRowCount(); + const firstRowColumns = columnsController.getVisibleColumns(0); + + expect(rowCount).toBe(2); + + const expandColumn = firstRowColumns.find((col) => col.command === 'expand'); + expect(expandColumn).toBeDefined(); + expect(expandColumn.rowspan).toBe(2); + }); + + it('should place expand columns only in the first header row with grouped columns', async () => { + const { instance } = await createDataGrid({ + dataSource: [ + { + TestField1: 'group1', TestField2: 'group2', TestField3: 'val3', TestField4: 'val4', + }, + ], + columns: [ + { dataField: 'TestField1', caption: 'Column 1', groupIndex: 0 }, + { + caption: 'Band Column 1', + columns: [ + { dataField: 'TestField2', caption: 'Column 2', groupIndex: 1 }, + { dataField: 'TestField3', caption: 'Column 3' }, + { caption: 'Band Column 2', columns: [{ dataField: 'TestField4', caption: 'Column 4' }] }, + ], + }, + ], + }); + + const columnsController = instance.getController('columns'); + const rowCount = columnsController.getRowCount(); + + // Row 0: expand columns + Band Column 1 + const firstRowColumns = columnsController.getVisibleColumns(0); + const expandColumnsInFirstRow = firstRowColumns.filter((col) => col.command === 'expand'); + + expect(rowCount).toBe(3); + expect(firstRowColumns.length).toBe(3); + expect(expandColumnsInFirstRow.length).toBe(2); + + expandColumnsInFirstRow.forEach((col) => { + expect(col.rowspan).toBe(3); + }); + + const bandColumn = firstRowColumns.find((col) => col.caption === 'Band Column 1'); + + expect(bandColumn).toBeDefined(); + expect(bandColumn.isBand).toBe(true); + expect(bandColumn.rowspan).toBeUndefined(); + + // Row 1: Column 3 + Band Column 2 + const secondRowColumns = columnsController.getVisibleColumns(1); + const expandColumnsInSecondRow = secondRowColumns.filter((col) => col.command === 'expand'); + + expect(secondRowColumns.length).toBe(2); + expect(expandColumnsInSecondRow.length).toBe(0); + + const column3 = secondRowColumns.find((col) => col.caption === 'Column 3'); + + expect(column3).toBeDefined(); + expect(column3.rowspan).toBe(2); + + const bandColumn2 = secondRowColumns.find((col) => col.caption === 'Band Column 2'); + + expect(bandColumn2).toBeDefined(); + expect(bandColumn2.isBand).toBe(true); + expect(bandColumn2.rowspan).toBeUndefined(); + + // Row 2: Column 4 + const thirdRowColumns = columnsController.getVisibleColumns(2); + const expandColumnsInThirdRow = thirdRowColumns.filter((col) => col.command === 'expand'); + const column4 = thirdRowColumns.find((col) => col.caption === 'Column 4'); + + expect(expandColumnsInThirdRow.length).toBe(0); + expect(column4).toBeDefined(); + expect(column4.rowspan).toBeUndefined(); + }); + + it('should place expand columns only in the first header row with showWhenGrouped', async () => { + const { instance } = await createDataGrid({ + dataSource: [ + { field1: 'g1', field2: 'g2', field3: 'g3' }, + ], + columns: [{ + dataField: 'field1', + showWhenGrouped: true, + groupIndex: 0, + }, { + caption: 'band2', + columns: [{ + dataField: 'field2', + showWhenGrouped: true, + groupIndex: 1, + }, { + caption: 'band3', + columns: [{ + dataField: 'field3', + showWhenGrouped: true, + groupIndex: 2, + }], + }], + }], + }); + + const columnsController = instance.getController('columns'); + const rowCount = columnsController.getRowCount(); + + expect(rowCount).toBe(3); + + // Row 0: expand columns with rowspan=3, data columns with rowspan=3, band column + const firstRowColumns = columnsController.getVisibleColumns(0); + const expandColumnsRow0 = firstRowColumns.filter((col) => col.command === 'expand'); + + expect(expandColumnsRow0.length).toBe(3); + expandColumnsRow0.forEach((col) => { + expect(col.rowspan).toBe(3); + }); + + // showWhenGrouped data columns should be in the first row with rowspan=3 + const field1Col = firstRowColumns.find((col) => col.caption === 'Field 1' && !col.command); + expect(field1Col).toBeDefined(); + expect(field1Col.rowspan).toBe(3); + + // band2 should be in the first row without rowspan (it has children) + const band2Col = firstRowColumns.find((col) => col.caption === 'band2'); + expect(band2Col).toBeDefined(); + expect(band2Col.rowspan).toBeUndefined(); + + // Row 1: no expand columns + const secondRowColumns = columnsController.getVisibleColumns(1); + const expandColumnsRow1 = secondRowColumns.filter((col) => col.command === 'expand'); + + expect(expandColumnsRow1.length).toBe(0); + + // band3 should be in the second row + const band3Col = secondRowColumns.find((col) => col.caption === 'band3'); + expect(band3Col).toBeDefined(); + + // Row 2: no expand columns + const thirdRowColumns = columnsController.getVisibleColumns(2); + const expandColumnsRow2 = thirdRowColumns.filter((col) => col.command === 'expand'); + + expect(expandColumnsRow2.length).toBe(0); + }); + + it('should not set rowspan on expand columns when there is only one header row with grouped showWhenGrouped columns', async () => { + const { instance } = await createDataGrid({ + dataSource: [ + { + field1: 'val1', field2: 'val2', field3: 'g1', field4: 'val4', + }, + ], + columns: [ + 'field1', + 'field2', + { dataField: 'field3', showWhenGrouped: true, groupIndex: 0 }, + ], + }); + + const columnsController = instance.getController('columns'); + const rowCount = columnsController.getRowCount(); + + expect(rowCount).toBe(1); + + const visibleColumns = columnsController.getVisibleColumns(0); + const expandColumn = visibleColumns.find((col) => col.command === 'expand' || col.type === 'groupExpand'); + + expect(expandColumn).toBeDefined(); + expect(expandColumn.rowspan).toBeUndefined(); + }); + + it('should not set rowspan on expand column when there is a single header row', async () => { + const { instance } = await createDataGrid({ + dataSource, + columns: ['id', 'name'], + masterDetail: { + enabled: true, + }, + }); + + const columnsController = instance.getController('columns'); + const rowCount = columnsController.getRowCount(); + const firstRowColumns = columnsController.getVisibleColumns(0); + + const expandColumn = firstRowColumns.find((col) => col.command === 'expand'); + + expect(rowCount).toBe(1); + expect(expandColumn).toBeDefined(); + expect(expandColumn.rowspan).toBeUndefined(); + }); + }); }); diff --git a/packages/devextreme/js/__internal/grids/grid_core/columns_controller/m_columns_controller.ts b/packages/devextreme/js/__internal/grids/grid_core/columns_controller/m_columns_controller.ts index e6811452bf48..e015309d51b1 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/columns_controller/m_columns_controller.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/columns_controller/m_columns_controller.ts @@ -3,7 +3,6 @@ import dateLocalization from '@js/common/core/localization/date'; import messageLocalization from '@js/common/core/localization/message'; import { DataSource } from '@js/common/data/data_source/data_source'; import { normalizeDataSourceOptions } from '@js/common/data/data_source/utils'; -import type { ColumnAIOptions, ColumnBase } from '@js/common/grids'; import config from '@js/core/config'; import $ from '@js/core/renderer'; import Callbacks from '@js/core/utils/callbacks'; @@ -13,14 +12,14 @@ import { extend } from '@js/core/utils/extend'; import { each, map } from '@js/core/utils/iterator'; import { orderEach } from '@js/core/utils/object'; import { - isDefined, isFunction, isNumeric, isObject, isPlainObject, - isString, + isDefined, isFunction, isNumeric, isObject, isPlainObject, isString, } from '@js/core/utils/type'; import variableWrapper from '@js/core/utils/variable_wrapper'; import Store from '@js/data/abstract_store'; import filterUtils from '@js/ui/shared/filtering'; import errors from '@js/ui/widget/ui.errors'; import inflector from '@ts/core/utils/m_inflector'; +import type { Column } from '@ts/grids/grid_core/columns_controller/types'; import type { DataController } from '@ts/grids/grid_core/data_controller/m_data_controller'; import type { FocusController } from '@ts/grids/grid_core/focus/m_focus'; import type { StateStoringController } from '@ts/grids/grid_core/state_storing/m_state_storing_core'; @@ -71,7 +70,8 @@ import { isSortOrderValid, mergeColumns, moveColumnToGroup, - numberToString, processBandColumns, + numberToString, + processBandColumns, processExpandColumns, resetBandColumnsCache, resetColumnsCache, @@ -84,15 +84,9 @@ import { updateSerializers, } from './m_columns_controller_utils'; -export interface Column extends ColumnBase { - parseValue: (text: string) => unknown; - index?: number; - groupIndex?: number; - type?: string; - visibleWidth?: string | number; - hidingPriority?: number; - ai?: ColumnAIOptions; - command?: string; +interface IndexedColumns { + positiveIndexedColumns: Record[][]; + negativeIndexedColumns: Record[]; } export class ColumnsController extends modules.Controller { @@ -739,9 +733,9 @@ export class ColumnsController extends modules.Controller { }); } - private _compileVisibleColumnsCore() { + private _compileVisibleColumnsCore(): Column[][] { const bandColumnsCache = this.getBandColumnsCache(); - const columns = mergeColumns(this, this._columns, this._commandColumns, true); + const columns: Column[] = mergeColumns(this, this._columns, this._commandColumns, true); processBandColumns(this, columns, bandColumnsCache); @@ -758,18 +752,18 @@ export class ColumnsController extends modules.Controller { return visibleColumns; } - private _getIndexedColumns(columns) { + private _getIndexedColumns(columns: Column[]): IndexedColumns { const rtlEnabled = this.option('rtlEnabled'); const rowCount = this.getRowCount(); const columnDigitsCount = digitsCount(columns.length); const bandColumnsCache = this.getBandColumnsCache(); - const positiveIndexedColumns: any = []; - const negativeIndexedColumns: any = []; + const positiveIndexedColumns: IndexedColumns['positiveIndexedColumns'] = []; + const negativeIndexedColumns: IndexedColumns['negativeIndexedColumns'] = []; for (let i = 0; i < rowCount; i += 1) { - negativeIndexedColumns[i] = [{}]; + negativeIndexedColumns[i] = {}; // 0 - fixed columns on the left side // 1 - not fixed columns @@ -778,26 +772,30 @@ export class ColumnsController extends modules.Controller { } columns.forEach((column) => { - let { visibleIndex } = column; - let indexedColumns; - - const parentBandColumns = getParentBandColumns(column.index, bandColumnsCache.columnParentByIndex); - + const { visibleIndex } = column; const isVisible = this._isColumnVisible(column); const isInGroupPanel = this._isColumnInGroupPanel(column); if (isVisible && !isInGroupPanel) { + const parentBandColumns = getParentBandColumns( + column.index, + bandColumnsCache.columnParentByIndex, + ); const rowIndex = parentBandColumns.length; + let targetIndex: string | number = visibleIndex ?? 'undefined'; + // eslint-disable-next-line @typescript-eslint/init-declarations + let indexedColumns: Record; - if (visibleIndex < 0) { - visibleIndex = -visibleIndex; + if (isDefined(visibleIndex) && visibleIndex < 0) { + targetIndex = -visibleIndex; indexedColumns = negativeIndexedColumns[rowIndex]; } else { column.fixed = parentBandColumns[0]?.fixed ?? column.fixed; column.fixedPosition = parentBandColumns[0]?.fixedPosition ?? column.fixedPosition; if (column.fixed && column.fixedPosition !== StickyPosition.Sticky) { - const isDefaultCommandColumn = !!column.command && !gridCoreUtils.isCustomCommandColumn(this._columns, column); + const isDefaultCommandColumn = !!column.command + && !gridCoreUtils.isCustomCommandColumn(this._columns, column); let isFixedToEnd = column.fixedPosition === 'right'; @@ -814,15 +812,16 @@ export class ColumnsController extends modules.Controller { } if (parentBandColumns.length) { - visibleIndex = numberToString(visibleIndex, columnDigitsCount); + targetIndex = numberToString(targetIndex, columnDigitsCount); for (let i = parentBandColumns.length - 1; i >= 0; i -= 1) { - visibleIndex = numberToString(parentBandColumns[i].visibleIndex, columnDigitsCount) + visibleIndex; + const { visibleIndex: parentVisibleIndex } = parentBandColumns[i]; + targetIndex = `${numberToString(parentVisibleIndex, columnDigitsCount)}${targetIndex}`; } } - indexedColumns[visibleIndex] = indexedColumns[visibleIndex] || []; - indexedColumns[visibleIndex].push(column); + indexedColumns[targetIndex] = indexedColumns[targetIndex] || []; + indexedColumns[targetIndex].push(column); } }); @@ -831,41 +830,50 @@ export class ColumnsController extends modules.Controller { }; } - private _getVisibleColumnsFromIndexed({ positiveIndexedColumns, negativeIndexedColumns }) { - const result: any = []; - - const rowCount = this.getRowCount(); - const expandColumns = mergeColumns(this, this.getExpandColumns(), this._columns); - - let rowspanGroupColumns = 0; - let rowspanExpandColumns = 0; + private _getVisibleColumnsFromIndexed({ + positiveIndexedColumns, + negativeIndexedColumns, + }: IndexedColumns): Column[][] { + const result: Column[][] = []; + const rowCount: number = this.getRowCount(); + const expandColumns: Column[] = mergeColumns(this, this.getExpandColumns(), this._columns); + // Process header rows columns for (let rowIndex = 0; rowIndex < rowCount; rowIndex += 1) { result.push([]); - orderEach(negativeIndexedColumns[rowIndex], (_, columns) => { - result[rowIndex].unshift.apply(result[rowIndex], columns); + orderEach(negativeIndexedColumns[rowIndex], (_: string, columns: Column[]) => { + result[rowIndex].unshift(...columns); }); + } - const firstPositiveIndexColumn = result[rowIndex].length; - const positiveIndexedRowColumns = positiveIndexedColumns[rowIndex]; + const firstExpandColumnIndex = result[0].length; - positiveIndexedRowColumns.forEach((columnsByFixing) => { - orderEach(columnsByFixing, (_, columnsByVisibleIndex) => { - result[rowIndex].push.apply(result[rowIndex], columnsByVisibleIndex); + for (let rowIndex = 0; rowIndex < rowCount; rowIndex += 1) { + positiveIndexedColumns[rowIndex].forEach((columnsByFixing) => { + orderEach(columnsByFixing, (_: string, columnsByVisibleIndex: Column[]) => { + result[rowIndex].push(...columnsByVisibleIndex); }); }); - - // The order of processing is important - if (rowspanExpandColumns <= rowIndex) { - rowspanExpandColumns += processExpandColumns.call(this, result[rowIndex], expandColumns, DETAIL_COMMAND_COLUMN_NAME, firstPositiveIndexColumn); - } - - if (rowspanGroupColumns <= rowIndex) { - rowspanGroupColumns += processExpandColumns.call(this, result[rowIndex], expandColumns, GROUP_COMMAND_COLUMN_NAME, firstPositiveIndexColumn); - } } + // The order of processing is important + processExpandColumns( + result[0], + expandColumns, + DETAIL_COMMAND_COLUMN_NAME, + firstExpandColumnIndex, + rowCount, + ); + processExpandColumns( + result[0], + expandColumns, + GROUP_COMMAND_COLUMN_NAME, + firstExpandColumnIndex, + rowCount, + ); + + // Process table body columns result.push(getDataColumns(result)); return result; diff --git a/packages/devextreme/js/__internal/grids/grid_core/columns_controller/m_columns_controller_utils.ts b/packages/devextreme/js/__internal/grids/grid_core/columns_controller/m_columns_controller_utils.ts index 1f0267472113..36fe855a3c56 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/columns_controller/m_columns_controller_utils.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/columns_controller/m_columns_controller_utils.ts @@ -5,13 +5,14 @@ import { equalByValue } from '@js/core/utils/common'; import { compileGetter, compileSetter } from '@js/core/utils/data'; import dateSerialization from '@js/core/utils/date_serialization'; import { extend } from '@js/core/utils/extend'; -import { each, map } from '@js/core/utils/iterator'; +import { each } from '@js/core/utils/iterator'; import { deepExtendArraySafe } from '@js/core/utils/object'; import { getDefaultAlignment } from '@js/core/utils/position'; import { isDefined, isFunction, isNumeric, isObject, isString, type, } from '@js/core/utils/type'; import variableWrapper from '@js/core/utils/variable_wrapper'; +import type { DataGridCommandColumnType } from '@js/ui/data_grid'; import errors from '@js/ui/widget/ui.errors'; import { HIDDEN_COLUMNS_WIDTH } from '../adaptivity/const'; @@ -32,8 +33,8 @@ import { USER_STATE_FIELD_NAMES_15_1, VIRTUAL_COMMAND_COLUMN_NAME, } from './const'; -import type { Column, ColumnsController } from './m_columns_controller'; -import type { ColumnIndex, DropLocationNames } from './types'; +import type { ColumnsController } from './m_columns_controller'; +import type { Column, ColumnIndex, DropLocationNames } from './types'; const warnFixedInChildColumnsOnce = (controller: ColumnsController, childColumns: any[]): void => { if (controller?._isWarnedAboutUnsupportedProperties) return; @@ -859,26 +860,22 @@ export const getFixedPosition = function (that: ColumnsController, column) { return column.fixedPosition; }; -export const processExpandColumns = function (columns, expandColumns, type, columnIndex) { - let customColumnIndex; - const rowCount = this.getRowCount(); - let rowspan = columns[columnIndex] && columns[columnIndex].rowspan; - let expandColumnsByType = expandColumns.filter((column) => column.type === type); - - columns.forEach((column, index) => { - if (column.type === type) { - customColumnIndex = index; - rowspan = columns[index + 1] ? columns[index + 1].rowspan : rowCount; - } - }); - - if (rowspan > 1) { - expandColumnsByType = map(expandColumnsByType, (expandColumn) => extend({}, expandColumn, { rowspan })); - } - expandColumnsByType.unshift.apply(expandColumnsByType, isDefined(customColumnIndex) ? [customColumnIndex, 1] : [columnIndex, 0]); - columns.splice.apply(columns, expandColumnsByType); - - return rowspan || 1; +export const processExpandColumns = ( + columns: Column[], + expandColumns: Column[], + commandType: DataGridCommandColumnType, + columnIndex: number, + rowspan: number, +): void => { + const expandColumnsByType = expandColumns + .filter((column) => column.type === commandType) + .map((column): Column => (rowspan > 1 ? { ...column, rowspan } : column)); + + const customExpandColumnIndex = columns.findIndex((column) => column.type === commandType); + const targetIndex = customExpandColumnIndex >= 0 ? customExpandColumnIndex : columnIndex; + const deleteCount = customExpandColumnIndex >= 0 ? 1 : 0; + + columns.splice(targetIndex, deleteCount, ...expandColumnsByType); }; export const digitsCount = function (number) { diff --git a/packages/devextreme/js/__internal/grids/grid_core/columns_controller/types.ts b/packages/devextreme/js/__internal/grids/grid_core/columns_controller/types.ts index 90dd5a1e741e..2b9864d5a68d 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/columns_controller/types.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/columns_controller/types.ts @@ -1,8 +1,6 @@ -import type { - COLUMN_CHOOSER_LOCATION, - GROUP_LOCATION, - HEADERS_LOCATION, -} from './const'; +import type { ColumnAIOptions, ColumnBase } from '@js/common/grids'; + +import type { COLUMN_CHOOSER_LOCATION, GROUP_LOCATION, HEADERS_LOCATION } from './const'; export type DropLocationNames = typeof GROUP_LOCATION | typeof COLUMN_CHOOSER_LOCATION @@ -12,3 +10,16 @@ export type ColumnIndex = number | { rowIndex: number; columnIndex: number; }; + +export interface Column extends ColumnBase { + parseValue?: (text: string) => unknown; + index?: number; + groupIndex?: number; + type?: string; + visibleWidth?: string | number; + hidingPriority?: number; + ai?: ColumnAIOptions; + command?: string; + rowspan?: number; + colspan?: number; +} diff --git a/packages/devextreme/js/__internal/grids/grid_core/editing/types.ts b/packages/devextreme/js/__internal/grids/grid_core/editing/types.ts index e3360e331618..7eb05c693db1 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/editing/types.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/editing/types.ts @@ -1,4 +1,5 @@ -import type { Column } from '../columns_controller/m_columns_controller'; +import type { Column } from '@ts/grids/grid_core/columns_controller/types'; + import type { Item, UserData } from '../data_controller/m_data_controller'; import type { RowKey } from '../m_types'; import type { INSERT_INDEX } from './const'; diff --git a/packages/devextreme/js/__internal/grids/grid_core/keyboard_navigation/m_column_keyboard_navigation_core.ts b/packages/devextreme/js/__internal/grids/grid_core/keyboard_navigation/m_column_keyboard_navigation_core.ts index 4de2724c236e..8d02ff6c4998 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/keyboard_navigation/m_column_keyboard_navigation_core.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/keyboard_navigation/m_column_keyboard_navigation_core.ts @@ -1,6 +1,6 @@ import { isDefined, isEmptyObject } from '@js/core/utils/type'; +import type { Column } from '@ts/grids/grid_core/columns_controller/types'; -import type { Column } from '../columns_controller/m_columns_controller'; import { Direction } from './const'; import type { ColumnFocusDispatcher } from './m_column_focus_dispatcher'; import { KeyboardNavigationController as KeyboardNavigationControllerCore } from './m_keyboard_navigation_core'; diff --git a/packages/devextreme/js/__internal/grids/grid_core/keyboard_navigation/m_headers_keyboard_navigation.ts b/packages/devextreme/js/__internal/grids/grid_core/keyboard_navigation/m_headers_keyboard_navigation.ts index 0ff0e50ef9c7..9ceff9618e61 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/keyboard_navigation/m_headers_keyboard_navigation.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/keyboard_navigation/m_headers_keyboard_navigation.ts @@ -5,8 +5,8 @@ import type { dxElementWrapper } from '@js/core/renderer'; import $ from '@js/core/renderer'; import { getBoundingRect } from '@js/core/utils/position'; import { isDefined } from '@js/core/utils/type'; +import type { Column } from '@ts/grids/grid_core/columns_controller/types'; -import type { Column } from '../columns_controller/m_columns_controller'; import type { Views } from '../m_types'; import { StickyPosition } from '../sticky_columns/const'; import { GridCoreStickyColumnsDom } from '../sticky_columns/dom'; diff --git a/packages/devextreme/js/__internal/grids/grid_core/keyboard_navigation/m_keyboard_navigation.ts b/packages/devextreme/js/__internal/grids/grid_core/keyboard_navigation/m_keyboard_navigation.ts index 52cec10e8e0e..fbb694f5e8bb 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/keyboard_navigation/m_keyboard_navigation.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/keyboard_navigation/m_keyboard_navigation.ts @@ -26,12 +26,12 @@ import * as accessibility from '@js/ui/shared/accessibility'; import { isElementInDom } from '@ts/core/utils/m_dom'; import { focused } from '@ts/core/utils/m_selectors'; import type { AdaptiveColumnsController } from '@ts/grids/grid_core/adaptivity/m_adaptivity'; +import type { Column } from '@ts/grids/grid_core/columns_controller/types'; import type { DataController } from '@ts/grids/grid_core/data_controller/m_data_controller'; import type { EditingController } from '@ts/grids/grid_core/editing/m_editing'; import type { RowsView } from '@ts/grids/grid_core/views/m_rows_view'; import { memoize } from '@ts/utils/memoize'; -import type { Column } from '../columns_controller/m_columns_controller'; import { EDIT_FORM_CLASS, EDIT_MODE_BATCH, diff --git a/packages/devextreme/js/__internal/grids/grid_core/m_utils.ts b/packages/devextreme/js/__internal/grids/grid_core/m_utils.ts index f48397602623..105dc0a84584 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/m_utils.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/m_utils.ts @@ -20,10 +20,10 @@ import formatHelper from '@js/format_helper'; import LoadPanel from '@js/ui/load_panel'; import sharedFiltering from '@js/ui/shared/filtering'; import { isNumeric } from '@ts/core/utils/m_type'; +import type { Column } from '@ts/grids/grid_core/columns_controller/types'; import type { ColumnPoint } from '@ts/grids/grid_core/m_types'; import { AI_COLUMN_NAME } from './ai_column/const'; -import type { Column } from './columns_controller/m_columns_controller'; import { isEqualSelectors, isSelectorEqualWithCallback } from './utils/index'; const BASE_LOAD_PANEL_Z_INDEX = 1000; diff --git a/packages/devextreme/js/__internal/grids/grid_core/search/m_search.ts b/packages/devextreme/js/__internal/grids/grid_core/search/m_search.ts index 30d0a86685fb..95af6a7fc774 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/search/m_search.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/search/m_search.ts @@ -1,13 +1,13 @@ /* eslint-disable max-classes-per-file */ -/* eslint-disable @typescript-eslint/method-signature-style */ + import messageLocalization from '@js/common/core/localization/message'; import type { LangParams } from '@js/common/data'; import dataQuery from '@js/common/data/query'; import domAdapter from '@js/core/dom_adapter'; import $ from '@js/core/renderer'; import { compileGetter, toComparable } from '@js/core/utils/data'; +import type { Column } from '@ts/grids/grid_core/columns_controller/types'; -import type { Column } from '../columns_controller/m_columns_controller'; import type { DataController, Filter } from '../data_controller/m_data_controller'; import type { HeaderPanel } from '../header_panel/m_header_panel'; import type { ModuleType } from '../m_types'; @@ -270,7 +270,6 @@ const rowsView = ( for (let i = 0; i < $contents.length; i++) { const node = $contents.get(i); if (node.nodeType === 3) { - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing const normalizedText = stringNormalizer(node.textContent ?? node.nodeValue ?? ''); if (normalizedText.includes(normalizedSearchText)) { resultTextNodes.push(node); diff --git a/packages/devextreme/js/__internal/grids/grid_core/selection/m_selection.ts b/packages/devextreme/js/__internal/grids/grid_core/selection/m_selection.ts index 503d34b73238..d8c8bdd6ae8e 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/selection/m_selection.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/selection/m_selection.ts @@ -16,7 +16,8 @@ import { isDefined, isObject } from '@js/core/utils/type'; import errors from '@js/ui/widget/ui.errors'; import supportUtils from '@ts/core/utils/m_support'; import type { ColumnHeadersView } from '@ts/grids/grid_core/column_headers/m_column_headers'; -import type { Column, ColumnsController } from '@ts/grids/grid_core/columns_controller/m_columns_controller'; +import type { ColumnsController } from '@ts/grids/grid_core/columns_controller/m_columns_controller'; +import type { Column } from '@ts/grids/grid_core/columns_controller/types'; import type { ContextMenuController } from '@ts/grids/grid_core/context_menu/m_context_menu'; import type { ModuleType } from '@ts/grids/grid_core/m_types'; import type { StateStoringController } from '@ts/grids/grid_core/state_storing/m_state_storing_core'; diff --git a/packages/devextreme/js/__internal/grids/grid_core/views/m_columns_view.ts b/packages/devextreme/js/__internal/grids/grid_core/views/m_columns_view.ts index de48318c3aea..1a03c37e6126 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/views/m_columns_view.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/views/m_columns_view.ts @@ -31,10 +31,11 @@ import supportUtils from '@ts/core/utils/m_support'; import type { AdaptiveColumnsController } from '@ts/grids/grid_core/adaptivity/m_adaptivity'; import type { ColumnChooserController, ColumnChooserView } from '@ts/grids/grid_core/column_chooser/m_column_chooser'; import { ColumnStateMixin } from '@ts/grids/grid_core/column_state_mixin/m_column_state_mixin'; +import type { Column } from '@ts/grids/grid_core/columns_controller/types'; import type { EditorFactory } from '@ts/grids/grid_core/editor_factory/m_editor_factory'; import type { SelectionController } from '@ts/grids/grid_core/selection/m_selection'; -import type { Column, ColumnsController } from '../columns_controller/m_columns_controller'; +import type { ColumnsController } from '../columns_controller/m_columns_controller'; import type { DataController } from '../data_controller/m_data_controller'; import modules from '../m_modules'; import gridCoreUtils from '../m_utils'; diff --git a/packages/devextreme/js/__internal/grids/grid_core/views/utils.ts b/packages/devextreme/js/__internal/grids/grid_core/views/utils.ts index 36311a9b1ec2..8044b9662d49 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/views/utils.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/views/utils.ts @@ -1,5 +1,6 @@ +import type { Column } from '@ts/grids/grid_core/columns_controller/types'; + import { AI_COLUMN_NAME } from '../ai_column/const'; -import type { Column } from '../columns_controller/m_columns_controller'; import gridCoreUtils from '../m_utils'; export const getCellText = ( diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/columnsController.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/columnsController.tests.js index 448e2d2ee2a8..622149cb8e07 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/columnsController.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/columnsController.tests.js @@ -7787,83 +7787,6 @@ QUnit.module('Band columns', { beforeEach: setupModule, afterEach: teardownModul assert.ok(!visibleColumns[1].isBand, 'data column'); }); - QUnit.test('getVisibleColumns with rowIndex and grouped columns', function(assert) { - // arrange - this.applyOptions({ - columns: [ - { dataField: 'TestField1', caption: 'Column 1', groupIndex: 0 }, - { - caption: 'Band Column 1', columns: [ - { dataField: 'TestField2', caption: 'Column 2', groupIndex: 1 }, - { dataField: 'TestField3', caption: 'Column 3' }, - { caption: 'Band Column 2', columns: [{ dataField: 'TestField4', caption: 'Column 4' }] } - ] - } - ] - }); - - // assert - assert.ok(this.columnsController.isInitialized()); - - // act - let visibleColumns = this.columnsController.getVisibleColumns(0); - - // assert - assert.equal(visibleColumns.length, 3, 'count column'); - - // first column - assert.strictEqual(visibleColumns[0].caption, 'Column 1', 'caption of the first column of the first row'); - assert.strictEqual(visibleColumns[0].command, 'expand', 'command column'); - assert.ok(!visibleColumns[0].rowspan, 'rowspan of the first column of the first row'); - - // second column - assert.strictEqual(visibleColumns[1].caption, 'Column 2', 'caption of the second column of the first row'); - assert.strictEqual(visibleColumns[1].command, 'expand', 'command column'); - assert.ok(!visibleColumns[1].rowspan, 'rowspan of the second column of the first row'); - - // third column - assert.strictEqual(visibleColumns[2].caption, 'Band Column 1', 'caption of the second column of the first row'); - assert.equal(visibleColumns[2].colspan, 2, 'colspan of the second column of the first row'); - assert.ok(visibleColumns[2].isBand, 'band column'); - - // act - visibleColumns = this.columnsController.getVisibleColumns(1); - - // assert - assert.equal(visibleColumns.length, 4, 'count column'); - - // first column - assert.strictEqual(visibleColumns[0].caption, 'Column 1', 'caption of the first column of the second row'); - assert.strictEqual(visibleColumns[0].command, 'expand', 'command column'); - assert.equal(visibleColumns[0].rowspan, 2, 'rowspan of the first column of the second row'); - - // second column - assert.strictEqual(visibleColumns[1].caption, 'Column 2', 'caption of the second column of the second row'); - assert.strictEqual(visibleColumns[1].command, 'expand', 'command column'); - assert.equal(visibleColumns[1].rowspan, 2, 'rowspan of the second column of the second row'); - - // third column - assert.strictEqual(visibleColumns[2].caption, 'Column 3', 'caption of the third column of the second row'); - assert.equal(visibleColumns[2].rowspan, 2, 'rowspan of the third column of the second row'); - assert.ok(!visibleColumns[2].isBand, 'data column'); - - // fourth column - assert.strictEqual(visibleColumns[3].caption, 'Band Column 2', 'caption of the fourth column of the second row'); - assert.equal(visibleColumns[3].colspan, 1, 'colspan of the fourth column of the second row'); - assert.ok(visibleColumns[3].isBand, 'band column'); - - // act - visibleColumns = this.columnsController.getVisibleColumns(2); - - // assert - assert.equal(visibleColumns.length, 1, 'count column'); - - // first column - assert.strictEqual(visibleColumns[0].caption, 'Column 4', 'caption of the first column of the third row'); - assert.ok(!visibleColumns[0].rowspan, 'rowspan of the first column of the third row'); - assert.ok(!visibleColumns[0].isBand, 'data column'); - }); - QUnit.test('getVisibleColumnIndex with rowIndex', function(assert) { // arrange this.applyOptions({ @@ -7991,46 +7914,6 @@ QUnit.module('Band columns', { beforeEach: setupModule, afterEach: teardownModul assert.notOk(thirdRowColumns[0].rowspan, 'rowspan of the first column of the third row'); }); - // T895529 - QUnit.test('getVisibleColumns when there are grouped columns with showWhenGrouped', function(assert) { - // arrange - this.applyOptions({ - columns: [ - { - caption: 'Band 1', - columns: ['field1', 'field2'] - }, - { - caption: 'Band 2', - columns: [{ dataField: 'field3', showWhenGrouped: true, groupIndex: 0 }, 'field4'] - } - ] - }); - - // assert - assert.ok(this.columnsController.isInitialized()); - - // act - const visibleColumns = this.columnsController.getVisibleColumns(); - - assert.equal(visibleColumns.length, 5, 'column count in second row'); - assert.equal(visibleColumns[0].type, 'groupExpand', 'type of the first column'); - assert.equal(visibleColumns[0].colspan, undefined, 'colspan of the first column'); - assert.equal(visibleColumns[0].rowspan, undefined, 'rowspan of the first column'); - assert.equal(visibleColumns[1].caption, 'Field 1', 'caption of the second column'); - assert.equal(visibleColumns[1].colspan, undefined, 'colspan of the second column'); - assert.equal(visibleColumns[1].rowspan, undefined, 'rowspan of the second column'); - assert.equal(visibleColumns[2].caption, 'Field 2', 'caption of the third column'); - assert.equal(visibleColumns[2].colspan, undefined, 'colspan of the third column'); - assert.equal(visibleColumns[2].rowspan, undefined, 'rowspan of the third column'); - assert.equal(visibleColumns[3].caption, 'Field 3', 'caption of the fourth column'); - assert.equal(visibleColumns[3].colspan, undefined, 'colspan of the fourth column'); - assert.equal(visibleColumns[3].rowspan, undefined, 'rowspan of the fourth column'); - assert.equal(visibleColumns[4].caption, 'Field 4', 'caption of the fifth column'); - assert.equal(visibleColumns[4].colspan, undefined, 'colspan of the fifth column'); - assert.equal(visibleColumns[4].rowspan, undefined, 'rowspan of the fifth column'); - }); - QUnit.test('getFixedColumns for data columns', function(assert) { // arrange this.applyOptions({ @@ -8950,31 +8833,6 @@ QUnit.module('Band columns', { beforeEach: setupModule, afterEach: teardownModul assert.notOk(this.columnsController.isBandColumnsUsed(), 'band column is not used'); }); - // T647024 - QUnit.test('Expand column must have the right rowspan', function(assert) { - // arrange - - this.applyOptions({ - columns: [ - { caption: 'Band column 1', columns: ['Column1', 'Column2'] }, - { dataField: 'Column3', caption: 'Column 3' } - ], - masterDetail: { - enabled: true - } - }); - - // act - this.columnsController.getVisibleColumns(); - this.columnsController.resetColumnsCache(); - const visibleColumns = this.columnsController.getVisibleColumns(); - - // assert - assert.strictEqual(visibleColumns.length, 4, 'column count'); - assert.strictEqual(visibleColumns[0].command, 'expand', 'expand column'); - assert.ok(!visibleColumns[0].rowspan, 'rowspan of the expand column'); - }); - // T670211 QUnit.test('Delete band column via API', function(assert) { // arrange diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/columnsHeadersView.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/columnsHeadersView.tests.js index 1ca0fb9cd380..fa396d44cd8f 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/columnsHeadersView.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/columnsHeadersView.tests.js @@ -2795,11 +2795,15 @@ QUnit.module('Headers with band columns', { // act const $headerCells = $testElement.find('.dx-row.dx-column-lines.dx-header-row').children(); + const $expandHeaders = $headerCells.filter('.dx-command-expand'); + const $dataColumnHeaders = $headerCells.filter(':not(.dx-command-expand)'); // assert - assert.equal($headerCells.length, 4, 'header cell count'); + assert.equal($headerCells.length, 3, 'header cell count'); + assert.equal($expandHeaders.length, 1, 'single expand header cell for 2 rows'); - $headerCells.each((_, headerCellElement) => { + assert.strictEqual($expandHeaders.eq(0).attr('rowspan'), '2', 'expand header cell has correct rowspan'); + $dataColumnHeaders.each((_, headerCellElement) => { assert.strictEqual($(headerCellElement).attr('rowspan'), undefined); }); });