diff --git a/e2e/testcafe-devextreme/tests/dataGrid/headerFilter/headerFilter.ts b/e2e/testcafe-devextreme/tests/dataGrid/headerFilter/headerFilter.ts index 82ed392665df..d74b59c62174 100644 --- a/e2e/testcafe-devextreme/tests/dataGrid/headerFilter/headerFilter.ts +++ b/e2e/testcafe-devextreme/tests/dataGrid/headerFilter/headerFilter.ts @@ -283,3 +283,36 @@ test('Data should be filtered if True is selected in the header filter when case showBorders: true, headerFilter: { visible: true }, })); + +test('[T1284200] Should handle dxList "selectAll" when has unselected items on the second page', async (t) => { + const dataGrid = new DataGrid(GRID_CONTAINER); + const headerCell = dataGrid.getHeaders() + .getHeaderRow(0) + .getHeaderCell(0); + const filterIconElement = headerCell.getFilterIcon(); + const headerFilter = new HeaderFilter(); + const list = headerFilter.getList(); + + await t + .click(filterIconElement) + .click(list.selectAll.checkBox.element); + + await t.expect(list.selectAll.checkBox.isChecked).ok(); + + await t.click(list.selectAll.checkBox.element); + + await t.expect(list.selectAll.checkBox.isChecked).notOk(); +}).before(async () => createWidget('dxDataGrid', { + dataSource: new Array(100).fill(null).map((_, idx) => ({ + id: idx, + })), + keyExpr: 'id', + columns: [{ + dataField: 'id', + filterType: 'exclude', + filterValues: [70], + }], + headerFilter: { + visible: true, + }, +})); diff --git a/e2e/testcafe-devextreme/tests/pivotGrid/headerFilter.ts b/e2e/testcafe-devextreme/tests/pivotGrid/headerFilter.ts index e8883d9ac7ba..db905f8b2e89 100644 --- a/e2e/testcafe-devextreme/tests/pivotGrid/headerFilter.ts +++ b/e2e/testcafe-devextreme/tests/pivotGrid/headerFilter.ts @@ -1,5 +1,6 @@ import { createScreenshotsComparer } from 'devextreme-screenshot-comparer'; import PivotGrid from 'devextreme-testcafe-models/pivotGrid'; +import HeaderFilter from 'devextreme-testcafe-models/dataGrid/headers/headerFilter'; import { createWidget } from '../../helpers/createWidget'; import url from '../../helpers/getPageUrl'; import { testScreenshot } from '../../helpers/themeUtils'; @@ -8,9 +9,11 @@ import { sales } from './data'; fixture.disablePageReloads`pivotGrid_headerFilter` .page(url(__dirname, '../container.html')); +const PIVOT_GRID_SELECTOR = '#container'; + test.meta({ unstable: true })('Header filter popup', async (t) => { const { takeScreenshot, compareResults } = createScreenshotsComparer(t); - const pivotGrid = new PivotGrid('#container'); + const pivotGrid = new PivotGrid(PIVOT_GRID_SELECTOR); await t.click(pivotGrid.getColumnHeaderArea().getHeaderFilterIcon()); @@ -52,3 +55,40 @@ test.meta({ unstable: true })('Header filter popup', async (t) => { }, }); }); + +test('[T1284200] Should handle dxList "selectAll" when has unselected items on the second page', async (t) => { + const pivotGrid = new PivotGrid(PIVOT_GRID_SELECTOR); + + const filterIconElement = pivotGrid.getColumnHeaderArea().getHeaderFilterIcon(); + const headerFilter = new HeaderFilter(); + const list = headerFilter.getList(); + + await t + .click(filterIconElement) + .click(list.selectAll.checkBox.element); + + await t.expect(list.selectAll.checkBox.isChecked).ok(); + + await t.click(list.selectAll.checkBox.element); + + await t.expect(list.selectAll.checkBox.isChecked).notOk(); +}).before(async () => createWidget('dxPivotGrid', { + dataSource: { + fields: [ + { + dataField: 'id', + area: 'column', + filterType: 'exclude', + filterValues: [70], + }, + ], + store: new Array(100).fill(null).map((_, idx) => ({ + id: idx, + })), + }, + allowSorting: true, + allowFiltering: true, + fieldPanel: { + visible: true, + }, +})); diff --git a/packages/devextreme/js/__internal/grids/grid_core/header_filter/m_header_filter_core.ts b/packages/devextreme/js/__internal/grids/grid_core/header_filter/m_header_filter_core.ts index a03c6ff85d0d..9feb0f0ad416 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/header_filter/m_header_filter_core.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/header_filter/m_header_filter_core.ts @@ -3,10 +3,13 @@ import '@ts/ui/list/modules/m_search'; import '@ts/ui/list/modules/m_selection'; import $ from '@js/core/renderer'; +import type { DeferredObj } from '@js/core/utils/deferred'; import { extend } from '@js/core/utils/extend'; import { each } from '@js/core/utils/iterator'; -import { isDefined, isFunction } from '@js/core/utils/type'; +import { isDeferred, isDefined, isFunction } from '@js/core/utils/type'; import messageLocalization from '@js/localization/message'; +import type dxCheckBox from '@js/ui/check_box'; +import type dxList from '@js/ui/list'; import List from '@js/ui/list_light'; import Popup from '@js/ui/popup/ui.popup'; import TreeView from '@js/ui/tree_view'; @@ -28,20 +31,45 @@ function resetChildrenItemSelection(items) { } } -function getSelectAllCheckBox(listComponent) { +function getSelectAllCheckBox(listComponent): dxCheckBox { const selector = listComponent.NAME === 'dxTreeView' ? '.dx-treeview-select-all-item' : '.dx-list-select-all-checkbox'; return listComponent.$element().find(selector).dxCheckBox('instance'); } -function updateListSelectAllState(e, filterValues) { - if (e.component.option('searchValue')) { +function updateListSelectAllState( + // NOTE: In runtime dxList's "unselectAll" returns Deferred. + // But in d.ts dxList has a void return type. + listComponent: Omit & { unselectAll: () => (DeferredObj | void) }, + filterValues: any[], +): void { + if (listComponent.option('searchValue')) { return; } - const selectAllCheckBox = getSelectAllCheckBox(e.component); - if (selectAllCheckBox && filterValues && filterValues.length) { + const selectAllCheckBox = getSelectAllCheckBox(listComponent); + + if (selectAllCheckBox && filterValues?.length) { selectAllCheckBox.option('value', undefined); + + // NOTE: T1284200 fix + // We manually set checkbox state (value) above + // So, list do nothing because inner list component's "select all" state + // doesn't react to our manual update. + // Therefore -> we should handle first "select all" checkbox click manually. + // And after it return original "onValueChanged" handler back. + const originalValueChanged = selectAllCheckBox.option('onValueChanged'); + selectAllCheckBox.option('onValueChanged', (event) => { + selectAllCheckBox.option('onValueChanged', originalValueChanged); + + const deferred = listComponent.unselectAll(); + + if (isDeferred(deferred)) { + (deferred as DeferredObj).always(() => { originalValueChanged?.(event); }); + } else { + originalValueChanged?.(event); + } + }); } } @@ -329,11 +357,12 @@ export class HeaderFilterView extends Modules.View { showSelectionControls: true, selectionMode: needShowSelectAllCheckbox ? 'all' : 'multiple', onOptionChanged, - onSelectionChanged(e) { - const items = e.component.option('items'); - const selectedItems = e.component.option('selectedItems'); + onSelectionChanged(event) { + const { component: listComponent } = event; + const items = listComponent.option('items'); + const selectedItems = listComponent.option('selectedItems'); - if (!e.component._selectedItemsUpdating && !e.component.option('searchValue') && !options.isFilterBuilder) { + if (!listComponent._selectedItemsUpdating && !listComponent.option('searchValue') && !options.isFilterBuilder) { const filterValues = options.filterValues || []; const isExclude = options.filterType === 'exclude'; if (selectedItems.length === 0 && items.length && (filterValues.length <= 1 || isExclude && filterValues.length === items.length - 1)) { @@ -365,11 +394,11 @@ export class HeaderFilterView extends Modules.View { } }); - updateListSelectAllState(e, options.filterValues); + updateListSelectAllState(listComponent, options.filterValues); }, onContentReady(e) { - const { component } = e; - const items = component.option('items'); + const { component: listComponent } = e; + const items = listComponent.option('items'); const selectedItems: any = []; each(items, function () { @@ -377,11 +406,12 @@ export class HeaderFilterView extends Modules.View { selectedItems.push(this); } }); - component._selectedItemsUpdating = true; - component.option('selectedItems', selectedItems); - component._selectedItemsUpdating = false; - updateListSelectAllState(e, options.filterValues); + listComponent._selectedItemsUpdating = true; + listComponent.option('selectedItems', selectedItems); + listComponent._selectedItemsUpdating = false; + + updateListSelectAllState(listComponent, options.filterValues); }, }), );