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
This commit is contained in:
simcha90 2021-04-20 14:07:08 +03:00 committed by GitHub
parent 6119d8e998
commit 7e5440a359
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 374 additions and 44 deletions

View File

@ -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();

View File

@ -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: {} },
};

View File

@ -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();

View File

@ -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"

View File

@ -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';

View File

@ -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}

View File

@ -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 }],
}),
),
);
});
});

View File

@ -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';

View File

@ -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])

View File

@ -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,

View File

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

View File

@ -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('');
});
});

View File

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