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 };
+};

Reply via email to