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
This commit is contained in:
simcha90 2021-04-20 12:24:12 +03:00 committed by GitHub
parent 9d67b46576
commit d386e66f59
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 498 additions and 113 deletions

View File

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

View File

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

View File

@ -20,7 +20,7 @@ export default {
id: 1234,
slug: 'dashboardSlug',
metadata: {
filter_configuration: [
native_filter_configuration: [
{
id: 'DefaultsID',
filterType: 'filter_select',

View File

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

View File

@ -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(),
});
const setup = (props: FiltersBarProps) => (
<Provider store={mockStore}>
<FilterBar {...props} />
</Provider>
);
test('should render', () => {
const mockedProps = createProps();
const { container } = render(setup(mockedProps));
expect(container).toBeInTheDocument();
});
test('should render the "Filters" heading', () => {
const mockedProps = createProps();
render(setup(mockedProps));
expect(screen.getByText('Filters')).toBeInTheDocument();
});
test('should render the "Clear all" option', () => {
const mockedProps = createProps();
render(setup(mockedProps));
expect(screen.getByText('Clear all')).toBeInTheDocument();
});
test('should render the "Apply" option', () => {
const mockedProps = createProps();
render(setup(mockedProps));
expect(screen.getByText('Apply')).toBeInTheDocument();
});
test('should render the collapse icon', () => {
const mockedProps = createProps();
render(setup(mockedProps));
expect(screen.getByRole('img', { name: 'collapse' })).toBeInTheDocument();
});
test('should render the filter icon', () => {
const mockedProps = createProps();
render(setup(mockedProps));
expect(screen.getByRole('img', { name: 'filter' })).toBeInTheDocument();
});
test('should render the filter control name', () => {
const mockedProps = createProps();
render(setup(mockedProps));
expect(screen.getByText('test')).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();
// @ts-ignore
mockCore.makeApi = jest.fn();
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(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';
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'));
};
const addFilterSetFlow = async () => {
// add filter set
userEvent.click(screen.getByText('Filter Sets (0)'));
expect(screen.getByTestId(getTestId('new-filter-set-button'))).toBeDisabled();
// check description
expect(screen.getByText('Filters (1)')).toBeInTheDocument();
expect(screen.getByText(FILTER_NAME)).toBeInTheDocument();
expect(screen.getAllByText('Last week').length).toBe(2);
// apply filters
userEvent.click(screen.getByTestId(getTestId('apply-button')));
expect(screen.getByTestId(getTestId('new-filter-set-button'))).toBeEnabled();
// 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();
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');
});
});

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 => ({

View File

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

View File

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

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 { 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>

View File

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