mirror of https://github.com/apache/superset.git
test: Adds tests to the filter scope components (#13887)
This commit is contained in:
parent
3b11654c5a
commit
4602ead10c
|
@ -19,8 +19,8 @@
|
||||||
/* eslint-disable no-unused-expressions */
|
/* eslint-disable no-unused-expressions */
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import sinon from 'sinon';
|
import sinon from 'sinon';
|
||||||
import { shallow, mount } from 'enzyme';
|
import { mount } from 'enzyme';
|
||||||
|
import { ThemeProvider, supersetTheme } from '@superset-ui/core';
|
||||||
import CheckboxControl from 'src/explore/components/controls/CheckboxControl';
|
import CheckboxControl from 'src/explore/components/controls/CheckboxControl';
|
||||||
import ControlHeader from 'src/explore/components/ControlHeader';
|
import ControlHeader from 'src/explore/components/ControlHeader';
|
||||||
import Checkbox from 'src/components/Checkbox';
|
import Checkbox from 'src/components/Checkbox';
|
||||||
|
@ -36,20 +36,21 @@ describe('CheckboxControl', () => {
|
||||||
let wrapper;
|
let wrapper;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
wrapper = shallow(<CheckboxControl {...defaultProps} />);
|
wrapper = mount(
|
||||||
|
<ThemeProvider theme={supersetTheme}>
|
||||||
|
<CheckboxControl {...defaultProps} />
|
||||||
|
</ThemeProvider>,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders a Checkbox', () => {
|
it('renders a Checkbox', () => {
|
||||||
const controlHeader = wrapper.find(ControlHeader);
|
const controlHeader = wrapper.childAt(0).find(ControlHeader);
|
||||||
expect(controlHeader).toHaveLength(1);
|
expect(controlHeader).toHaveLength(1);
|
||||||
|
expect(controlHeader.find(Checkbox)).toHaveLength(1);
|
||||||
const headerWrapper = controlHeader.shallow();
|
|
||||||
expect(headerWrapper.find(Checkbox)).toHaveLength(1);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Checks the box when the label is clicked', () => {
|
it('Checks the box when the label is clicked', () => {
|
||||||
const fullComponent = mount(<CheckboxControl {...defaultProps} />);
|
const fullComponent = wrapper.childAt(0);
|
||||||
|
|
||||||
const spy = sinon.spy(fullComponent.instance(), 'onChange');
|
const spy = sinon.spy(fullComponent.instance(), 'onChange');
|
||||||
|
|
||||||
fullComponent.instance().forceUpdate();
|
fullComponent.instance().forceUpdate();
|
||||||
|
|
|
@ -17,51 +17,64 @@
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { useTheme } from '@superset-ui/core';
|
||||||
|
|
||||||
export const CheckboxChecked = () => (
|
export const CheckboxChecked = () => {
|
||||||
<svg
|
const theme = useTheme();
|
||||||
width="18"
|
return (
|
||||||
height="18"
|
<svg
|
||||||
viewBox="0 0 18 18"
|
width="18"
|
||||||
fill="none"
|
height="18"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
viewBox="0 0 18 18"
|
||||||
>
|
fill="none"
|
||||||
<path
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
d="M16 0H2C0.89 0 0 0.9 0 2V16C0 17.1 0.89 18 2 18H16C17.11 18 18 17.1 18 16V2C18 0.9 17.11 0 16 0Z"
|
>
|
||||||
fill="#20A7C9"
|
<path
|
||||||
/>
|
d="M16 0H2C0.89 0 0 0.9 0 2V16C0 17.1 0.89 18 2 18H16C17.11 18 18 17.1 18 16V2C18 0.9 17.11 0 16 0Z"
|
||||||
<path d="M7 14L2 9L3.41 7.59L7 11.17L14.59 3.58L16 5L7 14Z" fill="white" />
|
fill={theme.colors.primary.base}
|
||||||
</svg>
|
/>
|
||||||
);
|
<path
|
||||||
|
d="M7 14L2 9L3.41 7.59L7 11.17L14.59 3.58L16 5L7 14Z"
|
||||||
|
fill="white"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const CheckboxHalfChecked = () => (
|
export const CheckboxHalfChecked = () => {
|
||||||
<svg
|
const theme = useTheme();
|
||||||
width="18"
|
return (
|
||||||
height="18"
|
<svg
|
||||||
viewBox="0 0 18 18"
|
width="18"
|
||||||
fill="none"
|
height="18"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
viewBox="0 0 18 18"
|
||||||
>
|
fill="none"
|
||||||
<path
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
d="M16 0H2C0.9 0 0 0.9 0 2V16C0 17.1 0.9 18 2 18H16C17.1 18 18 17.1 18 16V2C18 0.9 17.1 0 16 0Z"
|
>
|
||||||
fill="#999999"
|
<path
|
||||||
/>
|
d="M16 0H2C0.9 0 0 0.9 0 2V16C0 17.1 0.9 18 2 18H16C17.1 18 18 17.1 18 16V2C18 0.9 17.1 0 16 0Z"
|
||||||
<path d="M14 10H4V8H14V10Z" fill="white" />
|
fill={theme.colors.grayscale.light1}
|
||||||
</svg>
|
/>
|
||||||
);
|
<path d="M14 10H4V8H14V10Z" fill="white" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const CheckboxUnchecked = () => (
|
export const CheckboxUnchecked = () => {
|
||||||
<svg
|
const theme = useTheme();
|
||||||
width="18"
|
return (
|
||||||
height="18"
|
<svg
|
||||||
viewBox="0 0 18 18"
|
width="18"
|
||||||
fill="none"
|
height="18"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
viewBox="0 0 18 18"
|
||||||
>
|
fill="none"
|
||||||
<path
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
d="M16 0H2C0.9 0 0 0.9 0 2V16C0 17.1 0.9 18 2 18H16C17.1 18 18 17.1 18 16V2C18 0.9 17.1 0 16 0Z"
|
>
|
||||||
fill="#CCCCCC"
|
<path
|
||||||
/>
|
d="M16 0H2C0.9 0 0 0.9 0 2V16C0 17.1 0.9 18 2 18H16C17.1 18 18 17.1 18 16V2C18 0.9 17.1 0 16 0Z"
|
||||||
<path d="M16 2V16H2V2H16V2Z" fill="white" />
|
fill={theme.colors.grayscale.light2}
|
||||||
</svg>
|
/>
|
||||||
);
|
<path d="M16 2V16H2V2H16V2Z" fill="white" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
|
@ -19,7 +19,6 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import cx from 'classnames';
|
import cx from 'classnames';
|
||||||
|
|
||||||
import FormLabel from 'src/components/FormLabel';
|
import FormLabel from 'src/components/FormLabel';
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
|
|
|
@ -19,10 +19,9 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import CheckboxTree from 'react-checkbox-tree';
|
import CheckboxTree from 'react-checkbox-tree';
|
||||||
|
import { filterScopeSelectorTreeNodePropShape } from 'src/dashboard/util/propShapes';
|
||||||
import treeIcons from './treeIcons';
|
import treeIcons from './treeIcons';
|
||||||
import renderFilterFieldTreeNodes from './renderFilterFieldTreeNodes';
|
import renderFilterFieldTreeNodes from './renderFilterFieldTreeNodes';
|
||||||
import { filterScopeSelectorTreeNodePropShape } from '../../util/propShapes';
|
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
activeKey: PropTypes.string,
|
activeKey: PropTypes.string,
|
||||||
|
|
|
@ -0,0 +1,373 @@
|
||||||
|
/**
|
||||||
|
* 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 React from 'react';
|
||||||
|
import { supersetTheme } from '@superset-ui/core';
|
||||||
|
import { render, screen } from 'spec/helpers/testing-library';
|
||||||
|
import userEvent from '@testing-library/user-event';
|
||||||
|
import FilterScopeSelector from './FilterScopeSelector';
|
||||||
|
|
||||||
|
const ROOT_ID = 'ROOT_ID';
|
||||||
|
const GRID = 'GRID';
|
||||||
|
const TABS = 'TABS';
|
||||||
|
const TAB = 'TAB';
|
||||||
|
const FILTER_A = 'FILTER_A';
|
||||||
|
const FILTER_B = 'FILTER_B';
|
||||||
|
const FILTER_C = 'FILTER_C';
|
||||||
|
const TAB_A = 'TAB_A';
|
||||||
|
const TAB_B = 'TAB_B';
|
||||||
|
const CHART_A = 'CHART_A';
|
||||||
|
const CHART_B = 'CHART_B';
|
||||||
|
const CHART_C = 'CHART_C';
|
||||||
|
const CHART_D = 'CHART_D';
|
||||||
|
|
||||||
|
const EXPAND_ALL = 'Expand all';
|
||||||
|
const COLLAPSE_ALL = 'Collapse all';
|
||||||
|
const CHECKED = 'checked';
|
||||||
|
const UNCHECKED = 'unchecked';
|
||||||
|
const INDETERMINATE = 'indeterminate';
|
||||||
|
const ALL_FILTERS = 'All filters';
|
||||||
|
const ALL_CHARTS = 'All charts';
|
||||||
|
|
||||||
|
const createProps = () => ({
|
||||||
|
dashboardFilters: {
|
||||||
|
1: {
|
||||||
|
chartId: 1,
|
||||||
|
componentId: 'component-id',
|
||||||
|
datasourceId: 'datasource-id',
|
||||||
|
directPathToFilter: [],
|
||||||
|
isDateFilter: false,
|
||||||
|
isInstantFilter: false,
|
||||||
|
filterName: FILTER_A,
|
||||||
|
columns: { column_b: undefined, column_c: undefined },
|
||||||
|
labels: { column_b: FILTER_B, column_c: FILTER_C },
|
||||||
|
scopes: {
|
||||||
|
column_b: { immune: [], scope: [ROOT_ID] },
|
||||||
|
column_c: { immune: [], scope: [ROOT_ID] },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
layout: {
|
||||||
|
ROOT_ID: { children: [GRID], id: ROOT_ID, type: 'ROOT' },
|
||||||
|
GRID: {
|
||||||
|
children: [TABS],
|
||||||
|
id: GRID,
|
||||||
|
type: GRID,
|
||||||
|
parents: [ROOT_ID],
|
||||||
|
},
|
||||||
|
TABS: {
|
||||||
|
children: [TAB_A, TAB_B],
|
||||||
|
id: TABS,
|
||||||
|
type: TABS,
|
||||||
|
parents: [ROOT_ID, GRID],
|
||||||
|
},
|
||||||
|
TAB_A: {
|
||||||
|
meta: { text: TAB_A },
|
||||||
|
children: [CHART_A, CHART_B],
|
||||||
|
id: TAB_A,
|
||||||
|
type: TAB,
|
||||||
|
parents: [ROOT_ID, GRID, TABS],
|
||||||
|
},
|
||||||
|
TAB_B: {
|
||||||
|
meta: { text: TAB_B },
|
||||||
|
children: [CHART_C, CHART_D],
|
||||||
|
id: TAB_B,
|
||||||
|
type: TAB,
|
||||||
|
parents: [ROOT_ID, GRID, TABS],
|
||||||
|
},
|
||||||
|
CHART_A: {
|
||||||
|
meta: {
|
||||||
|
chartId: 2,
|
||||||
|
sliceName: CHART_A,
|
||||||
|
},
|
||||||
|
children: [],
|
||||||
|
id: CHART_A,
|
||||||
|
type: 'CHART',
|
||||||
|
parents: [ROOT_ID, GRID, TABS, TAB_A],
|
||||||
|
},
|
||||||
|
CHART_B: {
|
||||||
|
meta: {
|
||||||
|
chartId: 3,
|
||||||
|
sliceName: CHART_B,
|
||||||
|
},
|
||||||
|
children: [],
|
||||||
|
id: CHART_B,
|
||||||
|
type: 'CHART',
|
||||||
|
parents: [ROOT_ID, GRID, TABS, TAB_A],
|
||||||
|
},
|
||||||
|
CHART_C: {
|
||||||
|
meta: {
|
||||||
|
chartId: 4,
|
||||||
|
sliceName: CHART_C,
|
||||||
|
},
|
||||||
|
children: [],
|
||||||
|
id: CHART_C,
|
||||||
|
type: 'CHART',
|
||||||
|
parents: [ROOT_ID, GRID, TABS, TAB_B],
|
||||||
|
},
|
||||||
|
CHART_D: {
|
||||||
|
meta: {
|
||||||
|
chartId: 5,
|
||||||
|
sliceName: CHART_D,
|
||||||
|
},
|
||||||
|
children: [],
|
||||||
|
id: CHART_D,
|
||||||
|
type: 'CHART',
|
||||||
|
parents: [ROOT_ID, GRID, TABS, TAB_B],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
updateDashboardFiltersScope: jest.fn(),
|
||||||
|
setUnsavedChanges: jest.fn(),
|
||||||
|
onCloseModal: jest.fn(),
|
||||||
|
});
|
||||||
|
|
||||||
|
type CheckboxState = 'checked' | 'unchecked' | 'indeterminate';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unfortunatelly react-checkbox-tree doesn't provide an easy way to
|
||||||
|
* access the checkbox icon. We need this function to find the element.
|
||||||
|
*/
|
||||||
|
function getCheckboxIcon(element: HTMLElement): Element {
|
||||||
|
const parent = element.parentElement!;
|
||||||
|
if (parent.classList.contains('rct-text')) {
|
||||||
|
return parent.children[1];
|
||||||
|
}
|
||||||
|
return getCheckboxIcon(parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unfortunatelly when using react-checkbox-tree, the only perceived change of a
|
||||||
|
* checkbox state change is the fill color of the SVG icon.
|
||||||
|
*/
|
||||||
|
function getCheckboxState(name: string): CheckboxState {
|
||||||
|
const element = screen.getByRole('link', { name });
|
||||||
|
const svgPath = getCheckboxIcon(element).children[1].children[0].children[0];
|
||||||
|
const fill = svgPath.getAttribute('fill');
|
||||||
|
return fill === supersetTheme.colors.primary.base
|
||||||
|
? CHECKED
|
||||||
|
: fill === supersetTheme.colors.grayscale.light1
|
||||||
|
? INDETERMINATE
|
||||||
|
: UNCHECKED;
|
||||||
|
}
|
||||||
|
|
||||||
|
function clickCheckbox(name: string) {
|
||||||
|
const element = screen.getByRole('link', { name });
|
||||||
|
const checkboxLabel = getCheckboxIcon(element);
|
||||||
|
userEvent.click(checkboxLabel);
|
||||||
|
}
|
||||||
|
|
||||||
|
test('renders with empty filters', () => {
|
||||||
|
render(<FilterScopeSelector {...createProps()} dashboardFilters={{}} />, {
|
||||||
|
useRedux: true,
|
||||||
|
});
|
||||||
|
expect(screen.getByText('Configure filter scopes')).toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
screen.getByText('There are no filters in this dashboard.'),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
expect(screen.getByRole('button', { name: 'Close' })).toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
screen.queryByRole('button', { name: 'Save' }),
|
||||||
|
).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('renders with filters values', () => {
|
||||||
|
render(<FilterScopeSelector {...createProps()} />, { useRedux: true });
|
||||||
|
expect(screen.getByRole('link', { name: FILTER_A })).toBeInTheDocument();
|
||||||
|
expect(screen.getByRole('link', { name: FILTER_B })).toBeInTheDocument();
|
||||||
|
expect(screen.getByRole('link', { name: FILTER_C })).toBeInTheDocument();
|
||||||
|
expect(screen.getByRole('link', { name: TAB_A })).toBeInTheDocument();
|
||||||
|
expect(screen.getByRole('link', { name: TAB_B })).toBeInTheDocument();
|
||||||
|
expect(screen.queryByText(CHART_A)).not.toBeInTheDocument();
|
||||||
|
expect(screen.queryByText(CHART_B)).not.toBeInTheDocument();
|
||||||
|
expect(screen.queryByText(CHART_C)).not.toBeInTheDocument();
|
||||||
|
expect(screen.queryByText(CHART_D)).not.toBeInTheDocument();
|
||||||
|
expect(screen.getByRole('button', { name: 'Close' })).toBeInTheDocument();
|
||||||
|
expect(screen.getByRole('button', { name: 'Save' })).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('collapses/expands all filters', () => {
|
||||||
|
render(<FilterScopeSelector {...createProps()} />, {
|
||||||
|
useRedux: true,
|
||||||
|
});
|
||||||
|
userEvent.click(screen.getAllByRole('button', { name: COLLAPSE_ALL })[0]);
|
||||||
|
expect(screen.getByRole('link', { name: ALL_FILTERS })).toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
screen.queryByRole('link', { name: FILTER_A }),
|
||||||
|
).not.toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
screen.queryByRole('link', { name: FILTER_B }),
|
||||||
|
).not.toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
screen.queryByRole('link', { name: FILTER_C }),
|
||||||
|
).not.toBeInTheDocument();
|
||||||
|
userEvent.click(screen.getAllByRole('button', { name: EXPAND_ALL })[0]);
|
||||||
|
expect(screen.getByRole('link', { name: ALL_FILTERS })).toBeInTheDocument();
|
||||||
|
expect(screen.getByRole('link', { name: FILTER_A })).toBeInTheDocument();
|
||||||
|
expect(screen.getByRole('link', { name: FILTER_B })).toBeInTheDocument();
|
||||||
|
expect(screen.getByRole('link', { name: FILTER_C })).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('collapses/expands all charts', () => {
|
||||||
|
render(<FilterScopeSelector {...createProps()} />, {
|
||||||
|
useRedux: true,
|
||||||
|
});
|
||||||
|
userEvent.click(screen.getAllByRole('button', { name: COLLAPSE_ALL })[1]);
|
||||||
|
expect(screen.getByText(ALL_CHARTS)).toBeInTheDocument();
|
||||||
|
expect(screen.queryByText(CHART_A)).not.toBeInTheDocument();
|
||||||
|
expect(screen.queryByText(CHART_B)).not.toBeInTheDocument();
|
||||||
|
expect(screen.queryByText(CHART_C)).not.toBeInTheDocument();
|
||||||
|
expect(screen.queryByText(CHART_D)).not.toBeInTheDocument();
|
||||||
|
userEvent.click(screen.getAllByRole('button', { name: EXPAND_ALL })[1]);
|
||||||
|
expect(screen.getByText(ALL_CHARTS)).toBeInTheDocument();
|
||||||
|
expect(screen.getByRole('link', { name: CHART_A })).toBeInTheDocument();
|
||||||
|
expect(screen.getByRole('link', { name: CHART_B })).toBeInTheDocument();
|
||||||
|
expect(screen.getByRole('link', { name: CHART_C })).toBeInTheDocument();
|
||||||
|
expect(screen.getByRole('link', { name: CHART_D })).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('searches for a chart', () => {
|
||||||
|
render(<FilterScopeSelector {...createProps()} />, {
|
||||||
|
useRedux: true,
|
||||||
|
});
|
||||||
|
userEvent.type(screen.getByPlaceholderText('Search...'), CHART_C);
|
||||||
|
expect(screen.queryByRole('link', { name: CHART_A })).not.toBeInTheDocument();
|
||||||
|
expect(screen.queryByRole('link', { name: CHART_B })).not.toBeInTheDocument();
|
||||||
|
expect(screen.getByRole('link', { name: CHART_C })).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('selects a leaf filter', () => {
|
||||||
|
render(<FilterScopeSelector {...createProps()} />, {
|
||||||
|
useRedux: true,
|
||||||
|
});
|
||||||
|
expect(getCheckboxState(FILTER_C)).toBe(UNCHECKED);
|
||||||
|
clickCheckbox(FILTER_C);
|
||||||
|
expect(getCheckboxState(FILTER_C)).toBe(CHECKED);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('selects a leaf chart', () => {
|
||||||
|
render(<FilterScopeSelector {...createProps()} />, {
|
||||||
|
useRedux: true,
|
||||||
|
});
|
||||||
|
userEvent.click(screen.getAllByRole('button', { name: EXPAND_ALL })[1]);
|
||||||
|
expect(getCheckboxState(CHART_D)).toBe(UNCHECKED);
|
||||||
|
clickCheckbox(CHART_D);
|
||||||
|
expect(getCheckboxState(CHART_D)).toBe(CHECKED);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('selects a branch of filters', () => {
|
||||||
|
render(<FilterScopeSelector {...createProps()} />, {
|
||||||
|
useRedux: true,
|
||||||
|
});
|
||||||
|
expect(getCheckboxState(FILTER_A)).toBe(UNCHECKED);
|
||||||
|
expect(getCheckboxState(FILTER_B)).toBe(UNCHECKED);
|
||||||
|
expect(getCheckboxState(FILTER_C)).toBe(UNCHECKED);
|
||||||
|
clickCheckbox(FILTER_A);
|
||||||
|
expect(getCheckboxState(FILTER_A)).toBe(CHECKED);
|
||||||
|
expect(getCheckboxState(FILTER_B)).toBe(CHECKED);
|
||||||
|
expect(getCheckboxState(FILTER_C)).toBe(CHECKED);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('selects a branch of charts', () => {
|
||||||
|
render(<FilterScopeSelector {...createProps()} />, {
|
||||||
|
useRedux: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const tabA = screen.getByText(TAB_A);
|
||||||
|
userEvent.click(tabA);
|
||||||
|
|
||||||
|
expect(getCheckboxState(TAB_A)).toBe(UNCHECKED);
|
||||||
|
expect(getCheckboxState(CHART_A)).toBe(UNCHECKED);
|
||||||
|
expect(getCheckboxState(CHART_B)).toBe(UNCHECKED);
|
||||||
|
clickCheckbox(TAB_A);
|
||||||
|
expect(getCheckboxState(TAB_A)).toBe(CHECKED);
|
||||||
|
expect(getCheckboxState(CHART_A)).toBe(CHECKED);
|
||||||
|
expect(getCheckboxState(CHART_B)).toBe(CHECKED);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('selects all filters', () => {
|
||||||
|
render(<FilterScopeSelector {...createProps()} />, {
|
||||||
|
useRedux: true,
|
||||||
|
});
|
||||||
|
userEvent.click(screen.getAllByRole('button', { name: EXPAND_ALL })[0]);
|
||||||
|
expect(getCheckboxState(ALL_FILTERS)).toBe(UNCHECKED);
|
||||||
|
expect(getCheckboxState(FILTER_A)).toBe(UNCHECKED);
|
||||||
|
expect(getCheckboxState(FILTER_B)).toBe(UNCHECKED);
|
||||||
|
expect(getCheckboxState(FILTER_C)).toBe(UNCHECKED);
|
||||||
|
clickCheckbox(ALL_FILTERS);
|
||||||
|
expect(getCheckboxState(ALL_FILTERS)).toBe(CHECKED);
|
||||||
|
expect(getCheckboxState(FILTER_A)).toBe(CHECKED);
|
||||||
|
expect(getCheckboxState(FILTER_B)).toBe(CHECKED);
|
||||||
|
expect(getCheckboxState(FILTER_C)).toBe(CHECKED);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('selects all charts', () => {
|
||||||
|
render(<FilterScopeSelector {...createProps()} />, {
|
||||||
|
useRedux: true,
|
||||||
|
});
|
||||||
|
userEvent.click(screen.getAllByRole('button', { name: EXPAND_ALL })[1]);
|
||||||
|
expect(getCheckboxState(TAB_A)).toBe(UNCHECKED);
|
||||||
|
expect(getCheckboxState(CHART_A)).toBe(UNCHECKED);
|
||||||
|
expect(getCheckboxState(CHART_B)).toBe(UNCHECKED);
|
||||||
|
expect(getCheckboxState(TAB_B)).toBe(UNCHECKED);
|
||||||
|
expect(getCheckboxState(CHART_C)).toBe(UNCHECKED);
|
||||||
|
expect(getCheckboxState(CHART_D)).toBe(UNCHECKED);
|
||||||
|
clickCheckbox(ALL_CHARTS);
|
||||||
|
expect(getCheckboxState(TAB_A)).toBe(CHECKED);
|
||||||
|
expect(getCheckboxState(CHART_A)).toBe(CHECKED);
|
||||||
|
expect(getCheckboxState(CHART_B)).toBe(CHECKED);
|
||||||
|
expect(getCheckboxState(TAB_B)).toBe(CHECKED);
|
||||||
|
expect(getCheckboxState(CHART_C)).toBe(CHECKED);
|
||||||
|
expect(getCheckboxState(CHART_D)).toBe(CHECKED);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('triggers onClose', () => {
|
||||||
|
const onCloseModal = jest.fn();
|
||||||
|
render(
|
||||||
|
<FilterScopeSelector {...createProps()} onCloseModal={onCloseModal} />,
|
||||||
|
{
|
||||||
|
useRedux: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
expect(onCloseModal).toHaveBeenCalledTimes(0);
|
||||||
|
userEvent.click(screen.getByRole('button', { name: 'Close' }));
|
||||||
|
expect(onCloseModal).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('triggers onSave', () => {
|
||||||
|
const updateDashboardFiltersScope = jest.fn();
|
||||||
|
const setUnsavedChanges = jest.fn();
|
||||||
|
const onCloseModal = jest.fn();
|
||||||
|
render(
|
||||||
|
<FilterScopeSelector
|
||||||
|
{...createProps()}
|
||||||
|
updateDashboardFiltersScope={updateDashboardFiltersScope}
|
||||||
|
setUnsavedChanges={setUnsavedChanges}
|
||||||
|
onCloseModal={onCloseModal}
|
||||||
|
/>,
|
||||||
|
{
|
||||||
|
useRedux: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
expect(updateDashboardFiltersScope).toHaveBeenCalledTimes(0);
|
||||||
|
expect(setUnsavedChanges).toHaveBeenCalledTimes(0);
|
||||||
|
expect(onCloseModal).toHaveBeenCalledTimes(0);
|
||||||
|
userEvent.click(screen.getByRole('button', { name: 'Save' }));
|
||||||
|
expect(updateDashboardFiltersScope).toHaveBeenCalledTimes(1);
|
||||||
|
expect(setUnsavedChanges).toHaveBeenCalledTimes(1);
|
||||||
|
expect(onCloseModal).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
|
@ -18,7 +18,6 @@
|
||||||
*/
|
*/
|
||||||
import React, { RefObject } from 'react';
|
import React, { RefObject } from 'react';
|
||||||
import { styled } from '@superset-ui/core';
|
import { styled } from '@superset-ui/core';
|
||||||
|
|
||||||
import ModalTrigger from 'src/components/ModalTrigger';
|
import ModalTrigger from 'src/components/ModalTrigger';
|
||||||
import FilterScope from 'src/dashboard/containers/FilterScope';
|
import FilterScope from 'src/dashboard/containers/FilterScope';
|
||||||
|
|
||||||
|
|
|
@ -22,23 +22,23 @@ import cx from 'classnames';
|
||||||
import Button from 'src/components/Button';
|
import Button from 'src/components/Button';
|
||||||
import { t, styled } from '@superset-ui/core';
|
import { t, styled } from '@superset-ui/core';
|
||||||
|
|
||||||
import buildFilterScopeTreeEntry from '../../util/buildFilterScopeTreeEntry';
|
import buildFilterScopeTreeEntry from 'src/dashboard/util/buildFilterScopeTreeEntry';
|
||||||
import getFilterScopeNodesTree from '../../util/getFilterScopeNodesTree';
|
import getFilterScopeNodesTree from 'src/dashboard/util/getFilterScopeNodesTree';
|
||||||
import getFilterFieldNodesTree from '../../util/getFilterFieldNodesTree';
|
import getFilterFieldNodesTree from 'src/dashboard/util/getFilterFieldNodesTree';
|
||||||
import getFilterScopeParentNodes from '../../util/getFilterScopeParentNodes';
|
import getFilterScopeParentNodes from 'src/dashboard/util/getFilterScopeParentNodes';
|
||||||
import getKeyForFilterScopeTree from '../../util/getKeyForFilterScopeTree';
|
import getKeyForFilterScopeTree from 'src/dashboard/util/getKeyForFilterScopeTree';
|
||||||
import getSelectedChartIdForFilterScopeTree from '../../util/getSelectedChartIdForFilterScopeTree';
|
import getSelectedChartIdForFilterScopeTree from 'src/dashboard/util/getSelectedChartIdForFilterScopeTree';
|
||||||
import getFilterScopeFromNodesTree from '../../util/getFilterScopeFromNodesTree';
|
import getFilterScopeFromNodesTree from 'src/dashboard/util/getFilterScopeFromNodesTree';
|
||||||
import getRevertedFilterScope from '../../util/getRevertedFilterScope';
|
import getRevertedFilterScope from 'src/dashboard/util/getRevertedFilterScope';
|
||||||
import FilterScopeTree from './FilterScopeTree';
|
import { getChartIdsInFilterScope } from 'src/dashboard/util/activeDashboardFilters';
|
||||||
import FilterFieldTree from './FilterFieldTree';
|
|
||||||
import { getChartIdsInFilterScope } from '../../util/activeDashboardFilters';
|
|
||||||
import {
|
import {
|
||||||
getChartIdAndColumnFromFilterKey,
|
getChartIdAndColumnFromFilterKey,
|
||||||
getDashboardFilterKey,
|
getDashboardFilterKey,
|
||||||
} from '../../util/getDashboardFilterKey';
|
} from 'src/dashboard/util/getDashboardFilterKey';
|
||||||
import { ALL_FILTERS_ROOT } from '../../util/constants';
|
import { ALL_FILTERS_ROOT } from 'src/dashboard/util/constants';
|
||||||
import { dashboardFilterPropShape } from '../../util/propShapes';
|
import { dashboardFilterPropShape } from 'src/dashboard/util/propShapes';
|
||||||
|
import FilterScopeTree from './FilterScopeTree';
|
||||||
|
import FilterFieldTree from './FilterFieldTree';
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
dashboardFilters: PropTypes.objectOf(dashboardFilterPropShape).isRequired,
|
dashboardFilters: PropTypes.objectOf(dashboardFilterPropShape).isRequired,
|
||||||
|
|
|
@ -19,10 +19,9 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import CheckboxTree from 'react-checkbox-tree';
|
import CheckboxTree from 'react-checkbox-tree';
|
||||||
|
import { filterScopeSelectorTreeNodePropShape } from 'src/dashboard/util/propShapes';
|
||||||
import renderFilterScopeTreeNodes from './renderFilterScopeTreeNodes';
|
import renderFilterScopeTreeNodes from './renderFilterScopeTreeNodes';
|
||||||
import treeIcons from './treeIcons';
|
import treeIcons from './treeIcons';
|
||||||
import { filterScopeSelectorTreeNodePropShape } from '../../util/propShapes';
|
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
nodes: PropTypes.arrayOf(filterScopeSelectorTreeNodePropShape).isRequired,
|
nodes: PropTypes.arrayOf(filterScopeSelectorTreeNodePropShape).isRequired,
|
||||||
|
|
|
@ -17,7 +17,6 @@
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import FilterFieldItem from './FilterFieldItem';
|
import FilterFieldItem from './FilterFieldItem';
|
||||||
|
|
||||||
export default function renderFilterFieldTreeNodes({ nodes, activeKey }) {
|
export default function renderFilterFieldTreeNodes({ nodes, activeKey }) {
|
||||||
|
|
|
@ -19,8 +19,8 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import cx from 'classnames';
|
import cx from 'classnames';
|
||||||
|
|
||||||
import ChartIcon from '../../../components/ChartIcon';
|
import ChartIcon from 'src/components/ChartIcon';
|
||||||
import { CHART_TYPE } from '../../util/componentTypes';
|
import { CHART_TYPE } from 'src/dashboard/util/componentTypes';
|
||||||
|
|
||||||
function traverse({ currentNode = {}, selectedChartId }) {
|
function traverse({ currentNode = {}, selectedChartId }) {
|
||||||
if (!currentNode) {
|
if (!currentNode) {
|
||||||
|
|
|
@ -98,10 +98,14 @@ const Header: FC<HeaderProps> = ({
|
||||||
<span>{t('Filters')}</span>
|
<span>{t('Filters')}</span>
|
||||||
{canEdit && (
|
{canEdit && (
|
||||||
<FilterConfigurationLink createNewOnOpen={filterValues.length === 0}>
|
<FilterConfigurationLink createNewOnOpen={filterValues.length === 0}>
|
||||||
<Icon name="edit" data-test="create-filter" />
|
<Icon name="edit" role="button" data-test="create-filter" />
|
||||||
</FilterConfigurationLink>
|
</FilterConfigurationLink>
|
||||||
)}
|
)}
|
||||||
<Icon name="expand" onClick={() => toggleFiltersBar(false)} />
|
<Icon
|
||||||
|
name="expand"
|
||||||
|
role="button"
|
||||||
|
onClick={() => toggleFiltersBar(false)}
|
||||||
|
/>
|
||||||
</TitleArea>
|
</TitleArea>
|
||||||
<ActionButtons>
|
<ActionButtons>
|
||||||
<Button
|
<Button
|
||||||
|
|
Loading…
Reference in New Issue