This is an automated email from the ASF dual-hosted git repository.
villebro pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/superset.git
The following commit(s) were added to refs/heads/master by this push:
new d386e66 test(native-filters): add integration tests for filter bar
(#14098)
d386e66 is described below
commit d386e66f590c9df9b7063a15248700603b0f5e4d
Author: simcha90 <[email protected]>
AuthorDate: Tue Apr 20 12:24:12 2021 +0300
test(native-filters): add integration tests for filter bar (#14098)
* test: add tests for filter bar
* test: merge filter bar tests with master
* test: add test for filter set
* test: filter set tests
* test: merge with master
* test: fix tests for filter bar
* fix: fix CR notes
* fix: fix CR notes
---
.../integration/dashboard/nativeFilters.test.ts | 26 +-
superset-frontend/jest.config.js | 2 +-
.../spec/fixtures/mockDashboardInfo.js | 2 +-
superset-frontend/src/components/Icon/index.tsx | 4 +-
.../nativeFilters/FilterBar/FilterBar.test.tsx | 418 ++++++++++++++++++---
.../FilterBar/FilterConfigurationLink/index.tsx | 19 +-
.../FilterBar/FilterSets/EditSection.tsx | 3 +-
.../FilterBar/FilterSets/FilterSetUnit.tsx | 22 +-
.../FilterBar/FilterSets/FiltersHeader.tsx | 7 +-
.../nativeFilters/FilterBar/FilterSets/Footer.tsx | 5 +-
.../nativeFilters/FilterBar/FilterSets/index.tsx | 9 +-
.../nativeFilters/FilterBar/Header/index.tsx | 22 +-
.../components/nativeFilters/FilterBar/index.tsx | 21 +-
.../FilterScope/FilterScope.test.tsx} | 0
.../FiltersConfigForm/FiltersConfigForm.tsx | 5 +-
.../FiltersConfigModal/FiltersConfigModal.tsx | 6 +
.../nativeFilters/FiltersConfigModal/utils.ts | 2 +-
.../controls/DateFilterControl/DateFilterLabel.tsx | 8 +-
superset-frontend/src/utils/common.js | 12 +
19 files changed, 489 insertions(+), 104 deletions(-)
diff --git
a/superset-frontend/cypress-base/cypress/integration/dashboard/nativeFilters.test.ts
b/superset-frontend/cypress-base/cypress/integration/dashboard/nativeFilters.test.ts
index fbb1aea..84e6f4e 100644
---
a/superset-frontend/cypress-base/cypress/integration/dashboard/nativeFilters.test.ts
+++
b/superset-frontend/cypress-base/cypress/integration/dashboard/nativeFilters.test.ts
@@ -52,12 +52,12 @@ describe('Nativefilters', () => {
it('should show filter bar and allow user to create filters ', () => {
cy.get('[data-test="filter-bar"]').should('be.visible');
- cy.get('[data-test="collapse"]').click();
- cy.get('[data-test="create-filter"]').click();
+ cy.get('[data-test="filter-bar__expand-button"]').click();
+ cy.get('[data-test="filter-bar__create-filter"]').click();
cy.get('.ant-modal').should('be.visible');
cy.get('.ant-modal')
- .find('[data-test="name-input"]')
+ .find('[data-test="filters-config-modal__name-input"]')
.click()
.type('Country name');
@@ -90,14 +90,14 @@ describe('Nativefilters', () => {
it('should filter dashboard with selected filter value', () => {
cy.get('[data-test="form-item-value"]').should('be.visible').click();
cy.get('.ant-select-selection-search').type('Hong Kong{enter}');
- cy.get('[data-test="filter-apply-button"]').click();
+ cy.get('[data-test="filter-bar__apply-button"]').click();
cy.get('.treemap').within(() => {
cy.contains('HKG').should('be.visible');
cy.contains('USA').should('not.exist');
});
});
xit('default value is respected after revisit', () => {
- cy.get('[data-test="create-filter"]').click();
+ cy.get('[data-test="filter-bar__create-filter"]').click();
cy.get('.ant-modal').should('be.visible');
// TODO: replace with proper wait for filter to finish loading
cy.wait(1000);
@@ -118,7 +118,7 @@ describe('Nativefilters', () => {
cy.contains('Sweden');
});
it('should allow for deleted filter restore', () => {
- cy.get('[data-test="create-filter"]').click();
+ cy.get('[data-test="filter-bar__create-filter"]').click();
cy.get('.ant-modal').should('be.visible');
cy.get('.ant-tabs-nav-list').within(() => {
cy.get('.ant-tabs-tab-remove').click();
@@ -132,7 +132,7 @@ describe('Nativefilters', () => {
});
it('should stop filtering when filter is removed', () => {
- cy.get('[data-test="create-filter"]').click();
+ cy.get('[data-test="filter-bar__create-filter"]').click();
cy.get('.ant-modal').should('be.visible');
cy.get('.ant-tabs-nav-list').within(() => {
cy.get('.ant-tabs-tab-remove').click();
@@ -149,12 +149,12 @@ describe('Nativefilters', () => {
describe('Parent Filters', () => {
it('should allow for creating parent filters ', () => {
cy.get('[data-test="filter-bar"]').should('be.visible');
- cy.get('[data-test="collapse"]').click();
- cy.get('[data-test="create-filter"]').click();
+ cy.get('[data-test="filter-bar__expand-button"]').click();
+ cy.get('[data-test="filter-bar__create-filter"]').click();
cy.get('.ant-modal').should('be.visible');
cy.get('.ant-modal')
- .find('[data-test="name-input"]')
+ .find('[data-test="filters-config-modal__name-input"]')
.click()
.type('Country name');
@@ -180,12 +180,12 @@ describe('Nativefilters', () => {
.should('be.visible')
.click();
- cy.get('[data-test="create-filter"]').click();
+ cy.get('[data-test="filter-bar__create-filter"]').click();
cy.get('.ant-modal').first().should('be.visible');
cy.get('[data-test=add-filter-button]').first().click();
cy.get('.ant-modal')
- .find('[data-test="name-input"]')
+ .find('[data-test="filters-config-modal__name-input"]')
.last()
.click()
.type('Region Name');
@@ -250,7 +250,7 @@ describe('Nativefilters', () => {
});
it('should stop filtering when parent filter is removed', () => {
- cy.get('[data-test="create-filter"]').click();
+ cy.get('[data-test="filter-bar__create-filter"]').click();
cy.get('.ant-modal').should('be.visible');
cy.get('.ant-tabs-nav-list').within(() => {
cy.get('.ant-tabs-tab-remove').click({ multiple: true });
diff --git a/superset-frontend/jest.config.js b/superset-frontend/jest.config.js
index ce7226a..aef9dd5 100644
--- a/superset-frontend/jest.config.js
+++ b/superset-frontend/jest.config.js
@@ -20,7 +20,7 @@ module.exports = {
testRegex: '(\\/spec|\\/src)\\/.*(_spec|\\.test)\\.(j|t)sx?$',
moduleNameMapper: {
'\\.(css|less)$': '<rootDir>/spec/__mocks__/styleMock.js',
- '\\.(gif|ttf|eot)$': '<rootDir>/spec/__mocks__/fileMock.js',
+ '\\.(gif|ttf|eot|png)$': '<rootDir>/spec/__mocks__/fileMock.js',
'\\.svg$': '<rootDir>/spec/__mocks__/svgrMock.js',
'^src/(.*)$': '<rootDir>/src/$1',
'^spec/(.*)$': '<rootDir>/spec/$1',
diff --git a/superset-frontend/spec/fixtures/mockDashboardInfo.js
b/superset-frontend/spec/fixtures/mockDashboardInfo.js
index 68f3974..4aaba5a 100644
--- a/superset-frontend/spec/fixtures/mockDashboardInfo.js
+++ b/superset-frontend/spec/fixtures/mockDashboardInfo.js
@@ -20,7 +20,7 @@ export default {
id: 1234,
slug: 'dashboardSlug',
metadata: {
- filter_configuration: [
+ native_filter_configuration: [
{
id: 'DefaultsID',
filterType: 'filter_select',
diff --git a/superset-frontend/src/components/Icon/index.tsx
b/superset-frontend/src/components/Icon/index.tsx
index 707d9e3..693354d 100644
--- a/superset-frontend/src/components/Icon/index.tsx
+++ b/superset-frontend/src/components/Icon/index.tsx
@@ -410,12 +410,14 @@ export const iconsRegistry: Record<
export interface IconProps extends SVGProps<SVGSVGElement> {
name: IconName;
+ 'data-test'?: string;
}
const Icon = ({
name,
color = '#666666',
viewBox = '0 0 24 24',
+ 'data-test': dataTest,
...rest
}: IconProps) => {
const Component = iconsRegistry[name];
@@ -426,7 +428,7 @@ const Icon = ({
aria-label={name}
color={color}
viewBox={viewBox}
- data-test={name}
+ data-test={dataTest ?? name}
{...rest}
/>
);
diff --git
a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterBar.test.tsx
b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterBar.test.tsx
index a942e25..032133c 100644
---
a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterBar.test.tsx
+++
b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterBar.test.tsx
@@ -16,71 +16,379 @@
* specific language governing permissions and limitations
* under the License.
*/
+
import React from 'react';
-import { render, screen } from 'spec/helpers/testing-library';
-import userEvent from '@testing-library/user-event';
-import { mockStore } from 'spec/fixtures/mockStore';
+import { render, screen, cleanup } from 'spec/helpers/testing-library';
import { Provider } from 'react-redux';
-import FilterBar, { FiltersBarProps } from '.';
+import userEvent from '@testing-library/user-event';
+import { getMockStore, mockStore } from 'spec/fixtures/mockStore';
+import * as mockCore from '@superset-ui/core';
+import { testWithId } from 'src/utils/common';
+import { FeatureFlag } from 'src/featureFlags';
+import { Preset } from '@superset-ui/core';
+import { TimeFilterPlugin, SelectFilterPlugin } from 'src/filters/components';
+import { DATE_FILTER_CONTROL_TEST_ID } from
'src/explore/components/controls/DateFilterControl/DateFilterLabel';
+import fetchMock from 'fetch-mock';
+import { waitFor } from '@testing-library/react';
+import FilterBar, { FILTER_BAR_TEST_ID } from '.';
+import { FILTERS_CONFIG_MODAL_TEST_ID } from
'../FiltersConfigModal/FiltersConfigModal';
-const createProps = () => ({
- filtersOpen: false,
- toggleFiltersBar: jest.fn(),
-});
+// @ts-ignore
+mockCore.makeApi = jest.fn();
-const setup = (props: FiltersBarProps) => (
- <Provider store={mockStore}>
- <FilterBar {...props} />
- </Provider>
-);
+class MainPreset extends Preset {
+ constructor() {
+ super({
+ name: 'Legacy charts',
+ plugins: [
+ new TimeFilterPlugin().configure({ key: 'filter_time' }),
+ new SelectFilterPlugin().configure({ key: 'filter_select' }),
+ ],
+ });
+ }
+}
-test('should render', () => {
- const mockedProps = createProps();
- const { container } = render(setup(mockedProps));
- expect(container).toBeInTheDocument();
-});
+const getTestId = testWithId(FILTER_BAR_TEST_ID, true);
+const getModalTestId = testWithId(FILTERS_CONFIG_MODAL_TEST_ID, true);
+const getDateControlTestId = testWithId(DATE_FILTER_CONTROL_TEST_ID, true);
-test('should render the "Filters" heading', () => {
- const mockedProps = createProps();
- render(setup(mockedProps));
- expect(screen.getByText('Filters')).toBeInTheDocument();
-});
+const FILTER_NAME = 'Time filter 1';
+const FILTER_SET_NAME = 'New filter set';
-test('should render the "Clear all" option', () => {
- const mockedProps = createProps();
- render(setup(mockedProps));
- expect(screen.getByText('Clear all')).toBeInTheDocument();
-});
+const addFilterFlow = () => {
+ // open filter config modal
+ userEvent.click(screen.getByTestId(getTestId('collapsable')));
+ userEvent.click(screen.getByTestId(getTestId('create-filter')));
+ // select filter
+ userEvent.click(screen.getByText('Select filter'));
+ userEvent.click(screen.getByText('Time filter'));
+ userEvent.type(screen.getByTestId(getModalTestId('name-input')),
FILTER_NAME);
+ userEvent.click(screen.getByText('Save'));
+};
-test('should render the "Apply" option', () => {
- const mockedProps = createProps();
- render(setup(mockedProps));
- expect(screen.getByText('Apply')).toBeInTheDocument();
-});
+const addFilterSetFlow = async () => {
+ // add filter set
+ userEvent.click(screen.getByText('Filter Sets (0)'));
+
expect(screen.getByTestId(getTestId('new-filter-set-button'))).toBeDisabled();
-test('should render the collapse icon', () => {
- const mockedProps = createProps();
- render(setup(mockedProps));
- expect(screen.getByRole('img', { name: 'collapse' })).toBeInTheDocument();
-});
+ // check description
+ expect(screen.getByText('Filters (1)')).toBeInTheDocument();
+ expect(screen.getByText(FILTER_NAME)).toBeInTheDocument();
+ expect(screen.getAllByText('Last week').length).toBe(2);
-test('should render the filter icon', () => {
- const mockedProps = createProps();
- render(setup(mockedProps));
- expect(screen.getByRole('img', { name: 'filter' })).toBeInTheDocument();
-});
+ // apply filters
+ userEvent.click(screen.getByTestId(getTestId('apply-button')));
+ expect(screen.getByTestId(getTestId('new-filter-set-button'))).toBeEnabled();
-test('should render the filter control name', () => {
- const mockedProps = createProps();
- render(setup(mockedProps));
- expect(screen.getByText('test')).toBeInTheDocument();
-});
+ // create filter set
+ userEvent.click(screen.getByText('Create new filter set'));
+ userEvent.click(screen.getByText('Create'));
+
+ // check filter set created
+ expect(await screen.findByRole('img', { name: 'check'
})).toBeInTheDocument();
+ expect(screen.getByTestId(getTestId('filter-set-wrapper'))).toHaveAttribute(
+ 'data-selected',
+ 'true',
+ );
+};
+
+const changeFilterValue = async () => {
+ userEvent.click(screen.getAllByText('Last week')[0]);
+ userEvent.click(screen.getByDisplayValue('Last day'));
+ expect(await screen.findByText(/2021-04-13/)).toBeInTheDocument();
+ userEvent.click(screen.getByTestId(getDateControlTestId('apply-button')));
+};
+
+describe('FilterBar', () => {
+ const toggleFiltersBar = jest.fn();
+ const closedBarProps = {
+ filtersOpen: false,
+ toggleFiltersBar,
+ };
+ const openedBarProps = {
+ filtersOpen: true,
+ toggleFiltersBar,
+ };
+ const noFiltersState = {
+ dashboardInfo: {
+ dash_edit_perm: true,
+ metadata: {
+ native_filter_configuration: [],
+ },
+ },
+ dataMask: {},
+ nativeFilters: { filters: {}, filterSets: {} },
+ };
+
+ const mockApi = jest.fn(async data => {
+ const json = JSON.parse(data.json_metadata);
+ const filterId = json.native_filter_configuration[0].id;
+ return {
+ id: 1234,
+ result: {
+ json_metadata: `{
+
"label_colors":{"Girls":"#FF69B4","Boys":"#ADD8E6","girl":"#FF69B4","boy":"#ADD8E6"},
+ "native_filter_configuration":[{
+ "id":"${filterId}",
+ "name":"${FILTER_NAME}",
+ "filterType":"filter_time",
+ "targets":[{"datasetId":11,"column":{"name":"color"}}],
+ "defaultValue":null,
+ "controlValues":{},
+ "cascadeParentIds":[],
+ "scope":{"rootPath":["ROOT_ID"],"excluded":[]},
+ "isInstant":false
+ }],
+ "filter_sets_configuration":[{
+ "name":"${FILTER_SET_NAME}",
+ "id":"${json.filter_sets_configuration?.[0].id}",
+ "nativeFilters":{
+ "${filterId}":{
+ "id":"${filterId}",
+ "name":"${FILTER_NAME}",
+ "filterType":"filter_time",
+ "targets":[{}],
+ "defaultValue":"Last week",
+ "controlValues":{},
+ "cascadeParentIds":[],
+ "scope":{"rootPath":["ROOT_ID"],"excluded":[]},
+ "isInstant":false
+ }
+ },
+ "dataMask":{
+ "${filterId}":{
+ "extraFormData":{"override_form_data":{"time_range":"Last
week"}},
+ "filterState":{"value":"Last week"},
+ "ownState":{},
+ "id":"${filterId}"
+ }
+ }
+ }]
+ }`,
+ },
+ };
+ });
+
+ new MainPreset().register();
+ beforeEach(() => {
+ toggleFiltersBar.mockClear();
+ fetchMock.get(
+ 'http://localhost/api/v1/time_range/?q=%27Last%20day%27',
+ {
+ result: {
+ since: '2021-04-13T00:00:00',
+ until: '2021-04-14T00:00:00',
+ timeRange: 'Last day',
+ },
+ },
+ { overwriteRoutes: true },
+ );
+ fetchMock.get(
+ 'http://localhost/api/v1/time_range/?q=%27Last%20week%27',
+ {
+ result: {
+ since: '2021-04-07T00:00:00',
+ until: '2021-04-14T00:00:00',
+ timeRange: 'Last week',
+ },
+ },
+ { overwriteRoutes: true },
+ );
+
+ // @ts-ignore
+ mockCore.makeApi = jest.fn(() => mockApi);
+ });
+
+ afterEach(() => {
+ cleanup();
+ jest.clearAllMocks();
+ });
+
+ const renderWrapper = (props = closedBarProps, state?: object) =>
+ render(
+ <Provider store={state ? getMockStore(state) : mockStore}>
+ <FilterBar {...props} />
+ </Provider>,
+ );
+
+ it('should render', () => {
+ const { container } = renderWrapper();
+ expect(container).toBeInTheDocument();
+ });
+
+ it('should render the "Filters" heading', () => {
+ renderWrapper();
+ expect(screen.getByText('Filters')).toBeInTheDocument();
+ });
+
+ it('should render the "Clear all" option', () => {
+ renderWrapper();
+ expect(screen.getByText('Clear all')).toBeInTheDocument();
+ });
+
+ it('should render the "Apply" option', () => {
+ renderWrapper();
+ expect(screen.getByText('Apply')).toBeInTheDocument();
+ });
+
+ it('should render the collapse icon', () => {
+ renderWrapper();
+ expect(screen.getByRole('img', { name: 'collapse' })).toBeInTheDocument();
+ });
+
+ it('should render the filter icon', () => {
+ renderWrapper();
+ expect(screen.getByRole('img', { name: 'filter' })).toBeInTheDocument();
+ });
+
+ it('should render the filter control name', () => {
+ renderWrapper();
+ expect(screen.getByText('test')).toBeInTheDocument();
+ });
+
+ it('should toggle', () => {
+ renderWrapper();
+ const collapse = screen.getByRole('img', { name: 'collapse' });
+ expect(toggleFiltersBar).not.toHaveBeenCalled();
+ userEvent.click(collapse);
+ expect(toggleFiltersBar).toHaveBeenCalled();
+ });
+
+ it('open filter bar', () => {
+ renderWrapper();
+ expect(screen.getByTestId(getTestId('filter-icon'))).toBeInTheDocument();
+ expect(screen.getByTestId(getTestId('expand-button'))).toBeInTheDocument();
+
+ userEvent.click(screen.getByTestId(getTestId('collapsable')));
+ expect(toggleFiltersBar).toHaveBeenCalledWith(true);
+ });
+
+ it('no edit filter button by disabled permissions', () => {
+ renderWrapper(openedBarProps, {
+ ...noFiltersState,
+ dashboardInfo: { metadata: {} },
+ });
+
+ expect(
+ screen.queryByTestId(getTestId('create-filter')),
+ ).not.toBeInTheDocument();
+ });
+
+ it('close filter bar', () => {
+ renderWrapper(openedBarProps);
+ const collapseButton = screen.getByTestId(getTestId('collapse-button'));
+
+ expect(collapseButton).toBeInTheDocument();
+ userEvent.click(collapseButton);
+
+ expect(toggleFiltersBar).toHaveBeenCalledWith(false);
+ });
+
+ it('no filters', () => {
+ renderWrapper(openedBarProps, noFiltersState);
+
+ expect(screen.getByTestId(getTestId('clear-button'))).toBeDisabled();
+ expect(screen.getByTestId(getTestId('apply-button'))).toBeDisabled();
+ });
+
+ it('create filter and apply it flow', async () => {
+ // @ts-ignore
+ global.featureFlags = {
+ [FeatureFlag.DASHBOARD_NATIVE_FILTERS_SET]: true,
+ [FeatureFlag.DASHBOARD_NATIVE_FILTERS]: true,
+ };
+ renderWrapper(openedBarProps, noFiltersState);
+ expect(screen.getByTestId(getTestId('apply-button'))).toBeDisabled();
+
+ addFilterFlow();
+
+ await screen.findByText('All Filters (1)');
+
+ // apply filter
+ expect(screen.getByTestId(getTestId('apply-button'))).toBeEnabled();
+ userEvent.click(screen.getByTestId(getTestId('apply-button')));
+ expect(screen.getByTestId(getTestId('apply-button'))).toBeDisabled();
+ });
+
+ it('add and apply filter set', async () => {
+ // @ts-ignore
+ global.featureFlags = {
+ [FeatureFlag.DASHBOARD_NATIVE_FILTERS_SET]: true,
+ };
+ renderWrapper(openedBarProps, noFiltersState);
+
+ addFilterFlow();
+
+ await screen.findByText('All Filters (1)');
+ expect(screen.getByTestId(getTestId('apply-button'))).toBeEnabled();
+
+ await addFilterSetFlow();
+
+ // change filter
+ userEvent.click(screen.getByText('All Filters (1)'));
+ expect(screen.getByTestId(getTestId('apply-button'))).toBeDisabled();
+
+ await changeFilterValue();
+ await waitFor(() => expect(screen.getAllByText('Last
day').length).toBe(2));
+
+ // apply new filter value
+ expect(screen.getByTestId(getTestId('apply-button'))).toBeEnabled();
+ userEvent.click(screen.getByTestId(getTestId('apply-button')));
+ expect(screen.getByTestId(getTestId('apply-button'))).toBeDisabled();
+
+ // applying filter set
+ userEvent.click(screen.getByText('Filter Sets (1)'));
+ expect(
+ await screen.findByText('Create new filter set'),
+ ).toBeInTheDocument();
+ expect(
+ screen.getByTestId(getTestId('filter-set-wrapper')),
+ ).not.toHaveAttribute('data-selected', 'true');
+ userEvent.click(screen.getByTestId(getTestId('filter-set-wrapper')));
+ expect(await screen.findByText('Last week')).toBeInTheDocument();
+ expect(screen.getByTestId(getTestId('apply-button'))).toBeEnabled();
+ userEvent.click(screen.getByTestId(getTestId('apply-button')));
+ expect(screen.getByTestId(getTestId('apply-button'))).toBeDisabled();
+ });
+
+ it('add and edit filter set', async () => {
+ // @ts-ignore
+ global.featureFlags = {
+ [FeatureFlag.DASHBOARD_NATIVE_FILTERS_SET]: true,
+ [FeatureFlag.DASHBOARD_NATIVE_FILTERS]: true,
+ };
+ renderWrapper(openedBarProps, noFiltersState);
+
+ addFilterFlow();
+
+ await screen.findByText('All Filters (1)');
+ expect(screen.getByTestId(getTestId('apply-button'))).toBeEnabled();
+
+ await addFilterSetFlow();
+
+ userEvent.click(screen.getByTestId(getTestId('filter-set-menu-button')));
+ userEvent.click(screen.getByText('Edit'));
+
+ await changeFilterValue();
+ await waitFor(() => expect(screen.getAllByText('Last
day').length).toBe(1));
+
+ // apply new changes and save them
+ expect(
+ screen.getByTestId(getTestId('filter-set-edit-save')),
+ ).toBeDisabled();
+ expect(screen.getByTestId(getTestId('apply-button'))).toBeEnabled();
+ userEvent.click(screen.getByTestId(getTestId('apply-button')));
+ expect(screen.getByTestId(getTestId('apply-button'))).toBeDisabled();
+
+
expect(screen.getByTestId(getTestId('filter-set-edit-save'))).toBeEnabled();
+ userEvent.click(screen.getByTestId(getTestId('filter-set-edit-save')));
+ expect(screen.queryByText('Save')).not.toBeInTheDocument();
-test('should toggle', () => {
- const mockedProps = createProps();
- render(setup(mockedProps));
- const collapse = screen.getByRole('img', { name: 'collapse' });
- expect(mockedProps.toggleFiltersBar).not.toHaveBeenCalled();
- userEvent.click(collapse);
- expect(mockedProps.toggleFiltersBar).toHaveBeenCalled();
+ expect(
+ Object.values(
+ JSON.parse(mockApi.mock.calls[2][0].json_metadata)
+ .filter_sets_configuration[0].dataMask as object,
+ )[0]?.filterState?.value,
+ ).toBe('Last day');
+ });
});
diff --git
a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterConfigurationLink/index.tsx
b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterConfigurationLink/index.tsx
index db125d7..71717aa 100644
---
a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterConfigurationLink/index.tsx
+++
b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterConfigurationLink/index.tsx
@@ -19,13 +19,20 @@
import React, { useState } from 'react';
import { useDispatch } from 'react-redux';
import { setFilterConfiguration } from 'src/dashboard/actions/nativeFilters';
+import Button from 'src/components/Button';
+import { styled } from '@superset-ui/core';
import { FilterConfiguration } from
'src/dashboard/components/nativeFilters/types';
import { FiltersConfigModal } from
'src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigModal';
+import { getFilterBarTestId } from '..';
export interface FCBProps {
createNewOnOpen?: boolean;
}
+const HeaderButton = styled(Button)`
+ padding: 0;
+`;
+
export const FilterConfigurationLink: React.FC<FCBProps> = ({
createNewOnOpen,
children,
@@ -38,14 +45,20 @@ export const FilterConfigurationLink: React.FC<FCBProps> =
({
}
async function submit(filterConfig: FilterConfiguration) {
- await dispatch(setFilterConfiguration(filterConfig));
+ dispatch(await setFilterConfiguration(filterConfig));
close();
}
return (
<>
- {/* eslint-disable-next-line jsx-a11y/no-static-element-interactions */}
- <div onClick={() => setOpen(true)}>{children}</div>
+ <HeaderButton
+ {...getFilterBarTestId('create-filter')}
+ buttonStyle="link"
+ buttonSize="xsmall"
+ onClick={() => setOpen(true)}
+ >
+ {children}
+ </HeaderButton>
<FiltersConfigModal
isOpen={isOpen}
onSave={submit}
diff --git
a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterSets/EditSection.tsx
b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterSets/EditSection.tsx
index c77ea56..6ab44b4 100644
---
a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterSets/EditSection.tsx
+++
b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterSets/EditSection.tsx
@@ -28,6 +28,7 @@ import { ActionButtons } from './Footer';
import { useDataMask, useFilters, useFilterSets } from '../state';
import { APPLY_FILTERS_HINT, findExistingFilterSet } from './utils';
import { useFilterSetNameDuplicated } from './state';
+import { getFilterBarTestId } from '../index';
const Wrapper = styled.div`
display: grid;
@@ -156,7 +157,7 @@ const EditSection: FC<EditSectionProps> = ({
htmlType="submit"
buttonSize="small"
onClick={handleSave}
- data-test="filter-set-edit-save"
+ {...getFilterBarTestId('filter-set-edit-save')}
>
{t('Save')}
</Button>
diff --git
a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterSets/FilterSetUnit.tsx
b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterSets/FilterSetUnit.tsx
index 2f62893..5132e42 100644
---
a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterSets/FilterSetUnit.tsx
+++
b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterSets/FilterSetUnit.tsx
@@ -23,7 +23,13 @@ import { DataMaskState } from 'src/dataMask/types';
import { CheckOutlined, EllipsisOutlined } from '@ant-design/icons';
import { HandlerFunction, styled, supersetTheme, t } from '@superset-ui/core';
import { Tooltip } from 'src/common/components/Tooltip';
+import Button from 'src/components/Button';
import FiltersHeader from './FiltersHeader';
+import { getFilterBarTestId } from '..';
+
+const HeaderButton = styled(Button)`
+ padding: 0;
+`;
const TitleText = styled.div`
display: flex;
@@ -34,9 +40,10 @@ const TitleText = styled.div`
const IconsBlock = styled.div`
display: flex;
justify-content: flex-end;
- align-items: flex-start;
- & > * {
- ${({ theme }) => `padding-left: ${theme.gridUnit * 2}px`};
+ align-items: center;
+ & > *,
+ & > button.superset-button {
+ ${({ theme }) => `margin-left: ${theme.gridUnit * 2}px`};
}
`;
@@ -102,12 +109,17 @@ const FilterSetUnit: FC<FilterSetUnitProps> = ({
placement="bottomRight"
trigger={['click']}
>
- <EllipsisOutlined
+ <HeaderButton
onClick={e => {
e.stopPropagation();
e.preventDefault();
}}
- />
+ {...getFilterBarTestId('filter-set-menu-button')}
+ buttonStyle="link"
+ buttonSize="xsmall"
+ >
+ <EllipsisOutlined />
+ </HeaderButton>
</Dropdown>
)}
</IconsBlock>
diff --git
a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterSets/FiltersHeader.tsx
b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterSets/FiltersHeader.tsx
index 3b4b91a..4a3a626 100644
---
a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterSets/FiltersHeader.tsx
+++
b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterSets/FiltersHeader.tsx
@@ -25,6 +25,7 @@ import { areObjectsEqual } from 'src/reduxUtils';
import { FilterSet } from 'src/dashboard/reducers/types';
import { getFilterValueForDisplay } from './utils';
import { useFilters } from '../state';
+import { getFilterBarTestId } from '../index';
const FilterHeader = styled.div`
display: flex;
@@ -118,7 +119,11 @@ const FiltersHeader: FC<FiltersHeaderProps> = ({ dataMask,
filterSet }) => {
<CaretDownOutlined rotate={isActive ? 0 : 180} />
)}
>
- <Collapse.Panel header={getFiltersHeader()} key="filters">
+ <Collapse.Panel
+ {...getFilterBarTestId('collapse-filter-set-description')}
+ header={getFiltersHeader()}
+ key="filters"
+ >
{resultFilters.map(getFilterRow)}
</Collapse.Panel>
</StyledCollapse>
diff --git
a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterSets/Footer.tsx
b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterSets/Footer.tsx
index 5681c26..e967f95 100644
---
a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterSets/Footer.tsx
+++
b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterSets/Footer.tsx
@@ -22,6 +22,7 @@ import Button from 'src/components/Button';
import { Tooltip } from 'src/common/components/Tooltip';
import { APPLY_FILTERS_HINT } from './utils';
import { useFilterSetNameDuplicated } from './state';
+import { getFilterBarTestId } from '..';
export type FooterProps = {
filterSetName: string;
@@ -90,7 +91,7 @@ const Footer: FC<FooterProps> = ({
htmlType="submit"
buttonSize="small"
onClick={onCreate}
- data-test="filter-set-create-button"
+ {...getFilterBarTestId('create-filter-set-button')}
>
{t('Create')}
</Button>
@@ -104,8 +105,8 @@ const Footer: FC<FooterProps> = ({
disabled={disabled}
buttonStyle="tertiary"
buttonSize="small"
- data-test="filter-set-create-new-button"
onClick={onEdit}
+ {...getFilterBarTestId('new-filter-set-button')}
>
{t('Create new filter set')}
</Button>
diff --git
a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterSets/index.tsx
b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterSets/index.tsx
index 6513bfa..8947ceb 100644
---
a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterSets/index.tsx
+++
b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterSets/index.tsx
@@ -29,6 +29,7 @@ import { Filter } from '../../types';
import { useFilters, useDataMask, useFilterSets } from '../state';
import Footer from './Footer';
import FilterSetUnit from './FilterSetUnit';
+import { getFilterBarTestId } from '..';
const FilterSetsWrapper = styled.div`
display: grid;
@@ -45,7 +46,7 @@ const FilterSetsWrapper = styled.div`
const FilterSetUnitWrapper = styled.div<{
onClick?: HandlerFunction;
- selected?: boolean;
+ 'data-selected'?: boolean;
}>`
display: grid;
align-items: center;
@@ -57,7 +58,7 @@ const FilterSetUnitWrapper = styled.div<{
border-bottom: 1px solid ${({ theme }) => theme.colors.grayscale.light2};
padding: ${({ theme }) => `${theme.gridUnit * 3}px ${theme.gridUnit * 2}px`};
cursor: ${({ onClick }) => (!onClick ? 'auto' : 'pointer')};
- ${({ theme, selected }) =>
+ ${({ theme, 'data-selected': selected }) =>
`background: ${selected ? theme.colors.primary.light5 : 'transparent'}`};
`;
@@ -97,6 +98,7 @@ const FilterSets: React.FC<FilterSetsProps> = ({
if (isFilterSetChanged) {
return;
}
+
const foundFilterSet = findExistingFilterSet({
dataMaskSelected,
filterSetFilterValues,
@@ -246,7 +248,8 @@ const FilterSets: React.FC<FilterSetsProps> = ({
)}
{filterSetFilterValues.map(filterSet => (
<FilterSetUnitWrapper
- selected={filterSet.id === selectedFiltersSetId}
+ {...getFilterBarTestId('filter-set-wrapper')}
+ data-selected={filterSet.id === selectedFiltersSetId}
onClick={(e: MouseEvent<HTMLElement>) =>
takeFilterSet(filterSet.id, e.target as HTMLElement)
}
diff --git
a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/Header/index.tsx
b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/Header/index.tsx
index 28423ea..f9844a9 100644
---
a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/Header/index.tsx
+++
b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/Header/index.tsx
@@ -27,6 +27,7 @@ import { DataMaskState, DataMaskStateWithId } from
'src/dataMask/types';
import FilterConfigurationLink from
'src/dashboard/components/nativeFilters/FilterBar/FilterConfigurationLink';
import { useFilters } from
'src/dashboard/components/nativeFilters/FilterBar/state';
import { Filter } from 'src/dashboard/components/nativeFilters/types';
+import { getFilterBarTestId } from '..';
const TitleArea = styled.h4`
display: flex;
@@ -55,6 +56,10 @@ const ActionButtons = styled.div`
}
`;
+const HeaderButton = styled(Button)`
+ padding: 0;
+`;
+
type HeaderProps = {
toggleFiltersBar: (arg0: boolean) => void;
onApply: () => void;
@@ -98,14 +103,17 @@ const Header: FC<HeaderProps> = ({
<span>{t('Filters')}</span>
{canEdit && (
<FilterConfigurationLink createNewOnOpen={filterValues.length === 0}>
- <Icon name="edit" role="button" data-test="create-filter" />
+ <Icon name="edit" data-test="create-filter" />
</FilterConfigurationLink>
)}
- <Icon
- name="expand"
- role="button"
+ <HeaderButton
+ {...getFilterBarTestId('collapse-button')}
+ buttonStyle="link"
+ buttonSize="xsmall"
onClick={() => toggleFiltersBar(false)}
- />
+ >
+ <Icon name="expand" />
+ </HeaderButton>
</TitleArea>
<ActionButtons>
<Button
@@ -113,7 +121,7 @@ const Header: FC<HeaderProps> = ({
buttonStyle="tertiary"
buttonSize="small"
onClick={handleClearAll}
- data-test="filter-reset-button"
+ {...getFilterBarTestId('clear-button')}
>
{t('Clear all')}
</Button>
@@ -123,7 +131,7 @@ const Header: FC<HeaderProps> = ({
htmlType="submit"
buttonSize="small"
onClick={onApply}
- data-test="filter-apply-button"
+ {...getFilterBarTestId('apply-button')}
>
{t('Apply')}
</Button>
diff --git
a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/index.tsx
b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/index.tsx
index 87b296b..eea6aab 100644
---
a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/index.tsx
+++
b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/index.tsx
@@ -29,6 +29,7 @@ import { updateDataMask } from 'src/dataMask/actions';
import { DataMaskState } from 'src/dataMask/types';
import { useImmer } from 'use-immer';
import { areObjectsEqual } from 'src/reduxUtils';
+import { testWithId } from 'src/utils/common';
import { Filter } from 'src/dashboard/components/nativeFilters/types';
import { mapParentFiltersToChildren, TabIds } from './utils';
import FilterSets from './FilterSets';
@@ -43,7 +44,10 @@ import EditSection from './FilterSets/EditSection';
import Header from './Header';
import FilterControls from './FilterControls/FilterControls';
-const barWidth = `250px`;
+const BAR_WIDTH = `250px`;
+
+export const FILTER_BAR_TEST_ID = 'filter-bar';
+export const getFilterBarTestId = testWithId(FILTER_BAR_TEST_ID);
const BarWrapper = styled.div`
width: ${({ theme }) => theme.gridUnit * 8}px;
@@ -51,7 +55,7 @@ const BarWrapper = styled.div`
margin: 0;
}
&.open {
- width: ${barWidth}; // arbitrary...
+ width: ${BAR_WIDTH}; // arbitrary...
}
`;
@@ -66,7 +70,7 @@ const Bar = styled.div`
left: 0;
flex-direction: column;
flex-grow: 1;
- width: ${barWidth}; // arbitrary...
+ width: ${BAR_WIDTH}; // arbitrary...
background: ${({ theme }) => theme.colors.grayscale.light5};
border-right: 1px solid ${({ theme }) => theme.colors.grayscale.light2};
min-height: 100%;
@@ -163,7 +167,6 @@ const FilterBar: React.FC<FiltersBarProps> = ({
const filterValues = Object.values<Filter>(filters);
const dataMaskApplied = useDataMask();
const [isFilterSetChanged, setIsFilterSetChanged] = useState(false);
-
const cascadeChildren = useMemo(
() => mapParentFiltersToChildren(filterValues),
[filterValues],
@@ -210,13 +213,17 @@ const FilterBar: React.FC<FiltersBarProps> = ({
!isInitialized || areObjectsEqual(dataMaskSelected, lastAppliedFilterData);
return (
- <BarWrapper data-test="filter-bar" className={cx({ open: filtersOpen })}>
+ <BarWrapper {...getFilterBarTestId()} className={cx({ open: filtersOpen
})}>
<CollapsedBar
+ {...getFilterBarTestId('collapsable')}
className={cx({ open: !filtersOpen })}
onClick={() => toggleFiltersBar(true)}
>
- <StyledCollapseIcon name="collapse" />
- <Icon name="filter" />
+ <StyledCollapseIcon
+ name="collapse"
+ {...getFilterBarTestId('expand-button')}
+ />
+ <Icon name="filter" {...getFilterBarTestId('filter-icon')} />
</CollapsedBar>
<Bar className={cx({ open: filtersOpen })}>
<Header
diff --git
a/superset-frontend/spec/javascripts/dashboard/components/nativeFilters/FilterScope_spec.tsx
b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/FilterScope/FilterScope.test.tsx
similarity index 100%
rename from
superset-frontend/spec/javascripts/dashboard/components/nativeFilters/FilterScope_spec.tsx
rename to
superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/FilterScope/FilterScope.test.tsx
diff --git
a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/FiltersConfigForm.tsx
b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/FiltersConfigForm.tsx
index 97b4288..c6ebf9a 100644
---
a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/FiltersConfigForm.tsx
+++
b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/FiltersConfigForm.tsx
@@ -43,6 +43,7 @@ import ControlItems from './ControlItems';
import FilterScope from './FilterScope/FilterScope';
import RemovedFilter from './RemovedFilter';
import DefaultValue from './DefaultValue';
+import { getFiltersConfigModalTestId } from '../FiltersConfigModal';
const StyledContainer = styled.div`
display: flex;
@@ -153,15 +154,15 @@ export const FiltersConfigForm:
React.FC<FiltersConfigFormProps> = ({
label={<StyledLabel>{t('Filter name')}</StyledLabel>}
initialValue={filterToEdit?.name}
rules={[{ required: !removed, message: t('Name is required') }]}
- data-test="name-input"
>
- <Input />
+ <Input {...getFiltersConfigModalTestId('name-input')} />
</StyledFormItem>
<StyledFormItem
name={['filters', filterId, 'filterType']}
rules={[{ required: !removed, message: t('Name is required') }]}
initialValue={filterToEdit?.filterType || 'filter_select'}
label={<StyledLabel>{t('Filter Type')}</StyledLabel>}
+ {...getFiltersConfigModalTestId('filter-type')}
>
<Select
options={nativeFilterVizTypes.map(filterType => ({
diff --git
a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigModal.tsx
b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigModal.tsx
index 4ae5413..17733b3 100644
---
a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigModal.tsx
+++
b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigModal.tsx
@@ -22,6 +22,7 @@ import { t, styled } from '@superset-ui/core';
import { Form } from 'src/common/components';
import { StyledModal } from 'src/components/Modal';
import ErrorBoundary from 'src/components/ErrorBoundary';
+import { testWithId } from 'src/utils/common';
import { useFilterConfigMap, useFilterConfiguration } from '../state';
import { FilterRemoval, NativeFiltersForm } from './types';
import { FilterConfiguration } from '../types';
@@ -49,6 +50,11 @@ export const StyledForm = styled(Form)`
width: 100%;
`;
+export const FILTERS_CONFIG_MODAL_TEST_ID = 'filters-config-modal';
+export const getFiltersConfigModalTestId = testWithId(
+ FILTERS_CONFIG_MODAL_TEST_ID,
+);
+
export interface FiltersConfigModalProps {
isOpen: boolean;
initialFilterId?: string;
diff --git
a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/utils.ts
b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/utils.ts
index 4b3588f..1e07071 100644
---
a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/utils.ts
+++
b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/utils.ts
@@ -141,7 +141,7 @@ export const createHandleSave = (
}
return {
id,
- controlValues: formInputs.controlValues,
+ controlValues: formInputs.controlValues ?? {},
name: formInputs.name,
filterType: formInputs.filterType,
// for now there will only ever be one target
diff --git
a/superset-frontend/src/explore/components/controls/DateFilterControl/DateFilterLabel.tsx
b/superset-frontend/src/explore/components/controls/DateFilterControl/DateFilterLabel.tsx
index a8e0749..5540246 100644
---
a/superset-frontend/src/explore/components/controls/DateFilterControl/DateFilterLabel.tsx
+++
b/superset-frontend/src/explore/components/controls/DateFilterControl/DateFilterLabel.tsx
@@ -41,7 +41,7 @@ import { Tooltip } from 'src/common/components/Tooltip';
import { DEFAULT_TIME_RANGE } from 'src/explore/constants';
import { useDebouncedEffect } from 'src/explore/exploreUtils';
import { SLOW_DEBOUNCE } from 'src/constants';
-
+import { testWithId } from 'src/utils/common';
import { SelectOptionType, FrameType } from './types';
import {
COMMON_RANGE_VALUES_SET,
@@ -176,6 +176,11 @@ interface DateFilterControlProps {
endpoints?: TimeRangeEndpoints;
}
+export const DATE_FILTER_CONTROL_TEST_ID = 'date-filter-control';
+export const getDateFilterControlTestId = testWithId(
+ DATE_FILTER_CONTROL_TEST_ID,
+);
+
export default function DateFilterLabel(props: DateFilterControlProps) {
const { value = DEFAULT_TIME_RANGE, endpoints, onChange } = props;
const [actualTimeRange, setActualTimeRange] = useState<string>(value);
@@ -324,6 +329,7 @@ export default function DateFilterLabel(props:
DateFilterControlProps) {
disabled={!validTimeRange}
key="apply"
onClick={onSave}
+ {...getDateFilterControlTestId('apply-button')}
>
{t('APPLY')}
</Button>
diff --git a/superset-frontend/src/utils/common.js
b/superset-frontend/src/utils/common.js
index 974268c..5939668 100644
--- a/superset-frontend/src/utils/common.js
+++ b/superset-frontend/src/utils/common.js
@@ -125,3 +125,15 @@ export const detectOS = () => {
return 'Unknown OS';
};
+
+// Using bem standard
+export const testWithId = (prefix, idOnly = false) => id => {
+ if (!id) {
+ return idOnly ? prefix : { 'data-test': prefix };
+ }
+ if (!prefix) {
+ return idOnly ? id : { 'data-test': id };
+ }
+ const newId = `${prefix}__${id}`;
+ return idOnly ? newId : { 'data-test': newId };
+};