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 7e5440a test(native-filters): Filter config modal test (#14245)
7e5440a is described below
commit 7e5440a359e90b1d36f1caa7fc2fb31c816ede49
Author: simcha90 <[email protected]>
AuthorDate: Tue Apr 20 14:07:08 2021 +0300
test(native-filters): Filter config modal test (#14245)
* 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
* test: filter config modal
* test: add select filter
* feat: filter config modal
* lint: fix lint
* lint: fix TS
* test: test testWithId
---
.../integration/dashboard/nativeFilters.test.ts | 18 +-
superset-frontend/spec/fixtures/mockStore.js | 31 +++
.../nativeFilters/FilterBar/FilterBar.test.tsx | 39 ++--
.../FilterBar/FilterConfigurationLink/index.tsx | 1 +
.../components/nativeFilters/FilterBar/index.tsx | 2 +-
.../FiltersConfigForm/FiltersConfigForm.tsx | 2 +-
.../FiltersConfigModal/FiltersConfigModal.test.tsx | 213 +++++++++++++++++++++
.../FiltersConfigModal/FiltersConfigModal.tsx | 2 +-
.../nativeFilters/FiltersConfigModal/utils.ts | 2 +-
.../controls/DateFilterControl/DateFilterLabel.tsx | 2 +-
superset-frontend/src/utils/common.js | 12 --
superset-frontend/src/utils/testUtils.test.ts | 54 ++++++
superset-frontend/src/utils/testUtils.ts | 40 ++++
13 files changed, 374 insertions(+), 44 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 84e6f4e..ce73e4f 100644
---
a/superset-frontend/cypress-base/cypress/integration/dashboard/nativeFilters.test.ts
+++
b/superset-frontend/cypress-base/cypress/integration/dashboard/nativeFilters.test.ts
@@ -62,11 +62,13 @@ describe('Nativefilters', () => {
.type('Country name');
cy.get('.ant-modal')
- .find('[data-test="datasource-input"]')
+ .find('[data-test="filters-config-modal__datasource-input"]')
.click()
.type('wb_health_population');
- cy.get('.ant-modal [data-test="datasource-input"] .Select__menu')
+ cy.get(
+ '.ant-modal [data-test="filters-config-modal__datasource-input"]
.Select__menu',
+ )
.contains('wb_health_population')
.click();
@@ -159,11 +161,13 @@ describe('Nativefilters', () => {
.type('Country name');
cy.get('.ant-modal')
- .find('[data-test="datasource-input"]')
+ .find('[data-test="filters-config-modal__datasource-input"]')
.click()
.type('wb_health_population');
- cy.get('.ant-modal [data-test="datasource-input"] .Select__menu')
+ cy.get(
+ '.ant-modal [data-test="filters-config-modal__datasource-input"]
.Select__menu',
+ )
.contains('wb_health_population')
.click();
@@ -191,12 +195,14 @@ describe('Nativefilters', () => {
.type('Region Name');
cy.get('.ant-modal')
- .find('[data-test="datasource-input"]')
+ .find('[data-test="filters-config-modal__datasource-input"]')
.last()
.click()
.type('wb_health_population');
- cy.get('.ant-modal [data-test="datasource-input"] .Select__menu')
+ cy.get(
+ '.ant-modal [data-test="filters-config-modal__datasource-input"]
.Select__menu',
+ )
.last()
.contains('wb_health_population')
.click();
diff --git a/superset-frontend/spec/fixtures/mockStore.js
b/superset-frontend/spec/fixtures/mockStore.js
index 8bf1a07..66434c5 100644
--- a/superset-frontend/spec/fixtures/mockStore.js
+++ b/superset-frontend/spec/fixtures/mockStore.js
@@ -124,3 +124,34 @@ export const getMockStoreWithNativeFilters = () =>
},
},
});
+
+export const stateWithoutNativeFilters = {
+ ...mockState,
+ charts: {
+ ...mockState.charts,
+ [sliceIdWithAppliedFilter]: {
+ ...mockState.charts[sliceId],
+ queryResponse: {
+ status: 'success',
+ applied_filters: [{ column: 'region' }],
+ rejected_filters: [],
+ },
+ },
+ [sliceIdWithRejectedFilter]: {
+ ...mockState.charts[sliceId],
+ queryResponse: {
+ status: 'success',
+ applied_filters: [],
+ rejected_filters: [{ column: 'region', reason: 'not_in_datasource' }],
+ },
+ },
+ },
+ dashboardInfo: {
+ dash_edit_perm: true,
+ metadata: {
+ native_filter_configuration: [],
+ },
+ },
+ dataMask: {},
+ nativeFilters: { filters: {}, filterSets: {} },
+};
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 032133c..4b1cee2 100644
---
a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterBar.test.tsx
+++
b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterBar.test.tsx
@@ -21,9 +21,13 @@ import React from 'react';
import { render, screen, cleanup } from 'spec/helpers/testing-library';
import { Provider } from 'react-redux';
import userEvent from '@testing-library/user-event';
-import { getMockStore, mockStore } from 'spec/fixtures/mockStore';
+import {
+ getMockStore,
+ mockStore,
+ stateWithoutNativeFilters,
+} from 'spec/fixtures/mockStore';
import * as mockCore from '@superset-ui/core';
-import { testWithId } from 'src/utils/common';
+import { testWithId } from 'src/utils/testUtils';
import { FeatureFlag } from 'src/featureFlags';
import { Preset } from '@superset-ui/core';
import { TimeFilterPlugin, SelectFilterPlugin } from 'src/filters/components';
@@ -48,9 +52,12 @@ class MainPreset extends Preset {
}
}
-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);
+const getTestId = testWithId<string>(FILTER_BAR_TEST_ID, true);
+const getModalTestId = testWithId<string>(FILTERS_CONFIG_MODAL_TEST_ID, true);
+const getDateControlTestId = testWithId<string>(
+ DATE_FILTER_CONTROL_TEST_ID,
+ true,
+);
const FILTER_NAME = 'Time filter 1';
const FILTER_SET_NAME = 'New filter set';
@@ -100,6 +107,7 @@ const changeFilterValue = async () => {
};
describe('FilterBar', () => {
+ new MainPreset().register();
const toggleFiltersBar = jest.fn();
const closedBarProps = {
filtersOpen: false,
@@ -109,16 +117,6 @@ describe('FilterBar', () => {
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);
@@ -169,7 +167,6 @@ describe('FilterBar', () => {
};
});
- new MainPreset().register();
beforeEach(() => {
toggleFiltersBar.mockClear();
fetchMock.get(
@@ -265,7 +262,7 @@ describe('FilterBar', () => {
it('no edit filter button by disabled permissions', () => {
renderWrapper(openedBarProps, {
- ...noFiltersState,
+ ...stateWithoutNativeFilters,
dashboardInfo: { metadata: {} },
});
@@ -285,7 +282,7 @@ describe('FilterBar', () => {
});
it('no filters', () => {
- renderWrapper(openedBarProps, noFiltersState);
+ renderWrapper(openedBarProps, stateWithoutNativeFilters);
expect(screen.getByTestId(getTestId('clear-button'))).toBeDisabled();
expect(screen.getByTestId(getTestId('apply-button'))).toBeDisabled();
@@ -297,7 +294,7 @@ describe('FilterBar', () => {
[FeatureFlag.DASHBOARD_NATIVE_FILTERS_SET]: true,
[FeatureFlag.DASHBOARD_NATIVE_FILTERS]: true,
};
- renderWrapper(openedBarProps, noFiltersState);
+ renderWrapper(openedBarProps, stateWithoutNativeFilters);
expect(screen.getByTestId(getTestId('apply-button'))).toBeDisabled();
addFilterFlow();
@@ -315,7 +312,7 @@ describe('FilterBar', () => {
global.featureFlags = {
[FeatureFlag.DASHBOARD_NATIVE_FILTERS_SET]: true,
};
- renderWrapper(openedBarProps, noFiltersState);
+ renderWrapper(openedBarProps, stateWithoutNativeFilters);
addFilterFlow();
@@ -357,7 +354,7 @@ describe('FilterBar', () => {
[FeatureFlag.DASHBOARD_NATIVE_FILTERS_SET]: true,
[FeatureFlag.DASHBOARD_NATIVE_FILTERS]: true,
};
- renderWrapper(openedBarProps, noFiltersState);
+ renderWrapper(openedBarProps, stateWithoutNativeFilters);
addFilterFlow();
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 71717aa..49dea83 100644
---
a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterConfigurationLink/index.tsx
+++
b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterConfigurationLink/index.tsx
@@ -51,6 +51,7 @@ export const FilterConfigurationLink: React.FC<FCBProps> = ({
return (
<>
+ {/* eslint-disable-next-line jsx-a11y/no-static-element-interactions */}
<HeaderButton
{...getFilterBarTestId('create-filter')}
buttonStyle="link"
diff --git
a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/index.tsx
b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/index.tsx
index eea6aab..4771f03 100644
---
a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/index.tsx
+++
b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/index.tsx
@@ -29,7 +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 { testWithId } from 'src/utils/testUtils';
import { Filter } from 'src/dashboard/components/nativeFilters/types';
import { mapParentFiltersToChildren, TabIds } from './utils';
import FilterSets from './FilterSets';
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 c6ebf9a..0a9ba42 100644
---
a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/FiltersConfigForm.tsx
+++
b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/FiltersConfigForm.tsx
@@ -187,7 +187,7 @@ export const FiltersConfigForm:
React.FC<FiltersConfigFormProps> = ({
initialValue={{ value: initDatasetId }}
label={<StyledLabel>{t('Dataset')}</StyledLabel>}
rules={[{ required: !removed, message: t('Dataset is required') }]}
- data-test="datasource-input"
+ {...getFiltersConfigModalTestId('datasource-input')}
>
<SupersetResourceSelect
initialId={initDatasetId}
diff --git
a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigModal.test.tsx
b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigModal.test.tsx
new file mode 100644
index 0000000..234970b
--- /dev/null
+++
b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigModal.test.tsx
@@ -0,0 +1,213 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import { Preset } from '@superset-ui/core';
+import { SelectFilterPlugin, TimeFilterPlugin } from 'src/filters/components';
+import { render, cleanup, screen } from 'spec/helpers/testing-library';
+import { Provider } from 'react-redux';
+import {
+ getMockStore,
+ mockStore,
+ stateWithoutNativeFilters,
+} from 'spec/fixtures/mockStore';
+import fetchMock from 'fetch-mock';
+import React from 'react';
+import userEvent from '@testing-library/user-event';
+import { testWithId } from 'src/utils/testUtils';
+import { waitFor } from '@testing-library/react';
+import {
+ FILTERS_CONFIG_MODAL_TEST_ID,
+ FiltersConfigModal,
+} from './FiltersConfigModal';
+
+jest.useFakeTimers();
+
+class MainPreset extends Preset {
+ constructor() {
+ super({
+ name: 'Legacy charts',
+ plugins: [
+ new TimeFilterPlugin().configure({ key: 'filter_time' }),
+ new SelectFilterPlugin().configure({ key: 'filter_select' }),
+ ],
+ });
+ }
+}
+
+const getTestId = testWithId<string>(FILTERS_CONFIG_MODAL_TEST_ID, true);
+
+describe('FilterConfigModal', () => {
+ new MainPreset().register();
+ const onSave = jest.fn();
+ const newFilterProps = {
+ isOpen: true,
+ initialFilterId: undefined,
+ createNewOnOpen: true,
+ onSave,
+ onCancel: jest.fn(),
+ };
+ fetchMock.get(
+ 'glob:*/api/v1/dataset/?q=*',
+ {
+ count: 1,
+ ids: [11],
+ label_columns: {
+ id: 'Id',
+ table_name: 'Table Name',
+ },
+ list_columns: ['id', 'table_name'],
+ order_columns: ['table_name'],
+ result: [
+ {
+ id: 11,
+ owners: [],
+ table_name: 'birth_names',
+ },
+ ],
+ },
+ { overwriteRoutes: true },
+ );
+ fetchMock.get(
+ 'glob:*/api/v1/dataset/11',
+ {
+ description_columns: {},
+ id: 3,
+ label_columns: {
+ columns: 'Columns',
+ table_name: 'Table Name',
+ },
+ result: {
+ columns: [
+ {
+ column_name: 'name',
+ groupby: true,
+ id: 334,
+ },
+ ],
+ table_name: 'birth_names',
+ },
+ show_columns: ['id', 'table_name'],
+ },
+ { overwriteRoutes: true },
+ );
+ fetchMock.post(
+ 'glob:*/api/v1/chart/data',
+ {
+ result: [
+ {
+ status: 'success',
+ data: [
+ { name: 'Aaron', count: 453 },
+ { name: 'Abigail', count: 228 },
+ { name: 'Adam', count: 454 },
+ ],
+ applied_filters: [{ column: 'name' }],
+ },
+ ],
+ },
+ { overwriteRoutes: true },
+ );
+
+ const renderWrapper = (
+ props = newFilterProps,
+ state: object = stateWithoutNativeFilters,
+ ) =>
+ render(
+ <Provider
+ store={state ? getMockStore(stateWithoutNativeFilters) : mockStore}
+ >
+ <FiltersConfigModal {...props} />
+ </Provider>,
+ );
+
+ afterEach(() => {
+ cleanup();
+ jest.clearAllMocks();
+ });
+
+ it('Create Select Filter (with datasource and columns) with specific filter
scope', async () => {
+ renderWrapper();
+
+ const FILTER_NAME = 'Select Filter 1';
+
+ // fill name
+ userEvent.type(screen.getByTestId(getTestId('name-input')), FILTER_NAME);
+
+ // fill dataset
+ await waitFor(() =>
+ expect(screen.queryByText('Loading...')).not.toBeInTheDocument(),
+ );
+ userEvent.click(
+ screen
+ .getByTestId(getTestId('datasource-input'))
+ .querySelector('.Select__indicators')!,
+ );
+ userEvent.click(screen.getByText('birth_names'));
+
+ // fill column
+ userEvent.click(screen.getByText('Select...'));
+ await waitFor(() =>
+ expect(screen.queryByText('Loading...')).not.toBeInTheDocument(),
+ );
+ userEvent.click(screen.getByText('name'));
+
+ // fill controls
+ expect(screen.getByText('Multiple select').parentElement!).toHaveAttribute(
+ 'class',
+ 'ant-checkbox-wrapper ant-checkbox-wrapper-checked',
+ );
+ userEvent.click(screen.getByText('Multiple select'));
+ expect(
+ screen.getByText('Multiple select').parentElement!,
+ ).not.toHaveAttribute(
+ 'class',
+ 'ant-checkbox-wrapper ant-checkbox-wrapper-checked',
+ );
+
+ // choose default value
+ userEvent.click(await screen.findByText('3 options'));
+ userEvent.click(screen.getByTitle('Abigail'));
+
+ // fill scoping
+ userEvent.click(screen.getByText('Apply to specific panels'));
+ userEvent.click(screen.getByText('CHART_ID'));
+
+ // saving
+ userEvent.click(screen.getByText('Save'));
+ await waitFor(() =>
+ expect(onSave.mock.calls[0][0][0]).toEqual(
+ expect.objectContaining({
+ cascadeParentIds: [],
+ controlValues: {
+ defaultToFirstItem: false,
+ enableEmptyFilter: false,
+ inverseSelection: false,
+ multiSelect: false,
+ sortAscending: true,
+ },
+ defaultValue: ['Abigail'],
+ filterType: 'filter_select',
+ isInstant: false,
+ name: 'Select Filter 1',
+ scope: { excluded: [], rootPath: [] },
+ targets: [{ column: { name: 'name' }, datasetId: 11 }],
+ }),
+ ),
+ );
+ });
+});
diff --git
a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigModal.tsx
b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigModal.tsx
index 17733b3..2100ef6 100644
---
a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigModal.tsx
+++
b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigModal.tsx
@@ -22,7 +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 { testWithId } from 'src/utils/testUtils';
import { useFilterConfigMap, useFilterConfiguration } from '../state';
import { FilterRemoval, NativeFiltersForm } from './types';
import { FilterConfiguration } from '../types';
diff --git
a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/utils.ts
b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/utils.ts
index 1e07071..9c71a8c 100644
---
a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/utils.ts
+++
b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/utils.ts
@@ -123,7 +123,7 @@ export const createHandleSave = (
removedFilters,
setCurrentFilterId,
);
- if (values == null) return;
+ if (values === null) return;
const newFilterConfig: FilterConfiguration = filterIds
.filter(id => !removedFilters[id])
diff --git
a/superset-frontend/src/explore/components/controls/DateFilterControl/DateFilterLabel.tsx
b/superset-frontend/src/explore/components/controls/DateFilterControl/DateFilterLabel.tsx
index 5540246..3009949 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 { testWithId } from 'src/utils/testUtils';
import { SelectOptionType, FrameType } from './types';
import {
COMMON_RANGE_VALUES_SET,
diff --git a/superset-frontend/src/utils/common.js
b/superset-frontend/src/utils/common.js
index 5939668..974268c 100644
--- a/superset-frontend/src/utils/common.js
+++ b/superset-frontend/src/utils/common.js
@@ -125,15 +125,3 @@ 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 };
-};
diff --git a/superset-frontend/src/utils/testUtils.test.ts
b/superset-frontend/src/utils/testUtils.test.ts
new file mode 100644
index 0000000..2411db7
--- /dev/null
+++ b/superset-frontend/src/utils/testUtils.test.ts
@@ -0,0 +1,54 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { testWithId } from './testUtils';
+
+describe('testUtils', () => {
+ it('testWithId with prefix only', () => {
+ expect(testWithId('prefix')()).toEqual({ 'data-test': 'prefix' });
+ });
+
+ it('testWithId with prefix only and idOnly', () => {
+ expect(testWithId('prefix', true)()).toEqual('prefix');
+ });
+
+ it('testWithId with id only', () => {
+ expect(testWithId()('id')).toEqual({ 'data-test': 'id' });
+ });
+
+ it('testWithId with id only and idOnly', () => {
+ expect(testWithId(undefined, true)('id')).toEqual('id');
+ });
+
+ it('testWithId with prefix and id', () => {
+ expect(testWithId('prefix')('id')).toEqual({ 'data-test': 'prefix__id' });
+ });
+
+ it('testWithId with prefix and id and idOnly', () => {
+ expect(testWithId('prefix', true)('id')).toEqual('prefix__id');
+ });
+
+ it('testWithId without prefix and id', () => {
+ expect(testWithId()()).toEqual({ 'data-test': '' });
+ });
+
+ it('testWithId without prefix and id and idOnly', () => {
+ expect(testWithId(undefined, true)()).toEqual('');
+ });
+});
diff --git a/superset-frontend/src/utils/testUtils.ts
b/superset-frontend/src/utils/testUtils.ts
new file mode 100644
index 0000000..de3dee8
--- /dev/null
+++ b/superset-frontend/src/utils/testUtils.ts
@@ -0,0 +1,40 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import { JsonObject } from '@superset-ui/core';
+
+type TestWithIdType<T> = T extends string ? string : { 'data-test': string };
+
+// Using bem standard
+export const testWithId = <T extends string | JsonObject = JsonObject>(
+ prefix?: string,
+ idOnly = false,
+) => (id?: string): TestWithIdType<T> => {
+ if (!id && prefix) {
+ return (idOnly ? prefix : { 'data-test': prefix }) as TestWithIdType<T>;
+ }
+ if (id && !prefix) {
+ return (idOnly ? id : { 'data-test': id }) as TestWithIdType<T>;
+ }
+ if (!id && !prefix) {
+ console.warn('testWithId function has missed "prefix" and "id" params');
+ return (idOnly ? '' : { 'data-test': '' }) as TestWithIdType<T>;
+ }
+ const newId = `${prefix}__${id}`;
+ return (idOnly ? newId : { 'data-test': newId }) as TestWithIdType<T>;
+};