chore(dashboard): migrate enzyme to RTL (#26260)

This commit is contained in:
JUST.in DO IT 2024-02-07 09:17:34 -08:00 committed by GitHub
parent 918057e6c7
commit f8c75ca50b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 1106 additions and 1137 deletions

View File

@ -17,77 +17,94 @@
* under the License.
*/
import React from 'react';
import { shallow } from 'enzyme';
import sinon from 'sinon';
import { fireEvent, render } from 'spec/helpers/testing-library';
import DashboardComponent from 'src/dashboard/containers/DashboardComponent';
import DashboardGrid from 'src/dashboard/components/DashboardGrid';
import DragDroppable from 'src/dashboard/components/dnd/DragDroppable';
import newComponentFactory from 'src/dashboard/util/newComponentFactory';
import { DASHBOARD_GRID_TYPE } from 'src/dashboard/util/componentTypes';
import { GRID_COLUMN_COUNT } from 'src/dashboard/util/constants';
describe('DashboardGrid', () => {
const props = {
depth: 1,
editMode: false,
gridComponent: {
...newComponentFactory(DASHBOARD_GRID_TYPE),
children: ['a'],
},
handleComponentDrop() {},
resizeComponent() {},
width: 500,
isComponentVisible: true,
setDirectPathToChild() {},
};
const args = { id: 'id', widthMultiple: 1, heightMultiple: 3 };
function setup(overrideProps) {
const wrapper = shallow(<DashboardGrid {...props} {...overrideProps} />);
return wrapper;
}
jest.mock(
'src/dashboard/containers/DashboardComponent',
() =>
({ onResizeStart, onResizeStop }) =>
(
<button
type="button"
data-test="mock-dashboard-component"
onClick={() => onResizeStart()}
onBlur={() => onResizeStop(args)}
>
Mock
</button>
),
);
it('should render a div with class "dashboard-grid"', () => {
const wrapper = setup();
expect(wrapper.find('.dashboard-grid')).toExist();
const props = {
depth: 1,
editMode: false,
gridComponent: {
...newComponentFactory(DASHBOARD_GRID_TYPE),
children: ['a'],
},
handleComponentDrop() {},
resizeComponent() {},
width: 500,
isComponentVisible: true,
setDirectPathToChild() {},
};
function setup(overrideProps) {
return render(<DashboardGrid {...props} {...overrideProps} />, {
useRedux: true,
useDnd: true,
});
}
it('should render one DashboardComponent for each gridComponent child', () => {
const wrapper = setup({
gridComponent: { ...props.gridComponent, children: ['a', 'b'] },
});
expect(wrapper.find(DashboardComponent)).toHaveLength(2);
test('should render a div with class "dashboard-grid"', () => {
const { container } = setup();
expect(container.querySelector('.dashboard-grid')).toBeInTheDocument();
});
test('should render one DashboardComponent for each gridComponent child', () => {
const { getAllByTestId } = setup({
gridComponent: { ...props.gridComponent, children: ['a', 'b'] },
});
expect(getAllByTestId('mock-dashboard-component')).toHaveLength(2);
});
it('should render two empty DragDroppables in editMode to increase the drop target zone', () => {
const viewMode = setup({ editMode: false });
const editMode = setup({ editMode: true });
expect(viewMode.find(DragDroppable)).toHaveLength(0);
expect(editMode.find(DragDroppable)).toHaveLength(2);
});
test('should render two empty DragDroppables in editMode to increase the drop target zone', () => {
const { queryAllByTestId } = setup({ editMode: false });
expect(queryAllByTestId('dragdroppable-object').length).toEqual(0);
const { getAllByTestId } = setup({ editMode: true });
expect(getAllByTestId('dragdroppable-object').length).toEqual(2);
});
it('should render grid column guides when resizing', () => {
const wrapper = setup({ editMode: true });
expect(wrapper.find('.grid-column-guide')).not.toExist();
test('should render grid column guides when resizing', () => {
const { container, getAllByTestId } = setup({ editMode: true });
expect(container.querySelector('.grid-column-guide')).not.toBeInTheDocument();
wrapper.setState({ isResizing: true });
// map handleResizeStart to the onClick prop of the mock DashboardComponent
fireEvent.click(getAllByTestId('mock-dashboard-component')[0]);
expect(wrapper.find('.grid-column-guide')).toHaveLength(GRID_COLUMN_COUNT);
});
expect(container.querySelectorAll('.grid-column-guide')).toHaveLength(
GRID_COLUMN_COUNT,
);
});
it('should call resizeComponent when a child DashboardComponent calls resizeStop', () => {
const resizeComponent = sinon.spy();
const args = { id: 'id', widthMultiple: 1, heightMultiple: 3 };
const wrapper = setup({ resizeComponent });
const dashboardComponent = wrapper.find(DashboardComponent).first();
dashboardComponent.prop('onResizeStop')(args);
test('should call resizeComponent when a child DashboardComponent calls resizeStop', () => {
const resizeComponent = jest.fn();
const { getAllByTestId } = setup({ resizeComponent });
const dashboardComponent = getAllByTestId('mock-dashboard-component')[0];
fireEvent.blur(dashboardComponent);
expect(resizeComponent.callCount).toBe(1);
expect(resizeComponent.getCall(0).args[0]).toEqual({
id: 'id',
width: 1,
height: 3,
});
expect(resizeComponent).toHaveBeenCalledTimes(1);
expect(resizeComponent).toHaveBeenCalledWith({
id: 'id',
width: 1,
height: 3,
});
});

View File

@ -17,11 +17,8 @@
* under the License.
*/
import React from 'react';
import { shallow } from 'enzyme';
import { Provider } from 'react-redux';
import { Store } from 'redux';
import * as SupersetUI from '@superset-ui/core';
import { styledMount as mount } from 'spec/helpers/theming';
import { render } from 'spec/helpers/testing-library';
import {
CHART_RENDERING_SUCCEEDED,
CHART_UPDATE_SUCCEEDED,
@ -36,123 +33,105 @@ import { sliceId } from 'spec/fixtures/mockChartQueries';
import { dashboardFilters } from 'spec/fixtures/mockDashboardFilters';
import { dashboardWithFilter } from 'spec/fixtures/mockDashboardLayout';
jest.mock(
'src/dashboard/components/FiltersBadge/DetailsPanel',
() =>
({ children }: { children: React.ReactNode }) =>
<div data-test="mock-details-panel">{children}</div>,
);
const defaultStore = getMockStoreWithFilters();
function setup(store: Store = defaultStore) {
return mount(
<Provider store={store}>
<FiltersBadge chartId={sliceId} />
</Provider>,
);
return render(<FiltersBadge chartId={sliceId} />, { store });
}
describe('FiltersBadge', () => {
// there's this bizarre "active filters" thing
// that doesn't actually use any kind of state management.
// Have to set variables in there.
buildActiveFilters({
dashboardFilters,
components: dashboardWithFilter,
// there's this bizarre "active filters" thing
// that doesn't actually use any kind of state management.
// Have to set variables in there.
buildActiveFilters({
dashboardFilters,
components: dashboardWithFilter,
});
describe('for dashboard filters', () => {
test('does not show number when there are no active filters', () => {
const store = getMockStoreWithFilters();
// start with basic dashboard state, dispatch an event to simulate query completion
store.dispatch({
type: CHART_UPDATE_SUCCEEDED,
key: sliceId,
queriesResponse: [
{
status: 'success',
applied_filters: [],
rejected_filters: [],
},
],
dashboardFilters,
});
const { queryByTestId } = setup(store);
expect(queryByTestId('applied-filter-count')).not.toBeInTheDocument();
});
beforeEach(() => {
// shallow rendering in enzyme doesn't propagate contexts correctly,
// so we have to mock the hook.
// See https://medium.com/7shifts-engineering-blog/testing-usecontext-react-hook-with-enzyme-shallow-da062140fc83
jest
.spyOn(SupersetUI, 'useTheme')
.mockImplementation(() => SupersetUI.supersetTheme);
});
describe('for dashboard filters', () => {
it("doesn't show number when there are no active filters", () => {
const store = getMockStoreWithFilters();
// start with basic dashboard state, dispatch an event to simulate query completion
store.dispatch({
type: CHART_UPDATE_SUCCEEDED,
key: sliceId,
queriesResponse: [
{
status: 'success',
applied_filters: [],
rejected_filters: [],
},
],
dashboardFilters,
});
const wrapper = shallow(
<Provider store={store}>
<FiltersBadge chartId={sliceId} />,
</Provider>,
);
expect(wrapper.find('[data-test="applied-filter-count"]')).not.toExist();
});
it('shows the indicator when filters have been applied', () => {
const store = getMockStoreWithFilters();
// start with basic dashboard state, dispatch an event to simulate query completion
store.dispatch({
type: CHART_UPDATE_SUCCEEDED,
key: sliceId,
queriesResponse: [
{
status: 'success',
applied_filters: [{ column: 'region' }],
rejected_filters: [],
},
],
dashboardFilters,
});
store.dispatch({ type: CHART_RENDERING_SUCCEEDED, key: sliceId });
const wrapper = setup(store);
expect(wrapper.find('DetailsPanelPopover')).toExist();
expect(
wrapper.find('[data-test="applied-filter-count"] .current'),
).toHaveText('1');
expect(wrapper.find('WarningFilled')).not.toExist();
});
});
describe('for native filters', () => {
it("doesn't show number when there are no active filters", () => {
const store = getMockStoreWithNativeFilters();
// start with basic dashboard state, dispatch an event to simulate query completion
store.dispatch({
type: CHART_UPDATE_SUCCEEDED,
key: sliceId,
queriesResponse: [
{
status: 'success',
applied_filters: [],
rejected_filters: [],
},
],
});
store.dispatch({ type: CHART_RENDERING_SUCCEEDED, key: sliceId });
const wrapper = setup(store);
expect(wrapper.find('[data-test="applied-filter-count"]')).not.toExist();
});
it('shows the indicator when filters have been applied', () => {
const store = getMockStoreWithNativeFilters();
// start with basic dashboard state, dispatch an event to simulate query completion
store.dispatch({
type: CHART_UPDATE_SUCCEEDED,
key: sliceId,
queriesResponse: [
{
status: 'success',
applied_filters: [{ column: 'region' }],
rejected_filters: [],
},
],
});
store.dispatch({ type: CHART_RENDERING_SUCCEEDED, key: sliceId });
const wrapper = setup(store);
expect(wrapper.find('DetailsPanelPopover')).toExist();
expect(
wrapper.find('[data-test="applied-filter-count"] .current'),
).toHaveText('1');
expect(wrapper.find('WarningFilled')).not.toExist();
test('shows the indicator when filters have been applied', () => {
const store = getMockStoreWithFilters();
// start with basic dashboard state, dispatch an event to simulate query completion
store.dispatch({
type: CHART_UPDATE_SUCCEEDED,
key: sliceId,
queriesResponse: [
{
status: 'success',
applied_filters: [{ column: 'region' }],
rejected_filters: [],
},
],
dashboardFilters,
});
store.dispatch({ type: CHART_RENDERING_SUCCEEDED, key: sliceId });
const { getByTestId } = setup(store);
expect(getByTestId('applied-filter-count')).toHaveTextContent('1');
expect(getByTestId('mock-details-panel')).toBeInTheDocument();
});
});
describe('for native filters', () => {
test('does not show number when there are no active filters', () => {
const store = getMockStoreWithNativeFilters();
// start with basic dashboard state, dispatch an event to simulate query completion
store.dispatch({
type: CHART_UPDATE_SUCCEEDED,
key: sliceId,
queriesResponse: [
{
status: 'success',
applied_filters: [],
rejected_filters: [],
},
],
});
store.dispatch({ type: CHART_RENDERING_SUCCEEDED, key: sliceId });
const { queryByTestId } = setup(store);
expect(queryByTestId('applied-filter-count')).not.toBeInTheDocument();
});
test('shows the indicator when filters have been applied', () => {
const store = getMockStoreWithNativeFilters();
// start with basic dashboard state, dispatch an event to simulate query completion
store.dispatch({
type: CHART_UPDATE_SUCCEEDED,
key: sliceId,
queriesResponse: [
{
status: 'success',
applied_filters: [{ column: 'region' }],
rejected_filters: [],
},
],
});
store.dispatch({ type: CHART_RENDERING_SUCCEEDED, key: sliceId });
const { getByTestId } = setup(store);
expect(getByTestId('applied-filter-count')).toHaveTextContent('1');
expect(getByTestId('mock-details-panel')).toBeInTheDocument();
});
});

View File

@ -17,15 +17,10 @@
* under the License.
*/
import React from 'react';
import { mount } from 'enzyme';
import { Provider } from 'react-redux';
import { render } from 'spec/helpers/testing-library';
import fetchMock from 'fetch-mock';
import {
supersetTheme,
SupersetClient,
ThemeProvider,
} from '@superset-ui/core';
import { SupersetClient } from '@superset-ui/core';
import Modal from 'src/components/Modal';
import PropertiesModal from 'src/dashboard/components/PropertiesModal';
@ -73,15 +68,10 @@ describe.skip('PropertiesModal', () => {
};
function setup(overrideProps) {
return mount(
<Provider store={mockStore}>
<PropertiesModal {...requiredProps} {...overrideProps} />
</Provider>,
{
wrappingComponent: ThemeProvider,
wrappingComponentProps: { theme: supersetTheme },
},
);
return render(<PropertiesModal {...requiredProps} {...overrideProps} />, {
useRedux: true,
store: mockStore,
});
}
describe('onColorSchemeChange', () => {

View File

@ -17,54 +17,12 @@
* under the License.
*/
import React from 'react';
import { mount } from 'enzyme';
import { render, screen } from 'spec/helpers/testing-library';
import userEvent from '@testing-library/user-event';
import fetchMock from 'fetch-mock';
import ModalTrigger from 'src/components/ModalTrigger';
import RefreshIntervalModal from 'src/dashboard/components/RefreshIntervalModal';
import HeaderActionsDropdown from 'src/dashboard/components/Header/HeaderActionsDropdown';
import Alert from 'src/components/Alert';
import { supersetTheme, ThemeProvider } from '@superset-ui/core';
describe('RefreshIntervalModal - Enzyme', () => {
const getMountWrapper = (props: any) =>
mount(<RefreshIntervalModal {...props} />, {
wrappingComponent: ThemeProvider,
wrappingComponentProps: {
theme: supersetTheme,
},
});
const mockedProps = {
triggerNode: <i className="fa fa-edit" />,
refreshFrequency: 10,
onChange: jest.fn(),
editMode: true,
refreshIntervalOptions: [],
};
it('should show warning message', () => {
const props = {
...mockedProps,
refreshLimit: 3600,
refreshWarning: 'Show warning',
};
const wrapper = getMountWrapper(props);
wrapper.find('span[role="button"]').simulate('click');
// @ts-ignore (for handleFrequencyChange)
wrapper.instance().handleFrequencyChange(30);
wrapper.update();
expect(wrapper.find(ModalTrigger).find(Alert)).toExist();
// @ts-ignore (for handleFrequencyChange)
wrapper.instance().handleFrequencyChange(3601);
wrapper.update();
expect(wrapper.find(ModalTrigger).find(Alert)).not.toExist();
wrapper.unmount();
});
});
const createProps = () => ({
addSuccessToast: jest.fn(),
@ -150,103 +108,99 @@ const defaultRefreshIntervalModalProps = {
refreshIntervalOptions: [],
};
describe('RefreshIntervalModal - RTL', () => {
it('is valid', () => {
expect(
React.isValidElement(
<RefreshIntervalModal {...defaultRefreshIntervalModalProps} />,
),
).toBe(true);
test('is valid', () => {
expect(
React.isValidElement(
<RefreshIntervalModal {...defaultRefreshIntervalModalProps} />,
),
).toBe(true);
});
test('renders refresh interval modal', async () => {
render(setup(editModeOnProps));
await openRefreshIntervalModal();
// Assert that modal exists by checking for the modal title
expect(screen.getByText('Refresh interval')).toBeVisible();
});
test('renders refresh interval options', async () => {
render(setup(editModeOnProps));
await openRefreshIntervalModal();
await displayOptions();
// Assert that both "Don't refresh" instances exist
// - There will be two at this point, the default option and the dropdown option
const dontRefreshInstances = screen.getAllByText(/don't refresh/i);
expect(dontRefreshInstances).toHaveLength(2);
dontRefreshInstances.forEach(option => {
expect(option).toBeInTheDocument();
});
it('renders refresh interval modal', async () => {
render(setup(editModeOnProps));
await openRefreshIntervalModal();
// Assert that modal exists by checking for the modal title
expect(screen.getByText('Refresh interval')).toBeVisible();
});
it('renders refresh interval options', async () => {
render(setup(editModeOnProps));
await openRefreshIntervalModal();
await displayOptions();
// Assert that both "Don't refresh" instances exist
// - There will be two at this point, the default option and the dropdown option
const dontRefreshInstances = screen.getAllByText(/don't refresh/i);
expect(dontRefreshInstances).toHaveLength(2);
dontRefreshInstances.forEach(option => {
expect(option).toBeInTheDocument();
});
// Assert that all the other options exist
const options = [
screen.getByText(/10 seconds/i),
screen.getByText(/30 seconds/i),
screen.getByText(/1 minute/i),
screen.getByText(/5 minutes/i),
screen.getByText(/30 minutes/i),
screen.getByText(/1 hour/i),
screen.getByText(/6 hours/i),
screen.getByText(/12 hours/i),
screen.getByText(/24 hours/i),
];
options.forEach(option => {
expect(option).toBeInTheDocument();
});
});
it('should change selected value', async () => {
render(setup(editModeOnProps));
await openRefreshIntervalModal();
// Initial selected value should be "Don't refresh"
const selectedValue = screen.getByText(/don't refresh/i);
expect(selectedValue.title).toMatch(/don't refresh/i);
// Display options and select "10 seconds"
await displayOptions();
userEvent.click(screen.getByText(/10 seconds/i));
// Selected value should now be "10 seconds"
expect(selectedValue.title).toMatch(/10 seconds/i);
expect(selectedValue.title).not.toMatch(/don't refresh/i);
});
it('should save a newly-selected value', async () => {
render(setup(editModeOnProps));
await openRefreshIntervalModal();
await displayOptions();
// Select a new interval and click save
userEvent.click(screen.getByText(/10 seconds/i));
userEvent.click(screen.getByRole('button', { name: /save/i }));
expect(editModeOnProps.setRefreshFrequency).toHaveBeenCalled();
expect(editModeOnProps.setRefreshFrequency).toHaveBeenCalledWith(
10,
editModeOnProps.editMode,
);
expect(editModeOnProps.addSuccessToast).toHaveBeenCalled();
});
it('should show warning message', async () => {
// TODO (lyndsiWilliams): This test is incomplete
const warningProps = {
...editModeOnProps,
refreshLimit: 3600,
refreshWarning: 'Show warning',
};
render(setup(warningProps));
await openRefreshIntervalModal();
await displayOptions();
userEvent.click(screen.getByText(/30 seconds/i));
userEvent.click(screen.getByRole('button', { name: /save/i }));
// screen.debug(screen.getByRole('alert'));
expect.anything();
// Assert that all the other options exist
const options = [
screen.getByText(/10 seconds/i),
screen.getByText(/30 seconds/i),
screen.getByText(/1 minute/i),
screen.getByText(/5 minutes/i),
screen.getByText(/30 minutes/i),
screen.getByText(/1 hour/i),
screen.getByText(/6 hours/i),
screen.getByText(/12 hours/i),
screen.getByText(/24 hours/i),
];
options.forEach(option => {
expect(option).toBeInTheDocument();
});
});
test('should change selected value', async () => {
render(setup(editModeOnProps));
await openRefreshIntervalModal();
// Initial selected value should be "Don't refresh"
const selectedValue = screen.getByText(/don't refresh/i);
expect(selectedValue.title).toMatch(/don't refresh/i);
// Display options and select "10 seconds"
await displayOptions();
userEvent.click(screen.getByText(/10 seconds/i));
// Selected value should now be "10 seconds"
expect(selectedValue.title).toMatch(/10 seconds/i);
expect(selectedValue.title).not.toMatch(/don't refresh/i);
});
test('should save a newly-selected value', async () => {
render(setup(editModeOnProps));
await openRefreshIntervalModal();
await displayOptions();
// Select a new interval and click save
userEvent.click(screen.getByText(/10 seconds/i));
userEvent.click(screen.getByRole('button', { name: /save/i }));
expect(editModeOnProps.setRefreshFrequency).toHaveBeenCalled();
expect(editModeOnProps.setRefreshFrequency).toHaveBeenCalledWith(
10,
editModeOnProps.editMode,
);
expect(editModeOnProps.addSuccessToast).toHaveBeenCalled();
});
test('should show warning message', async () => {
const warningProps = {
...editModeOnProps,
refreshLimit: 3600,
refreshWarning: 'Show warning',
};
const { getByRole, queryByRole } = render(setup(warningProps));
await openRefreshIntervalModal();
await displayOptions();
userEvent.click(screen.getByText(/30 seconds/i));
expect(getByRole('alert')).toBeInTheDocument();
userEvent.click(screen.getByText(/6 hours/i));
expect(queryByRole('alert')).not.toBeInTheDocument();
});

View File

@ -17,12 +17,10 @@
* under the License.
*/
import React from 'react';
import { shallow } from 'enzyme';
import sinon from 'sinon';
import { fireEvent, render } from 'spec/helpers/testing-library';
import { FeatureFlag } from '@superset-ui/core';
import Chart from 'src/dashboard/components/gridComponents/Chart';
import SliceHeader from 'src/dashboard/components/SliceHeader';
import ChartContainer from 'src/components/Chart/ChartContainer';
import * as exploreUtils from 'src/explore/exploreUtils';
import { sliceEntitiesForChart as sliceEntities } from 'spec/fixtures/mockSliceEntities';
import mockDatasource from 'spec/fixtures/mockDatasource';
@ -30,146 +28,183 @@ import chartQueries, {
sliceId as queryId,
} from 'spec/fixtures/mockChartQueries';
describe('Chart', () => {
const props = {
id: queryId,
width: 100,
height: 100,
updateSliceName() {},
const props = {
id: queryId,
width: 100,
height: 100,
updateSliceName() {},
// from redux
maxRows: 666,
chart: chartQueries[queryId],
formData: chartQueries[queryId].form_data,
datasource: mockDatasource[sliceEntities.slices[queryId].datasource],
slice: {
...sliceEntities.slices[queryId],
description_markeddown: 'markdown',
owners: [],
},
sliceName: sliceEntities.slices[queryId].slice_name,
timeout: 60,
filters: {},
refreshChart() {},
toggleExpandSlice() {},
addFilter() {},
logEvent() {},
handleToggleFullSize() {},
changeFilter() {},
setFocusedFilterField() {},
unsetFocusedFilterField() {},
addSuccessToast() {},
addDangerToast() {},
exportCSV() {},
exportFullCSV() {},
exportXLSX() {},
exportFullXLSX() {},
componentId: 'test',
dashboardId: 111,
editMode: false,
isExpanded: false,
supersetCanExplore: false,
supersetCanCSV: false,
};
// from redux
maxRows: 666,
chart: chartQueries[queryId],
formData: chartQueries[queryId].form_data,
datasource: mockDatasource[sliceEntities.slices[queryId].datasource],
slice: {
...sliceEntities.slices[queryId],
description_markeddown: 'markdown',
owners: [],
viz_type: 'table',
},
sliceName: sliceEntities.slices[queryId].slice_name,
timeout: 60,
filters: {},
refreshChart() {},
toggleExpandSlice() {},
addFilter() {},
logEvent() {},
handleToggleFullSize() {},
changeFilter() {},
setFocusedFilterField() {},
unsetFocusedFilterField() {},
addSuccessToast() {},
addDangerToast() {},
exportCSV() {},
exportFullCSV() {},
exportXLSX() {},
exportFullXLSX() {},
componentId: 'test',
dashboardId: 111,
editMode: false,
isExpanded: false,
supersetCanExplore: false,
supersetCanCSV: false,
supersetCanShare: false,
};
function setup(overrideProps) {
const wrapper = shallow(
<Chart.WrappedComponent {...props} {...overrideProps} />,
);
return wrapper;
}
function setup(overrideProps) {
return render(<Chart.WrappedComponent {...props} {...overrideProps} />, {
useRedux: true,
useRouter: true,
});
}
it('should render a SliceHeader', () => {
const wrapper = setup();
expect(wrapper.find(SliceHeader)).toExist();
});
it('should render a ChartContainer', () => {
const wrapper = setup();
expect(wrapper.find(ChartContainer)).toExist();
});
it('should render a description if it has one and isExpanded=true', () => {
const wrapper = setup();
expect(wrapper.find('.slice_description')).not.toExist();
wrapper.setProps({ ...props, isExpanded: true });
expect(wrapper.find('.slice_description')).toExist();
});
it('should calculate the description height if it has one and isExpanded=true', () => {
const spy = jest.spyOn(
Chart.WrappedComponent.prototype,
'getDescriptionHeight',
);
const wrapper = setup({ isExpanded: true });
expect(wrapper.find('.slice_description')).toExist();
expect(spy).toHaveBeenCalled();
});
it('should call refreshChart when SliceHeader calls forceRefresh', () => {
const refreshChart = sinon.spy();
const wrapper = setup({ refreshChart });
wrapper.instance().forceRefresh();
expect(refreshChart.callCount).toBe(1);
});
it('should call changeFilter when ChartContainer calls changeFilter', () => {
const changeFilter = sinon.spy();
const wrapper = setup({ changeFilter });
wrapper.instance().changeFilter();
expect(changeFilter.callCount).toBe(1);
});
it('should call exportChart when exportCSV is clicked', () => {
const stubbedExportCSV = sinon
.stub(exploreUtils, 'exportChart')
.returns(() => {});
const wrapper = setup();
wrapper.instance().exportCSV(props.slice.sliceId);
expect(stubbedExportCSV.calledOnce).toBe(true);
expect(stubbedExportCSV.lastCall.args[0]).toEqual(
expect.objectContaining({
formData: expect.anything(),
resultType: 'full',
resultFormat: 'csv',
}),
);
exploreUtils.exportChart.restore();
});
it('should call exportChart with row_limit props.maxRows when exportFullCSV is clicked', () => {
const stubbedExportCSV = sinon
.stub(exploreUtils, 'exportChart')
.returns(() => {});
const wrapper = setup();
wrapper.instance().exportFullCSV(props.slice.sliceId);
expect(stubbedExportCSV.calledOnce).toBe(true);
expect(stubbedExportCSV.lastCall.args[0].formData.row_limit).toEqual(666);
exploreUtils.exportChart.restore();
});
it('should call exportChart when exportXLSX is clicked', () => {
const stubbedExportXLSX = sinon
.stub(exploreUtils, 'exportChart')
.returns(() => {});
const wrapper = setup();
wrapper.instance().exportXLSX(props.slice.sliceId);
expect(stubbedExportXLSX.calledOnce).toBe(true);
expect(stubbedExportXLSX.lastCall.args[0]).toEqual(
expect.objectContaining({
formData: expect.anything(),
resultType: 'full',
resultFormat: 'xlsx',
}),
);
exploreUtils.exportChart.restore();
});
it('should call exportChart with row_limit props.maxRows when exportFullXLSX is clicked', () => {
const stubbedExportXLSX = sinon
.stub(exploreUtils, 'exportChart')
.returns(() => {});
const wrapper = setup();
wrapper.instance().exportFullXLSX(props.slice.sliceId);
expect(stubbedExportXLSX.calledOnce).toBe(true);
expect(stubbedExportXLSX.lastCall.args[0].formData.row_limit).toEqual(666);
exploreUtils.exportChart.restore();
});
test('should render a SliceHeader', () => {
const { getByTestId, container } = setup();
expect(getByTestId('slice-header')).toBeInTheDocument();
expect(container.querySelector('.slice_description')).not.toBeInTheDocument();
});
test('should render a ChartContainer', () => {
const { getByTestId } = setup();
expect(getByTestId('chart-container')).toBeInTheDocument();
});
test('should render a description if it has one and isExpanded=true', () => {
const { container } = setup({ isExpanded: true });
expect(container.querySelector('.slice_description')).toBeInTheDocument();
});
test('should calculate the description height if it has one and isExpanded=true', () => {
const spy = jest.spyOn(
Chart.WrappedComponent.prototype,
'getDescriptionHeight',
);
const { container } = setup({ isExpanded: true });
expect(container.querySelector('.slice_description')).toBeInTheDocument();
expect(spy).toHaveBeenCalled();
});
test('should call refreshChart when SliceHeader calls forceRefresh', () => {
const refreshChart = jest.fn();
const { getByText, getByRole } = setup({ refreshChart });
fireEvent.click(getByRole('button', { name: 'More Options' }));
fireEvent.click(getByText('Force refresh'));
expect(refreshChart).toHaveBeenCalled();
});
test.skip('should call changeFilter when ChartContainer calls changeFilter', () => {
const changeFilter = jest.fn();
const wrapper = setup({ changeFilter });
wrapper.instance().changeFilter();
expect(changeFilter.callCount).toBe(1);
});
test('should call exportChart when exportCSV is clicked', async () => {
const stubbedExportCSV = jest
.spyOn(exploreUtils, 'exportChart')
.mockImplementation(() => {});
const { findByText, getByRole } = setup({ supersetCanCSV: true });
fireEvent.click(getByRole('button', { name: 'More Options' }));
fireEvent.mouseOver(getByRole('button', { name: 'Download' }));
const exportAction = await findByText('Export to .CSV');
fireEvent.click(exportAction);
expect(stubbedExportCSV).toHaveBeenCalledTimes(1);
expect(stubbedExportCSV).toHaveBeenCalledWith(
expect.objectContaining({
formData: expect.anything(),
resultType: 'full',
resultFormat: 'csv',
}),
);
stubbedExportCSV.mockRestore();
});
test('should call exportChart with row_limit props.maxRows when exportFullCSV is clicked', async () => {
global.featureFlags = {
[FeatureFlag.AllowFullCsvExport]: true,
};
const stubbedExportCSV = jest
.spyOn(exploreUtils, 'exportChart')
.mockImplementation(() => {});
const { findByText, getByRole } = setup({ supersetCanCSV: true });
fireEvent.click(getByRole('button', { name: 'More Options' }));
fireEvent.mouseOver(getByRole('button', { name: 'Download' }));
const exportAction = await findByText('Export to full .CSV');
fireEvent.click(exportAction);
expect(stubbedExportCSV).toHaveBeenCalledTimes(1);
expect(stubbedExportCSV).toHaveBeenCalledWith(
expect.objectContaining({
formData: expect.objectContaining({
row_limit: 666,
}),
resultType: 'full',
resultFormat: 'csv',
}),
);
stubbedExportCSV.mockRestore();
});
test('should call exportChart when exportXLSX is clicked', async () => {
const stubbedExportXLSX = jest
.spyOn(exploreUtils, 'exportChart')
.mockImplementation(() => {});
const { findByText, getByRole } = setup({ supersetCanCSV: true });
fireEvent.click(getByRole('button', { name: 'More Options' }));
fireEvent.mouseOver(getByRole('button', { name: 'Download' }));
const exportAction = await findByText('Export to Excel');
fireEvent.click(exportAction);
expect(stubbedExportXLSX).toHaveBeenCalledTimes(1);
expect(stubbedExportXLSX).toHaveBeenCalledWith(
expect.objectContaining({
resultType: 'full',
resultFormat: 'xlsx',
}),
);
stubbedExportXLSX.mockRestore();
});
test('should call exportChart with row_limit props.maxRows when exportFullXLSX is clicked', async () => {
global.featureFlags = {
[FeatureFlag.AllowFullCsvExport]: true,
};
const stubbedExportXLSX = jest
.spyOn(exploreUtils, 'exportChart')
.mockImplementation(() => {});
const { findByText, getByRole } = setup({ supersetCanCSV: true });
fireEvent.click(getByRole('button', { name: 'More Options' }));
fireEvent.mouseOver(getByRole('button', { name: 'Download' }));
const exportAction = await findByText('Export to full Excel');
fireEvent.click(exportAction);
expect(stubbedExportXLSX).toHaveBeenCalledTimes(1);
expect(stubbedExportXLSX).toHaveBeenCalledWith(
expect.objectContaining({
formData: expect.objectContaining({
row_limit: 666,
}),
resultType: 'full',
resultFormat: 'xlsx',
}),
);
stubbedExportXLSX.mockRestore();
});

View File

@ -16,158 +16,179 @@
* specific language governing permissions and limitations
* under the License.
*/
import { Provider } from 'react-redux';
import React from 'react';
import { mount } from 'enzyme';
import sinon from 'sinon';
import { MemoryRouter } from 'react-router-dom';
import { supersetTheme, ThemeProvider } from '@superset-ui/core';
import { DndProvider } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';
import { fireEvent, render } from 'spec/helpers/testing-library';
import BackgroundStyleDropdown from 'src/dashboard/components/menu/BackgroundStyleDropdown';
import Column from 'src/dashboard/components/gridComponents/Column';
import DashboardComponent from 'src/dashboard/containers/DashboardComponent';
import DeleteComponentButton from 'src/dashboard/components/DeleteComponentButton';
import DragDroppable from 'src/dashboard/components/dnd/DragDroppable';
import HoverMenu from 'src/dashboard/components/menu/HoverMenu';
import IconButton from 'src/dashboard/components/IconButton';
import ResizableContainer from 'src/dashboard/components/resizable/ResizableContainer';
import WithPopoverMenu from 'src/dashboard/components/menu/WithPopoverMenu';
import { getMockStore } from 'spec/fixtures/mockStore';
import { dashboardLayout as mockLayout } from 'spec/fixtures/mockDashboardLayout';
import { initialState } from 'src/SqlLab/fixtures';
describe('Column', () => {
const columnWithoutChildren = {
...mockLayout.present.COLUMN_ID,
children: [],
};
const props = {
id: 'COLUMN_ID',
parentId: 'ROW_ID',
component: mockLayout.present.COLUMN_ID,
parentComponent: mockLayout.present.ROW_ID,
index: 0,
depth: 2,
editMode: false,
availableColumnCount: 12,
minColumnWidth: 2,
columnWidth: 50,
occupiedColumnCount: 6,
onResizeStart() {},
onResize() {},
onResizeStop() {},
handleComponentDrop() {},
deleteComponent() {},
updateComponents() {},
};
jest.mock(
'src/dashboard/containers/DashboardComponent',
() =>
({ availableColumnCount, depth }) =>
(
<div data-test="mock-dashboard-component" depth={depth}>
{availableColumnCount}
</div>
),
);
jest.mock(
'src/dashboard/components/menu/WithPopoverMenu',
() =>
({ children }) =>
<div data-test="mock-with-popover-menu">{children}</div>,
);
jest.mock(
'src/dashboard/components/DeleteComponentButton',
() =>
({ onDelete }) =>
(
<button
type="button"
data-test="mock-delete-component-button"
onClick={onDelete}
>
Delete
</button>
),
);
function setup(overrideProps) {
// We have to wrap provide DragDropContext for the underlying DragDroppable
// otherwise we cannot assert on DragDroppable children
const mockStore = getMockStore({
...initialState,
});
const wrapper = mount(
<Provider store={mockStore}>
<MemoryRouter>
<DndProvider backend={HTML5Backend}>
<Column {...props} {...overrideProps} />
</DndProvider>
</MemoryRouter>
</Provider>,
{
wrappingComponent: ThemeProvider,
wrappingComponentProps: { theme: supersetTheme },
},
);
return wrapper;
}
const columnWithoutChildren = {
...mockLayout.present.COLUMN_ID,
children: [],
};
const props = {
id: 'COLUMN_ID',
parentId: 'ROW_ID',
component: mockLayout.present.COLUMN_ID,
parentComponent: mockLayout.present.ROW_ID,
index: 0,
depth: 2,
editMode: false,
availableColumnCount: 12,
minColumnWidth: 2,
columnWidth: 50,
occupiedColumnCount: 6,
onResizeStart() {},
onResize() {},
onResizeStop() {},
handleComponentDrop() {},
deleteComponent() {},
updateComponents() {},
};
it('should render a DragDroppable', () => {
// don't count child DragDroppables
const wrapper = setup({ component: columnWithoutChildren });
expect(wrapper.find(DragDroppable)).toExist();
function setup(overrideProps) {
// We have to wrap provide DragDropContext for the underlying DragDroppable
// otherwise we cannot assert on DragDroppable children
const mockStore = getMockStore({
...initialState,
});
it('should render a WithPopoverMenu', () => {
// don't count child DragDroppables
const wrapper = setup({ component: columnWithoutChildren });
expect(wrapper.find(WithPopoverMenu)).toExist();
return render(<Column {...props} {...overrideProps} />, {
store: mockStore,
useDnd: true,
useRouter: true,
});
}
it('should render a ResizableContainer', () => {
// don't count child DragDroppables
const wrapper = setup({ component: columnWithoutChildren });
expect(wrapper.find(ResizableContainer)).toExist();
});
it('should render a HoverMenu in editMode', () => {
let wrapper = setup({ component: columnWithoutChildren });
expect(wrapper.find(HoverMenu)).not.toExist();
// we cannot set props on the Row because of the WithDragDropContext wrapper
wrapper = setup({ component: columnWithoutChildren, editMode: true });
expect(wrapper.find(HoverMenu)).toExist();
});
it('should render a DeleteComponentButton in editMode', () => {
let wrapper = setup({ component: columnWithoutChildren });
expect(wrapper.find(DeleteComponentButton)).not.toExist();
// we cannot set props on the Row because of the WithDragDropContext wrapper
wrapper = setup({ component: columnWithoutChildren, editMode: true });
expect(wrapper.find(DeleteComponentButton)).toExist();
});
it('should render a BackgroundStyleDropdown when focused', () => {
let wrapper = setup({ component: columnWithoutChildren });
expect(wrapper.find(BackgroundStyleDropdown)).not.toExist();
// we cannot set props on the Row because of the WithDragDropContext wrapper
wrapper = setup({ component: columnWithoutChildren, editMode: true });
wrapper
.find(IconButton)
.at(1) // first one is delete button
.simulate('click');
expect(wrapper.find(BackgroundStyleDropdown)).toExist();
});
it('should call deleteComponent when deleted', () => {
const deleteComponent = sinon.spy();
const wrapper = setup({ editMode: true, deleteComponent });
wrapper.find(DeleteComponentButton).simulate('click');
expect(deleteComponent.callCount).toBe(1);
});
it('should pass its own width as availableColumnCount to children', () => {
const wrapper = setup();
const dashboardComponent = wrapper.find(DashboardComponent).first();
expect(dashboardComponent.props().availableColumnCount).toBe(
props.component.meta.width,
);
});
it('should pass appropriate dimensions to ResizableContainer', () => {
const wrapper = setup({ component: columnWithoutChildren });
const columnWidth = columnWithoutChildren.meta.width;
const resizableProps = wrapper.find(ResizableContainer).props();
expect(resizableProps.adjustableWidth).toBe(true);
expect(resizableProps.adjustableHeight).toBe(false);
expect(resizableProps.widthStep).toBe(props.columnWidth);
expect(resizableProps.widthMultiple).toBe(columnWidth);
expect(resizableProps.minWidthMultiple).toBe(props.minColumnWidth);
expect(resizableProps.maxWidthMultiple).toBe(
props.availableColumnCount + columnWidth,
);
});
it('should increment the depth of its children', () => {
const wrapper = setup();
const dashboardComponent = wrapper.find(DashboardComponent);
expect(dashboardComponent.props().depth).toBe(props.depth + 1);
});
test('should render a DragDroppable', () => {
// don't count child DragDroppables
const { getByTestId } = setup({ component: columnWithoutChildren });
expect(getByTestId('dragdroppable-object')).toBeInTheDocument();
});
test('should skip rendering HoverMenu and DeleteComponentButton when not in editMode', () => {
const { container, queryByTestId } = setup({
component: columnWithoutChildren,
});
expect(container.querySelector('.hover-menu')).not.toBeInTheDocument();
expect(queryByTestId('mock-delete-component-button')).not.toBeInTheDocument();
});
test('should render a WithPopoverMenu', () => {
// don't count child DragDroppables
const { getByTestId } = setup({ component: columnWithoutChildren });
expect(getByTestId('mock-with-popover-menu')).toBeInTheDocument();
});
test('should render a ResizableContainer', () => {
// don't count child DragDroppables
const { container } = setup({ component: columnWithoutChildren });
expect(container.querySelector('.resizable-container')).toBeInTheDocument();
});
test('should render a HoverMenu in editMode', () => {
// we cannot set props on the Row because of the WithDragDropContext wrapper
const { container } = setup({
component: columnWithoutChildren,
editMode: true,
});
expect(container.querySelector('.hover-menu')).toBeInTheDocument();
});
test('should render a DeleteComponentButton in editMode', () => {
// we cannot set props on the Row because of the WithDragDropContext wrapper
const { getByTestId } = setup({
component: columnWithoutChildren,
editMode: true,
});
expect(getByTestId('mock-delete-component-button')).toBeInTheDocument();
});
test.skip('should render a BackgroundStyleDropdown when focused', () => {
let wrapper = setup({ component: columnWithoutChildren });
expect(wrapper.find(BackgroundStyleDropdown)).not.toExist();
// we cannot set props on the Row because of the WithDragDropContext wrapper
wrapper = setup({ component: columnWithoutChildren, editMode: true });
wrapper
.find(IconButton)
.at(1) // first one is delete button
.simulate('click');
expect(wrapper.find(BackgroundStyleDropdown)).toExist();
});
test('should call deleteComponent when deleted', () => {
const deleteComponent = jest.fn();
const { getByTestId } = setup({ editMode: true, deleteComponent });
fireEvent.click(getByTestId('mock-delete-component-button'));
expect(deleteComponent).toHaveBeenCalledTimes(1);
});
test('should pass its own width as availableColumnCount to children', () => {
const { getByTestId } = setup();
expect(getByTestId('mock-dashboard-component')).toHaveTextContent(
props.component.meta.width,
);
});
test.skip('should pass appropriate dimensions to ResizableContainer', () => {
const { container } = setup({ component: columnWithoutChildren });
const columnWidth = columnWithoutChildren.meta.width;
expect(container.querySelector('.resizable-container')).toEqual({
columnWidth,
});
// const resizableProps = wrapper.find(ResizableContainer).props();
// expect(resizableProps.adjustableWidth).toBe(true);
// expect(resizableProps.adjustableHeight).toBe(false);
// expect(resizableProps.widthStep).toBe(props.columnWidth);
// expect(resizableProps.widthMultiple).toBe(columnWidth);
// expect(resizableProps.minWidthMultiple).toBe(props.minColumnWidth);
// expect(resizableProps.maxWidthMultiple).toBe(
// props.availableColumnCount + columnWidth,
// );
});
test('should increment the depth of its children', () => {
const { getByTestId } = setup();
expect(getByTestId('mock-dashboard-component')).toHaveAttribute(
'depth',
`${props.depth + 1}`,
);
});

View File

@ -16,134 +16,153 @@
* specific language governing permissions and limitations
* under the License.
*/
import { Provider } from 'react-redux';
import React from 'react';
import { mount } from 'enzyme';
import sinon from 'sinon';
import { DndProvider } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';
import { MemoryRouter } from 'react-router-dom';
import { fireEvent, render } from 'spec/helpers/testing-library';
import BackgroundStyleDropdown from 'src/dashboard/components/menu/BackgroundStyleDropdown';
import DashboardComponent from 'src/dashboard/containers/DashboardComponent';
import DeleteComponentButton from 'src/dashboard/components/DeleteComponentButton';
import DragDroppable from 'src/dashboard/components/dnd/DragDroppable';
import HoverMenu from 'src/dashboard/components/menu/HoverMenu';
import IconButton from 'src/dashboard/components/IconButton';
import Row from 'src/dashboard/components/gridComponents/Row';
import WithPopoverMenu from 'src/dashboard/components/menu/WithPopoverMenu';
import { DASHBOARD_GRID_ID } from 'src/dashboard/util/constants';
import { supersetTheme, ThemeProvider } from '@superset-ui/core';
import { getMockStore } from 'spec/fixtures/mockStore';
import { dashboardLayout as mockLayout } from 'spec/fixtures/mockDashboardLayout';
import { initialState } from 'src/SqlLab/fixtures';
describe('Row', () => {
const rowWithoutChildren = { ...mockLayout.present.ROW_ID, children: [] };
const props = {
id: 'ROW_ID',
parentId: DASHBOARD_GRID_ID,
component: mockLayout.present.ROW_ID,
parentComponent: mockLayout.present[DASHBOARD_GRID_ID],
index: 0,
depth: 2,
editMode: false,
availableColumnCount: 12,
columnWidth: 50,
occupiedColumnCount: 6,
onResizeStart() {},
onResize() {},
onResizeStop() {},
handleComponentDrop() {},
deleteComponent() {},
updateComponents() {},
};
jest.mock(
'src/dashboard/containers/DashboardComponent',
() =>
({ availableColumnCount, depth }) =>
(
<div data-test="mock-dashboard-component" depth={depth}>
{availableColumnCount}
</div>
),
);
function setup(overrideProps) {
// We have to wrap provide DragDropContext for the underlying DragDroppable
// otherwise we cannot assert on DragDroppable children
const mockStore = getMockStore({
...initialState,
});
const wrapper = mount(
<Provider store={mockStore}>
<MemoryRouter>
<DndProvider backend={HTML5Backend}>
<Row {...props} {...overrideProps} />
</DndProvider>
</MemoryRouter>
</Provider>,
{
wrappingComponent: ThemeProvider,
wrappingComponentProps: { theme: supersetTheme },
},
);
return wrapper;
}
jest.mock(
'src/dashboard/components/menu/WithPopoverMenu',
() =>
({ children }) =>
<div data-test="mock-with-popover-menu">{children}</div>,
);
it('should render a DragDroppable', () => {
// don't count child DragDroppables
const wrapper = setup({ component: rowWithoutChildren });
expect(wrapper.find(DragDroppable)).toExist();
jest.mock(
'src/dashboard/components/DeleteComponentButton',
() =>
({ onDelete }) =>
(
<button
type="button"
data-test="mock-delete-component-button"
onClick={onDelete}
>
Delete
</button>
),
);
const rowWithoutChildren = { ...mockLayout.present.ROW_ID, children: [] };
const props = {
id: 'ROW_ID',
parentId: DASHBOARD_GRID_ID,
component: mockLayout.present.ROW_ID,
parentComponent: mockLayout.present[DASHBOARD_GRID_ID],
index: 0,
depth: 2,
editMode: false,
availableColumnCount: 12,
columnWidth: 50,
occupiedColumnCount: 6,
onResizeStart() {},
onResize() {},
onResizeStop() {},
handleComponentDrop() {},
deleteComponent() {},
updateComponents() {},
};
function setup(overrideProps) {
// We have to wrap provide DragDropContext for the underlying DragDroppable
// otherwise we cannot assert on DragDroppable children
const mockStore = getMockStore({
...initialState,
});
it('should render a WithPopoverMenu', () => {
// don't count child DragDroppables
const wrapper = setup({ component: rowWithoutChildren });
expect(wrapper.find(WithPopoverMenu)).toExist();
return render(<Row {...props} {...overrideProps} />, {
store: mockStore,
useDnd: true,
useRouter: true,
});
}
it('should render a HoverMenu in editMode', () => {
let wrapper = setup({ component: rowWithoutChildren });
expect(wrapper.find(HoverMenu)).not.toExist();
// we cannot set props on the Row because of the WithDragDropContext wrapper
wrapper = setup({ component: rowWithoutChildren, editMode: true });
expect(wrapper.find(HoverMenu)).toExist();
});
it('should render a DeleteComponentButton in editMode', () => {
let wrapper = setup({ component: rowWithoutChildren });
expect(wrapper.find(DeleteComponentButton)).not.toExist();
// we cannot set props on the Row because of the WithDragDropContext wrapper
wrapper = setup({ component: rowWithoutChildren, editMode: true });
expect(wrapper.find(DeleteComponentButton)).toExist();
});
it('should render a BackgroundStyleDropdown when focused', () => {
let wrapper = setup({ component: rowWithoutChildren });
expect(wrapper.find(BackgroundStyleDropdown)).not.toExist();
// we cannot set props on the Row because of the WithDragDropContext wrapper
wrapper = setup({ component: rowWithoutChildren, editMode: true });
wrapper
.find(IconButton)
.at(1) // first one is delete button
.simulate('click');
expect(wrapper.find(BackgroundStyleDropdown)).toExist();
});
it('should call deleteComponent when deleted', () => {
const deleteComponent = sinon.spy();
const wrapper = setup({ editMode: true, deleteComponent });
wrapper.find(DeleteComponentButton).simulate('click');
expect(deleteComponent.callCount).toBe(1);
});
it('should pass appropriate availableColumnCount to children', () => {
const wrapper = setup();
const dashboardComponent = wrapper.find(DashboardComponent).first();
expect(dashboardComponent.props().availableColumnCount).toBe(
props.availableColumnCount - props.occupiedColumnCount,
);
});
it('should increment the depth of its children', () => {
const wrapper = setup();
const dashboardComponent = wrapper.find(DashboardComponent).first();
expect(dashboardComponent.props().depth).toBe(props.depth + 1);
});
test('should render a DragDroppable', () => {
// don't count child DragDroppables
const { getByTestId } = setup({ component: rowWithoutChildren });
expect(getByTestId('dragdroppable-object')).toBeInTheDocument();
});
test('should skip rendering HoverMenu and DeleteComponentButton when not in editMode', () => {
const { container, queryByTestId } = setup({
component: rowWithoutChildren,
});
expect(container.querySelector('.hover-menu')).not.toBeInTheDocument();
expect(queryByTestId('mock-delete-component-button')).not.toBeInTheDocument();
});
test('should render a WithPopoverMenu', () => {
// don't count child DragDroppables
const { getByTestId } = setup({ component: rowWithoutChildren });
expect(getByTestId('mock-with-popover-menu')).toBeInTheDocument();
});
test('should render a HoverMenu in editMode', () => {
const { container } = setup({
component: rowWithoutChildren,
editMode: true,
});
expect(container.querySelector('.hover-menu')).toBeInTheDocument();
});
test('should render a DeleteComponentButton in editMode', () => {
const { getByTestId } = setup({
component: rowWithoutChildren,
editMode: true,
});
expect(getByTestId('mock-delete-component-button')).toBeInTheDocument();
});
test.skip('should render a BackgroundStyleDropdown when focused', () => {
let wrapper = setup({ component: rowWithoutChildren });
expect(wrapper.find(BackgroundStyleDropdown)).not.toExist();
// we cannot set props on the Row because of the WithDragDropContext wrapper
wrapper = setup({ component: rowWithoutChildren, editMode: true });
wrapper
.find(IconButton)
.at(1) // first one is delete button
.simulate('click');
expect(wrapper.find(BackgroundStyleDropdown)).toExist();
});
test('should call deleteComponent when deleted', () => {
const deleteComponent = jest.fn();
const { getByTestId } = setup({ editMode: true, deleteComponent });
fireEvent.click(getByTestId('mock-delete-component-button'));
expect(deleteComponent).toHaveBeenCalledTimes(1);
});
test('should pass appropriate availableColumnCount to children', () => {
const { getByTestId } = setup();
expect(getByTestId('mock-dashboard-component')).toHaveTextContent(
props.availableColumnCount - props.occupiedColumnCount,
);
});
test('should increment the depth of its children', () => {
const { getByTestId } = setup();
expect(getByTestId('mock-dashboard-component')).toHaveAttribute(
'depth',
`${props.depth + 1}`,
);
});

View File

@ -16,20 +16,11 @@
* specific language governing permissions and limitations
* under the License.
*/
import { Provider } from 'react-redux';
import React from 'react';
import { shallow } from 'enzyme';
import sinon from 'sinon';
import { DndProvider } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';
import { fireEvent, render } from 'spec/helpers/testing-library';
import { LineEditableTabs } from 'src/components/Tabs';
import { AntdModal } from 'src/components';
import { styledMount as mount } from 'spec/helpers/theming';
import DashboardComponent from 'src/dashboard/containers/DashboardComponent';
import DeleteComponentButton from 'src/dashboard/components/DeleteComponentButton';
import HoverMenu from 'src/dashboard/components/menu/HoverMenu';
import DragDroppable from 'src/dashboard/components/dnd/DragDroppable';
import fetchMock from 'fetch-mock';
import { Tabs } from 'src/dashboard/components/gridComponents/Tabs';
import { DASHBOARD_ROOT_ID } from 'src/dashboard/util/constants';
import emptyDashboardLayout from 'src/dashboard/fixtures/emptyDashboardLayout';
@ -38,177 +29,167 @@ import { getMockStore } from 'spec/fixtures/mockStore';
import { nativeFilters } from 'spec/fixtures/mockNativeFilters';
import { initialState } from 'src/SqlLab/fixtures';
describe('Tabs', () => {
const props = {
id: 'TABS_ID',
parentId: DASHBOARD_ROOT_ID,
component: dashboardLayoutWithTabs.present.TABS_ID,
parentComponent: dashboardLayoutWithTabs.present[DASHBOARD_ROOT_ID],
index: 0,
depth: 1,
renderTabContent: true,
editMode: false,
availableColumnCount: 12,
columnWidth: 50,
dashboardId: 1,
onResizeStart() {},
onResize() {},
onResizeStop() {},
createComponent() {},
handleComponentDrop() {},
onChangeTab() {},
deleteComponent() {},
updateComponents() {},
logEvent() {},
dashboardLayout: emptyDashboardLayout,
nativeFilters: nativeFilters.filters,
};
jest.mock('src/dashboard/containers/DashboardComponent', () => ({ id }) => (
<div data-test="mock-dashboard-component">{id}</div>
));
const mockStore = getMockStore({
...initialState,
dashboardLayout: dashboardLayoutWithTabs,
dashboardFilters: {},
});
jest.mock(
'src/dashboard/components/DeleteComponentButton',
() =>
({ onDelete }) =>
(
<button
type="button"
data-test="mock-delete-component-button"
onClick={onDelete}
>
Delete
</button>
),
);
function setup(overrideProps) {
// We have to wrap provide DragDropContext for the underlying DragDroppable
// otherwise we cannot assert on DragDroppable children
const wrapper = mount(
<Provider store={mockStore}>
<DndProvider backend={HTML5Backend}>
<Tabs {...props} {...overrideProps} />
</DndProvider>
</Provider>,
);
return wrapper;
}
fetchMock.post('glob:*/r/shortener/', {});
it('should render a DragDroppable', () => {
// test just Tabs with no children DragDroppables
const wrapper = setup({ component: { ...props.component, children: [] } });
expect(wrapper.find(DragDroppable)).toExist();
});
const props = {
id: 'TABS_ID',
parentId: DASHBOARD_ROOT_ID,
component: dashboardLayoutWithTabs.present.TABS_ID,
parentComponent: dashboardLayoutWithTabs.present[DASHBOARD_ROOT_ID],
index: 0,
depth: 1,
renderTabContent: true,
editMode: false,
availableColumnCount: 12,
columnWidth: 50,
dashboardId: 1,
onResizeStart() {},
onResize() {},
onResizeStop() {},
createComponent() {},
handleComponentDrop() {},
onChangeTab() {},
deleteComponent() {},
updateComponents() {},
logEvent() {},
dashboardLayout: emptyDashboardLayout,
nativeFilters: nativeFilters.filters,
};
it('should render non-editable tabs', () => {
const wrapper = setup();
expect(wrapper.find(LineEditableTabs)).toExist();
expect(wrapper.find('.ant-tabs-nav-add').exists()).toBeFalsy();
});
it('should render a tab pane for each child', () => {
const wrapper = setup();
expect(wrapper.find(LineEditableTabs.TabPane)).toHaveLength(
props.component.children.length,
);
});
it('should render editable tabs in editMode', () => {
const wrapper = setup({ editMode: true });
expect(wrapper.find(LineEditableTabs)).toExist();
expect(wrapper.find('.ant-tabs-nav-add')).toExist();
});
it('should render a DashboardComponent for each child', () => {
// note: this does not test Tab content
const wrapper = setup({ renderTabContent: false });
expect(wrapper.find(DashboardComponent)).toHaveLength(
props.component.children.length,
);
});
it('should call createComponent if the (+) tab is clicked', () => {
const createComponent = sinon.spy();
const wrapper = setup({ editMode: true, createComponent });
wrapper
.find('[data-test="dashboard-component-tabs"] .ant-tabs-nav-add')
.last()
.simulate('click');
expect(createComponent.callCount).toBe(1);
});
it('should call onChangeTab when a tab is clicked', () => {
const onChangeTab = sinon.spy();
const wrapper = setup({ editMode: true, onChangeTab });
wrapper
.find('[data-test="dashboard-component-tabs"] .ant-tabs-tab')
.at(1) // will not call if it is already selected
.simulate('click');
expect(onChangeTab.callCount).toBe(1);
});
it('should not call onChangeTab when anchor link is clicked', () => {
const onChangeTab = sinon.spy();
const wrapper = setup({ editMode: true, onChangeTab });
wrapper
.find(
'[data-test="dashboard-component-tabs"] .ant-tabs-tab [role="button"]',
)
.at(1) // will not call if it is already selected
.simulate('click');
expect(onChangeTab.callCount).toBe(0);
});
it('should render a HoverMenu in editMode', () => {
let wrapper = setup();
expect(wrapper.find(HoverMenu)).not.toExist();
wrapper = setup({ editMode: true });
expect(wrapper.find(HoverMenu)).toExist();
});
it('should render a DeleteComponentButton in editMode', () => {
let wrapper = setup();
expect(wrapper.find(DeleteComponentButton)).not.toExist();
wrapper = setup({ editMode: true });
expect(wrapper.find(DeleteComponentButton)).toExist();
});
it('should call deleteComponent when deleted', () => {
const deleteComponent = sinon.spy();
const wrapper = setup({ editMode: true, deleteComponent });
wrapper.find(DeleteComponentButton).simulate('click');
expect(deleteComponent.callCount).toBe(1);
});
it('should direct display direct-link tab', () => {
let wrapper = shallow(<Tabs {...props} />);
// default show first tab child
expect(wrapper.state('tabIndex')).toBe(0);
// display child in directPathToChild list
const directPathToChild =
dashboardLayoutWithTabs.present.ROW_ID2.parents.slice();
const directLinkProps = {
...props,
directPathToChild,
};
wrapper = shallow(<Tabs {...directLinkProps} />);
expect(wrapper.state('tabIndex')).toBe(1);
});
it('should render Modal when clicked remove tab button', () => {
const deleteComponent = sinon.spy();
const modalMock = jest.spyOn(AntdModal, 'confirm');
const wrapper = setup({ editMode: true, deleteComponent });
wrapper.find('.ant-tabs-tab-remove').at(0).simulate('click');
expect(modalMock.mock.calls).toHaveLength(1);
expect(deleteComponent.callCount).toBe(0);
});
it('should set new tab key if dashboardId was changed', () => {
const wrapper = shallow(<Tabs {...props} />);
expect(wrapper.state('activeKey')).toBe('TAB_ID');
wrapper.setProps({
...props,
dashboardId: 2,
component: dashboardLayoutWithTabs.present.TAB_ID,
});
expect(wrapper.state('activeKey')).toBe('ROW_ID');
});
const mockStore = getMockStore({
...initialState,
dashboardLayout: dashboardLayoutWithTabs,
dashboardFilters: {},
});
function setup(overrideProps) {
return render(<Tabs {...props} {...overrideProps} />, {
useDnd: true,
useRouter: true,
store: mockStore,
});
}
test('should render a DragDroppable', () => {
// test just Tabs with no children DragDroppables
const { getByTestId } = setup({
component: { ...props.component, children: [] },
});
expect(getByTestId('dragdroppable-object')).toBeInTheDocument();
});
test('should render non-editable tabs', () => {
const { getAllByRole, container } = setup();
expect(getAllByRole('tab')[0]).toBeInTheDocument();
expect(container.querySelector('.ant-tabs-nav-add')).not.toBeInTheDocument();
});
test('should render a tab pane for each child', () => {
const { getAllByRole } = setup();
expect(getAllByRole('tab')).toHaveLength(props.component.children.length);
});
test('should render editable tabs in editMode', () => {
const { getAllByRole, container } = setup({ editMode: true });
expect(getAllByRole('tab')[0]).toBeInTheDocument();
expect(container.querySelector('.ant-tabs-nav-add')).toBeInTheDocument();
});
test('should render a DashboardComponent for each child', () => {
// note: this does not test Tab content
const { getAllByTestId } = setup({ renderTabContent: false });
expect(getAllByTestId('mock-dashboard-component')).toHaveLength(
props.component.children.length,
);
});
test('should call createComponent if the (+) tab is clicked', () => {
const createComponent = jest.fn();
const { getAllByRole } = setup({ editMode: true, createComponent });
const addButtons = getAllByRole('button', { name: 'Add tab' });
fireEvent.click(addButtons[0]);
expect(createComponent).toHaveBeenCalledTimes(1);
});
test('should call onChangeTab when a tab is clicked', () => {
const onChangeTab = jest.fn();
const { getByRole } = setup({ editMode: true, onChangeTab });
const newTab = getByRole('tab', { selected: false });
fireEvent.click(newTab);
expect(onChangeTab).toHaveBeenCalledTimes(1);
});
test('should not call onChangeTab when anchor link is clicked', () => {
const onChangeTab = jest.fn();
const { getByRole } = setup({ editMode: true, onChangeTab });
const currentTab = getByRole('tab', { selected: true });
fireEvent.click(currentTab);
expect(onChangeTab).toHaveBeenCalledTimes(0);
});
test('should render a HoverMenu in editMode', () => {
const { container } = setup({ editMode: true });
expect(container.querySelector('.hover-menu')).toBeInTheDocument();
});
test('should render a DeleteComponentButton in editMode', () => {
const { getByTestId } = setup({ editMode: true });
expect(getByTestId('mock-delete-component-button')).toBeInTheDocument();
});
test('should call deleteComponent when deleted', () => {
const deleteComponent = jest.fn();
const { getByTestId } = setup({ editMode: true, deleteComponent });
fireEvent.click(getByTestId('mock-delete-component-button'));
expect(deleteComponent).toHaveBeenCalledTimes(1);
});
test('should direct display direct-link tab', () => {
// display child in directPathToChild list
const directPathToChild =
dashboardLayoutWithTabs.present.ROW_ID2.parents.slice();
const directLinkProps = {
...props,
directPathToChild,
};
const { getByRole } = setup(directLinkProps);
expect(getByRole('tab', { selected: true })).toHaveTextContent('TAB_ID2');
});
test('should render Modal when clicked remove tab button', () => {
const deleteComponent = jest.fn();
const modalMock = jest.spyOn(AntdModal, 'confirm');
const { container } = setup({ editMode: true, deleteComponent });
fireEvent.click(container.querySelector('.ant-tabs-tab-remove'));
expect(modalMock).toHaveBeenCalledTimes(1);
expect(deleteComponent).toHaveBeenCalledTimes(0);
});
test('should set new tab key if dashboardId was changed', () => {
const { getByRole } = setup({
...props,
dashboardId: 2,
component: dashboardLayoutWithTabs.present.TAB_ID,
});
expect(getByRole('tab', { selected: true })).toHaveTextContent('ROW_ID');
});

View File

@ -17,29 +17,32 @@
* under the License.
*/
import React from 'react';
import { shallow } from 'enzyme';
import { render } from 'spec/helpers/testing-library';
import DraggableNewComponent from 'src/dashboard/components/gridComponents/new/DraggableNewComponent';
import NewColumn from 'src/dashboard/components/gridComponents/new/NewColumn';
import { NEW_COLUMN_ID } from 'src/dashboard/util/constants';
import { COLUMN_TYPE } from 'src/dashboard/util/componentTypes';
describe('NewColumn', () => {
function setup() {
return shallow(<NewColumn />);
}
jest.mock(
'src/dashboard/components/gridComponents/new/DraggableNewComponent',
() =>
({ type, id }) =>
<div data-test="mock-draggable-new-component">{`${type}:${id}`}</div>,
);
it('should render a DraggableNewComponent', () => {
const wrapper = setup();
expect(wrapper.find(DraggableNewComponent)).toExist();
});
function setup() {
return render(<NewColumn />);
}
it('should set appropriate type and id', () => {
const wrapper = setup();
expect(wrapper.find(DraggableNewComponent).props()).toMatchObject({
type: COLUMN_TYPE,
id: NEW_COLUMN_ID,
});
});
test('should render a DraggableNewComponent', () => {
const { getByTestId } = setup();
expect(getByTestId('mock-draggable-new-component')).toBeInTheDocument();
});
test('should set appropriate type and id', () => {
const { getByTestId } = setup();
expect(getByTestId('mock-draggable-new-component')).toHaveTextContent(
`${COLUMN_TYPE}:${NEW_COLUMN_ID}`,
);
});

View File

@ -17,29 +17,32 @@
* under the License.
*/
import React from 'react';
import { shallow } from 'enzyme';
import { render } from 'spec/helpers/testing-library';
import DraggableNewComponent from 'src/dashboard/components/gridComponents/new/DraggableNewComponent';
import NewDivider from 'src/dashboard/components/gridComponents/new/NewDivider';
import { NEW_DIVIDER_ID } from 'src/dashboard/util/constants';
import { DIVIDER_TYPE } from 'src/dashboard/util/componentTypes';
describe('NewDivider', () => {
function setup() {
return shallow(<NewDivider />);
}
jest.mock(
'src/dashboard/components/gridComponents/new/DraggableNewComponent',
() =>
({ type, id }) =>
<div data-test="mock-draggable-new-component">{`${type}:${id}`}</div>,
);
it('should render a DraggableNewComponent', () => {
const wrapper = setup();
expect(wrapper.find(DraggableNewComponent)).toExist();
});
function setup() {
return render(<NewDivider />);
}
it('should set appropriate type and id', () => {
const wrapper = setup();
expect(wrapper.find(DraggableNewComponent).props()).toMatchObject({
type: DIVIDER_TYPE,
id: NEW_DIVIDER_ID,
});
});
test('should render a DraggableNewComponent', () => {
const { getByTestId } = setup();
expect(getByTestId('mock-draggable-new-component')).toBeInTheDocument();
});
test('should set appropriate type and id', () => {
const { getByTestId } = setup();
expect(getByTestId('mock-draggable-new-component')).toHaveTextContent(
`${DIVIDER_TYPE}:${NEW_DIVIDER_ID}`,
);
});

View File

@ -17,29 +17,32 @@
* under the License.
*/
import React from 'react';
import { shallow } from 'enzyme';
import { render } from 'spec/helpers/testing-library';
import DraggableNewComponent from 'src/dashboard/components/gridComponents/new/DraggableNewComponent';
import NewHeader from 'src/dashboard/components/gridComponents/new/NewHeader';
import { NEW_HEADER_ID } from 'src/dashboard/util/constants';
import { HEADER_TYPE } from 'src/dashboard/util/componentTypes';
describe('NewHeader', () => {
function setup() {
return shallow(<NewHeader />);
}
jest.mock(
'src/dashboard/components/gridComponents/new/DraggableNewComponent',
() =>
({ type, id }) =>
<div data-test="mock-draggable-new-component">{`${type}:${id}`}</div>,
);
it('should render a DraggableNewComponent', () => {
const wrapper = setup();
expect(wrapper.find(DraggableNewComponent)).toExist();
});
function setup() {
return render(<NewHeader />);
}
it('should set appropriate type and id', () => {
const wrapper = setup();
expect(wrapper.find(DraggableNewComponent).props()).toMatchObject({
type: HEADER_TYPE,
id: NEW_HEADER_ID,
});
});
test('should render a DraggableNewComponent', () => {
const { getByTestId } = setup();
expect(getByTestId('mock-draggable-new-component')).toBeInTheDocument();
});
test('should set appropriate type and id', () => {
const { getByTestId } = setup();
expect(getByTestId('mock-draggable-new-component')).toHaveTextContent(
`${HEADER_TYPE}:${NEW_HEADER_ID}`,
);
});

View File

@ -17,29 +17,32 @@
* under the License.
*/
import React from 'react';
import { shallow } from 'enzyme';
import { render } from 'spec/helpers/testing-library';
import DraggableNewComponent from 'src/dashboard/components/gridComponents/new/DraggableNewComponent';
import NewRow from 'src/dashboard/components/gridComponents/new/NewRow';
import { NEW_ROW_ID } from 'src/dashboard/util/constants';
import { ROW_TYPE } from 'src/dashboard/util/componentTypes';
describe('NewRow', () => {
function setup() {
return shallow(<NewRow />);
}
jest.mock(
'src/dashboard/components/gridComponents/new/DraggableNewComponent',
() =>
({ type, id }) =>
<div data-test="mock-draggable-new-component">{`${type}:${id}`}</div>,
);
it('should render a DraggableNewComponent', () => {
const wrapper = setup();
expect(wrapper.find(DraggableNewComponent)).toExist();
});
function setup() {
return render(<NewRow />);
}
it('should set appropriate type and id', () => {
const wrapper = setup();
expect(wrapper.find(DraggableNewComponent).props()).toMatchObject({
type: ROW_TYPE,
id: NEW_ROW_ID,
});
});
test('should render a DraggableNewComponent', () => {
const { getByTestId } = setup();
expect(getByTestId('mock-draggable-new-component')).toBeInTheDocument();
});
test('should set appropriate type and id', () => {
const { getByTestId } = setup();
expect(getByTestId('mock-draggable-new-component')).toHaveTextContent(
`${ROW_TYPE}:${NEW_ROW_ID}`,
);
});

View File

@ -17,29 +17,32 @@
* under the License.
*/
import React from 'react';
import { shallow } from 'enzyme';
import { render } from 'spec/helpers/testing-library';
import DraggableNewComponent from 'src/dashboard/components/gridComponents/new/DraggableNewComponent';
import NewTabs from 'src/dashboard/components/gridComponents/new/NewTabs';
import { NEW_TABS_ID } from 'src/dashboard/util/constants';
import { TABS_TYPE } from 'src/dashboard/util/componentTypes';
describe('NewTabs', () => {
function setup() {
return shallow(<NewTabs />);
}
jest.mock(
'src/dashboard/components/gridComponents/new/DraggableNewComponent',
() =>
({ type, id }) =>
<div data-test="mock-draggable-new-component">{`${type}:${id}`}</div>,
);
it('should render a DraggableNewComponent', () => {
const wrapper = setup();
expect(wrapper.find(DraggableNewComponent)).toExist();
});
function setup() {
return render(<NewTabs />);
}
it('should set appropriate type and id', () => {
const wrapper = setup();
expect(wrapper.find(DraggableNewComponent).props()).toMatchObject({
type: TABS_TYPE,
id: NEW_TABS_ID,
});
});
test('should render a DraggableNewComponent', () => {
const { getByTestId } = setup();
expect(getByTestId('mock-draggable-new-component')).toBeInTheDocument();
});
test('should set appropriate type and id', () => {
const { getByTestId } = setup();
expect(getByTestId('mock-draggable-new-component')).toHaveTextContent(
`${TABS_TYPE}:${NEW_TABS_ID}`,
);
});

View File

@ -17,13 +17,11 @@
* under the License.
*/
import React from 'react';
import { shallow } from 'enzyme';
import { render } from 'spec/helpers/testing-library';
import HoverMenu from 'src/dashboard/components/menu/HoverMenu';
describe('HoverMenu', () => {
it('should render a div.hover-menu', () => {
const wrapper = shallow(<HoverMenu />);
expect(wrapper.find('.hover-menu')).toExist();
});
test('should render a div.hover-menu', () => {
const { container } = render(<HoverMenu />);
expect(container.querySelector('.hover-menu')).toBeInTheDocument();
});

View File

@ -17,71 +17,68 @@
* under the License.
*/
import React from 'react';
import { shallow } from 'enzyme';
import { fireEvent, render } from 'spec/helpers/testing-library';
import WithPopoverMenu from 'src/dashboard/components/menu/WithPopoverMenu';
describe('WithPopoverMenu', () => {
const props = {
children: <div id="child" />,
disableClick: false,
menuItems: [<div id="menu1" />, <div id="menu2" />],
onChangeFocus() {},
shouldFocus: () => true, // needed for mock
isFocused: false,
editMode: false,
};
const props = {
children: <div id="child" />,
disableClick: false,
menuItems: [<div id="menu1" />, <div id="menu2" />],
onChangeFocus() {},
shouldFocus: () => true, // needed for mock
isFocused: false,
editMode: false,
};
function setup(overrideProps) {
const wrapper = shallow(<WithPopoverMenu {...props} {...overrideProps} />);
return wrapper;
}
function setup(overrideProps) {
return render(<WithPopoverMenu {...props} {...overrideProps} />);
}
it('should render a div with class "with-popover-menu"', () => {
const wrapper = setup();
expect(wrapper.find('.with-popover-menu')).toExist();
});
it('should render the passed children', () => {
const wrapper = setup();
expect(wrapper.find('#child')).toExist();
});
it('should focus on click in editMode', () => {
const wrapper = setup();
expect(wrapper.state('isFocused')).toBe(false);
wrapper.simulate('click');
expect(wrapper.state('isFocused')).toBe(false);
wrapper.setProps({ ...props, editMode: true });
wrapper.simulate('click');
expect(wrapper.state('isFocused')).toBe(true);
});
it('should render menuItems when focused', () => {
const wrapper = setup({ editMode: true });
expect(wrapper.find('#menu1')).not.toExist();
expect(wrapper.find('#menu2')).not.toExist();
wrapper.simulate('click');
expect(wrapper.find('#menu1')).toExist();
expect(wrapper.find('#menu2')).toExist();
});
it('should not focus when disableClick=true', () => {
const wrapper = setup({ disableClick: true, editMode: true });
expect(wrapper.state('isFocused')).toBe(false);
wrapper.simulate('click');
expect(wrapper.state('isFocused')).toBe(false);
});
it('should use the passed shouldFocus func to determine if it should focus', () => {
const wrapper = setup({ editMode: true, shouldFocus: () => false });
expect(wrapper.state('isFocused')).toBe(false);
wrapper.simulate('click');
expect(wrapper.state('isFocused')).toBe(false);
});
test('should render a div with class "with-popover-menu"', () => {
const { container } = setup();
expect(container.querySelector('.with-popover-menu')).toBeInTheDocument();
});
test('should render the passed children', () => {
const { container } = setup();
expect(container.querySelector('#child')).toBeInTheDocument();
});
test('should focus on click in editMode', () => {
const { container } = setup({ editMode: true });
fireEvent.click(container.querySelector('.with-popover-menu'));
expect(
container.querySelector('.with-popover-menu--focused'),
).toBeInTheDocument();
});
test('should render menuItems when focused', () => {
const { container } = setup({ editMode: true });
expect(container.querySelector('#menu1')).not.toBeInTheDocument();
expect(container.querySelector('#menu2')).not.toBeInTheDocument();
fireEvent.click(container.querySelector('.with-popover-menu'));
expect(container.querySelector('#menu1')).toBeInTheDocument();
expect(container.querySelector('#menu2')).toBeInTheDocument();
});
test('should not focus when disableClick=true', () => {
const { container } = setup({ disableClick: true, editMode: true });
fireEvent.click(container.querySelector('.with-popover-menu'));
expect(
container.querySelector('.with-popover-menu--focused'),
).not.toBeInTheDocument();
});
test('should use the passed shouldFocus func to determine if it should focus', () => {
const { container } = setup({ editMode: true, shouldFocus: () => false });
expect(
container.querySelector('.with-popover-menu--focused'),
).not.toBeInTheDocument();
fireEvent.click(container.querySelector('.with-popover-menu'));
expect(
container.querySelector('.with-popover-menu--focused'),
).not.toBeInTheDocument();
});

View File

@ -423,7 +423,7 @@ function FiltersConfigModal({
)();
resetForm(true);
} else {
configFormRef.current.changeTab('configuration');
configFormRef.current?.changeTab?.('configuration');
}
};

View File

@ -16,18 +16,8 @@
* specific language governing permissions and limitations
* under the License.
*/
import { ReactWrapper } from 'enzyme';
import React from 'react';
import { DndProvider } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';
import { act } from 'react-dom/test-utils';
import { Provider } from 'react-redux';
import { mockStore } from 'spec/fixtures/mockStore';
import { styledMount as mount } from 'spec/helpers/theming';
import waitForComponentToPaint from 'spec/helpers/waitForComponentToPaint';
import { AntdDropdown } from 'src/components';
import { Menu } from 'src/components/Menu';
import Alert from 'src/components/Alert';
import { fireEvent, render } from 'spec/helpers/testing-library';
import FiltersConfigModal from 'src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigModal';
Object.defineProperty(window, 'matchMedia', {
@ -59,83 +49,56 @@ jest.mock('@superset-ui/core', () => ({
}),
}));
describe('FiltersConfigModal', () => {
const mockedProps = {
isOpen: true,
initialFilterId: 'NATIVE_FILTER-1',
createNewOnOpen: true,
onCancel: jest.fn(),
onSave: jest.fn(),
};
function setup(overridesProps?: any) {
return mount(
<Provider store={mockStore}>
<DndProvider backend={HTML5Backend}>
<FiltersConfigModal {...mockedProps} {...overridesProps} />
</DndProvider>
</Provider>,
);
}
const mockedProps = {
isOpen: true,
initialFilterId: 'NATIVE_FILTER-1',
createNewOnOpen: true,
onCancel: jest.fn(),
onSave: jest.fn(),
};
function setup(overridesProps?: any) {
return render(<FiltersConfigModal {...mockedProps} {...overridesProps} />, {
useDnd: true,
useRedux: true,
});
}
it('should be a valid react element', () => {
expect(React.isValidElement(<FiltersConfigModal {...mockedProps} />)).toBe(
true,
);
test('should be a valid react element', () => {
const { container } = setup();
expect(container).toBeInTheDocument();
});
test('the form validates required fields', async () => {
const onSave = jest.fn();
const { getByRole } = setup({ save: onSave });
fireEvent.change(getByRole('textbox', { name: 'Description' }), {
target: { value: 'test name' },
});
const saveButton = getByRole('button', { name: 'Save' });
fireEvent.click(saveButton);
expect(onSave).toHaveBeenCalledTimes(0);
});
describe('createNewOnOpen', () => {
test('does not show alert when there is no unsaved filters', async () => {
const onCancel = jest.fn();
const { getByRole } = setup({ onCancel, createNewOnOpen: false });
fireEvent.click(getByRole('button', { name: 'Cancel' }));
expect(onCancel).toHaveBeenCalledTimes(1);
});
it('the form validates required fields', async () => {
const onSave = jest.fn();
const wrapper = setup({ save: onSave });
act(() => {
wrapper
.find('input')
.first()
.simulate('change', { target: { value: 'test name' } });
wrapper.find('.ant-modal-footer button').at(1).simulate('click');
});
await waitForComponentToPaint(wrapper);
expect(onSave.mock.calls).toHaveLength(0);
});
describe('when click cancel', () => {
let onCancel: jest.Mock;
let wrapper: ReactWrapper;
beforeEach(() => {
onCancel = jest.fn();
wrapper = setup({ onCancel, createNewOnOpen: false });
});
async function clickCancel() {
act(() => {
wrapper.find('.ant-modal-footer button').at(0).simulate('click');
});
await waitForComponentToPaint(wrapper);
}
async function addFilter() {
act(() => {
wrapper.find(AntdDropdown).at(0).simulate('mouseEnter');
});
await waitForComponentToPaint(wrapper, 300);
act(() => {
wrapper.find(Menu.Item).at(0).simulate('click');
});
}
it('does not show alert when there is no unsaved filters', async () => {
await clickCancel();
expect(onCancel.mock.calls).toHaveLength(1);
});
it('shows correct alert message for unsaved filters', async () => {
await addFilter();
await clickCancel();
expect(onCancel.mock.calls).toHaveLength(0);
expect(wrapper.find(Alert).text()).toContain(
'There are unsaved changes.',
);
test('shows correct alert message for unsaved filters', async () => {
const onCancel = jest.fn();
const { getByRole, getByTestId, findByRole } = setup({
onCancel,
createNewOnOpen: false,
});
fireEvent.mouseOver(getByTestId('new-dropdown-icon'));
const addFilterButton = await findByRole('menuitem', { name: 'Filter' });
fireEvent.click(addFilterButton);
fireEvent.click(getByRole('button', { name: 'Cancel' }));
expect(onCancel).toHaveBeenCalledTimes(0);
expect(getByRole('alert')).toBeInTheDocument();
expect(getByRole('alert')).toHaveTextContent('There are unsaved changes.');
});
});