Skip to content

Commit 4e86e03

Browse files
authored
CardView - Filtering: can't set filter in filterBuilder if headerFilter.dataSource is present (DevExpress#29932)
Co-authored-by: Alyar <>
1 parent 64de6e5 commit 4e86e03

File tree

7 files changed

+113
-4
lines changed

7 files changed

+113
-4
lines changed

e2e/testcafe-devextreme/tests/cardView/headerFilter/common.functional.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import CardView from 'devextreme-testcafe-models/cardView';
2+
import FilterBuilder from 'devextreme-testcafe-models/filterBuilder';
23
import { createWidget } from '../../../helpers/createWidget';
34
import url from '../../../helpers/getPageUrl';
45
import { baseConfig } from './helpers/baseConfig';
@@ -410,3 +411,65 @@ test('The item\'s selection state should be correct after resetting the search',
410411
},
411412
});
412413
});
414+
415+
test('FilterBuilder should work with custom headerFilter data source', async (t) => {
416+
const cardView = new CardView('#container');
417+
const IS_ANY_OPERATION_ITEM_INDEX = 9;
418+
const ADD_CONDITION_ITEM_INDEX = 0;
419+
420+
await t
421+
.click(cardView.getHeaderPanel().getHeaderItem(0).getFilterIcon());
422+
423+
await t
424+
.expect(cardView.getHeaderFilterList().getItems().count)
425+
.eql(3)
426+
.click(cardView.getHeaderFilterPopup().getOkButton().element);
427+
428+
const filterBuilderPopup = await cardView.getFilterPanel().openFilterBuilderPopup(t);
429+
const filterBuilder = filterBuilderPopup.getFilterBuilder();
430+
await t
431+
.click(filterBuilder.getAddButton())
432+
.expect(FilterBuilder.getPopupTreeView().visible).ok()
433+
.click(FilterBuilder.getPopupTreeViewNode(ADD_CONDITION_ITEM_INDEX))
434+
.click(filterBuilder.getField(0, 'itemOperation').element)
435+
.click(FilterBuilder.getPopupTreeViewNode(IS_ANY_OPERATION_ITEM_INDEX))
436+
.click(filterBuilder.getField(0, 'itemValue').element)
437+
.click(cardView.getHeaderFilterList().getItem(1).element)
438+
.click(cardView.getHeaderFilterList().getItem(2).element)
439+
.click(cardView.getHeaderFilterPopup().getOkButton().element)
440+
.click(filterBuilderPopup.asPopup().getOkButton().element);
441+
442+
await t
443+
.expect(cardView.getCards().count)
444+
.eql(2)
445+
.expect(cardView.getCard(0).getFieldValueCell('Id').textContent)
446+
.eql('2')
447+
.expect(cardView.getCard(1).getFieldValueCell('Id').textContent)
448+
.eql('3');
449+
}).before(async () => {
450+
await createWidget('dxCardView', {
451+
...baseConfig,
452+
columns: [
453+
{
454+
dataField: 'id',
455+
headerFilter: {
456+
dataSource: [
457+
{ value: 1, text: '1' },
458+
{ value: 2, text: '2' },
459+
{ value: 3, text: '3' },
460+
],
461+
},
462+
},
463+
{
464+
dataField: 'title',
465+
},
466+
{
467+
dataField: 'name',
468+
},
469+
{
470+
dataField: 'lastName',
471+
},
472+
],
473+
filterPanel: { visible: true },
474+
});
475+
});

e2e/testcafe-devextreme/tests/editors/overlays/toolbarIntegration.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { createScreenshotsComparer } from 'devextreme-screenshot-comparer';
22
import Popup from 'devextreme-testcafe-models/popup';
33
import Popover from 'devextreme-testcafe-models/popover';
44
import Toolbar from 'devextreme-testcafe-models/toolbar/toolbar';
5+
import { Selector } from 'testcafe';
56
import { testScreenshot, isMaterialBased } from '../../../helpers/themeUtils';
67
import url from '../../../helpers/getPageUrl';
78
import { createWidget } from '../../../helpers/createWidget';
@@ -16,6 +17,9 @@ const dynamicFixture = isMaterialBased()
1617
dynamicFixture`Popup_toolbar`
1718
.page(url(__dirname, '../../container.html'));
1819

20+
const COMPONENT_SELECTOR = '#container';
21+
const CLOSE_BUTTON_SELECTOR = '.dx-closebutton';
22+
1923
[
2024
{ name: 'dxPopup', Class: Popup },
2125
{ name: 'dxPopover', Class: Popover },
@@ -24,7 +28,7 @@ dynamicFixture`Popup_toolbar`
2428
[true, false].forEach((rtlEnabled) => {
2529
safeSizeTest(`Extended toolbar should be used in ${name},rtlEnabled=${rtlEnabled},toolbar=${toolbar}`, async (t) => {
2630
const { takeScreenshot, compareResults } = createScreenshotsComparer(t);
27-
const instance = new Class('#container');
31+
const instance = new Class(COMPONENT_SELECTOR);
2832

2933
if (toolbar === 'top') {
3034
const topToolbar = new Toolbar(instance.getToolbar());
@@ -34,7 +38,7 @@ dynamicFixture`Popup_toolbar`
3438
await bottomToolbar.option('overflowMenuVisible', true);
3539
}
3640

37-
await t.hover(instance.getCloseButton().element);
41+
await t.hover(Selector(CLOSE_BUTTON_SELECTOR));
3842

3943
await testScreenshot(t, takeScreenshot, `${name.replace('dx', '')}_${toolbar}_toolbar_menu,rtlEnabled=${rtlEnabled}.png`);
4044

@@ -55,7 +59,7 @@ dynamicFixture`Popup_toolbar`
5559
rtlEnabled,
5660
visible: true,
5761
animation: undefined,
58-
target: '#container',
62+
target: COMPONENT_SELECTOR,
5963
hideOnOutsideClick: true,
6064
toolbarItems: [{
6165
location: 'before',

packages/devextreme/js/__internal/grids/new/grid_core/filtering/filter_visitors/filter_custom_operations_visitor.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ export class FilterCustomOperationsVisitor {
3232

3333
return getColumnByIndexOrName(columns, columnName);
3434
},
35+
/*
36+
Note: Root headerFilter options are used because the legacy code handles retrieving
37+
options for specific columns on its own
38+
*/
3539
getHeaderFilterOptions: (): HeaderFilterRootOptions => this.options.oneWay('headerFilter').peek(),
3640
headerFilterController: this.headerFilterController,
3741
};

packages/devextreme/js/__internal/grids/new/grid_core/filtering/header_filter/compatibility.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,23 @@
11
import type { Column } from '../../columns_controller/types';
2+
import { DataController } from '../../data_controller/index';
3+
import { OptionsController } from '../../options_controller/options_controller';
24
import { FilterController } from '../filter_controller';
5+
import { getDataSourceOptions } from './legacy_header_filter';
36
import { HeaderFilterViewController } from './view_controller';
47

58
export class CompatibilityHeaderFilterController {
69
public static dependencies = [
710
FilterController,
811
HeaderFilterViewController,
12+
DataController,
13+
OptionsController,
914
] as const;
1015

1116
constructor(
1217
private readonly realFilterController: FilterController,
1318
private readonly realHeaderFilterViewController: HeaderFilterViewController,
19+
private readonly realDataController: DataController,
20+
private readonly options: OptionsController,
1421
) {}
1522

1623
public getCustomFilterOperations(): unknown[] {
@@ -36,4 +43,21 @@ export class CompatibilityHeaderFilterController {
3643
public hideHeaderFilterMenu(): void {
3744
this.realHeaderFilterViewController.closePopup();
3845
}
46+
47+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
48+
public getDataSource(column: Column): any {
49+
const adapter = this.realDataController.getStoreLoadAdapter();
50+
const popupOptions = {
51+
column: { ...column },
52+
filterType: column.filterType,
53+
filterValues: column.filterValues,
54+
};
55+
/*
56+
Note: Root headerFilter options are used because the legacy code handles retrieving
57+
options for specific columns on its own
58+
*/
59+
const rootHeaderFilterOptions = this.options.oneWay('headerFilter').peek();
60+
61+
return getDataSourceOptions(adapter, popupOptions, rootHeaderFilterOptions, null);
62+
}
3963
}

packages/devextreme/js/__internal/grids/new/grid_core/filtering/header_filter/view_controller.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ export class HeaderFilterViewController {
4343
isFilterBuilder?: boolean,
4444
): void {
4545
const rootDataSource = this.dataController.getStoreLoadAdapter();
46+
/*
47+
Note: Root headerFilter options are used because the legacy code handles retrieving
48+
options for specific columns on its own
49+
*/
4650
const rootHeaderFilterOptions = this.options.oneWay('headerFilter').peek();
4751
const filterExpression = this.getFilterExpressionWithoutCurrentColumn(column);
4852
const type = getHeaderFilterListType(column);

packages/testcafe-models/overlay/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ export default class Overlay extends Widget {
2323

2424
// eslint-disable-next-line class-methods-use-this
2525
getWrapper(): Selector {
26-
return Selector(`.${CLASS.wrapper}`);
26+
// NOTE: Returns the last found wrapper if multiple popups opened
27+
return Selector(`.${CLASS.wrapper}`).nth(-1);
2728
}
2829

2930
// eslint-disable-next-line class-methods-use-this

packages/testcafe-models/popup.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ const CLASS = {
1818
navigatorCaption: 'dx-calendar-caption-button',
1919
viewsWrapper: 'dx-calendar-views-wrapper',
2020
};
21+
22+
const SELECTORS = {
23+
okButton: '[aria-label="OK"]',
24+
};
25+
2126
export default class Popup extends Overlay {
2227
public static className = '.dx-popup-wrapper';
2328

@@ -51,6 +56,10 @@ export default class Popup extends Overlay {
5156
return new Button(this.getWrapper().find(`.${CLASS.button}`).nth(index));
5257
}
5358

59+
getOkButton(): Button {
60+
return new Button(this.getWrapper().find(SELECTORS.okButton));
61+
}
62+
5463
getCloseButton(): Button {
5564
return new Button(this.getWrapper().find(`.${CLASS.closeButton}`));
5665
}

0 commit comments

Comments
 (0)