From 7e5440a359e90b1d36f1caa7fc2fb31c816ede49 Mon Sep 17 00:00:00 2001 From: simcha90 <56388545+simcha90@users.noreply.github.com> Date: Tue, 20 Apr 2021 14:07:08 +0300 Subject: [PATCH] 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 --- .../dashboard/nativeFilters.test.ts | 18 +- superset-frontend/spec/fixtures/mockStore.js | 31 +++ .../FilterBar/FilterBar.test.tsx | 39 ++-- .../FilterConfigurationLink/index.tsx | 1 + .../nativeFilters/FilterBar/index.tsx | 2 +- .../FiltersConfigForm/FiltersConfigForm.tsx | 2 +- .../FiltersConfigModal.test.tsx | 213 ++++++++++++++++++ .../FiltersConfigModal/FiltersConfigModal.tsx | 2 +- .../nativeFilters/FiltersConfigModal/utils.ts | 2 +- .../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(-) create mode 100644 superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigModal.test.tsx create mode 100644 superset-frontend/src/utils/testUtils.test.ts create mode 100644 superset-frontend/src/utils/testUtils.ts 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 84e6f4ec5..ce73e4f79 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 8bf1a0733..66434c515 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 032133c0f..4b1cee259 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(FILTER_BAR_TEST_ID, true); +const getModalTestId = testWithId(FILTERS_CONFIG_MODAL_TEST_ID, true); +const getDateControlTestId = testWithId( + 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 71717aa14..49dea8339 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 = ({ return ( <> + {/* eslint-disable-next-line jsx-a11y/no-static-element-interactions */} = ({ initialValue={{ value: initDatasetId }} label={{t('Dataset')}} rules={[{ required: !removed, message: t('Dataset is required') }]} - data-test="datasource-input" + {...getFiltersConfigModalTestId('datasource-input')} > (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( + + + , + ); + + 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 17733b3e6..2100ef618 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 1e070715a..9c71a8c86 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 5540246d8..30099498c 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 593966817..974268c58 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 000000000..2411db787 --- /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 000000000..de3dee83c --- /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 extends string ? string : { 'data-test': string }; + +// Using bem standard +export const testWithId = ( + prefix?: string, + idOnly = false, +) => (id?: string): TestWithIdType => { + if (!id && prefix) { + return (idOnly ? prefix : { 'data-test': prefix }) as TestWithIdType; + } + if (id && !prefix) { + return (idOnly ? id : { 'data-test': id }) as TestWithIdType; + } + if (!id && !prefix) { + console.warn('testWithId function has missed "prefix" and "id" params'); + return (idOnly ? '' : { 'data-test': '' }) as TestWithIdType; + } + const newId = `${prefix}__${id}`; + return (idOnly ? newId : { 'data-test': newId }) as TestWithIdType; +};