diff --git a/e2e/testcafe-devextreme/tests/cardView/filterPanel/api.filterBuilder.functional.ts b/e2e/testcafe-devextreme/tests/cardView/filterPanel/api.filterBuilder.functional.ts new file mode 100644 index 000000000000..78e03d467938 --- /dev/null +++ b/e2e/testcafe-devextreme/tests/cardView/filterPanel/api.filterBuilder.functional.ts @@ -0,0 +1,55 @@ +import CardView from 'devextreme-testcafe-models/cardView'; +import url from '../../../helpers/getPageUrl'; +import { createWidget } from '../../../helpers/createWidget'; +import { baseConfig } from './helpers/baseConfig'; + +fixture.disablePageReloads`CardView - FilterBuilder API` + .page(url(__dirname, '../../container.html')); + +test('filterBuilder.height API', async (t) => { + const cardView = new CardView('#container'); + const filterBuilderPopup = await cardView.getFilterPanel().openFilterBuilderPopup(t); + + await t + .expect(filterBuilderPopup.getFilterBuilder().element.clientHeight) + .eql(500); + + await cardView.apiOption('filterBuilder.height', 700); + + await t + .expect(filterBuilderPopup.getFilterBuilder().element.clientHeight) + .eql(700); +}).before(async () => { + await createWidget('dxCardView', { + ...baseConfig, + ...{ + filterBuilder: { + height: 500, + }, + }, + }); +}); + +test('filterBuilder.hint API', async (t) => { + const cardView = new CardView('#container'); + const filterBuilderPopup = await cardView.getFilterPanel().openFilterBuilderPopup(t); + + await t + .expect(filterBuilderPopup.getFilterBuilder().element.getAttribute('title')) + .eql('Test'); + + await cardView.apiOption('filterBuilder.hint', 'Test2'); + + await t + .expect(filterBuilderPopup.getFilterBuilder().element.getAttribute('title')) + .eql('Test2'); +}).before(async () => { + await createWidget('dxCardView', { + ...baseConfig, + ...{ + filterBuilder: { + hint: 'Test', + }, + }, + }); +}); diff --git a/e2e/testcafe-devextreme/tests/cardView/filterPanel/api.filterBuilderPopup.functional.ts b/e2e/testcafe-devextreme/tests/cardView/filterPanel/api.filterBuilderPopup.functional.ts new file mode 100644 index 000000000000..ef7fffb7cb4d --- /dev/null +++ b/e2e/testcafe-devextreme/tests/cardView/filterPanel/api.filterBuilderPopup.functional.ts @@ -0,0 +1,55 @@ +import CardView from 'devextreme-testcafe-models/cardView'; +import url from '../../../helpers/getPageUrl'; +import { createWidget } from '../../../helpers/createWidget'; +import { baseConfig } from './helpers/baseConfig'; + +fixture.disablePageReloads`CardView - FilterBuilderPopup API` + .page(url(__dirname, '../../container.html')); + +test('filterBuilderPopup.height API', async (t) => { + const cardView = new CardView('#container'); + const filterBuilderPopup = await cardView.getFilterPanel().openFilterBuilderPopup(t); + + await t + .expect(filterBuilderPopup.asPopup().content.offsetHeight) + .eql(500); + + await cardView.apiOption('filterBuilderPopup.height', 700); + + await t + .expect(filterBuilderPopup.asPopup().content.offsetHeight) + .eql(700); +}).before(async () => { + await createWidget('dxCardView', { + ...baseConfig, + ...{ + filterBuilderPopup: { + height: 500, + }, + }, + }); +}); + +test('filterBuilderPopup.title API', async (t) => { + const cardView = new CardView('#container'); + const filterBuilderPopup = await cardView.getFilterPanel().openFilterBuilderPopup(t); + + await t + .expect(filterBuilderPopup.asPopup().getToolbar().innerText) + .eql('Test'); + + await cardView.apiOption('filterBuilderPopup.title', 'Test2'); + + await t + .expect(filterBuilderPopup.asPopup().getToolbar().innerText) + .eql('Test2'); +}).before(async () => { + await createWidget('dxCardView', { + ...baseConfig, + ...{ + filterBuilderPopup: { + title: 'Test', + }, + }, + }); +}); diff --git a/e2e/testcafe-devextreme/tests/cardView/filterPanel/api.functional.ts b/e2e/testcafe-devextreme/tests/cardView/filterPanel/api.functional.ts new file mode 100644 index 000000000000..946a11305ff7 --- /dev/null +++ b/e2e/testcafe-devextreme/tests/cardView/filterPanel/api.functional.ts @@ -0,0 +1,194 @@ +import CardView from 'devextreme-testcafe-models/cardView'; +import url from '../../../helpers/getPageUrl'; +import { createWidget } from '../../../helpers/createWidget'; +import { baseConfig } from './helpers/baseConfig'; + +fixture.disablePageReloads`CardView - FilterPanel API` + .page(url(__dirname, '../../container.html')); + +test('filterPanel.customizeText API', async (t) => { + const cardView = new CardView('#container'); + + await t + .expect(cardView.getFilterPanel().getFilterText().element.innerText) + .eql('Men'); + + await cardView.apiOption('filterPanel.customizeText', (e) => { + if (e.text === '[Title] Equals \'Mr.\'') { + return 'Not women'; + } + if (e.text === '[Title] Equals \'Mrs.\'') { + return 'Not men'; + } + return e.text; + }); + + await t + .expect(cardView.getFilterPanel().getFilterText().element.innerText) + .eql('Not women'); +}).before(async () => { + await createWidget('dxCardView', { + ...baseConfig, + ...{ + filterValue: ['title', '=', 'Mr.'], + filterPanel: { + ...baseConfig.filterPanel, + customizeText(e) { + if (e.text === '[Title] Equals \'Mr.\'') { + return 'Men'; + } + if (e.text === '[Title] Equals \'Mrs.\'') { + return 'Women'; + } + return e.text; + }, + }, + }, + }); +}); + +test('filterEnabled API', async (t) => { + const cardView = new CardView('#container'); + await t + .expect(cardView.getFilterPanel().getFilterEnabledCheckbox().isChecked) + .notOk() + .expect(cardView.getCards().count) + .eql(4); + + await cardView.apiOption('filterPanel.filterEnabled', true); + + await t + .expect(cardView.getFilterPanel().getFilterEnabledCheckbox().isChecked) + .ok() + .expect(cardView.getCards().count) + .eql(3); +}).before(async () => { + await createWidget('dxCardView', { + ...baseConfig, + ...{ + filterValue: ['title', '=', 'Mr.'], + filterPanel: { + ...baseConfig.filterPanel, + filterEnabled: false, + }, + }, + }); +}); + +test('filterPanel.texts API', async (t) => { + const cardView = new CardView('#container'); + const filterPanel = cardView.getFilterPanel(); + await t + .expect(filterPanel.getFilterEnabledCheckbox().element.getAttribute('title')) + .eql('Custom Filter Enabled Hint') + .expect(filterPanel.getClearFilterButton().element.innerText) + .eql('Custom Clear Filter'); + + await cardView.apiOption('filterPanel.texts.clearFilter', 'Custom Clear Filter2'); + await cardView.apiOption('filterPanel.texts.filterEnabledHint', 'Custom Filter Enabled Hint2'); + + await t + .expect(filterPanel.getFilterEnabledCheckbox().element.getAttribute('title')) + .eql('Custom Filter Enabled Hint2') + .expect(filterPanel.getClearFilterButton().element.innerText) + .eql('Custom Clear Filter2'); + + await t + .click(filterPanel.getClearFilterButton().element) + .expect(filterPanel.getFilterText().element.innerText) + .eql('Custom Create Filter'); + + await cardView.apiOption('filterPanel.texts.createFilter', 'Custom Create Filter2'); + + await t + .expect(filterPanel.getFilterText().element.innerText) + .eql('Custom Create Filter2'); +}).before(async () => { + await createWidget('dxCardView', { + ...baseConfig, + ...{ + filterValue: ['title', '=', 'Mr.'], + filterPanel: { + ...baseConfig.filterPanel, + texts: { + clearFilter: 'Custom Clear Filter', + createFilter: 'Custom Create Filter', + filterEnabledHint: 'Custom Filter Enabled Hint', + }, + }, + }, + }); +}); + +test('filterPanel.visible API', async (t) => { + const cardView = new CardView('#container'); + + await t + .expect(cardView.getFilterPanel().element.exists) + .notOk(); + + await cardView.apiOption('filterPanel.visible', true); + + await t + .expect(cardView.getFilterPanel().element.exists) + .ok(); +}).before(async () => { + await createWidget('dxCardView', { + ...baseConfig, + ...{ + filterValue: ['title', '=', 'Mr.'], + filterPanel: { + ...baseConfig.filterPanel, + visible: false, + }, + }, + }); +}); + +test('filterValue API', async (t) => { + const cardView = new CardView('#container'); + const filterText = cardView.getFilterPanel().getFilterText(); + + await t + .expect(filterText.element.innerText) + .eql('[Title] Equals \'Mr.\''); + + await cardView.apiOption('filterValue', ['title', '=', 'Mrs.']); + + await t + .expect(filterText.element.innerText) + .eql('[Title] Equals \'Mrs.\''); +}).before(async () => { + await createWidget('dxCardView', { + ...baseConfig, + ...{ + filterValue: ['title', '=', 'Mr.'], + }, + }); +}); + +test('clearFilter API', async (t) => { + const cardView = new CardView('#container'); + const filterText = cardView.getFilterPanel().getFilterText(); + + await t + .expect(filterText.element.innerText) + .eql('[Title] Equals \'Mr.\'') + .expect(cardView.getCards().count) + .eql(3); + + await cardView.apiClearFilter(); + + await t + .expect(filterText.element.innerText) + .eql('Create Filter') + .expect(cardView.getCards().count) + .eql(4); +}).before(async () => { + await createWidget('dxCardView', { + ...baseConfig, + ...{ + filterValue: ['title', '=', 'Mr.'], + }, + }); +}); diff --git a/e2e/testcafe-devextreme/tests/cardView/filterPanel/behavior.functional.ts b/e2e/testcafe-devextreme/tests/cardView/filterPanel/behavior.functional.ts new file mode 100644 index 000000000000..edf47ccad66d --- /dev/null +++ b/e2e/testcafe-devextreme/tests/cardView/filterPanel/behavior.functional.ts @@ -0,0 +1,235 @@ +import CardView from 'devextreme-testcafe-models/cardView'; +import Button from 'devextreme-testcafe-models/button'; +import url from '../../../helpers/getPageUrl'; +import { createWidget } from '../../../helpers/createWidget'; +import { baseConfig } from './helpers/baseConfig'; + +fixture.disablePageReloads`CardView - FilterPanel Behavior` + .page(url(__dirname, '../../container.html')); + +test('filterEnabled checkbox switches the filter by click', async (t) => { + const cardView = new CardView('#container'); + const filterEnabledCheckbox = cardView.getFilterPanel().getFilterEnabledCheckbox(); + await t + .expect(filterEnabledCheckbox.isChecked) + .notOk() + .expect(cardView.getCards().count) + .eql(4); + + await t + .click(filterEnabledCheckbox.element) + .expect(filterEnabledCheckbox.isChecked) + .ok() + .expect(cardView.getCards().count) + .eql(3); + + await t + .click(filterEnabledCheckbox.element) + .expect(filterEnabledCheckbox.isChecked) + .notOk() + .expect(cardView.getCards().count) + .eql(4); +}).before(async () => { + await createWidget('dxCardView', { + ...baseConfig, + ...{ + filterValue: ['title', '=', 'Mr.'], + filterPanel: { + ...baseConfig.filterPanel, + filterEnabled: false, + }, + }, + }); +}); + +test('filterEnabled checkbox switches the filter by keyboard', async (t) => { + const cardView = new CardView('#container'); + const startButton = new Button('#otherContainer'); + const filterEnabledCheckbox = cardView.getFilterPanel().getFilterEnabledCheckbox(); + + await t + .expect(filterEnabledCheckbox.isChecked) + .notOk() + .expect(cardView.getCards().count) + .eql(4); + + await t + .click(startButton.element) + .pressKey('shift+tab shift+tab shift+tab shift+tab') + .pressKey('space') + .expect(filterEnabledCheckbox.isChecked) + .ok() + .expect(cardView.getCards().count) + .eql(3); + + await t + .click(startButton.element) // TODO: remove this when checkbox focus loosing is fixed + .pressKey('shift+tab shift+tab shift+tab shift+tab') + .pressKey('space') + .expect(filterEnabledCheckbox.isChecked) + .notOk() + .expect(cardView.getCards().count) + .eql(4); +}).before(async () => { + await createWidget('dxButton', { + text: 'Click Here First', + }, '#otherContainer'); + + await createWidget('dxCardView', { + ...baseConfig, + ...{ + filterValue: ['title', '=', 'Mr.'], + filterPanel: { + ...baseConfig.filterPanel, + filterEnabled: false, + }, + }, + }); +}); + +test('FilterIcon opens popup by click', async (t) => { + const cardView = new CardView('#container'); + const popup = cardView.getFilterPanel().getFilterBuilderPopup(); + + await t + .expect(popup.element.exists) + .notOk() + .click(cardView.getFilterPanel().getIconFilter().element) + .expect(popup.element.exists) + .ok(); +}).before(async () => { + await createWidget('dxCardView', { + ...baseConfig, + }); +}); + +test('FilterIcon opens popup by keyboard', async (t) => { + const cardView = new CardView('#container'); + const startButton = new Button('#otherContainer'); + const popup = cardView.getFilterPanel().getFilterBuilderPopup(); + + await t + .expect(popup.element.exists) + .notOk() + .click(startButton.element) + .pressKey('shift+tab shift+tab') + .pressKey('enter') + .expect(popup.element.exists) + .ok(); +}).before(async () => { + await createWidget('dxButton', { + text: 'Click Here First', + }, '#otherContainer'); + + await createWidget('dxCardView', { + ...baseConfig, + }); +}); + +test('FilterText opens popup by click', async (t) => { + const cardView = new CardView('#container'); + const popup = cardView.getFilterPanel().getFilterBuilderPopup(); + + await t + .expect(popup.element.exists) + .notOk() + .click(cardView.getFilterPanel().getFilterText().element) + .expect(popup.element.exists) + .ok(); +}).before(async () => { + await createWidget('dxCardView', { + ...baseConfig, + }); +}); + +test('FilterText opens popup by click by keyboard', async (t) => { + const cardView = new CardView('#container'); + const startButton = new Button('#otherContainer'); + const popup = cardView.getFilterPanel().getFilterBuilderPopup(); + + await t + .expect(popup.element.exists) + .notOk() + .click(startButton.element) + .pressKey('shift+tab') + .pressKey('enter') + .expect(popup.element.exists) + .ok(); +}).before(async () => { + await createWidget('dxButton', { + text: 'Click Here First', + }, '#otherContainer'); + + await createWidget('dxCardView', { + ...baseConfig, + }); +}); + +test('ClearFilter button clears filter by click', async (t) => { + const cardView = new CardView('#container'); + + await t + .expect(cardView.option('filterValue')) + .eql(['title', '=', 'Mr.']); + + await t + .click(cardView.getFilterPanel().getClearFilterButton().element) + .expect(cardView.option('filterValue')) + .eql(null); +}).before(async () => { + await createWidget('dxCardView', { + ...baseConfig, + filterValue: ['title', '=', 'Mr.'], + }); +}); + +test('ClearFilter button clears filter by keyboard', async (t) => { + const cardView = new CardView('#container'); + const startButton = new Button('#otherContainer'); + + await t + .expect(cardView.option('filterValue')) + .eql(['title', '=', 'Mr.']); + + await t + .click(startButton.element) + .pressKey('shift+tab') + .pressKey('enter') + .expect(cardView.option('filterValue')) + .eql(null); +}).before(async () => { + await createWidget('dxCardView', { + ...baseConfig, + filterValue: ['title', '=', 'Mr.'], + }); + + await createWidget('dxButton', { + text: 'Click Here First', + }, '#otherContainer'); +}); + +test('Focus returns to FilterIcon after FilterPopup is closed', async (t) => { + const cardView = new CardView('#container'); + const startButton = new Button('#otherContainer'); + const filterIcon = cardView.getFilterPanel().getIconFilter(); + + await t + .click(startButton.element) + .pressKey('shift+tab shift+tab') + .expect(filterIcon.element.focused) + .ok() + .pressKey('enter') + .expect(filterIcon.element.focused) + .notOk() + .pressKey('esc') + .expect(filterIcon.element.focused) + .ok(); +}).before(async () => { + await createWidget('dxCardView', { + ...baseConfig, + }); + + await createWidget('dxButton', { + text: 'Click Here First', + }, '#otherContainer'); +}); diff --git a/e2e/testcafe-devextreme/tests/cardView/filterPanel/behavior.themes.ts b/e2e/testcafe-devextreme/tests/cardView/filterPanel/behavior.themes.ts new file mode 100644 index 000000000000..e92c62e92a14 --- /dev/null +++ b/e2e/testcafe-devextreme/tests/cardView/filterPanel/behavior.themes.ts @@ -0,0 +1,30 @@ +import CardView from 'devextreme-testcafe-models/cardView'; +import { createScreenshotsComparer } from 'devextreme-screenshot-comparer'; +import url from '../../../helpers/getPageUrl'; +import { createWidget } from '../../../helpers/createWidget'; +import { baseConfig } from './helpers/baseConfig'; +import { testScreenshot } from '../../../helpers/themeUtils'; + +fixture.disablePageReloads`CardView - FilterPanel Appearance` + .page(url(__dirname, '../../container.html')); + +test('FilterPanel and FilterBuilderPopup screenshots', async (t) => { + const cardView = new CardView('#container'); + const popup = cardView.getFilterPanel().getFilterBuilderPopup(); + const { takeScreenshot, compareResults } = createScreenshotsComparer(t); + + await testScreenshot(t, takeScreenshot, 'cardView_FilterPanel.png', { element: cardView.getFilterPanel().element }); + + await t.click(cardView.getFilterPanel().getIconFilter().element); + + await testScreenshot(t, takeScreenshot, 'cardView_FilterBuilderPopup.png', { element: popup.element }); + + await t + .expect(compareResults.isValid()) + .ok(compareResults.errorMessages()); +}).before(async () => { + await createWidget('dxCardView', { + ...baseConfig, + filterValue: ['title', '=', 'Mr.'], + }); +}); diff --git a/e2e/testcafe-devextreme/tests/cardView/filterPanel/etalons/cardView_FilterBuilderPopup (fluent-blue-light).png b/e2e/testcafe-devextreme/tests/cardView/filterPanel/etalons/cardView_FilterBuilderPopup (fluent-blue-light).png new file mode 100644 index 000000000000..a661de8f19c0 Binary files /dev/null and b/e2e/testcafe-devextreme/tests/cardView/filterPanel/etalons/cardView_FilterBuilderPopup (fluent-blue-light).png differ diff --git a/e2e/testcafe-devextreme/tests/cardView/filterPanel/etalons/cardView_FilterBuilderPopup (generic-light).png b/e2e/testcafe-devextreme/tests/cardView/filterPanel/etalons/cardView_FilterBuilderPopup (generic-light).png new file mode 100644 index 000000000000..2e77b00baff1 Binary files /dev/null and b/e2e/testcafe-devextreme/tests/cardView/filterPanel/etalons/cardView_FilterBuilderPopup (generic-light).png differ diff --git a/e2e/testcafe-devextreme/tests/cardView/filterPanel/etalons/cardView_FilterBuilderPopup (material-blue-light).png b/e2e/testcafe-devextreme/tests/cardView/filterPanel/etalons/cardView_FilterBuilderPopup (material-blue-light).png new file mode 100644 index 000000000000..73aecd38b13b Binary files /dev/null and b/e2e/testcafe-devextreme/tests/cardView/filterPanel/etalons/cardView_FilterBuilderPopup (material-blue-light).png differ diff --git a/e2e/testcafe-devextreme/tests/cardView/filterPanel/etalons/cardView_FilterPanel (fluent-blue-light).png b/e2e/testcafe-devextreme/tests/cardView/filterPanel/etalons/cardView_FilterPanel (fluent-blue-light).png new file mode 100644 index 000000000000..6f5e86366ec8 Binary files /dev/null and b/e2e/testcafe-devextreme/tests/cardView/filterPanel/etalons/cardView_FilterPanel (fluent-blue-light).png differ diff --git a/e2e/testcafe-devextreme/tests/cardView/filterPanel/etalons/cardView_FilterPanel (generic-light).png b/e2e/testcafe-devextreme/tests/cardView/filterPanel/etalons/cardView_FilterPanel (generic-light).png new file mode 100644 index 000000000000..b0343b4560a9 Binary files /dev/null and b/e2e/testcafe-devextreme/tests/cardView/filterPanel/etalons/cardView_FilterPanel (generic-light).png differ diff --git a/e2e/testcafe-devextreme/tests/cardView/filterPanel/etalons/cardView_FilterPanel (material-blue-light).png b/e2e/testcafe-devextreme/tests/cardView/filterPanel/etalons/cardView_FilterPanel (material-blue-light).png new file mode 100644 index 000000000000..e0b9976b3b6b Binary files /dev/null and b/e2e/testcafe-devextreme/tests/cardView/filterPanel/etalons/cardView_FilterPanel (material-blue-light).png differ diff --git a/e2e/testcafe-devextreme/tests/cardView/filterPanel/helpers/baseConfig.ts b/e2e/testcafe-devextreme/tests/cardView/filterPanel/helpers/baseConfig.ts new file mode 100644 index 000000000000..8e9cb7ecf2a0 --- /dev/null +++ b/e2e/testcafe-devextreme/tests/cardView/filterPanel/helpers/baseConfig.ts @@ -0,0 +1,22 @@ +import { data } from '../../helpers/simpleArrayData'; + +export const baseConfig = { + dataSource: data, + columns: [ + { + dataField: 'id', + }, + { + dataField: 'title', + }, + { + dataField: 'name', + }, + { + dataField: 'lastName', + }, + ], + filterPanel: { + visible: true, + }, +}; diff --git a/e2e/testcafe-devextreme/tests/cardView/headerFilter/a11y.functional.ts b/e2e/testcafe-devextreme/tests/cardView/headerFilter/a11y.functional.ts new file mode 100644 index 000000000000..b1a60beae0f2 --- /dev/null +++ b/e2e/testcafe-devextreme/tests/cardView/headerFilter/a11y.functional.ts @@ -0,0 +1,66 @@ +import CardView from 'devextreme-testcafe-models/cardView'; +import url from '../../../helpers/getPageUrl'; +import { createWidget } from '../../../helpers/createWidget'; + +fixture.disablePageReloads`HeaderFilter.A11y.Functional` + .page(url(__dirname, '../../container.html')); + +const CARD_VIEW_SELECTOR = '#container'; + +test('should open popup by enter if filter icon in the focused state', async (t) => { + const cardView = new CardView(CARD_VIEW_SELECTOR); + + const firstHeaderItem = cardView + .getHeaderPanel() + .getHeaderItem(); + await t.click(firstHeaderItem.element) + .pressKey('alt+down'); + + // NOTE: We check list here, because this list rendered inside popup + const list = cardView.getHeaderFilterList(); + + await t.expect(list.element.exists).ok(); +}).before(async () => createWidget('dxCardView', { + dataSource: [ + { A: 'A_0' }, + { A: 'A_1' }, + { A: 'A_2' }, + ], + columns: [{ dataField: 'A', caption: 'LONG_COLUMN_A_CAPTION' }], + headerFilter: { + visible: true, + }, + height: 600, +})); + +test('should return focus on the same icon after the popup closing', async (t) => { + const cardView = new CardView(CARD_VIEW_SELECTOR); + + const firstHeaderItem = cardView + .getHeaderPanel() + .getHeaderItem(); + await t.click(firstHeaderItem.element) + .pressKey('alt+down'); + + // NOTE: We check list here, because this list rendered inside popup + const list = cardView.getHeaderFilterList(); + await t.expect(list.element.exists).ok(); + + await t + .pressKey('tab') + .pressKey('tab') + .pressKey('enter'); + + await t.expect(firstHeaderItem.element.focused).ok(); +}).before(async () => createWidget('dxCardView', { + dataSource: [ + { A: 'A_0' }, + { A: 'A_1' }, + { A: 'A_2' }, + ], + columns: [{ dataField: 'A', caption: 'LONG_COLUMN_A_CAPTION' }], + headerFilter: { + visible: true, + }, + height: 600, +})); diff --git a/e2e/testcafe-devextreme/tests/cardView/headerFilter/functional.ts b/e2e/testcafe-devextreme/tests/cardView/headerFilter/functional.ts new file mode 100644 index 000000000000..8863daec461c --- /dev/null +++ b/e2e/testcafe-devextreme/tests/cardView/headerFilter/functional.ts @@ -0,0 +1,542 @@ +import CardView from 'devextreme-testcafe-models/cardView'; +import url from '../../../helpers/getPageUrl'; +import { createWidget } from '../../../helpers/createWidget'; + +// TODO: Write test with remote DataSource after remote grouping will be supported +// TODO: Write integration test with filtering after filtering will be implemented +fixture.disablePageReloads`HeaderFilter.Functional` + .page(url(__dirname, '../../container.html')); + +const CARD_VIEW_SELECTOR = '#container'; + +test('list should contain all column values', async (t) => { + const cardView = new CardView(CARD_VIEW_SELECTOR); + + const filterIcon = cardView + .getHeaderPanel() + .getHeaderItem() + .getFilterIcon(); + await t.click(filterIcon); + + const list = cardView.getHeaderFilterList(); + const itemCount = await list.getItems().count; + + await t.expect(itemCount).eql(5); + + for (let idx = 0; idx < 5; idx += 1) { + await t.expect(list.getItem(idx).text).eql(`A_${idx}`); + } + + await t.click(cardView.element); +}).before(async () => createWidget('dxCardView', { + columns: ['A', 'B', 'C'], + dataSource: [ + { A: 'A_0', B: 'B_0', C: 'C_0' }, + { A: 'A_1', B: 'B_1', C: 'C_1' }, + { A: 'A_2', B: 'B_2', C: 'C_2' }, + { A: 'A_3', B: 'B_3', C: 'C_3' }, + { A: 'A_4', B: 'B_4', C: 'C_4' }, + ], + headerFilter: { + visible: true, + }, + height: 600, +})); + +test('list should contain all column values from all pages', async (t) => { + const cardView = new CardView(CARD_VIEW_SELECTOR); + + const filterIcon = cardView + .getHeaderPanel() + .getHeaderItem() + .getFilterIcon(); + await t.click(filterIcon); + + const list = cardView.getHeaderFilterList(); + const itemCount = await list.getItems().count; + + await t.expect(itemCount).eql(5); + + for (let idx = 0; idx < 5; idx += 1) { + await t.expect(list.getItem(idx).text).eql(`A_${idx}`); + } + + await t.click(cardView.element); +}).before(async () => createWidget('dxCardView', { + columns: ['A', 'B', 'C'], + dataSource: [ + { A: 'A_0', B: 'B_0', C: 'C_0' }, + { A: 'A_1', B: 'B_1', C: 'C_1' }, + { A: 'A_2', B: 'B_2', C: 'C_2' }, + { A: 'A_3', B: 'B_3', C: 'C_3' }, + { A: 'A_4', B: 'B_4', C: 'C_4' }, + ], + headerFilter: { + visible: true, + }, + paging: { + pageSize: 1, + pageIndex: 0, + }, + height: 600, +})); + +test('list should contain all values from computed column', async (t) => { + const cardView = new CardView(CARD_VIEW_SELECTOR); + + const filterIcon = cardView + .getHeaderPanel() + .getHeaderItem() + .getFilterIcon(); + await t.click(filterIcon); + + const list = cardView.getHeaderFilterList(); + const itemCount = await list.getItems().count; + + await t.expect(itemCount).eql(5); + + for (let idx = 0; idx < 3; idx += 1) { + await t.expect(list.getItem(idx).text).eql(`A_${idx}_B_${idx}`); + } + + await t.click(cardView.element); +}).before(async () => createWidget('dxCardView', { + dataSource: [ + { A: 'A_0', B: 'B_0', C: 'C_0' }, + { A: 'A_1', B: 'B_1', C: 'C_1' }, + { A: 'A_2', B: 'B_2', C: 'C_2' }, + { A: 'A_3', B: 'B_3', C: 'C_3' }, + { A: 'A_4', B: 'B_4', C: 'C_4' }, + ], + columns: [ + { + caption: 'Computed', + calculateCellValue: (data) => `${data.A}_${data.B}`, + }, + ], + headerFilter: { + visible: true, + }, + height: 600, +})); + +test('should support custom dataSource', async (t) => { + const cardView = new CardView(CARD_VIEW_SELECTOR); + + const filterIcon = cardView + .getHeaderPanel() + .getHeaderItem() + .getFilterIcon(); + await t.click(filterIcon); + + const list = cardView.getHeaderFilterList(); + const itemCount = await list.getItems().count; + + await t.expect(itemCount).eql(3); + + for (let idx = 0; idx < 3; idx += 1) { + await t.expect(list.getItem(idx).text).eql(`CUSTOM_${idx}`); + } + + await t.click(cardView.element); +}).before(async () => { + await createWidget('dxCardView', { + dataSource: [ + { A: 'A_0', B: 'B_0', C: 'C_0' }, + { A: 'A_1', B: 'B_1', C: 'C_1' }, + { A: 'A_2', B: 'B_2', C: 'C_2' }, + { A: 'A_3', B: 'B_3', C: 'C_3' }, + { A: 'A_4', B: 'B_4', C: 'C_4' }, + ], + columns: [ + { + dataField: 'A', + headerFilter: { + dataSource: [ + { text: 'CUSTOM_0', value: 0 }, + { text: 'CUSTOM_1', value: 1 }, + { text: 'CUSTOM_2', value: 2 }, + ], + }, + }, + 'B', + 'C', + ], + headerFilter: { + visible: true, + }, + height: 600, + }); +}); + +test('should update column options with filterType and values (regular selection)', async (t) => { + const cardView = new CardView(CARD_VIEW_SELECTOR); + + const filterIcon = cardView + .getHeaderPanel() + .getHeaderItem() + .getFilterIcon(); + await t.click(filterIcon); + + const popup = cardView.getHeaderFilterPopup(); + const list = cardView.getHeaderFilterList(); + + const okBtn = popup.getButton(0); + const firstItem = list.getItem(0); + const secondItem = list.getItem(1); + + await t.click(firstItem.element) + .click(secondItem.element) + .click(okBtn.element); + + const columnOptions = await cardView.getColumnOption('A'); + + await t + .expect(columnOptions.headerFilter.filterType).eql(undefined) + .expect(columnOptions.headerFilter.values).eql(['A_0', 'A_1']); + + await t.click(cardView.element); +}).before(async () => { + await createWidget('dxCardView', { + columns: ['A', 'B', 'C'], + dataSource: [ + { A: 'A_0', B: 'B_0', C: 'C_0' }, + { A: 'A_1', B: 'B_1', C: 'C_1' }, + { A: 'A_2', B: 'B_2', C: 'C_2' }, + { A: 'A_3', B: 'B_3', C: 'C_3' }, + { A: 'A_4', B: 'B_4', C: 'C_4' }, + ], + headerFilter: { + visible: true, + }, + height: 600, + }); +}); + +test('should update column options with filterType and values (selectAll case #0)', async (t) => { + const cardView = new CardView(CARD_VIEW_SELECTOR); + + const filterIcon = cardView + .getHeaderPanel() + .getHeaderItem() + .getFilterIcon(); + await t.click(filterIcon); + + const popup = cardView.getHeaderFilterPopup(); + const list = cardView.getHeaderFilterList(); + + const okBtn = popup.getButton(0); + const selectAllCheckbox = list.selectAll.element; + + await t.click(selectAllCheckbox) + .click(okBtn.element); + + const columnOptions = await cardView.getColumnOption('A'); + + await t + .expect(columnOptions.headerFilter.filterType).eql('exclude') + .expect(columnOptions.headerFilter.values).eql(null); + + await t.click(cardView.element); +}).before(async () => createWidget('dxCardView', { + columns: ['A', 'B', 'C'], + dataSource: [ + { A: 'A_0', B: 'B_0', C: 'C_0' }, + { A: 'A_1', B: 'B_1', C: 'C_1' }, + { A: 'A_2', B: 'B_2', C: 'C_2' }, + { A: 'A_3', B: 'B_3', C: 'C_3' }, + { A: 'A_4', B: 'B_4', C: 'C_4' }, + ], + headerFilter: { + visible: true, + }, + height: 600, +})); + +test('should update column options with filterType and values (selectAll case #1)', async (t) => { + const cardView = new CardView(CARD_VIEW_SELECTOR); + + const filterIcon = cardView + .getHeaderPanel() + .getHeaderItem() + .getFilterIcon(); + await t.click(filterIcon); + + const popup = cardView.getHeaderFilterPopup(); + const list = cardView.getHeaderFilterList(); + + const okBtn = popup.getButton(0); + const selectAllCheckbox = list.selectAll.element; + const firstItem = list.getItem(2); + const secondItem = list.getItem(3); + + await t.click(selectAllCheckbox) + .click(firstItem.element) + .click(secondItem.element) + .click(okBtn.element); + + const columnOptions = await cardView.getColumnOption('A'); + + await t + .expect(columnOptions.headerFilter.filterType).eql('exclude') + .expect(columnOptions.headerFilter.values).eql(['A_2', 'A_3']); + + await t.click(cardView.element); +}).before(async () => createWidget('dxCardView', { + columns: ['A', 'B', 'C'], + dataSource: [ + { A: 'A_0', B: 'B_0', C: 'C_0' }, + { A: 'A_1', B: 'B_1', C: 'C_1' }, + { A: 'A_2', B: 'B_2', C: 'C_2' }, + { A: 'A_3', B: 'B_3', C: 'C_3' }, + { A: 'A_4', B: 'B_4', C: 'C_4' }, + ], + headerFilter: { + visible: true, + }, + height: 600, +})); + +test('should apply filter from options (type: "include" by default)', async (t) => { + const cardView = new CardView(CARD_VIEW_SELECTOR); + + const filterIcon = cardView + .getHeaderPanel() + .getHeaderItem() + .getFilterIcon(); + await t.click(filterIcon); + + const list = cardView.getHeaderFilterList(); + + const firstItem = list.getItem(0); + const secondItem = list.getItem(1); + const thirdItem = list.getItem(2); + + await t + .expect(firstItem.checkBox.isChecked).ok() + .expect(secondItem.checkBox.isChecked).ok() + .expect(thirdItem.checkBox.isChecked) + .notOk(); + + await t.click(cardView.element); +}).before(async () => createWidget('dxCardView', { + dataSource: [ + { A: 'A_0', B: 'B_0', C: 'C_0' }, + { A: 'A_1', B: 'B_1', C: 'C_1' }, + { A: 'A_2', B: 'B_2', C: 'C_2' }, + { A: 'A_3', B: 'B_3', C: 'C_3' }, + { A: 'A_4', B: 'B_4', C: 'C_4' }, + ], + columns: [ + { + dataField: 'A', + headerFilter: { + values: ['A_0', 'A_1'], + }, + }, + 'B', + 'C', + ], + headerFilter: { + visible: true, + }, + height: 600, +})); + +test('should apply filter from options (type: "include")', async (t) => { + const cardView = new CardView(CARD_VIEW_SELECTOR); + + const filterIcon = cardView + .getHeaderPanel() + .getHeaderItem() + .getFilterIcon(); + await t.click(filterIcon); + + const list = cardView.getHeaderFilterList(); + + const firstItem = list.getItem(0); + const secondItem = list.getItem(1); + const thirdItem = list.getItem(2); + + await t + .expect(firstItem.checkBox.isChecked).ok() + .expect(secondItem.checkBox.isChecked).ok() + .expect(thirdItem.checkBox.isChecked) + .notOk(); + + await t.click(cardView.element); +}).before(async () => createWidget('dxCardView', { + dataSource: [ + { A: 'A_0', B: 'B_0', C: 'C_0' }, + { A: 'A_1', B: 'B_1', C: 'C_1' }, + { A: 'A_2', B: 'B_2', C: 'C_2' }, + { A: 'A_3', B: 'B_3', C: 'C_3' }, + { A: 'A_4', B: 'B_4', C: 'C_4' }, + ], + columns: [ + { + dataField: 'A', + headerFilter: { + filterType: 'include', + values: ['A_0', 'A_1'], + }, + }, + 'B', + 'C', + ], + headerFilter: { + visible: true, + }, + height: 600, +})); + +test('should apply filter from options (type: "exclude")', async (t) => { + const cardView = new CardView(CARD_VIEW_SELECTOR); + + await t.expect(cardView.getHeaderFilterPopup().element.visible).notOk(); + + const filterIcon = cardView + .getHeaderPanel() + .getHeaderItem() + .getFilterIcon(); + await t.click(filterIcon); + + const list = cardView.getHeaderFilterList(); + + const firstItem = list.getItem(0); + const secondItem = list.getItem(1); + const thirdItem = list.getItem(2); + + await t + .expect(firstItem.checkBox.isChecked).ok() + .expect(secondItem.checkBox.isChecked).ok() + .expect(thirdItem.checkBox.isChecked) + .notOk(); + + await t.click(cardView.element); +}).before(async () => createWidget('dxCardView', { + dataSource: [ + { A: 'A_0', B: 'B_0', C: 'C_0' }, + { A: 'A_1', B: 'B_1', C: 'C_1' }, + { A: 'A_2', B: 'B_2', C: 'C_2' }, + { A: 'A_3', B: 'B_3', C: 'C_3' }, + { A: 'A_4', B: 'B_4', C: 'C_4' }, + ], + columns: [ + { + dataField: 'A', + headerFilter: { + filterType: 'exclude', + values: ['A_2', 'A_3', 'A_4'], + }, + }, + 'B', + 'C', + ], + headerFilter: { + visible: true, + }, + height: 600, +})); + +test('should not update column options if popup cancel btn clicked', async (t) => { + const cardView = new CardView(CARD_VIEW_SELECTOR); + + const filterIcon = cardView + .getHeaderPanel() + .getHeaderItem() + .getFilterIcon(); + await t.click(filterIcon); + + const popup = cardView.getHeaderFilterPopup(); + const list = cardView.getHeaderFilterList(); + + const cancelBtn = popup.getButton(1); + const firstItem = list.getItem(0); + const secondItem = list.getItem(1); + + await t + .click(firstItem.element) + .click(secondItem.element) + .click(cancelBtn.element); + + const columnOptions = await cardView.getColumnOption('A'); + + await t + .expect(columnOptions.headerFilter.filterType).eql(undefined) + .expect(columnOptions.headerFilter.values).eql(['A_4']); + + await t.click(cardView.element); +}).before(async () => createWidget('dxCardView', { + dataSource: [ + { A: 'A_0', B: 'B_0', C: 'C_0' }, + { A: 'A_1', B: 'B_1', C: 'C_1' }, + { A: 'A_2', B: 'B_2', C: 'C_2' }, + { A: 'A_3', B: 'B_3', C: 'C_3' }, + { A: 'A_4', B: 'B_4', C: 'C_4' }, + ], + columns: [ + { + dataField: 'A', + headerFilter: { + values: ['A_4'], + }, + }, + 'B', + 'C', + ], + headerFilter: { + visible: true, + }, + height: 600, +})); + +test('should support custom translations', async (t) => { + const cardView = new CardView(CARD_VIEW_SELECTOR); + + const filterIcon = cardView + .getHeaderPanel() + .getHeaderItem() + .getFilterIcon(); + await t.click(filterIcon); + + const popup = cardView.getHeaderFilterPopup(); + const list = cardView.getHeaderFilterList(); + const doneBtn = popup.getButton(0); + const closeBtn = popup.getButton(1); + const firstItem = list.getItem(0); + + await t.expect(doneBtn.text) + .eql('TEST_OK') + .expect(closeBtn.text) + .eql('TEST_CANCEL') + .expect(firstItem.text) + .eql('TEST_EMPTY'); + + await t.click(cardView.element); +}).before(async () => createWidget('dxCardView', { + dataSource: [ + { A: 'A_0', B: 'B_0', C: 'C_0' }, + { A: 'A_1', B: 'B_1', C: 'C_1' }, + { A: 'A_2', B: 'B_2', C: 'C_2' }, + { A: 'A_3', B: 'B_3', C: 'C_3' }, + { A: 'A_4', B: 'B_4', C: 'C_4' }, + ], + columns: [ + { + dataField: 'A', + calculateCellValue: () => undefined, + }, + 'B', + 'C', + ], + headerFilter: { + visible: true, + texts: { + ok: 'TEST_OK', + cancel: 'TEST_CANCEL', + emptyValue: 'TEST_EMPTY', + }, + }, + height: 600, +})); diff --git a/e2e/testcafe-devextreme/tests/cardView/headerFilter/visual.ts b/e2e/testcafe-devextreme/tests/cardView/headerFilter/visual.ts new file mode 100644 index 000000000000..9cba523769ad --- /dev/null +++ b/e2e/testcafe-devextreme/tests/cardView/headerFilter/visual.ts @@ -0,0 +1,112 @@ +import CardView from 'devextreme-testcafe-models/cardView'; +import { createScreenshotsComparer } from 'devextreme-screenshot-comparer'; +import url from '../../../helpers/getPageUrl'; +import { createWidget } from '../../../helpers/createWidget'; +import { testScreenshot } from '../../../helpers/themeUtils'; + +// TODO: Unskip this fixture after markup will be stabilized +fixture.skip`HeaderFilter.Visual` + .page(url(__dirname, '../../container.html')); + +const CARD_VIEW_SELECTOR = '#container'; + +test('popup with list', async (t) => { + const { takeScreenshot, compareResults } = createScreenshotsComparer(t); + const cardView = new CardView(CARD_VIEW_SELECTOR); + + const filterIcon = cardView + .getHeaderPanel() + .getHeaderItem() + .getFilterIcon(); + await t.click(filterIcon); + + await testScreenshot(t, takeScreenshot, 'card-view_header-filter_popup-with-list.png', { element: cardView.element }); + + await t + .expect(compareResults.isValid()) + .ok(compareResults.errorMessages()); +}).before(async () => createWidget('dxCardView', { + dataSource: [ + { A: 'A_0', B: 'B_0', C: 'C_0' }, + { A: 'A_1', B: 'B_1', C: 'C_1' }, + { A: 'A_2', B: 'B_2', C: 'C_2' }, + { A: 'A_3', B: 'B_3', C: 'C_3' }, + { A: 'A_4', B: 'B_4', C: 'C_4' }, + ], + headerFilter: { + visible: true, + }, + height: 600, +})); + +test('popup with search', async (t) => { + const { takeScreenshot, compareResults } = createScreenshotsComparer(t); + const cardView = new CardView(CARD_VIEW_SELECTOR); + + const filterIcon = cardView + .getHeaderPanel() + .getHeaderItem() + .getFilterIcon(); + await t.click(filterIcon); + + await testScreenshot(t, takeScreenshot, 'card-view_header-filter_popup-with-search.png', { element: cardView.element }); + + await t + .expect(compareResults.isValid()) + .ok(compareResults.errorMessages()); +}).before(async () => createWidget('dxCardView', { + dataSource: [ + { A: 'A_0', B: 'B_0', C: 'C_0' }, + { A: 'A_1', B: 'B_1', C: 'C_1' }, + { A: 'A_2', B: 'B_2', C: 'C_2' }, + { A: 'A_3', B: 'B_3', C: 'C_3' }, + { A: 'A_4', B: 'B_4', C: 'C_4' }, + ], + headerFilter: { + visible: true, + search: { + enabled: true, + }, + }, + height: 600, +})); + +test('popup with tree', async (t) => { + const { takeScreenshot, compareResults } = createScreenshotsComparer(t); + const cardView = new CardView(CARD_VIEW_SELECTOR); + + const filterIcon = cardView + .getHeaderPanel() + .getHeaderItem() + .getFilterIcon(); + await t.click(filterIcon); + + await testScreenshot(t, takeScreenshot, 'card-view_header-filter_popup-with-tree.png', { element: cardView.element }); + + await t + .expect(compareResults.isValid()) + .ok(compareResults.errorMessages()); +}).before(async () => createWidget('dxCardView', { + dataSource: [ + { A: '2024-01-01', B: 'B_0', C: 'C_0' }, + { A: '2024-01-01', B: 'B_1', C: 'C_1' }, + { A: '2024-01-01', B: 'B_2', C: 'C_2' }, + { A: '2025-01-01', B: 'B_3', C: 'C_3' }, + { A: '2025-01-01', B: 'B_4', C: 'C_4' }, + { A: '2026-01-01', B: 'B_5', C: 'C_5' }, + ], + columns: [ + { + dataField: 'A', + dataType: 'date', + // TODO calculateCellValue issue: Remove after task will be complete + calculateCellValue: ({ A }) => new Date(A), + }, + 'B', + 'C', + ], + headerFilter: { + visible: true, + }, + height: 600, +})); diff --git a/e2e/testcafe-devextreme/tests/cardView/sorting/etalons/cardview_allow_sorting_api (fluent-blue-light).png b/e2e/testcafe-devextreme/tests/cardView/sorting/etalons/cardview_allow_sorting_api (fluent-blue-light).png index 4fd6fa46af65..207e2d3c6c45 100644 Binary files a/e2e/testcafe-devextreme/tests/cardView/sorting/etalons/cardview_allow_sorting_api (fluent-blue-light).png and b/e2e/testcafe-devextreme/tests/cardView/sorting/etalons/cardview_allow_sorting_api (fluent-blue-light).png differ diff --git a/e2e/testcafe-devextreme/tests/cardView/sorting/etalons/cardview_allow_sorting_api (generic-light).png b/e2e/testcafe-devextreme/tests/cardView/sorting/etalons/cardview_allow_sorting_api (generic-light).png index 4f613c8823f1..4732bb1955de 100644 Binary files a/e2e/testcafe-devextreme/tests/cardView/sorting/etalons/cardview_allow_sorting_api (generic-light).png and b/e2e/testcafe-devextreme/tests/cardView/sorting/etalons/cardview_allow_sorting_api (generic-light).png differ diff --git a/e2e/testcafe-devextreme/tests/cardView/sorting/etalons/cardview_allow_sorting_api (material-blue-light).png b/e2e/testcafe-devextreme/tests/cardView/sorting/etalons/cardview_allow_sorting_api (material-blue-light).png index 4f478508c862..a7a290562508 100644 Binary files a/e2e/testcafe-devextreme/tests/cardView/sorting/etalons/cardview_allow_sorting_api (material-blue-light).png and b/e2e/testcafe-devextreme/tests/cardView/sorting/etalons/cardview_allow_sorting_api (material-blue-light).png differ diff --git a/e2e/testcafe-devextreme/tests/cardView/sorting/etalons/cardview_calculate_sort_value_is_filed_api (fluent-blue-light).png b/e2e/testcafe-devextreme/tests/cardView/sorting/etalons/cardview_calculate_sort_value_is_filed_api (fluent-blue-light).png index d6af8ac82aba..b467ddad97c7 100644 Binary files a/e2e/testcafe-devextreme/tests/cardView/sorting/etalons/cardview_calculate_sort_value_is_filed_api (fluent-blue-light).png and b/e2e/testcafe-devextreme/tests/cardView/sorting/etalons/cardview_calculate_sort_value_is_filed_api (fluent-blue-light).png differ diff --git a/e2e/testcafe-devextreme/tests/cardView/sorting/etalons/cardview_calculate_sort_value_is_filed_api (generic-light).png b/e2e/testcafe-devextreme/tests/cardView/sorting/etalons/cardview_calculate_sort_value_is_filed_api (generic-light).png index 511a9bce6c8d..40677cd1deb1 100644 Binary files a/e2e/testcafe-devextreme/tests/cardView/sorting/etalons/cardview_calculate_sort_value_is_filed_api (generic-light).png and b/e2e/testcafe-devextreme/tests/cardView/sorting/etalons/cardview_calculate_sort_value_is_filed_api (generic-light).png differ diff --git a/e2e/testcafe-devextreme/tests/cardView/sorting/etalons/cardview_calculate_sort_value_is_filed_api (material-blue-light).png b/e2e/testcafe-devextreme/tests/cardView/sorting/etalons/cardview_calculate_sort_value_is_filed_api (material-blue-light).png index b0a668aaaa01..36f50ebf8503 100644 Binary files a/e2e/testcafe-devextreme/tests/cardView/sorting/etalons/cardview_calculate_sort_value_is_filed_api (material-blue-light).png and b/e2e/testcafe-devextreme/tests/cardView/sorting/etalons/cardview_calculate_sort_value_is_filed_api (material-blue-light).png differ diff --git a/e2e/testcafe-devextreme/tests/cardView/sorting/etalons/cardview_calculate_sort_value_is_function_api (fluent-blue-light).png b/e2e/testcafe-devextreme/tests/cardView/sorting/etalons/cardview_calculate_sort_value_is_function_api (fluent-blue-light).png index 8e1c851fe8ec..439f71cfa073 100644 Binary files a/e2e/testcafe-devextreme/tests/cardView/sorting/etalons/cardview_calculate_sort_value_is_function_api (fluent-blue-light).png and b/e2e/testcafe-devextreme/tests/cardView/sorting/etalons/cardview_calculate_sort_value_is_function_api (fluent-blue-light).png differ diff --git a/e2e/testcafe-devextreme/tests/cardView/sorting/etalons/cardview_calculate_sort_value_is_function_api (generic-light).png b/e2e/testcafe-devextreme/tests/cardView/sorting/etalons/cardview_calculate_sort_value_is_function_api (generic-light).png index 38d2fdf29e97..a9582f15e672 100644 Binary files a/e2e/testcafe-devextreme/tests/cardView/sorting/etalons/cardview_calculate_sort_value_is_function_api (generic-light).png and b/e2e/testcafe-devextreme/tests/cardView/sorting/etalons/cardview_calculate_sort_value_is_function_api (generic-light).png differ diff --git a/e2e/testcafe-devextreme/tests/cardView/sorting/etalons/cardview_calculate_sort_value_is_function_api (material-blue-light).png b/e2e/testcafe-devextreme/tests/cardView/sorting/etalons/cardview_calculate_sort_value_is_function_api (material-blue-light).png index 16402b3da9b3..e41e1af32e21 100644 Binary files a/e2e/testcafe-devextreme/tests/cardView/sorting/etalons/cardview_calculate_sort_value_is_function_api (material-blue-light).png and b/e2e/testcafe-devextreme/tests/cardView/sorting/etalons/cardview_calculate_sort_value_is_function_api (material-blue-light).png differ diff --git a/e2e/testcafe-devextreme/tests/cardView/sorting/etalons/cardview_headers_default_render (fluent-blue-light).png b/e2e/testcafe-devextreme/tests/cardView/sorting/etalons/cardview_headers_default_render (fluent-blue-light).png index a6d10e08af1a..843050edd001 100644 Binary files a/e2e/testcafe-devextreme/tests/cardView/sorting/etalons/cardview_headers_default_render (fluent-blue-light).png and b/e2e/testcafe-devextreme/tests/cardView/sorting/etalons/cardview_headers_default_render (fluent-blue-light).png differ diff --git a/e2e/testcafe-devextreme/tests/cardView/sorting/etalons/cardview_headers_default_render (generic-light).png b/e2e/testcafe-devextreme/tests/cardView/sorting/etalons/cardview_headers_default_render (generic-light).png index 21f645dd344f..77d0f2c70249 100644 Binary files a/e2e/testcafe-devextreme/tests/cardView/sorting/etalons/cardview_headers_default_render (generic-light).png and b/e2e/testcafe-devextreme/tests/cardView/sorting/etalons/cardview_headers_default_render (generic-light).png differ diff --git a/e2e/testcafe-devextreme/tests/cardView/sorting/etalons/cardview_headers_default_render (material-blue-light).png b/e2e/testcafe-devextreme/tests/cardView/sorting/etalons/cardview_headers_default_render (material-blue-light).png index 9206a1df7499..eb295a312854 100644 Binary files a/e2e/testcafe-devextreme/tests/cardView/sorting/etalons/cardview_headers_default_render (material-blue-light).png and b/e2e/testcafe-devextreme/tests/cardView/sorting/etalons/cardview_headers_default_render (material-blue-light).png differ diff --git a/e2e/testcafe-devextreme/tests/cardView/sorting/etalons/cardview_headers_with_multiple_sorting_render (fluent-blue-light).png b/e2e/testcafe-devextreme/tests/cardView/sorting/etalons/cardview_headers_with_multiple_sorting_render (fluent-blue-light).png index 705fad70cf7e..3a65bffcc094 100644 Binary files a/e2e/testcafe-devextreme/tests/cardView/sorting/etalons/cardview_headers_with_multiple_sorting_render (fluent-blue-light).png and b/e2e/testcafe-devextreme/tests/cardView/sorting/etalons/cardview_headers_with_multiple_sorting_render (fluent-blue-light).png differ diff --git a/e2e/testcafe-devextreme/tests/cardView/sorting/etalons/cardview_headers_with_multiple_sorting_render (generic-light).png b/e2e/testcafe-devextreme/tests/cardView/sorting/etalons/cardview_headers_with_multiple_sorting_render (generic-light).png index 33113d4348ab..29b69c2441d9 100644 Binary files a/e2e/testcafe-devextreme/tests/cardView/sorting/etalons/cardview_headers_with_multiple_sorting_render (generic-light).png and b/e2e/testcafe-devextreme/tests/cardView/sorting/etalons/cardview_headers_with_multiple_sorting_render (generic-light).png differ diff --git a/e2e/testcafe-devextreme/tests/cardView/sorting/etalons/cardview_headers_with_multiple_sorting_render (material-blue-light).png b/e2e/testcafe-devextreme/tests/cardView/sorting/etalons/cardview_headers_with_multiple_sorting_render (material-blue-light).png index 45e72019ab16..9373893c8f37 100644 Binary files a/e2e/testcafe-devextreme/tests/cardView/sorting/etalons/cardview_headers_with_multiple_sorting_render (material-blue-light).png and b/e2e/testcafe-devextreme/tests/cardView/sorting/etalons/cardview_headers_with_multiple_sorting_render (material-blue-light).png differ diff --git a/e2e/testcafe-devextreme/tests/cardView/sorting/etalons/cardview_show_sort_indexes_api (fluent-blue-light).png b/e2e/testcafe-devextreme/tests/cardView/sorting/etalons/cardview_show_sort_indexes_api (fluent-blue-light).png index 8cc6569e1122..938535fdf824 100644 Binary files a/e2e/testcafe-devextreme/tests/cardView/sorting/etalons/cardview_show_sort_indexes_api (fluent-blue-light).png and b/e2e/testcafe-devextreme/tests/cardView/sorting/etalons/cardview_show_sort_indexes_api (fluent-blue-light).png differ diff --git a/e2e/testcafe-devextreme/tests/cardView/sorting/etalons/cardview_show_sort_indexes_api (generic-light).png b/e2e/testcafe-devextreme/tests/cardView/sorting/etalons/cardview_show_sort_indexes_api (generic-light).png index 83b2bdba4331..59b651a64d21 100644 Binary files a/e2e/testcafe-devextreme/tests/cardView/sorting/etalons/cardview_show_sort_indexes_api (generic-light).png and b/e2e/testcafe-devextreme/tests/cardView/sorting/etalons/cardview_show_sort_indexes_api (generic-light).png differ diff --git a/e2e/testcafe-devextreme/tests/cardView/sorting/etalons/cardview_show_sort_indexes_api (material-blue-light).png b/e2e/testcafe-devextreme/tests/cardView/sorting/etalons/cardview_show_sort_indexes_api (material-blue-light).png index 355e88d2ad9b..fe3c415f724e 100644 Binary files a/e2e/testcafe-devextreme/tests/cardView/sorting/etalons/cardview_show_sort_indexes_api (material-blue-light).png and b/e2e/testcafe-devextreme/tests/cardView/sorting/etalons/cardview_show_sort_indexes_api (material-blue-light).png differ diff --git a/e2e/testcafe-devextreme/tests/cardView/sorting/etalons/cardview_sort_index_api (fluent-blue-light).png b/e2e/testcafe-devextreme/tests/cardView/sorting/etalons/cardview_sort_index_api (fluent-blue-light).png index 6479bd472336..fb1a0b4f0391 100644 Binary files a/e2e/testcafe-devextreme/tests/cardView/sorting/etalons/cardview_sort_index_api (fluent-blue-light).png and b/e2e/testcafe-devextreme/tests/cardView/sorting/etalons/cardview_sort_index_api (fluent-blue-light).png differ diff --git a/e2e/testcafe-devextreme/tests/cardView/sorting/etalons/cardview_sort_index_api (generic-light).png b/e2e/testcafe-devextreme/tests/cardView/sorting/etalons/cardview_sort_index_api (generic-light).png index 4416f69f9bbf..aafec32238f9 100644 Binary files a/e2e/testcafe-devextreme/tests/cardView/sorting/etalons/cardview_sort_index_api (generic-light).png and b/e2e/testcafe-devextreme/tests/cardView/sorting/etalons/cardview_sort_index_api (generic-light).png differ diff --git a/e2e/testcafe-devextreme/tests/cardView/sorting/etalons/cardview_sort_index_api (material-blue-light).png b/e2e/testcafe-devextreme/tests/cardView/sorting/etalons/cardview_sort_index_api (material-blue-light).png index fa9ee26a29f5..ef359f90b4ad 100644 Binary files a/e2e/testcafe-devextreme/tests/cardView/sorting/etalons/cardview_sort_index_api (material-blue-light).png and b/e2e/testcafe-devextreme/tests/cardView/sorting/etalons/cardview_sort_index_api (material-blue-light).png differ diff --git a/e2e/testcafe-devextreme/tests/cardView/sorting/etalons/cardview_sorting_method_api (fluent-blue-light).png b/e2e/testcafe-devextreme/tests/cardView/sorting/etalons/cardview_sorting_method_api (fluent-blue-light).png index 399f125d7b89..266674f118b3 100644 Binary files a/e2e/testcafe-devextreme/tests/cardView/sorting/etalons/cardview_sorting_method_api (fluent-blue-light).png and b/e2e/testcafe-devextreme/tests/cardView/sorting/etalons/cardview_sorting_method_api (fluent-blue-light).png differ diff --git a/e2e/testcafe-devextreme/tests/cardView/sorting/etalons/cardview_sorting_method_api (generic-light).png b/e2e/testcafe-devextreme/tests/cardView/sorting/etalons/cardview_sorting_method_api (generic-light).png index fb93cb267d4e..b3c96e7dc545 100644 Binary files a/e2e/testcafe-devextreme/tests/cardView/sorting/etalons/cardview_sorting_method_api (generic-light).png and b/e2e/testcafe-devextreme/tests/cardView/sorting/etalons/cardview_sorting_method_api (generic-light).png differ diff --git a/e2e/testcafe-devextreme/tests/cardView/sorting/etalons/cardview_sorting_method_api (material-blue-light).png b/e2e/testcafe-devextreme/tests/cardView/sorting/etalons/cardview_sorting_method_api (material-blue-light).png index cdf598158061..d8c025dc1922 100644 Binary files a/e2e/testcafe-devextreme/tests/cardView/sorting/etalons/cardview_sorting_method_api (material-blue-light).png and b/e2e/testcafe-devextreme/tests/cardView/sorting/etalons/cardview_sorting_method_api (material-blue-light).png differ diff --git a/packages/devextreme-scss/scss/widgets/base/cardView/_index.scss b/packages/devextreme-scss/scss/widgets/base/cardView/_index.scss index 479e1b081f12..cd81dfb11b1d 100644 --- a/packages/devextreme-scss/scss/widgets/base/cardView/_index.scss +++ b/packages/devextreme-scss/scss/widgets/base/cardView/_index.scss @@ -12,3 +12,7 @@ background-color: $cardview-background-color; border-radius: $cardview-border-radius; } + +.dx-cardview-exclude-flexbox { + position: absolute; +} diff --git a/packages/devextreme-scss/scss/widgets/base/cardView/header_panel/item/_index.scss b/packages/devextreme-scss/scss/widgets/base/cardView/header_panel/item/_index.scss index 017cf710782e..8d4bc8f3b498 100644 --- a/packages/devextreme-scss/scss/widgets/base/cardView/header_panel/item/_index.scss +++ b/packages/devextreme-scss/scss/widgets/base/cardView/header_panel/item/_index.scss @@ -18,3 +18,16 @@ border: solid $cardview-header-item-border-width $cardview-header-item-hovered-border-color; } } + +.dx-cardview { + .dx-header-filter-icon { + @include dx-icon(filter); + + color: $cardview-header-filter-icon-empty-color; + font-size: $cardview-header-filter-icon-size; + + &--selected { + color: $cardview-header-filter-icon-selected-color; + } + } +} diff --git a/packages/devextreme-scss/scss/widgets/base/cardView/header_panel/item/_variables.scss b/packages/devextreme-scss/scss/widgets/base/cardView/header_panel/item/_variables.scss index 6e1d5c63136d..9e62897e1fa4 100644 --- a/packages/devextreme-scss/scss/widgets/base/cardView/header_panel/item/_variables.scss +++ b/packages/devextreme-scss/scss/widgets/base/cardView/header_panel/item/_variables.scss @@ -10,3 +10,6 @@ $cardview-header-item-padding-horizontal: 8px !default; $cardview-header-item-padding-vertical: 6px !default; $cardview-header-item-content-gap: 4px !default; +$cardview-header-filter-icon-size: null !default; +$cardview-header-filter-icon-empty-color: null !default; +$cardview-header-filter-icon-selected-color: null !default; diff --git a/packages/devextreme-scss/scss/widgets/fluent/cardView/_colors.scss b/packages/devextreme-scss/scss/widgets/fluent/cardView/_colors.scss index af0215b6d533..8dc0532a29a2 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/cardView/_colors.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/cardView/_colors.scss @@ -18,5 +18,8 @@ $cardview-header-item-hovered-border-color: #BDBDBD !default; $cardview-card-border-color: $base-border-color !default; $cardview-card-background-color: $base-bg !default; +$cardview-header-filter-icon-empty-color: $base-text-color !default; +$cardview-header-filter-icon-selected-color: $base-accent !default; + $cardview-card-content-field-value-highlight-color: $base-inverted-text-color !default; $cardview-card-content-field-value-highlight-background: $base-accent !default; diff --git a/packages/devextreme-scss/scss/widgets/fluent/cardView/_sizes.scss b/packages/devextreme-scss/scss/widgets/fluent/cardView/_sizes.scss index 2f6c2ca6b355..32bda5dd989e 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/cardView/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/cardView/_sizes.scss @@ -61,3 +61,4 @@ $cardview-card-content-cell-padding-horizontal: $cardview-fluent-paddings-12 !de $cardview-card-header-text-size: $cardview-fluent-text-size-16 !default; $cardview-card-header-border-radius: 8px !default; +$cardview-header-filter-icon-size: 20px !default; diff --git a/packages/devextreme-themebuilder/tests/data/dependencies.ts b/packages/devextreme-themebuilder/tests/data/dependencies.ts index 0297c842bab5..58ff9e9158d0 100644 --- a/packages/devextreme-themebuilder/tests/data/dependencies.ts +++ b/packages/devextreme-themebuilder/tests/data/dependencies.ts @@ -16,7 +16,7 @@ export const dependencies: FlatStylesDependencies = { buttongroup: ['validation', 'button'], dropdownbutton: ['validation', 'button', 'buttongroup', 'popup', 'loadindicator', 'loadpanel', 'scrollview', 'list'], calendar: ['validation', 'button'], - cardview: ['button', 'checkbox', 'list', 'loadindicator', 'loadpanel', 'numberbox', 'popup', 'scrollview', 'selectbox', 'sortable', 'textbox', 'toast', 'toolbar', 'validation'], + cardview: ['box', 'button', 'calendar', 'checkbox', 'datebox', 'filterbuilder', 'list', 'loadindicator', 'loadpanel', 'numberbox', 'popup', 'scrollview', 'selectbox', 'sortable', 'textbox', 'toast', 'toolbar', 'treeview', 'validation'], chat: ['button', 'loadindicator', 'loadpanel', 'scrollview', 'textbox', 'validation'], checkbox: ['validation'], numberbox: ['validation', 'button', 'loadindicator'], diff --git a/packages/devextreme/js/__internal/grids/grid_core/header_filter/m_header_filter.ts b/packages/devextreme/js/__internal/grids/grid_core/header_filter/m_header_filter.ts index 9701c24a428c..bebdc62db134 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/header_filter/m_header_filter.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/header_filter/m_header_filter.ts @@ -60,7 +60,7 @@ function ungroupUTCDates(items, dateParts?, dates?) { return dates; } -function convertDataFromUTCToLocal(data, column) { +export function convertDataFromUTCToLocal(data, column) { const dates = ungroupUTCDates(data); // @ts-expect-error const query = dataQuery(dates); @@ -72,11 +72,11 @@ function convertDataFromUTCToLocal(data, column) { return storeHelper.queryByOptions(query, { group }).toArray(); } -function isUTCFormat(format) { +export function isUTCFormat(format) { return format?.slice(-1) === 'Z' || format?.slice(-3) === '\'Z\''; } -const getFormatOptions = function (value, column, currentLevel) { +export const getFormatOptions = function (value, column, currentLevel) { const groupInterval = filterUtils.getGroupInterval(column); const result: any = gridCoreUtils.getFormatOptionsByColumn(column, 'headerFilter'); 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 4eaa86385eb8..17a9e96d867c 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 @@ -213,6 +213,7 @@ export class HeaderFilterView extends Modules.View { const $element = that.element(); const headerFilterOptions = this._normalizeHeaderFilterOptions(options); + const { hidePopupCallback } = options; const { height, width } = headerFilterOptions; const dxPopupOptions = { @@ -247,6 +248,7 @@ export class HeaderFilterView extends Modules.View { text: headerFilterOptions.texts.cancel, onClick() { that.hideHeaderFilterMenu(); + hidePopupCallback?.(); }, }, }, diff --git a/packages/devextreme/js/__internal/grids/new/card_view/__snapshots__/widget.test.ts.snap b/packages/devextreme/js/__internal/grids/new/card_view/__snapshots__/widget.test.ts.snap index 7401846f1f62..23187a4262ea 100644 --- a/packages/devextreme/js/__internal/grids/new/card_view/__snapshots__/widget.test.ts.snap +++ b/packages/devextreme/js/__internal/grids/new/card_view/__snapshots__/widget.test.ts.snap @@ -93,6 +93,13 @@ exports[`common initial render should be successfull 1`] = ` +
+
+
@@ -161,6 +168,16 @@ exports[`common initial render should be successfull 1`] = ` class="dx-gridcore-error-row" />
+
+
+
+
+
+
diff --git a/packages/devextreme/js/__internal/grids/new/card_view/header_panel/__snapshots__/item.test.tsx.snap b/packages/devextreme/js/__internal/grids/new/card_view/header_panel/__snapshots__/item.test.tsx.snap index 0d3c465aaacc..cf32532bde3f 100644 --- a/packages/devextreme/js/__internal/grids/new/card_view/header_panel/__snapshots__/item.test.tsx.snap +++ b/packages/devextreme/js/__internal/grids/new/card_view/header_panel/__snapshots__/item.test.tsx.snap @@ -1,5 +1,19 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`Item should render headerFilter icons if enabled 1`] = ` +
+
+ Column 1 + +
+
+`; + exports[`Item should render sort icons 1`] = `
+
`; @@ -25,6 +42,9 @@ exports[`Item should use column caption as text 1`] = ` tabindex="0" > my column caption +
`; diff --git a/packages/devextreme/js/__internal/grids/new/card_view/header_panel/header_panel.tsx b/packages/devextreme/js/__internal/grids/new/card_view/header_panel/header_panel.tsx index 5604c582a316..b661f60e5489 100644 --- a/packages/devextreme/js/__internal/grids/new/card_view/header_panel/header_panel.tsx +++ b/packages/devextreme/js/__internal/grids/new/card_view/header_panel/header_panel.tsx @@ -23,6 +23,12 @@ export interface HeaderPanelProps { onSortClick: (column: Column, e: MouseEvent) => void; + onFilterClick?: ( + element: Element, + column: Column, + onFilterCloseCallback?: () => void, + ) => void; + itemTemplate?: ComponentType<{ column: Column }>; itemCssClass?: string; @@ -64,6 +70,10 @@ export class HeaderPanel extends Component { onSortClick={(e): void => { this.props.onSortClick(column, e); }} template={this.props.itemTemplate} cssClass={this.props.itemCssClass} + onFilterClick={( + element: Element, + callback?: () => void, + ) => this.props.onFilterClick?.(element, column, callback)} /> ))} diff --git a/packages/devextreme/js/__internal/grids/new/card_view/header_panel/item.test.tsx b/packages/devextreme/js/__internal/grids/new/card_view/header_panel/item.test.tsx index c629938c7f5c..b99e6b13e936 100644 --- a/packages/devextreme/js/__internal/grids/new/card_view/header_panel/item.test.tsx +++ b/packages/devextreme/js/__internal/grids/new/card_view/header_panel/item.test.tsx @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-non-null-assertion */ import { describe, expect, it } from '@jest/globals'; import { render } from 'inferno'; @@ -39,4 +38,15 @@ describe('Item', () => { expect(el).toMatchSnapshot(); }); + + it('should render headerFilter icons if enabled', () => { + const el = setup({ + column: normalizeColumn({ + dataField: 'column1', + allowHeaderFiltering: true, + }), + }); + + expect(el).toMatchSnapshot(); + }); }); diff --git a/packages/devextreme/js/__internal/grids/new/card_view/header_panel/item.tsx b/packages/devextreme/js/__internal/grids/new/card_view/header_panel/item.tsx index 3275d35cc66d..4579ed903000 100644 --- a/packages/devextreme/js/__internal/grids/new/card_view/header_panel/item.tsx +++ b/packages/devextreme/js/__internal/grids/new/card_view/header_panel/item.tsx @@ -1,6 +1,7 @@ import type { Column } from '@ts/grids/new/grid_core/columns_controller/types'; +import { MultipleKeyDownHandler } from '@ts/grids/new/grid_core/keyboard_navigation/index'; import type { ComponentType } from 'inferno'; -import { Component } from 'inferno'; +import { Component, createRef } from 'inferno'; import type { Status } from './column_sortable'; @@ -69,18 +70,41 @@ export interface ItemProps { template?: ComponentType<{ column: Column }>; cssClass?: string; onSortClick?: (e: MouseEvent) => void; + onFilterClick?: ( + element: Element, + onFilterCloseCallback?: () => void, + ) => void; } export class Item extends Component { + private readonly containerRef = createRef(); + + private readonly keyboardHandler = new MultipleKeyDownHandler(['alt', 'arrowdown']); + public render(): JSX.Element { const Template = this.props.column.headerItemTemplate ?? this.props.template; const cssClass = `${CLASSES.item} ${this.props.column.headerItemCssClass ?? ''} ${this.props.cssClass ?? ''}`; + const { headerFilter } = this.props.column; + + const hasHeaderFilterValue = headerFilter?.filterType === 'exclude' + || !!headerFilter?.values?.length; + const headerFilterIconClass = [ + CLASSES.headerFilter.iconEmpty, + hasHeaderFilterValue ? CLASSES.headerFilter.iconFilled : '', + ].join(' '); + return (
this.keyboardHandler.onKeyDownHandler( + event, + this.onFilterKeyPressHandler, + )} + onKeyUp={this.keyboardHandler.onKeyUpHandler} > {this.props.status && ICONS[this.props.status]} {Template &&