diff --git a/e2e/testcafe-devextreme/tests/dataGrid/common/bandColumns/functional.ts b/e2e/testcafe-devextreme/tests/dataGrid/common/bandColumns/functional.ts new file mode 100644 index 000000000000..2246ffd71108 --- /dev/null +++ b/e2e/testcafe-devextreme/tests/dataGrid/common/bandColumns/functional.ts @@ -0,0 +1,123 @@ +import Button from 'devextreme-testcafe-models/button'; +import DataGrid from 'devextreme-testcafe-models/dataGrid'; +import url from '../../../../helpers/getPageUrl'; +import { createWidget } from '../../../../helpers/createWidget'; + +fixture.disablePageReloads`Band columns.Functional` + .page(url(__dirname, '../../../container.html')); + +const GRID_CONTAINER = '#container'; + +test('Changing dataField for a banded column with the columnOption method does not work as expected (T1210340)', async (t) => { + const dataGrid = new DataGrid(GRID_CONTAINER); + const changeFieldButton = new Button('#otherContainer'); + + await t + .expect(dataGrid.getDataCell(0, 4).element.innerText) + .eql('2353025') + .click(changeFieldButton.element) + .expect(dataGrid.getDataCell(0, 4).element.innerText) + .eql('0.672'); +}).before(async () => { + await createWidget('dxDataGrid', { + dataSource: [{ + id: 1, + Country: 'Brazil', + Area: 8515767, + Population_Urban: 0.85, + Population_Rural: 0.15, + Population_Total: 205809000, + GDP_Agriculture: 0.054, + GDP_Industry: 0.274, + GDP_Services: 0.672, + GDP_Total: 2353025, + }], + columns: [ + 'Country', + 'Area', { + caption: 'Population', + columns: [ + 'Population_Total', + 'Population_Urban', + ], + }, { + caption: 'Nominal GDP', + columns: [{ + caption: 'Total, mln $', + dataField: 'GDP_Total', + name: 'GDP_Total', + }, { + caption: 'By Sector', + columns: [{ + caption: 'Agriculture', + dataField: 'GDP_Agriculture', + }, { + caption: 'Industry', + dataField: 'GDP_Industry', + format: { + type: 'percent', + }, + }, { + caption: 'Services', + dataField: 'GDP_Services', + }], + }], + }], + keyExpr: 'id', + showBorders: true, + }); + + await createWidget('dxButton', { + text: 'Change fields', + onClick() { + const grid = ($('#container') as any).dxDataGrid('instance'); + grid.columnOption('GDP_Total', 'dataField', 'GDP_Services'); + }, + }, '#otherContainer'); +}); + +test('The first header class should update correctly when the first data column is hidden in responsive mode', async (t) => { + const dataGrid = new DataGrid(GRID_CONTAINER); + const firstHeaderRow = dataGrid.getHeaders().getHeaderRow(0); + const secondHeaderRow = dataGrid.getHeaders().getHeaderRow(1); + + await t + .expect(dataGrid.isReady()).ok() + .expect(firstHeaderRow.getHeaderCell(0).isFirstHeader) + .ok() + .expect(firstHeaderRow.getHeaderCell(2).isFirstHeader) + .notOk() + .expect(secondHeaderRow.getHeaderCell(0).isFirstHeader) + .ok() + .expect(secondHeaderRow.getHeaderCell(1).isFirstHeader) + .notOk(); + + await dataGrid.apiOption('width', 275); + + await t + .expect(firstHeaderRow.getHeaderCell(0).isFirstHeader) + .ok() + .expect(firstHeaderRow.getHeaderCell(2).isFirstHeader) + .notOk() + .expect(secondHeaderRow.getHeaderCell(0).isFirstHeader) + .notOk() + .expect(secondHeaderRow.getHeaderCell(1).isFirstHeader) + .ok(); +}).before(async () => { + await createWidget('dxDataGrid', { + width: 350, + columnWidth: 100, + columnHidingEnabled: true, + dataSource: [{ field1: 1, field2: 2, field3: 3 }], + columns: [ + { + caption: 'Band 1', + columns: [ + { dataField: 'field1', hidingPriority: 0 }, + { dataField: 'field2', hidingPriority: 1 }, + ], + }, + { dataField: 'field3', hidingPriority: 2 }, + ], + }); +}); diff --git a/e2e/testcafe-devextreme/tests/dataGrid/common/bandColumns/matrix.ts b/e2e/testcafe-devextreme/tests/dataGrid/common/bandColumns/matrix.ts new file mode 100644 index 000000000000..0b758f4a5131 --- /dev/null +++ b/e2e/testcafe-devextreme/tests/dataGrid/common/bandColumns/matrix.ts @@ -0,0 +1,174 @@ +import { createScreenshotsComparer } from 'devextreme-screenshot-comparer'; +import DataGrid from 'devextreme-testcafe-models/dataGrid'; +import url from '../../../../helpers/getPageUrl'; +import { createWidget } from '../../../../helpers/createWidget'; +import { testScreenshot } from '../../../../helpers/themeUtils'; +import { Themes } from '../../../../helpers/themes'; + +fixture.disablePageReloads`Band columns.Matrix` + .page(url(__dirname, '../../../container.html')); + +const GRID_CONTAINER = '#container'; + +const configs = [{ + showColumnLines: true, + rtlEnabled: false, +}, { + showColumnLines: true, + rtlEnabled: true, +}, { + showColumnLines: false, + rtlEnabled: false, +}, { + showColumnLines: false, + rtlEnabled: true, +}]; + +configs.forEach(( + { showColumnLines, rtlEnabled }: { showColumnLines: boolean; rtlEnabled: boolean; }, +): void => { + test.meta({ themes: [Themes.materialBlue, Themes.genericLight] })(`The grid with grouped and fixed columns should display correct vertical borders when showColumnLines=${showColumnLines} and rtl=${rtlEnabled}(T1318812)`, async (t) => { + const { takeScreenshot, compareResults } = createScreenshotsComparer(t); + const dataGrid = new DataGrid(GRID_CONTAINER); + + await t.expect(dataGrid.isReady()).ok(); + + await testScreenshot( + t, + takeScreenshot, + `T1318812__datagrid__band-and-grouped-and-fixed-columns__vertical-borders(showColumnLines=${showColumnLines},rtl=${rtlEnabled}).png`, + { element: dataGrid.element }, + ); + + await t.expect(compareResults.isValid()) + .ok(compareResults.errorMessages()); + }).before(async () => { + await createWidget('dxDataGrid', { + dataSource: [ + { + Col1: 'Data A', Col2: 'Desc A', Col3: 'Group 1', Col4: 'X', Col5: 100, Col6: 50, + }, + { + Col1: 'Data B', Col2: 'Desc B', Col3: 'Group 1', Col4: 'Y', Col5: 200, Col6: 20, + }, + { + Col1: 'Data C', Col2: 'Desc C', Col3: 'Group 2', Col4: 'Z', Col5: 300, Col6: 10, + }, + ], + columns: [ + { + caption: 'Band Column 1', + columns: [ + { + caption: 'Nested BandColumn 1', + columns: [ + { dataField: 'Col1', width: 150 }, + { dataField: 'Col2', width: 300 }, + { dataField: 'Col3', width: 300, groupIndex: 0 }, + ], + }, + ], + }, + { + caption: 'Band Column 2', + fixed: true, + columns: [ + { + caption: 'Nested Band Column 2', + columns: [ + { dataField: 'Col4', width: 120 }, + ], + }, + ], + }, + { + caption: 'Band Column 3', + columns: [ + { + caption: 'Nested Band Column 3', + columns: [ + { dataField: 'Col5', width: 150 }, + { dataField: 'Col6', width: 150 }, + ], + }, + ], + }, + ], + showColumnLines, + rtlEnabled, + columnWidth: 100, + }); + }); + + test.meta({ themes: [Themes.materialBlue, Themes.genericLight] })(`The grid should display correct vertical borders when showColumnLines=${showColumnLines} and rtl=${rtlEnabled}(T1318812)`, async (t) => { + const { takeScreenshot, compareResults } = createScreenshotsComparer(t); + const dataGrid = new DataGrid(GRID_CONTAINER); + + await t.expect(dataGrid.isReady()).ok(); + + await testScreenshot( + t, + takeScreenshot, + `T1318812__datagrid__band-columns__vertical-borders(showColumnLines=${showColumnLines},rtl=${rtlEnabled}).png`, + { element: dataGrid.element }, + ); + + await t.expect(compareResults.isValid()) + .ok(compareResults.errorMessages()); + }).before(async () => { + await createWidget('dxDataGrid', { + dataSource: [ + { + Col1: 'Data A', Col2: 'Desc A', Col3: 'Group 1', Col4: 'X', Col5: 100, Col6: 50, + }, + { + Col1: 'Data B', Col2: 'Desc B', Col3: 'Group 1', Col4: 'Y', Col5: 200, Col6: 20, + }, + { + Col1: 'Data C', Col2: 'Desc C', Col3: 'Group 2', Col4: 'Z', Col5: 300, Col6: 10, + }, + ], + columns: [ + 'Col1', + { + caption: 'Band Column 1', + columns: [ + { + caption: 'Nested BandColumn 1', + columns: [ + { dataField: 'Col2', width: 300 }, + { dataField: 'Col3', width: 300 }, + ], + }, + ], + }, + { + caption: 'Band Column 2', + columns: [ + { + caption: 'Nested Band Column 2', + columns: [ + { dataField: 'Col4', width: 120 }, + ], + }, + ], + }, + { + caption: 'Band Column 3', + columns: [ + { + caption: 'Nested Band Column 3', + columns: [ + { dataField: 'Col5', width: 150 }, + { dataField: 'Col6', width: 150 }, + ], + }, + ], + }, + ], + showColumnLines, + rtlEnabled, + columnWidth: 100, + }); + }); +}); diff --git a/e2e/testcafe-devextreme/tests/dataGrid/common/bandColumns/runtimeChange.ts b/e2e/testcafe-devextreme/tests/dataGrid/common/bandColumns/visual.ts similarity index 57% rename from e2e/testcafe-devextreme/tests/dataGrid/common/bandColumns/runtimeChange.ts rename to e2e/testcafe-devextreme/tests/dataGrid/common/bandColumns/visual.ts index a3c1a686041c..bcdbac329620 100644 --- a/e2e/testcafe-devextreme/tests/dataGrid/common/bandColumns/runtimeChange.ts +++ b/e2e/testcafe-devextreme/tests/dataGrid/common/bandColumns/visual.ts @@ -1,12 +1,11 @@ import { ClientFunction } from 'testcafe'; import { createScreenshotsComparer } from 'devextreme-screenshot-comparer'; -import Button from 'devextreme-testcafe-models/button'; import DataGrid from 'devextreme-testcafe-models/dataGrid'; import url from '../../../../helpers/getPageUrl'; import { createWidget } from '../../../../helpers/createWidget'; import { testScreenshot } from '../../../../helpers/themeUtils'; -fixture.disablePageReloads`Band columns: runtime change` +fixture.disablePageReloads`Band columns.Visual` .page(url(__dirname, '../../../container.html')); const GRID_CONTAINER = '#container'; @@ -114,71 +113,3 @@ test('Should change usual columns to band columns without error in React (T12136 showBorders: true, }); }); - -test('Changing dataField for a banded column with the columnOption method does not work as expected (T1210340)', async (t) => { - const dataGrid = new DataGrid(GRID_CONTAINER); - const changeFieldButton = new Button('#otherContainer'); - - await t - .expect(dataGrid.getDataCell(0, 4).element.innerText) - .eql('2353025') - .click(changeFieldButton.element) - .expect(dataGrid.getDataCell(0, 4).element.innerText) - .eql('0.672'); -}).before(async () => { - await createWidget('dxDataGrid', { - dataSource: [{ - id: 1, - Country: 'Brazil', - Area: 8515767, - Population_Urban: 0.85, - Population_Rural: 0.15, - Population_Total: 205809000, - GDP_Agriculture: 0.054, - GDP_Industry: 0.274, - GDP_Services: 0.672, - GDP_Total: 2353025, - }], - columns: [ - 'Country', - 'Area', { - caption: 'Population', - columns: [ - 'Population_Total', - 'Population_Urban', - ], - }, { - caption: 'Nominal GDP', - columns: [{ - caption: 'Total, mln $', - dataField: 'GDP_Total', - name: 'GDP_Total', - }, { - caption: 'By Sector', - columns: [{ - caption: 'Agriculture', - dataField: 'GDP_Agriculture', - }, { - caption: 'Industry', - dataField: 'GDP_Industry', - format: { - type: 'percent', - }, - }, { - caption: 'Services', - dataField: 'GDP_Services', - }], - }], - }], - keyExpr: 'id', - showBorders: true, - }); - - await createWidget('dxButton', { - text: 'Change fields', - onClick() { - const grid = ($('#container') as any).dxDataGrid('instance'); - grid.columnOption('GDP_Total', 'dataField', 'GDP_Services'); - }, - }, '#otherContainer'); -}); diff --git a/packages/devextreme-scss/scss/widgets/base/dataGrid/_index.scss b/packages/devextreme-scss/scss/widgets/base/dataGrid/_index.scss index b552e4c93f9a..de3667de6827 100644 --- a/packages/devextreme-scss/scss/widgets/base/dataGrid/_index.scss +++ b/packages/devextreme-scss/scss/widgets/base/dataGrid/_index.scss @@ -278,11 +278,8 @@ $datagrid-text-stub-background-image-path: null !default; border-right: none; } - .dx-datagrid-content .dx-datagrid-table .dx-row { - &.dx-column-lines > td.dx-datagrid-first-header, - .dx-datagrid-column-no-border { - border-left: none; - } + .dx-datagrid-content .dx-datagrid-table .dx-row .dx-datagrid-column-no-border { + border-left: none; } .dx-datagrid-content.dx-sortable { @@ -290,6 +287,10 @@ $datagrid-text-stub-background-image-path: null !default; } } + .dx-datagrid-content .dx-datagrid-table .dx-row > td.dx-datagrid-first-header { + border-left: none; + } + .dx-datagrid-sticky-column, .dx-datagrid-sticky-column-left, .dx-datagrid-sticky-column-right { background-color: $datagrid-base-background-color; } @@ -408,28 +409,27 @@ $datagrid-text-stub-background-image-path: null !default; } } - .dx-datagrid-content .dx-datagrid-table .dx-row { - &.dx-column-lines > td.dx-datagrid-first-header, - .dx-datagrid-column-no-border { - border-right: none; + .dx-datagrid-content .dx-datagrid-table .dx-row .dx-datagrid-column-no-border { + border-right: none; - &.dx-datagrid-sticky-column-border-left { - border-left: 2px solid; - border-left-color: $datagrid-border-color; - } + &.dx-datagrid-sticky-column-border-left { + border-left: 2px solid; + border-left-color: $datagrid-border-color; } } } - } - .dx-header-multi-row.dx-datagrid-sticky-columns .dx-datagrid-table { - .dx-column-lines > td:first-child { - border-left: none; - border-right: $datagrid-border; - border-right-color: $datagrid-border-color; + .dx-datagrid-content .dx-datagrid-table .dx-row > td.dx-datagrid-first-header { + border-right: none; } } + .dx-header-multi-row.dx-datagrid-sticky-columns .dx-datagrid-table .dx-column-lines > td:first-child { + border-left: none; + border-right: $datagrid-border; + border-right-color: $datagrid-border-color; + } + .dx-datagrid-form-buttons-container { float: left; diff --git a/packages/devextreme-scss/scss/widgets/base/treeList/_index.scss b/packages/devextreme-scss/scss/widgets/base/treeList/_index.scss index b78cb262e897..23a52ec51c57 100644 --- a/packages/devextreme-scss/scss/widgets/base/treeList/_index.scss +++ b/packages/devextreme-scss/scss/widgets/base/treeList/_index.scss @@ -90,11 +90,8 @@ $treelist-row-error-color: $datagrid-row-error-color; border-right: none; } - .dx-treelist-content .dx-treelist-table .dx-row { - &.dx-column-lines > td.dx-treelist-first-header, - .dx-treelist-column-no-border { - border-left: none; - } + .dx-treelist-content .dx-treelist-table .dx-row .dx-treelist-column-no-border { + border-left: none; } .dx-treelist-content.dx-sortable { @@ -102,6 +99,10 @@ $treelist-row-error-color: $datagrid-row-error-color; } } + .dx-treelist-content .dx-treelist-table .dx-row > td.dx-treelist-first-header { + border-left: none; + } + .dx-treelist-sticky-column, .dx-treelist-sticky-column-left, .dx-treelist-sticky-column-right { background-color: $treelist-base-background-color; } @@ -340,18 +341,19 @@ $treelist-row-error-color: $datagrid-row-error-color; } } - .dx-treelist-content .dx-treelist-table .dx-row { - &.dx-column-lines > td.dx-treelist-first-header, - .dx-treelist-column-no-border { - border-right: none; + .dx-treelist-content .dx-treelist-table .dx-row .dx-treelist-column-no-border { + border-right: none; - &.dx-treelist-sticky-column-border-left { - border-left: 2px solid; - border-left-color: $treelist-border-color; - } + &.dx-treelist-sticky-column-border-left { + border-left: 2px solid; + border-left-color: $treelist-border-color; } } } + + .dx-treelist-content .dx-treelist-table .dx-row > td.dx-treelist-first-header { + border-right: none; + } } .dx-header-multi-row.dx-treelist-sticky-columns { diff --git a/packages/devextreme-scss/scss/widgets/fluent/gridBase/_index.scss b/packages/devextreme-scss/scss/widgets/fluent/gridBase/_index.scss index 12bf0c59f4ab..93bb7c3785ec 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/gridBase/_index.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/gridBase/_index.scss @@ -742,38 +742,6 @@ $fluent-grid-base-group-panel-message-line-height: $fluent-button-text-line-heig } } } - - .dx-row { - &.dx-header-row { - > td { - border-right: 1px solid; - border-right-color: $fluent-grid-base-border-color; - } - } - } - } - - &.dx-header-multi-row:not(.dx-#{$widget-name}-sticky-columns) { - .dx-#{$widget-name}-content { - .dx-#{$widget-name}-table { - .dx-row { - &.dx-header-row { - > td { - border-left: 1px solid; - border-left-color: $fluent-grid-base-border-color; - - &:first-child { - border-left: none; - } - - &:last-child { - border-right: none; - } - } - } - } - } - } } &.dx-#{$widget-name}-sticky-columns { @@ -787,6 +755,11 @@ $fluent-grid-base-group-panel-message-line-height: $fluent-button-text-line-heig } } + .dx-header-multi-row .dx-header-row:not(.dx-column-lines) > td:not(.dx-#{$widget-name}-column-no-border) { + border-left: 1px solid; + border-left-color: $fluent-grid-base-border-color; + } + .dx-#{$widget-name}-filter-row { background-color: $datagrid-filter-row-background-color; @@ -1326,6 +1299,12 @@ $fluent-grid-base-group-panel-message-line-height: $fluent-button-text-line-heig padding-left: 0; } } + + .dx-header-multi-row .dx-header-row:not(.dx-column-lines) > td:not(.dx-#{$widget-name}-column-no-border) { + border-left: none; + border-right: 1px solid; + border-right-color: $fluent-grid-base-border-color; + } } .dx-header-filter-menu { diff --git a/packages/devextreme-scss/scss/widgets/generic/gridBase/_index.scss b/packages/devextreme-scss/scss/widgets/generic/gridBase/_index.scss index c0e690f753b2..14407d55f3f0 100644 --- a/packages/devextreme-scss/scss/widgets/generic/gridBase/_index.scss +++ b/packages/devextreme-scss/scss/widgets/generic/gridBase/_index.scss @@ -522,6 +522,11 @@ $generic-grid-base-cell-input-height: math.round($generic-base-line-height * $ge } } + .dx-header-multi-row .dx-header-row:not(.dx-column-lines) > td:not(.dx-#{$widget-name}-column-no-border) { + border-left: 1px solid; + border-left-color: $generic-grid-base-border-color; + } + .dx-#{$widget-name}-filter-row { .dx-menu { .dx-overlay-content { @@ -1172,6 +1177,12 @@ $generic-grid-base-cell-input-height: math.round($generic-base-line-height * $ge padding-left: 0; } } + + .dx-header-multi-row .dx-header-row:not(.dx-column-lines) > td:not(.dx-#{$widget-name}-column-no-border) { + border-left: none; + border-right: 1px solid; + border-right-color: $generic-grid-base-border-color; + } } .dx-command-ai-header-content { diff --git a/packages/devextreme-scss/scss/widgets/material/gridBase/_index.scss b/packages/devextreme-scss/scss/widgets/material/gridBase/_index.scss index 69495d7b86e1..cbd1bd41d8ec 100644 --- a/packages/devextreme-scss/scss/widgets/material/gridBase/_index.scss +++ b/packages/devextreme-scss/scss/widgets/material/gridBase/_index.scss @@ -717,38 +717,6 @@ $material-grid-base-group-panel-message-line-height: $material-button-text-line- } } } - - .dx-row { - &.dx-header-row { - > td { - border-right: 1px solid; - border-right-color: $material-grid-base-border-color; - } - } - } - } - - &.dx-header-multi-row:not(.dx-#{$widget-name}-sticky-columns) { - .dx-#{$widget-name}-content { - .dx-#{$widget-name}-table { - .dx-row { - &.dx-header-row { - > td { - border-left: 1px solid; - border-left-color: $material-grid-base-border-color; - - &:first-child { - border-left: none; - } - - &:last-child { - border-right: none; - } - } - } - } - } - } } &.dx-#{$widget-name}-sticky-columns { @@ -762,6 +730,11 @@ $material-grid-base-group-panel-message-line-height: $material-button-text-line- } } + .dx-header-multi-row .dx-header-row:not(.dx-column-lines) > td:not(.dx-#{$widget-name}-column-no-border) { + border-left: 1px solid; + border-left-color: $material-grid-base-border-color; + } + .dx-#{$widget-name}-filter-row { background-color: $datagrid-filter-row-background-color; @@ -1306,6 +1279,12 @@ $material-grid-base-group-panel-message-line-height: $material-button-text-line- padding-left: 0; } } + + .dx-header-multi-row .dx-header-row:not(.dx-column-lines) > td:not(.dx-#{$widget-name}-column-no-border) { + border-left: none; + border-right: 1px solid; + border-right-color: $material-grid-base-border-color; + } } .dx-header-filter-menu { 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 cccec361732f..57881da2b2ac 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 @@ -37,12 +37,19 @@ export abstract class GridCoreModel { return this.root.querySelector(`.${SELECTORS.aiPromptEditor}`) as HTMLElement; } - public getHeaderCells(): NodeListOf { - return this.root.querySelectorAll(`.${SELECTORS.headerRowClass} > td`); + public getHeaderRows(): NodeListOf { + return this.root.querySelectorAll(`.${SELECTORS.headerRowClass}`); } - public getHeaderCell(columnIndex: number): HeaderCellModel { - return new HeaderCellModel(this.getHeaderCells()[columnIndex], this.addWidgetPrefix.bind(this)); + public getHeaderCells(rowIndex = 0): NodeListOf { + return this.getHeaderRows()[rowIndex].querySelectorAll('td'); + } + + public getHeaderCell(rowIndex = 0, columnIndex = 0): HeaderCellModel { + return new HeaderCellModel( + this.getHeaderCells(rowIndex)[columnIndex], + this.addWidgetPrefix.bind(this), + ); } public getAIHeaderCell(columnIndex: number): AIHeaderCellModel { diff --git a/packages/devextreme/js/__internal/grids/grid_core/column_headers/__tests__/m_column_headers.test.ts b/packages/devextreme/js/__internal/grids/grid_core/column_headers/__tests__/m_column_headers.test.ts deleted file mode 100644 index 808eb1078b64..000000000000 --- a/packages/devextreme/js/__internal/grids/grid_core/column_headers/__tests__/m_column_headers.test.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { - afterEach, beforeEach, describe, expect, it, -} from '@jest/globals'; -import $ from '@js/core/renderer'; - -import { - afterTest, - beforeTest, - createDataGrid, -} from '../../__tests__/__mock__/helpers/utils'; - -describe('Column Headers', () => { - beforeEach(beforeTest); - afterEach(afterTest); - - describe('headerCellTemplate', () => { - it('should apply right alignment to number column when headerCellTemplate is used', async () => { - const { component } = await createDataGrid({ - dataSource: [], - showBorders: true, - headerFilter: { - visible: true, - }, - columns: [ - { - dataField: 'test', - dataType: 'number', - headerCellTemplate(headerElement) { - $('') - .text('Test') - .appendTo(headerElement); - }, - }, - ], - }); - expect(component.getHeaderCell(0).getAlignment()).toBe('right'); - }); - }); -}); diff --git a/packages/devextreme/js/__internal/grids/grid_core/column_headers/const.ts b/packages/devextreme/js/__internal/grids/grid_core/column_headers/const.ts index 5bc102d854f5..7b21f94c7b85 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/column_headers/const.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/column_headers/const.ts @@ -1,3 +1,4 @@ export const CLASSES = { cellContent: 'text-content', + firstHeader: 'first-header', }; diff --git a/packages/devextreme/js/__internal/grids/grid_core/column_headers/m_column_headers.integration.test.ts b/packages/devextreme/js/__internal/grids/grid_core/column_headers/m_column_headers.integration.test.ts new file mode 100644 index 000000000000..fe8371262c29 --- /dev/null +++ b/packages/devextreme/js/__internal/grids/grid_core/column_headers/m_column_headers.integration.test.ts @@ -0,0 +1,135 @@ +import { + afterEach, beforeEach, describe, expect, it, +} from '@jest/globals'; +import $ from '@js/core/renderer'; + +import { + afterTest, + beforeTest, + createDataGrid, +} from '../__tests__/__mock__/helpers/utils'; + +describe('Column Headers', () => { + beforeEach(beforeTest); + afterEach(afterTest); + + describe('headerCellTemplate', () => { + it('should apply right alignment to number column when headerCellTemplate is used', async () => { + const { component } = await createDataGrid({ + dataSource: [], + showBorders: true, + headerFilter: { + visible: true, + }, + columns: [ + { + dataField: 'test', + dataType: 'number', + headerCellTemplate(headerElement) { + $('') + .text('Test') + .appendTo(headerElement); + }, + }, + ], + }); + expect(component.getHeaderCell(0, 0).getAlignment()).toBe('right'); + }); + }); + + describe('toggleFirstHeaderClass', () => { + it('should add first-header class to the first column', async () => { + const { component } = await createDataGrid({ + dataSource: [{ field1: 1, field2: 2, field3: 3 }], + columns: [ + 'field1', + { + caption: 'Band', + columns: ['field2', 'field3'], + }, + ], + }); + + const $headerCell = $(component.getHeaderCell(0, 0).getElement()); + expect($headerCell.hasClass('dx-datagrid-first-header')).toBe(true); + }); + + it('should not add first-header class to non-first columns', async () => { + const { component } = await createDataGrid({ + dataSource: [{ field1: 1, field2: 2, field3: 3 }], + columns: [ + 'field1', + { + caption: 'Band', + columns: ['field2', 'field3'], + }, + ], + }); + + const $secondCellOfFirstRow = $(component.getHeaderCell(0, 1).getElement()); + const $firstCellOfSecondRow = $(component.getHeaderCell(1, 0).getElement()); + const $secondCellOfSecondRow = $(component.getHeaderCell(1, 1).getElement()); + + expect($secondCellOfFirstRow.hasClass('dx-datagrid-first-header')).toBe(false); + expect($firstCellOfSecondRow.hasClass('dx-datagrid-first-header')).toBe(false); + expect($secondCellOfSecondRow.hasClass('dx-datagrid-first-header')).toBe(false); + }); + + it('should update first-header class when first column visibility changes', async () => { + const { component } = await createDataGrid({ + dataSource: [{ field1: 1, field2: 2, field3: 3 }], + columns: [ + 'field1', + { + caption: 'Band', + columns: ['field2', 'field3'], + }, + ], + }); + + component.apiColumnOption('field1', 'visible', false); + + const $firstHeaderCell = $(component.getHeaderCell(0, 0).getElement()); + expect($firstHeaderCell.text()).toBe('Band'); + expect($firstHeaderCell.hasClass('dx-datagrid-first-header')).toBe(true); + }); + + it('should add first-header class when band column is first', async () => { + const { component } = await createDataGrid({ + dataSource: [{ field1: 1, field2: 2, field3: 3 }], + columns: [ + { + caption: 'Band', + columns: ['field1', 'field2'], + }, + 'field3', + ], + }); + + const $firstCellOfFirstRow = $(component.getHeaderCell(0, 0).getElement()); + const $firstCellOfSecondRow = $(component.getHeaderCell(1, 0).getElement()); + + expect($firstCellOfFirstRow.hasClass('dx-datagrid-first-header')).toBe(true); + expect($firstCellOfSecondRow.hasClass('dx-datagrid-first-header')).toBe(true); + }); + + it('should not add first-header class to non-first columns when band column is first', async () => { + const { component } = await createDataGrid({ + dataSource: [{ field1: 1, field2: 2, field3: 3 }], + columns: [ + { + caption: 'Band', + columns: ['field1', 'field2'], + }, + 'field3', + ], + }); + + const $secondCellOfFirstRow = $(component.getHeaderCell(0, 1).getElement()); + const $secondCellOfSecondRow = $(component.getHeaderCell(1, 1).getElement()); + + expect($secondCellOfFirstRow.hasClass('dx-datagrid-first-header')).toBe(false); + expect($secondCellOfSecondRow.hasClass('dx-datagrid-first-header')).toBe(false); + }); + }); +}); 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..e3f447f0e481 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 @@ -77,6 +77,36 @@ export class ColumnHeadersView extends ColumnContextMenuMixin(ColumnsView) { .toggleClass(HEADER_FILTER_INDICATOR_CLASS, !!$visibleIndicatorElements.filter(`.${this._getIndicatorClassName('headerFilter')}`).length); } + private toggleFirstHeaderClass( + $cell: dxElementWrapper, + column: Column, + rowIndex: number | null, + ): void { + const columnsController = this._columnsController; + const isFirstColumn = columnsController?.isFirstColumn(column, rowIndex); + + $cell.toggleClass(this.addWidgetPrefix(CLASSES.firstHeader), isFirstColumn); + } + + private updateFirstHeaderClasses(): void { + const $rows = this._getRowElementsCore().toArray(); + + $rows.forEach((row: Element, index: number) => { + const rowIndex = index; + const $cells = $(row).children('td').toArray(); + const columns = this.getColumns(rowIndex); + + $cells.forEach((cell: Element, cellIndex: number) => { + const $cell = $(cell); + const column = columns[cellIndex]; + + if (column) { + this.toggleFirstHeaderClass($cell, column, rowIndex); + } + }); + }); + } + protected createCellContent( $cell: dxElementWrapper, column: Column, @@ -359,8 +389,15 @@ export class ColumnHeadersView extends ColumnContextMenuMixin(ColumnsView) { const { column } = options; // @ts-expect-error const $cellElement = super._createCell.apply(this, arguments); + const rowCount = this.getRowCount(); + + if (rowCount > 1) { + this.toggleFirstHeaderClass($cellElement, column, options.rowIndex); + } - column.rowspan > 1 && options.rowType === 'header' && $cellElement.attr('rowSpan', column.rowspan); + if (column.rowspan > 1 && options.rowType === 'header') { + $cellElement.attr('rowSpan', column.rowspan); + } return $cellElement; } @@ -459,6 +496,17 @@ export class ColumnHeadersView extends ColumnContextMenuMixin(ColumnsView) { return returnAll ? $indicatorsContainer : $indicatorsContainer.filter(`:not(.${VISIBILITY_HIDDEN_CLASS})`); } + protected _resizeCore(): void { + const rowCount = this.getRowCount(); + const columnHidingEnabled = this.option('columnHidingEnabled'); + + super._resizeCore.apply(this); + + if (rowCount > 1 && columnHidingEnabled) { + this.updateFirstHeaderClasses(); + } + } + /** * @extended: tree_list/selection */ diff --git a/packages/devextreme/js/__internal/grids/grid_core/columns_controller/m_columns_controller.integration.test.ts b/packages/devextreme/js/__internal/grids/grid_core/columns_controller/m_columns_controller.integration.test.ts new file mode 100644 index 000000000000..234ef1a94e38 --- /dev/null +++ b/packages/devextreme/js/__internal/grids/grid_core/columns_controller/m_columns_controller.integration.test.ts @@ -0,0 +1,65 @@ +import { + afterEach, beforeEach, describe, expect, it, +} from '@jest/globals'; +import type { DataGridCommandColumnType } from '@js/ui/data_grid'; + +import { + afterTest, + beforeTest, + createDataGrid, +} from '../__tests__/__mock__/helpers/utils'; + +const UNSUPPORTED_GROUPING_COLUMN_TYPES = ['adaptive', 'buttons', 'detailExpand', 'groupExpand', 'selection', 'drag', 'ai']; + +const dataSource = [ + { id: 1, name: 'Item 1' }, + { id: 2, name: 'Item 2' }, + { id: 3, name: 'Item 3' }, +]; + +describe('Column Controller', () => { + beforeEach(beforeTest); + afterEach(afterTest); + + describe('Grouping for unsupported column types', () => { + describe.each(UNSUPPORTED_GROUPING_COLUMN_TYPES)('unsupported grouping column types', (columnType) => { + it(`Should have no group rows after put type property = ${columnType} (first load)`, async () => { + const { component } = await createDataGrid({ + dataSource, + showBorders: true, + columns: [ + 'id', + { + caption: 'Test', + type: columnType as DataGridCommandColumnType | undefined, + name: 'test', + groupIndex: 0, + }, + ], + }); + + const groupRow = component.getGroupRows(); + expect(groupRow.length).toBe(0); + }); + + it(`Should have no group rows after put type property = ${columnType} (dynamic update)`, async () => { + const { component } = await createDataGrid({ + dataSource, + showBorders: true, + columns: [ + 'id', + { + caption: 'Test', + name: 'AItest', + }, + ], + }); + + component.apiColumnOption('AItest', 'type', columnType as DataGridCommandColumnType | undefined); + + const groupRow = component.getGroupRows(); + expect(groupRow.length).toBe(0); + }); + }); + }); +}); 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..daeafa8c664d 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 @@ -1,65 +1,114 @@ import { - afterEach, beforeEach, describe, expect, it, + describe, expect, it, jest, } from '@jest/globals'; -import type { DataGridCommandColumnType } from '@js/ui/data_grid'; -import { - afterTest, - beforeTest, - createDataGrid, -} from '../__tests__/__mock__/helpers/utils'; +import type { ColumnsController } from './m_columns_controller'; +import { ColumnsController as ColumnsControllerClass } from './m_columns_controller'; + +interface ControllerConfig { + columns?: object[]; + options?: Record; +} -const UNSUPPORTED_GROUPING_COLUMN_TYPES = ['adaptive', 'buttons', 'detailExpand', 'groupExpand', 'selection', 'drag', 'ai']; +interface ComponentMock { + option: jest.Mock<(optionName: string) => unknown>; + _createComponent: jest.Mock; + element: jest.Mock<() => { jquery: boolean }>; + _controllers: { + data: { getDataSource: jest.Mock<() => null> }; + focus: Record; + stateStoring: Record; + }; +} -const dataSource = [ - { id: 1, name: 'Item 1' }, - { id: 2, name: 'Item 2' }, - { id: 3, name: 'Item 3' }, -]; +const createRealColumnsController = (config: ControllerConfig): ColumnsController => { + const componentMock: ComponentMock = { + option: jest.fn((optionName: string) => { + if (optionName === 'columns') return config.columns ?? []; + return config.options?.[optionName]; + }), + _createComponent: jest.fn(), + element: jest.fn(() => ({ + jquery: true, + })), + _controllers: { + data: { getDataSource: jest.fn(() => null) }, + focus: {}, + stateStoring: {}, + }, + }; -describe('Column Controller', () => { - beforeEach(beforeTest); - afterEach(afterTest); + const controller = new ColumnsControllerClass(componentMock); + controller.init(); - describe('Grouping for unsupported column types', () => { - describe.each(UNSUPPORTED_GROUPING_COLUMN_TYPES)('unsupported grouping column types', (columnType) => { - it(`Should have no group rows after put type property = ${columnType} (first load)`, async () => { - const { component } = await createDataGrid({ - dataSource, - showBorders: true, + return controller; +}; + +describe('Methods', () => { + describe('getVisibleDataColumnsByBandColumn', () => { + it('should return only visible data columns', () => { + const columns = [ + { + caption: 'Band', columns: [ - 'id', - { - caption: 'Test', - type: columnType as DataGridCommandColumnType | undefined, - name: 'test', - groupIndex: 0, - }, + { dataField: 'field1' }, + { dataField: 'field2', visible: false }, + { dataField: 'field3' }, ], - }); + }, + ]; + + const controller = createRealColumnsController({ columns }); + const bandColumn = controller.getColumns()[0]; + const result = controller.getVisibleDataColumnsByBandColumn(bandColumn.index); - const groupRow = component.getGroupRows(); - expect(groupRow.length).toBe(0); - }); + expect(result).toHaveLength(2); + expect(result.map((c) => c.dataField as string)).toEqual(['field1', 'field3']); + }); - it(`Should have no group rows after put type property = ${columnType} (dynamic update)`, async () => { - const { component } = await createDataGrid({ - dataSource, - showBorders: true, + it('should recursively get data columns from nested bands', () => { + const columns = [ + { + caption: 'Band 1', columns: [ - 'id', + { dataField: 'field1' }, { - caption: 'Test', - name: 'AItest', + caption: 'Band 2', + columns: [ + { dataField: 'field2' }, + { dataField: 'field3' }, + ], }, ], - }); + }, + ]; + + const controller = createRealColumnsController({ columns }); + const bandColumn = controller.getColumns()[0]; + const result = controller.getVisibleDataColumnsByBandColumn(bandColumn.index); + + expect(result).toHaveLength(3); + expect(result.map((c) => c.dataField as string)).toEqual(['field1', 'field2', 'field3']); + }); + + it('should exclude grouped columns without showWhenGrouped', () => { + const columns = [ + { + caption: 'Band', + columns: [ + { dataField: 'field1', groupIndex: 0 }, + { dataField: 'field2', groupIndex: 0, showWhenGrouped: true }, + { dataField: 'field3' }, + ], + }, + ]; - component.apiColumnOption('AItest', 'type', columnType as DataGridCommandColumnType | undefined); + const controller = createRealColumnsController({ columns }); + const bandColumn = controller.getColumns()[0]; + const result = controller.getVisibleDataColumnsByBandColumn(bandColumn.index); - const groupRow = component.getGroupRows(); - expect(groupRow.length).toBe(0); - }); + expect(result).toHaveLength(2); + expect(result.map((c) => c.dataField as string)).toEqual(['field2', 'field3']); }); }); }); 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 7969b5fd22c0..a72c6ef5f042 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 @@ -1866,7 +1866,7 @@ export class ColumnsController extends modules.Controller { public getVisibleDataColumnsByBandColumn(bandColumnIndex: number) { const that = this; const bandColumnsCache = that.getBandColumnsCache(); - const result = this.getChildrenByBandColumn(bandColumnIndex, bandColumnsCache.columnChildrenByIndex); + const result = getChildrenByBandColumn(bandColumnIndex, bandColumnsCache.columnChildrenByIndex, true); return result .filter((column) => !column.isBand && column.visible); diff --git a/packages/devextreme/js/__internal/grids/grid_core/sticky_columns/const.ts b/packages/devextreme/js/__internal/grids/grid_core/sticky_columns/const.ts index a983617b08b6..553970e1445a 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/sticky_columns/const.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/sticky_columns/const.ts @@ -13,7 +13,6 @@ export const CLASSES = { stickyColumnBorderRight: 'sticky-column-border-right', stickyColumnBorderLeft: 'sticky-column-border-left', stickyColumns: 'sticky-columns', - firstHeader: 'first-header', columnNoBorder: 'column-no-border', groupRowContainer: 'group-row-container', focusedFixedElement: 'dx-focused-fixed-element', diff --git a/packages/devextreme/js/__internal/grids/grid_core/sticky_columns/dom.ts b/packages/devextreme/js/__internal/grids/grid_core/sticky_columns/dom.ts index 0cb8682a0f38..f9ac8efe584e 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/sticky_columns/dom.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/sticky_columns/dom.ts @@ -29,10 +29,6 @@ const addStickyColumnClass = ($cell, fixedPosition, addWidgetPrefix): void => { } }; -const toggleFirstHeaderClass = ($cell, value, addWidgetPrefix): void => { - $cell.toggleClass(addWidgetPrefix(CLASSES.firstHeader), value); -}; - const toggleColumnNoBorderClass = ($cell, value, addWidgetPrefix): void => { $cell.toggleClass(addWidgetPrefix(CLASSES.columnNoBorder), value); }; @@ -349,7 +345,6 @@ const getNextHeaderCell = ( }; export const GridCoreStickyColumnsDom = { - toggleFirstHeaderClass, toggleColumnNoBorderClass, addStickyColumnClass, addStickyColumnBorderLeftClass, diff --git a/packages/devextreme/js/__internal/grids/grid_core/sticky_columns/m_sticky_columns.ts b/packages/devextreme/js/__internal/grids/grid_core/sticky_columns/m_sticky_columns.ts index 2dc035895c87..009ed0958c8d 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/sticky_columns/m_sticky_columns.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/sticky_columns/m_sticky_columns.ts @@ -9,6 +9,7 @@ import type { ResizingController } from '@ts/grids/grid_core/views/m_grid_view'; import { HIDDEN_COLUMNS_WIDTH } from '../adaptivity/const'; import type { ColumnHeadersView } from '../column_headers/m_column_headers'; +import type { Column } from '../columns_controller/m_columns_controller'; import type { ColumnsResizerViewController, DraggingHeaderViewController, @@ -83,9 +84,9 @@ const baseStickyColumns = >(Base: T) => class } } - private updateBorderCellClasses( + private toggleColumnNoBorderClass( $cell: dxElementWrapper, - column, + column: Column, rowIndex: number | null, ): void { const columnsController = this._columnsController; @@ -96,15 +97,12 @@ const baseStickyColumns = >(Base: T) => class rowIndex, isRowsView, ); - const isFirstColumn = columnsController?.isFirstColumn(column, rowIndex); GridCoreStickyColumnsDom .toggleColumnNoBorderClass($cell, needToRemoveBorder, this.addWidgetPrefix.bind(this)); - GridCoreStickyColumnsDom - .toggleFirstHeaderClass($cell, isFirstColumn, this.addWidgetPrefix.bind(this)); } - private _updateBorderClasses(): void { + private updateColumnNoBorderClasses(): void { const isColumnHeadersView = this.name === 'columnHeadersView'; const $rows = this._getRowElementsCore().not(`.${MASTER_DETAIL_CLASSES.detailRow}`).toArray(); @@ -120,7 +118,7 @@ const baseStickyColumns = >(Base: T) => class const column = columns[cellIndex]; if (column.visibleWidth !== HIDDEN_COLUMNS_WIDTH) { - this.updateBorderCellClasses($cell, column, rowIndex); + this.toggleColumnNoBorderClass($cell, column, rowIndex); } }); }); @@ -156,7 +154,7 @@ const baseStickyColumns = >(Base: T) => class const isExpandColumn = column.command && column.command === 'expand'; if (hasStickyColumns && !needToDisableStickyColumn(this._columnsController, column)) { - this.updateBorderCellClasses($cell, column, rowIndex); + this.toggleColumnNoBorderClass($cell, column, rowIndex); if (column.fixed) { const fixedPosition = getColumnFixedPosition(this._columnsController, column); @@ -243,19 +241,20 @@ const baseStickyColumns = >(Base: T) => class } } - protected _resizeCore() { + protected _resizeCore(): void { const hasStickyColumns = this.hasStickyColumns(); - const adaptiveColumns = this.getController('adaptiveColumns'); - const hidingColumnsQueue = adaptiveColumns?.getHidingColumnsQueue(); + const columnHidingEnabled = this.option('columnHidingEnabled'); - super._resizeCore.apply(this, arguments as any); + super._resizeCore.apply(this); - if (hasStickyColumns) { - this.setStickyOffsets(); + if (!hasStickyColumns) { + return; + } - if (hidingColumnsQueue?.length) { - this._updateBorderClasses(); - } + this.setStickyOffsets(); + + if (columnHidingEnabled) { + this.updateColumnNoBorderClasses(); } } diff --git a/packages/devextreme/js/__internal/grids/grid_core/sticky_columns/utils.test.ts b/packages/devextreme/js/__internal/grids/grid_core/sticky_columns/utils.test.ts new file mode 100644 index 000000000000..eab0dff479ed --- /dev/null +++ b/packages/devextreme/js/__internal/grids/grid_core/sticky_columns/utils.test.ts @@ -0,0 +1,108 @@ +import { + describe, expect, it, jest, +} from '@jest/globals'; + +import type { ColumnsController } from '../columns_controller/m_columns_controller'; +import { ColumnsController as ColumnsControllerClass } from '../columns_controller/m_columns_controller'; +import { needToRemoveColumnBorder } from './utils'; + +interface ControllerConfig { + columns?: object[]; + options?: Record; +} + +interface ComponentMock { + option: jest.Mock<(optionName: string) => unknown>; + _createComponent: jest.Mock; + element: jest.Mock<() => { jquery: boolean }>; + _controllers: { + data: { getDataSource: jest.Mock<() => null> }; + focus: Record; + stateStoring: Record; + }; +} + +const createRealColumnsController = (config: ControllerConfig): ColumnsController => { + const componentMock: ComponentMock = { + option: jest.fn((optionName: string) => { + if (optionName === 'columns') { + return config.columns ?? []; + } + + return config.options?.[optionName]; + }), + _createComponent: jest.fn(), + element: jest.fn(() => ({ + jquery: true, + })), + _controllers: { + data: { getDataSource: jest.fn(() => null) }, + focus: {}, + stateStoring: {}, + }, + }; + + const controller = new ColumnsControllerClass(componentMock); + controller.init(); + + return controller; +}; + +describe('needToRemoveColumnBorder', () => { + it('should not check parent column for grouped column', () => { + const columns = [ + { + dataField: 'field1', + fixed: true, + }, + { + caption: 'Band Column', + columns: [{ + dataField: 'field2', + groupIndex: 0, + }, { + dataField: 'field3', + }], + }, + ]; + + const controller = createRealColumnsController({ columns }); + const groupExpandColumn = controller.getVisibleColumns()[0]; + + expect(groupExpandColumn.type).toBe('groupExpand'); + expect(needToRemoveColumnBorder(controller, groupExpandColumn, 0)) + .toBe(false); + }); + + it('should return false for grouped column with showWhenGrouped', () => { + const columns = [ + { + dataField: 'field1', + fixed: true, + }, + { + caption: 'Band Column', + columns: [{ + dataField: 'field2', + groupIndex: 0, + showWhenGrouped: true, + }, { + dataField: 'field3', + }], + }, + ]; + + const controller = createRealColumnsController({ columns }); + const visibleColumns = controller.getVisibleColumns(); + const groupExpandColumn = visibleColumns[0]; + const groupedColumn = visibleColumns[2]; + + expect(groupExpandColumn.type).toBe('groupExpand'); + expect(needToRemoveColumnBorder(controller, groupExpandColumn, 0)) + .toBe(false); + + expect(groupedColumn.dataField).toBe('field2'); + expect(needToRemoveColumnBorder(controller, groupedColumn, 0)) + .toBe(true); + }); +}); diff --git a/packages/devextreme/js/__internal/grids/grid_core/sticky_columns/utils.ts b/packages/devextreme/js/__internal/grids/grid_core/sticky_columns/utils.ts index f2628a544425..e63045d143ac 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/sticky_columns/utils.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/sticky_columns/utils.ts @@ -115,7 +115,7 @@ const getStickyOffsetCore = function ( const isFirstOrLastColumn = function ( that: ColumnsController, - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types + column, rowIndex: number, onlyWithinBandColumn = false, @@ -243,7 +243,7 @@ export const needToRemoveColumnBorder = function ( isDataColumn = false, ): boolean { const visibleColumns = that.getVisibleColumns(isDataColumn ? null : rowIndex); - const parentColumn = that.getParentColumn(column); + const parentColumn = !isDefined(column.type) && that.getParentColumn(column); if (parentColumn) { const isFirstColumn = that.isFirstColumn(column, rowIndex, true); diff --git a/packages/devextreme/testing/helpers/gridBaseMocks.js b/packages/devextreme/testing/helpers/gridBaseMocks.js index 1cd526f839b2..7479b2ed8e01 100644 --- a/packages/devextreme/testing/helpers/gridBaseMocks.js +++ b/packages/devextreme/testing/helpers/gridBaseMocks.js @@ -649,6 +649,10 @@ module.exports = function($, gridCore, columnResizingReordering, domUtils, commo return true; }, + isFirstColumn: function() { + return false; + }, + getFirstDataColumnIndex: function() { const visibleColumns = this.getVisibleColumns(); const visibleColumnsLength = visibleColumns.length; diff --git a/packages/testcafe-models/dataGrid/headers/cell.ts b/packages/testcafe-models/dataGrid/headers/cell.ts index 2ba6e9f6c867..da9839500839 100644 --- a/packages/testcafe-models/dataGrid/headers/cell.ts +++ b/packages/testcafe-models/dataGrid/headers/cell.ts @@ -14,6 +14,7 @@ const CLASS = { stickyLeft: 'dx-datagrid-sticky-column-left', stickyRight: 'dx-datagrid-sticky-column-right', aiHeaderButton: 'dx-command-ai-header-button', + firstHeader: 'dx-datagrid-first-header', }; const getStickyClassNames = (position: StickyPosition | undefined): string[] => { @@ -38,6 +39,8 @@ export default class HeaderCell { isHidden: Promise; + isFirstHeader: Promise; + isSticky(position?: StickyPosition | undefined): Promise { return ClientFunction((element, stickyClassNames) => { const elementClassList = element().classList; @@ -56,6 +59,7 @@ export default class HeaderCell { this.element = headerRow.find(`td[aria-colindex='${index + 1}']`); this.isFocused = this.element.focused; this.isHidden = this.element.hasClass(Widget.addClassPrefix(widgetName, CLASS.hiddenColumn)); + this.isFirstHeader = this.element.hasClass(CLASS.firstHeader); } getFilterIcon(): Selector {