mirror of
https://github.com/apache/superset.git
synced 2026-04-19 08:04:53 +00:00
chore: Moves spec files to the src folder - iteration 9 (#17901)
This commit is contained in:
committed by
GitHub
parent
c95d6bf88f
commit
bb7e979142
@@ -1,196 +0,0 @@
|
||||
/**
|
||||
* 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 { Provider } from 'react-redux';
|
||||
import React from 'react';
|
||||
import { mount } from 'enzyme';
|
||||
import sinon from 'sinon';
|
||||
import fetchMock from 'fetch-mock';
|
||||
import { ParentSize } from '@vx/responsive';
|
||||
import { supersetTheme, ThemeProvider } from '@superset-ui/core';
|
||||
import Tabs from 'src/components/Tabs';
|
||||
import { DndProvider } from 'react-dnd';
|
||||
import { HTML5Backend } from 'react-dnd-html5-backend';
|
||||
import BuilderComponentPane from 'src/dashboard/components/BuilderComponentPane';
|
||||
import DashboardBuilder from 'src/dashboard/components/DashboardBuilder/DashboardBuilder';
|
||||
import DashboardComponent from 'src/dashboard/containers/DashboardComponent';
|
||||
import DashboardHeader from 'src/dashboard/containers/DashboardHeader';
|
||||
import DashboardGrid from 'src/dashboard/containers/DashboardGrid';
|
||||
import * as dashboardStateActions from 'src/dashboard/actions/dashboardState';
|
||||
import {
|
||||
dashboardLayout as undoableDashboardLayout,
|
||||
dashboardLayoutWithTabs as undoableDashboardLayoutWithTabs,
|
||||
} from 'spec/fixtures/mockDashboardLayout';
|
||||
import { mockStoreWithTabs, storeWithState } from 'spec/fixtures/mockStore';
|
||||
import mockState from 'spec/fixtures/mockState';
|
||||
import {
|
||||
DASHBOARD_ROOT_ID,
|
||||
DASHBOARD_GRID_ID,
|
||||
} from 'src/dashboard/util/constants';
|
||||
|
||||
fetchMock.get('glob:*/csstemplateasyncmodelview/api/read', {});
|
||||
|
||||
jest.mock('src/dashboard/actions/dashboardState');
|
||||
|
||||
describe('DashboardBuilder', () => {
|
||||
let favStarStub;
|
||||
let activeTabsStub;
|
||||
|
||||
beforeAll(() => {
|
||||
// this is invoked on mount, so we stub it instead of making a request
|
||||
favStarStub = sinon
|
||||
.stub(dashboardStateActions, 'fetchFaveStar')
|
||||
.returns({ type: 'mock-action' });
|
||||
activeTabsStub = sinon
|
||||
.stub(dashboardStateActions, 'setActiveTabs')
|
||||
.returns({ type: 'mock-action' });
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
favStarStub.restore();
|
||||
activeTabsStub.restore();
|
||||
});
|
||||
|
||||
function setup(overrideState = {}, overrideStore) {
|
||||
const store =
|
||||
overrideStore ??
|
||||
storeWithState({
|
||||
...mockState,
|
||||
dashboardLayout: undoableDashboardLayout,
|
||||
...overrideState,
|
||||
});
|
||||
return mount(
|
||||
<Provider store={store}>
|
||||
<DndProvider backend={HTML5Backend}>
|
||||
<DashboardBuilder />
|
||||
</DndProvider>
|
||||
</Provider>,
|
||||
{
|
||||
wrappingComponent: ThemeProvider,
|
||||
wrappingComponentProps: { theme: supersetTheme },
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
it('should render a StickyContainer with class "dashboard"', () => {
|
||||
const wrapper = setup();
|
||||
const stickyContainer = wrapper.find('[data-test="dashboard-content"]');
|
||||
expect(stickyContainer).toHaveLength(1);
|
||||
expect(stickyContainer.prop('className')).toBe('dashboard');
|
||||
});
|
||||
|
||||
it('should add the "dashboard--editing" class if editMode=true', () => {
|
||||
const wrapper = setup({ dashboardState: { editMode: true } });
|
||||
const stickyContainer = wrapper.find('[data-test="dashboard-content"]');
|
||||
expect(stickyContainer.prop('className')).toBe(
|
||||
'dashboard dashboard--editing',
|
||||
);
|
||||
});
|
||||
|
||||
it('should render a DragDroppable DashboardHeader', () => {
|
||||
const wrapper = setup();
|
||||
expect(wrapper.find(DashboardHeader)).toExist();
|
||||
});
|
||||
|
||||
it('should render a Sticky top-level Tabs if the dashboard has tabs', () => {
|
||||
const wrapper = setup(
|
||||
{ dashboardLayout: undoableDashboardLayoutWithTabs },
|
||||
mockStoreWithTabs,
|
||||
);
|
||||
|
||||
const sticky = wrapper.find('[data-test="top-level-tabs"]');
|
||||
const dashboardComponent = sticky.find(DashboardComponent);
|
||||
|
||||
const tabChildren =
|
||||
undoableDashboardLayoutWithTabs.present.TABS_ID.children;
|
||||
expect(dashboardComponent).toHaveLength(1 + tabChildren.length); // tab + tabs
|
||||
expect(dashboardComponent.at(0).prop('id')).toBe('TABS_ID');
|
||||
tabChildren.forEach((tabId, i) => {
|
||||
expect(dashboardComponent.at(i + 1).prop('id')).toBe(tabId);
|
||||
});
|
||||
});
|
||||
|
||||
it('should render one Tabs and two TabPane', () => {
|
||||
const wrapper = setup({ dashboardLayout: undoableDashboardLayoutWithTabs });
|
||||
const parentSize = wrapper.find(ParentSize);
|
||||
expect(parentSize.find(Tabs)).toHaveLength(1);
|
||||
expect(parentSize.find(Tabs.TabPane)).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('should render a TabPane and DashboardGrid for first Tab', () => {
|
||||
const wrapper = setup({ dashboardLayout: undoableDashboardLayoutWithTabs });
|
||||
const parentSize = wrapper.find(ParentSize);
|
||||
const expectedCount =
|
||||
undoableDashboardLayoutWithTabs.present.TABS_ID.children.length;
|
||||
expect(parentSize.find(Tabs.TabPane)).toHaveLength(expectedCount);
|
||||
expect(
|
||||
parentSize.find(Tabs.TabPane).first().find(DashboardGrid),
|
||||
).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('should render a TabPane and DashboardGrid for second Tab', () => {
|
||||
const wrapper = setup({
|
||||
dashboardLayout: undoableDashboardLayoutWithTabs,
|
||||
dashboardState: {
|
||||
...mockState,
|
||||
directPathToChild: [DASHBOARD_ROOT_ID, 'TABS_ID', 'TAB_ID2'],
|
||||
},
|
||||
});
|
||||
const parentSize = wrapper.find(ParentSize);
|
||||
const expectedCount =
|
||||
undoableDashboardLayoutWithTabs.present.TABS_ID.children.length;
|
||||
expect(parentSize.find(Tabs.TabPane)).toHaveLength(expectedCount);
|
||||
expect(
|
||||
parentSize.find(Tabs.TabPane).at(1).find(DashboardGrid),
|
||||
).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('should render a BuilderComponentPane if editMode=false and user selects "Insert Components" pane', () => {
|
||||
const wrapper = setup();
|
||||
expect(wrapper.find(BuilderComponentPane)).not.toExist();
|
||||
});
|
||||
|
||||
it('should render a BuilderComponentPane if editMode=true and user selects "Insert Components" pane', () => {
|
||||
const wrapper = setup({ dashboardState: { editMode: true } });
|
||||
expect(wrapper.find(BuilderComponentPane)).toExist();
|
||||
});
|
||||
|
||||
it('should change redux state if a top-level Tab is clicked', () => {
|
||||
dashboardStateActions.setDirectPathToChild = jest.fn(arg0 => ({
|
||||
type: 'type',
|
||||
arg0,
|
||||
}));
|
||||
const wrapper = setup({
|
||||
...mockStoreWithTabs,
|
||||
dashboardLayout: undoableDashboardLayoutWithTabs,
|
||||
});
|
||||
|
||||
expect(wrapper.find(Tabs).at(1).prop('activeKey')).toBe(DASHBOARD_GRID_ID);
|
||||
|
||||
wrapper
|
||||
.find('.dashboard-component-tabs .ant-tabs .ant-tabs-tab')
|
||||
.at(1)
|
||||
.simulate('click');
|
||||
|
||||
expect(dashboardStateActions.setDirectPathToChild).toHaveBeenCalledWith([
|
||||
'ROOT_ID',
|
||||
'TABS_ID',
|
||||
'TAB_ID2',
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -1,100 +0,0 @@
|
||||
/**
|
||||
* 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 { shallow } from 'enzyme';
|
||||
import sinon from 'sinon';
|
||||
|
||||
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() {},
|
||||
};
|
||||
|
||||
function setup(overrideProps) {
|
||||
const wrapper = shallow(<DashboardGrid {...props} {...overrideProps} />);
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
it('should render a div with class "dashboard-grid"', () => {
|
||||
const wrapper = setup();
|
||||
expect(wrapper.find('.dashboard-grid')).toExist();
|
||||
});
|
||||
|
||||
it('should render one DashboardComponent for each gridComponent child', () => {
|
||||
const wrapper = setup({
|
||||
gridComponent: { ...props.gridComponent, children: ['a', 'b'] },
|
||||
});
|
||||
expect(wrapper.find(DashboardComponent)).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);
|
||||
});
|
||||
|
||||
it('should render grid column guides when resizing', () => {
|
||||
const wrapper = setup({ editMode: true });
|
||||
expect(wrapper.find('.grid-column-guide')).not.toExist();
|
||||
|
||||
wrapper.setState({ isResizing: true });
|
||||
|
||||
expect(wrapper.find('.grid-column-guide')).toHaveLength(GRID_COLUMN_COUNT);
|
||||
});
|
||||
|
||||
it('should render a grid row guide when resizing', () => {
|
||||
const wrapper = setup();
|
||||
expect(wrapper.find('.grid-row-guide')).not.toExist();
|
||||
wrapper.setState({ isResizing: true, rowGuideTop: 10 });
|
||||
expect(wrapper.find('.grid-row-guide')).toExist();
|
||||
});
|
||||
|
||||
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);
|
||||
|
||||
expect(resizeComponent.callCount).toBe(1);
|
||||
expect(resizeComponent.getCall(0).args[0]).toEqual({
|
||||
id: 'id',
|
||||
width: 1,
|
||||
height: 3,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,246 +0,0 @@
|
||||
/**
|
||||
* 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 { shallow } from 'enzyme';
|
||||
import sinon from 'sinon';
|
||||
|
||||
import Dashboard from 'src/dashboard/components/Dashboard';
|
||||
import DashboardBuilder from 'src/dashboard/components/DashboardBuilder/DashboardBuilder';
|
||||
import { CHART_TYPE } from 'src/dashboard/util/componentTypes';
|
||||
import newComponentFactory from 'src/dashboard/util/newComponentFactory';
|
||||
|
||||
// mock data
|
||||
import chartQueries from 'spec/fixtures/mockChartQueries';
|
||||
import datasources from 'spec/fixtures/mockDatasource';
|
||||
import {
|
||||
extraFormData,
|
||||
NATIVE_FILTER_ID,
|
||||
layoutForSingleNativeFilter,
|
||||
singleNativeFiltersState,
|
||||
dataMaskWith1Filter,
|
||||
} from 'spec/fixtures/mockNativeFilters';
|
||||
import dashboardInfo from 'spec/fixtures/mockDashboardInfo';
|
||||
import { dashboardLayout } from 'spec/fixtures/mockDashboardLayout';
|
||||
import dashboardState from 'spec/fixtures/mockDashboardState';
|
||||
import { sliceEntitiesForChart as sliceEntities } from 'spec/fixtures/mockSliceEntities';
|
||||
import { getAllActiveFilters } from 'src/dashboard/util/activeAllDashboardFilters';
|
||||
|
||||
describe('Dashboard', () => {
|
||||
const props = {
|
||||
actions: {
|
||||
addSliceToDashboard() {},
|
||||
removeSliceFromDashboard() {},
|
||||
triggerQuery() {},
|
||||
logEvent() {},
|
||||
},
|
||||
initMessages: [],
|
||||
dashboardState,
|
||||
dashboardInfo,
|
||||
charts: chartQueries,
|
||||
activeFilters: {},
|
||||
ownDataCharts: {},
|
||||
slices: sliceEntities.slices,
|
||||
datasources,
|
||||
layout: dashboardLayout.present,
|
||||
timeout: 60,
|
||||
userId: dashboardInfo.userId,
|
||||
impressionId: 'id',
|
||||
loadStats: {},
|
||||
};
|
||||
|
||||
function setup(overrideProps) {
|
||||
const wrapper = shallow(<Dashboard {...props} {...overrideProps} />);
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
// activeFilters map use id_column) as key
|
||||
const OVERRIDE_FILTERS = {
|
||||
'1_region': { values: [], scope: [1] },
|
||||
'2_country_name': { values: ['USA'], scope: [1, 2] },
|
||||
'3_region': { values: [], scope: [1] },
|
||||
'3_country_name': { values: ['USA'], scope: [] },
|
||||
};
|
||||
|
||||
it('should render a DashboardBuilder', () => {
|
||||
const wrapper = setup();
|
||||
expect(wrapper.find(DashboardBuilder)).toExist();
|
||||
});
|
||||
|
||||
describe('UNSAFE_componentWillReceiveProps', () => {
|
||||
const layoutWithExtraChart = {
|
||||
...props.layout,
|
||||
1001: newComponentFactory(CHART_TYPE, { chartId: 1001 }),
|
||||
};
|
||||
|
||||
it('should call addSliceToDashboard if a new slice is added to the layout', () => {
|
||||
const wrapper = setup();
|
||||
const spy = sinon.spy(props.actions, 'addSliceToDashboard');
|
||||
wrapper.instance().UNSAFE_componentWillReceiveProps({
|
||||
...props,
|
||||
layout: layoutWithExtraChart,
|
||||
});
|
||||
spy.restore();
|
||||
expect(spy.callCount).toBe(1);
|
||||
});
|
||||
|
||||
it('should call removeSliceFromDashboard if a slice is removed from the layout', () => {
|
||||
const wrapper = setup({ layout: layoutWithExtraChart });
|
||||
const spy = sinon.spy(props.actions, 'removeSliceFromDashboard');
|
||||
const nextLayout = { ...layoutWithExtraChart };
|
||||
delete nextLayout[1001];
|
||||
|
||||
wrapper.instance().UNSAFE_componentWillReceiveProps({
|
||||
...props,
|
||||
layout: nextLayout,
|
||||
});
|
||||
spy.restore();
|
||||
expect(spy.callCount).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('componentDidUpdate', () => {
|
||||
let wrapper;
|
||||
let prevProps;
|
||||
let refreshSpy;
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = setup({ activeFilters: OVERRIDE_FILTERS });
|
||||
wrapper.instance().appliedFilters = OVERRIDE_FILTERS;
|
||||
prevProps = wrapper.instance().props;
|
||||
refreshSpy = sinon.spy(wrapper.instance(), 'refreshCharts');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
refreshSpy.restore();
|
||||
});
|
||||
|
||||
it('should not call refresh when is editMode', () => {
|
||||
wrapper.setProps({
|
||||
dashboardState: {
|
||||
...dashboardState,
|
||||
editMode: true,
|
||||
},
|
||||
});
|
||||
wrapper.instance().componentDidUpdate(prevProps);
|
||||
expect(refreshSpy.callCount).toBe(0);
|
||||
});
|
||||
|
||||
it('should not call refresh when there is no change', () => {
|
||||
wrapper.setProps({
|
||||
activeFilters: OVERRIDE_FILTERS,
|
||||
});
|
||||
wrapper.instance().componentDidUpdate(prevProps);
|
||||
expect(refreshSpy.callCount).toBe(0);
|
||||
expect(wrapper.instance().appliedFilters).toBe(OVERRIDE_FILTERS);
|
||||
});
|
||||
|
||||
it('should call refresh when native filters changed', () => {
|
||||
wrapper.setProps({
|
||||
activeFilters: {
|
||||
...OVERRIDE_FILTERS,
|
||||
...getAllActiveFilters({
|
||||
dataMask: dataMaskWith1Filter,
|
||||
nativeFilters: singleNativeFiltersState.filters,
|
||||
layout: layoutForSingleNativeFilter,
|
||||
}),
|
||||
},
|
||||
});
|
||||
wrapper.instance().componentDidUpdate(prevProps);
|
||||
expect(refreshSpy.callCount).toBe(1);
|
||||
expect(wrapper.instance().appliedFilters).toEqual({
|
||||
...OVERRIDE_FILTERS,
|
||||
[NATIVE_FILTER_ID]: {
|
||||
scope: [230],
|
||||
values: extraFormData,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should call refresh if a filter is added', () => {
|
||||
const newFilter = {
|
||||
gender: { values: ['boy', 'girl'], scope: [1] },
|
||||
};
|
||||
wrapper.setProps({
|
||||
activeFilters: newFilter,
|
||||
});
|
||||
expect(refreshSpy.callCount).toBe(1);
|
||||
expect(wrapper.instance().appliedFilters).toEqual(newFilter);
|
||||
});
|
||||
|
||||
it('should call refresh if a filter is removed', () => {
|
||||
wrapper.setProps({
|
||||
activeFilters: {},
|
||||
});
|
||||
expect(refreshSpy.callCount).toBe(1);
|
||||
expect(wrapper.instance().appliedFilters).toEqual({});
|
||||
});
|
||||
|
||||
it('should call refresh if a filter is changed', () => {
|
||||
const newFilters = {
|
||||
...OVERRIDE_FILTERS,
|
||||
'1_region': { values: ['Canada'], scope: [1] },
|
||||
};
|
||||
wrapper.setProps({
|
||||
activeFilters: newFilters,
|
||||
});
|
||||
expect(refreshSpy.callCount).toBe(1);
|
||||
expect(wrapper.instance().appliedFilters).toEqual(newFilters);
|
||||
expect(refreshSpy.getCall(0).args[0]).toEqual([1]);
|
||||
});
|
||||
|
||||
it('should call refresh with multiple chart ids', () => {
|
||||
const newFilters = {
|
||||
...OVERRIDE_FILTERS,
|
||||
'2_country_name': { values: ['New Country'], scope: [1, 2] },
|
||||
};
|
||||
wrapper.setProps({
|
||||
activeFilters: newFilters,
|
||||
});
|
||||
expect(refreshSpy.callCount).toBe(1);
|
||||
expect(wrapper.instance().appliedFilters).toEqual(newFilters);
|
||||
expect(refreshSpy.getCall(0).args[0]).toEqual([1, 2]);
|
||||
});
|
||||
|
||||
it('should call refresh if a filter scope is changed', () => {
|
||||
const newFilters = {
|
||||
...OVERRIDE_FILTERS,
|
||||
'3_country_name': { values: ['USA'], scope: [2] },
|
||||
};
|
||||
|
||||
wrapper.setProps({
|
||||
activeFilters: newFilters,
|
||||
});
|
||||
expect(refreshSpy.callCount).toBe(1);
|
||||
expect(refreshSpy.getCall(0).args[0]).toEqual([2]);
|
||||
});
|
||||
|
||||
it('should call refresh with empty [] if a filter is changed but scope is not applicable', () => {
|
||||
const newFilters = {
|
||||
...OVERRIDE_FILTERS,
|
||||
'3_country_name': { values: ['CHINA'], scope: [] },
|
||||
};
|
||||
|
||||
wrapper.setProps({
|
||||
activeFilters: newFilters,
|
||||
});
|
||||
expect(refreshSpy.callCount).toBe(1);
|
||||
expect(refreshSpy.getCall(0).args[0]).toEqual([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,225 +0,0 @@
|
||||
/**
|
||||
* 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 { shallow } from 'enzyme';
|
||||
import { supersetTheme } from '@superset-ui/core';
|
||||
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 {
|
||||
CHART_RENDERING_SUCCEEDED,
|
||||
CHART_UPDATE_SUCCEEDED,
|
||||
} from 'src/chart/chartAction';
|
||||
import { buildActiveFilters } from 'src/dashboard/util/activeDashboardFilters';
|
||||
import { FiltersBadge } from 'src/dashboard/components/FiltersBadge';
|
||||
import {
|
||||
getMockStoreWithFilters,
|
||||
getMockStoreWithNativeFilters,
|
||||
} from 'spec/fixtures/mockStore';
|
||||
import { sliceId } from 'spec/fixtures/mockChartQueries';
|
||||
import { dashboardFilters } from 'spec/fixtures/mockDashboardFilters';
|
||||
import { dashboardWithFilter } from 'spec/fixtures/mockDashboardLayout';
|
||||
import Icons from 'src/components/Icons';
|
||||
import { FeatureFlag } from 'src/featureFlags';
|
||||
|
||||
const defaultStore = getMockStoreWithFilters();
|
||||
function setup(store: Store = defaultStore) {
|
||||
return mount(
|
||||
<Provider store={store}>
|
||||
<FiltersBadge chartId={sliceId} />
|
||||
</Provider>,
|
||||
);
|
||||
}
|
||||
|
||||
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,
|
||||
});
|
||||
|
||||
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(() => 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"]')).toHaveText(
|
||||
'1',
|
||||
);
|
||||
expect(wrapper.find('WarningFilled')).not.toExist();
|
||||
});
|
||||
|
||||
it("shows a warning when there's a rejected filter", () => {
|
||||
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: [
|
||||
{ column: 'region', reason: 'not_in_datasource' },
|
||||
],
|
||||
},
|
||||
],
|
||||
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"]')).toHaveText(
|
||||
'0',
|
||||
);
|
||||
expect(
|
||||
wrapper.find('[data-test="incompatible-filter-count"]'),
|
||||
).toHaveText('1');
|
||||
// to look at the shape of the wrapper use:
|
||||
expect(wrapper.find(Icons.AlertSolid)).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', () => {
|
||||
// @ts-ignore
|
||||
global.featureFlags = {
|
||||
[FeatureFlag.DASHBOARD_NATIVE_FILTERS]: true,
|
||||
};
|
||||
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"]')).toHaveText(
|
||||
'1',
|
||||
);
|
||||
expect(wrapper.find('WarningFilled')).not.toExist();
|
||||
});
|
||||
|
||||
it("shows a warning when there's a rejected filter", () => {
|
||||
// @ts-ignore
|
||||
global.featureFlags = {
|
||||
[FeatureFlag.DASHBOARD_NATIVE_FILTERS]: true,
|
||||
};
|
||||
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: [
|
||||
{ column: 'region', reason: 'not_in_datasource' },
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
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"]')).toHaveText(
|
||||
'0',
|
||||
);
|
||||
expect(
|
||||
wrapper.find('[data-test="incompatible-filter-count"]'),
|
||||
).toHaveText('1');
|
||||
expect(wrapper.find(Icons.AlertSolid)).toExist();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,54 +0,0 @@
|
||||
/**
|
||||
* 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 { render } from 'spec/helpers/testing-library';
|
||||
|
||||
import MissingChart from 'src/dashboard/components/MissingChart';
|
||||
|
||||
type MissingChartProps = {
|
||||
height: number;
|
||||
};
|
||||
|
||||
const setup = (overrides?: MissingChartProps) => (
|
||||
<MissingChart height={100} {...overrides} />
|
||||
);
|
||||
|
||||
describe('MissingChart', () => {
|
||||
it('renders a .missing-chart-container', () => {
|
||||
const rendered = render(setup());
|
||||
|
||||
const missingChartContainer = rendered.container.querySelector(
|
||||
'.missing-chart-container',
|
||||
);
|
||||
expect(missingChartContainer).toBeVisible();
|
||||
});
|
||||
|
||||
it('renders a .missing-chart-body', () => {
|
||||
const rendered = render(setup());
|
||||
|
||||
const missingChartBody = rendered.container.querySelector(
|
||||
'.missing-chart-body',
|
||||
);
|
||||
const bodyText =
|
||||
'There is no chart definition associated with this component, could it have been deleted?<br><br>Delete this container and save to remove this message.';
|
||||
|
||||
expect(missingChartBody).toBeVisible();
|
||||
expect(missingChartBody?.innerHTML).toMatch(bodyText);
|
||||
});
|
||||
});
|
||||
@@ -1,337 +0,0 @@
|
||||
/**
|
||||
* 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 { mount } from 'enzyme';
|
||||
import { Provider } from 'react-redux';
|
||||
import fetchMock from 'fetch-mock';
|
||||
|
||||
import {
|
||||
supersetTheme,
|
||||
SupersetClient,
|
||||
ThemeProvider,
|
||||
} from '@superset-ui/core';
|
||||
|
||||
import Modal from 'src/components/Modal';
|
||||
import PropertiesModal from 'src/dashboard/components/PropertiesModal';
|
||||
import { mockStore } from 'spec/fixtures/mockStore';
|
||||
|
||||
const dashboardResult = {
|
||||
json: {
|
||||
result: {
|
||||
dashboard_title: 'New Title',
|
||||
slug: '/new',
|
||||
json_metadata: '{"something":"foo"}',
|
||||
owners: [],
|
||||
roles: [],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
fetchMock.restore();
|
||||
|
||||
fetchMock.get('glob:*/api/v1/dashboard/related/owners?*', {
|
||||
result: [],
|
||||
});
|
||||
|
||||
fetchMock.get('glob:*/api/v1/dashboard/*', {
|
||||
result: {
|
||||
dashboard_title: 'New Title',
|
||||
slug: '/new',
|
||||
json_metadata: '{"something":"foo"}',
|
||||
owners: [],
|
||||
roles: [],
|
||||
},
|
||||
});
|
||||
|
||||
// all these tests need to be moved to dashboard/components/PropertiesModal/PropertiesModal.test.tsx
|
||||
describe.skip('PropertiesModal', () => {
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
const requiredProps = {
|
||||
dashboardId: 1,
|
||||
show: true,
|
||||
addSuccessToast: () => {},
|
||||
};
|
||||
|
||||
function setup(overrideProps) {
|
||||
return mount(
|
||||
<Provider store={mockStore}>
|
||||
<PropertiesModal {...requiredProps} {...overrideProps} />
|
||||
</Provider>,
|
||||
{
|
||||
wrappingComponent: ThemeProvider,
|
||||
wrappingComponentProps: { theme: supersetTheme },
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
describe('onColorSchemeChange', () => {
|
||||
it('sets up a default state', () => {
|
||||
const wrapper = setup({ colorScheme: 'SUPERSET_DEFAULT' });
|
||||
expect(
|
||||
wrapper.find('PropertiesModal').instance().state.values.colorScheme,
|
||||
).toEqual('SUPERSET_DEFAULT');
|
||||
});
|
||||
describe('with a valid color scheme as an arg', () => {
|
||||
describe('without metadata', () => {
|
||||
const wrapper = setup({ colorScheme: 'SUPERSET_DEFAULT' });
|
||||
const modalInstance = wrapper.find('PropertiesModal').instance();
|
||||
it('updates the color scheme in the metadata', () => {
|
||||
const spy = jest.spyOn(modalInstance, 'onMetadataChange');
|
||||
modalInstance.onColorSchemeChange('SUPERSET_DEFAULT');
|
||||
expect(spy).toHaveBeenCalledWith(
|
||||
'{"something": "foo", "color_scheme": "SUPERSET_DEFAULT", "label_colors": {}}',
|
||||
);
|
||||
});
|
||||
});
|
||||
describe('with metadata', () => {
|
||||
describe('with color_scheme in the metadata', () => {
|
||||
it('will update the metadata', () => {
|
||||
const wrapper = setup();
|
||||
const modalInstance = wrapper.find('PropertiesModal').instance();
|
||||
modalInstance.setState({
|
||||
values: {
|
||||
json_metadata: '{"color_scheme": "foo"}',
|
||||
},
|
||||
});
|
||||
const spy = jest.spyOn(modalInstance, 'onMetadataChange');
|
||||
modalInstance.onColorSchemeChange('SUPERSET_DEFAULT');
|
||||
expect(spy).toHaveBeenCalledWith(
|
||||
'{"color_scheme": "SUPERSET_DEFAULT", "label_colors": {}}',
|
||||
);
|
||||
});
|
||||
});
|
||||
describe('without color_scheme in the metadata', () => {
|
||||
const wrapper = setup();
|
||||
const modalInstance = wrapper.find('PropertiesModal').instance();
|
||||
modalInstance.setState({
|
||||
values: {
|
||||
json_metadata: '{"timed_refresh_immune_slices": []}',
|
||||
},
|
||||
});
|
||||
it('will update the metadata', () => {
|
||||
const spy = jest.spyOn(modalInstance, 'onMetadataChange');
|
||||
modalInstance.onColorSchemeChange('SUPERSET_DEFAULT');
|
||||
expect(spy).toHaveBeenCalledWith(
|
||||
'{"something": "foo", "color_scheme": "SUPERSET_DEFAULT", "label_colors": {}}',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('with an invalid color scheme as an arg', () => {
|
||||
const wrapper = setup();
|
||||
const modalInstance = wrapper.find('PropertiesModal').instance();
|
||||
it('will raise an error', () => {
|
||||
const spy = jest.spyOn(Modal, 'error');
|
||||
expect(() =>
|
||||
modalInstance.onColorSchemeChange('THIS_WILL_NOT_WORK'),
|
||||
).toThrowError('A valid color scheme is required');
|
||||
expect(spy).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
describe('with an empty color scheme as an arg', () => {
|
||||
const wrapper = setup();
|
||||
const modalInstance = wrapper.find('PropertiesModal').instance();
|
||||
it('will not raise an error', () => {
|
||||
const spy = jest.spyOn(Modal, 'error');
|
||||
modalInstance.onColorSchemeChange('');
|
||||
expect(spy).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('onOwnersChange', () => {
|
||||
it('should update the state with the value passed', () => {
|
||||
const wrapper = setup();
|
||||
const modalInstance = wrapper.find('PropertiesModal').instance();
|
||||
const spy = jest.spyOn(modalInstance, 'updateFormState');
|
||||
const newOwners = [{ value: 1, label: 'foo' }];
|
||||
modalInstance.onOwnersChange(newOwners);
|
||||
expect(spy).toHaveBeenCalledWith('owners', newOwners);
|
||||
});
|
||||
});
|
||||
describe('onMetadataChange', () => {
|
||||
it('should update the state with the value passed', () => {
|
||||
const wrapper = setup();
|
||||
const modalInstance = wrapper.find('PropertiesModal').instance();
|
||||
const spy = jest.spyOn(modalInstance, 'updateFormState');
|
||||
modalInstance.onMetadataChange('foo');
|
||||
expect(spy).toHaveBeenCalledWith('json_metadata', 'foo');
|
||||
});
|
||||
});
|
||||
describe('onChange', () => {
|
||||
it('should update the state with the value passed', () => {
|
||||
const wrapper = setup();
|
||||
const modalInstance = wrapper.find('PropertiesModal').instance();
|
||||
const spy = jest.spyOn(modalInstance, 'updateFormState');
|
||||
modalInstance.onChange({ target: { name: 'test', value: 'foo' } });
|
||||
expect(spy).toHaveBeenCalledWith('test', 'foo');
|
||||
});
|
||||
});
|
||||
describe('fetchDashboardDetails', () => {
|
||||
it('should make an api call', () => {
|
||||
const spy = jest.spyOn(SupersetClient, 'get');
|
||||
const wrapper = setup();
|
||||
const modalInstance = wrapper.find('PropertiesModal').instance();
|
||||
modalInstance.fetchDashboardDetails();
|
||||
expect(spy).toHaveBeenCalledWith({
|
||||
endpoint: '/api/v1/dashboard/1',
|
||||
});
|
||||
});
|
||||
|
||||
it('should update state', async () => {
|
||||
const wrapper = setup();
|
||||
const modalInstance = wrapper.find('PropertiesModal').instance();
|
||||
const fetchSpy = jest
|
||||
.spyOn(SupersetClient, 'get')
|
||||
.mockResolvedValue(dashboardResult);
|
||||
modalInstance.fetchDashboardDetails();
|
||||
await fetchSpy();
|
||||
expect(modalInstance.state.values.colorScheme).toBeUndefined();
|
||||
expect(modalInstance.state.values.dashboard_title).toEqual('New Title');
|
||||
expect(modalInstance.state.values.slug).toEqual('/new');
|
||||
expect(modalInstance.state.values.json_metadata).toEqual(
|
||||
'{"something": "foo"}',
|
||||
);
|
||||
});
|
||||
|
||||
it('should call onOwnersChange', async () => {
|
||||
const wrapper = setup();
|
||||
const modalInstance = wrapper.find('PropertiesModal').instance();
|
||||
const fetchSpy = jest.spyOn(SupersetClient, 'get').mockResolvedValue({
|
||||
json: {
|
||||
result: {
|
||||
dashboard_title: 'New Title',
|
||||
slug: '/new',
|
||||
json_metadata: '{"something":"foo"}',
|
||||
owners: [{ id: 1, first_name: 'Al', last_name: 'Pacino' }],
|
||||
roles: [],
|
||||
},
|
||||
},
|
||||
});
|
||||
const onOwnersSpy = jest.spyOn(modalInstance, 'onOwnersChange');
|
||||
modalInstance.fetchDashboardDetails();
|
||||
await fetchSpy();
|
||||
expect(modalInstance.state.values.colorScheme).toBeUndefined();
|
||||
expect(onOwnersSpy).toHaveBeenCalledWith([
|
||||
{ value: 1, label: 'Al Pacino' },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should call onRolesChange', async () => {
|
||||
const wrapper = setup();
|
||||
const modalInstance = wrapper.find('PropertiesModal').instance();
|
||||
const fetchSpy = jest.spyOn(SupersetClient, 'get').mockResolvedValue({
|
||||
json: {
|
||||
result: {
|
||||
dashboard_title: 'New Title',
|
||||
slug: '/new',
|
||||
json_metadata: '{"something":"foo"}',
|
||||
owners: [],
|
||||
roles: [{ id: 1, name: 'Alpha' }],
|
||||
},
|
||||
},
|
||||
});
|
||||
const onRolwesSpy = jest.spyOn(modalInstance, 'onRolesChange');
|
||||
modalInstance.fetchDashboardDetails();
|
||||
await fetchSpy();
|
||||
expect(modalInstance.state.values.colorScheme).toBeUndefined();
|
||||
expect(onRolwesSpy).toHaveBeenCalledWith([{ value: 1, label: 'Alpha' }]);
|
||||
});
|
||||
|
||||
describe('when colorScheme is undefined as a prop', () => {
|
||||
describe('when color_scheme is defined in json_metadata', () => {
|
||||
const wrapper = setup();
|
||||
const modalInstance = wrapper.find('PropertiesModal').instance();
|
||||
it('should use the color_scheme from json_metadata in the api response', async () => {
|
||||
const fetchSpy = jest.spyOn(SupersetClient, 'get').mockResolvedValue({
|
||||
json: {
|
||||
result: {
|
||||
dashboard_title: 'New Title',
|
||||
slug: '/new',
|
||||
json_metadata: '{"color_scheme":"SUPERSET_DEFAULT"}',
|
||||
owners: [],
|
||||
roles: [],
|
||||
},
|
||||
},
|
||||
});
|
||||
modalInstance.fetchDashboardDetails();
|
||||
|
||||
// this below triggers the callback of the api call
|
||||
await fetchSpy();
|
||||
|
||||
expect(modalInstance.state.values.colorScheme).toEqual(
|
||||
'SUPERSET_DEFAULT',
|
||||
);
|
||||
});
|
||||
describe('when color_scheme is not defined in json_metadata', () => {
|
||||
const wrapper = setup();
|
||||
const modalInstance = wrapper.find('PropertiesModal').instance();
|
||||
it('should be undefined', async () => {
|
||||
const fetchSpy = jest
|
||||
.spyOn(SupersetClient, 'get')
|
||||
.mockResolvedValue(dashboardResult);
|
||||
modalInstance.fetchDashboardDetails();
|
||||
await fetchSpy();
|
||||
expect(modalInstance.state.values.colorScheme).toBeUndefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('when colorScheme is defined as a prop', () => {
|
||||
describe('when color_scheme is defined in json_metadata', () => {
|
||||
const wrapper = setup({ colorScheme: 'SUPERSET_DEFAULT' });
|
||||
const modalInstance = wrapper.find('PropertiesModal').instance();
|
||||
it('should use the color_scheme from json_metadata in the api response', async () => {
|
||||
const fetchSpy = jest.spyOn(SupersetClient, 'get').mockResolvedValue({
|
||||
json: {
|
||||
result: {
|
||||
dashboard_title: 'New Title',
|
||||
slug: '/new',
|
||||
json_metadata: '{"color_scheme":"SUPERSET_DEFAULT"}',
|
||||
owners: [],
|
||||
roles: [],
|
||||
},
|
||||
},
|
||||
});
|
||||
modalInstance.fetchDashboardDetails();
|
||||
await fetchSpy();
|
||||
expect(modalInstance.state.values.colorScheme).toEqual(
|
||||
'SUPERSET_DEFAULT',
|
||||
);
|
||||
});
|
||||
});
|
||||
describe('when color_scheme is not defined in json_metadata', () => {
|
||||
const wrapper = setup({ colorScheme: 'SUPERSET_DEFAULT' });
|
||||
const modalInstance = wrapper.find('PropertiesModal').instance();
|
||||
it('should use the colorScheme from the prop', async () => {
|
||||
const fetchSpy = jest
|
||||
.spyOn(SupersetClient, 'get')
|
||||
.mockResolvedValue(dashboardResult);
|
||||
modalInstance.fetchDashboardDetails();
|
||||
await fetchSpy();
|
||||
expect(modalInstance.state.values.colorScheme).toBeUndefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,237 +0,0 @@
|
||||
/**
|
||||
* 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 { 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,
|
||||
};
|
||||
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(),
|
||||
addDangerToast: jest.fn(),
|
||||
customCss: '#save-dash-split-button{margin-left: 100px;}',
|
||||
dashboardId: 1,
|
||||
dashboardInfo: {
|
||||
id: 1,
|
||||
dash_edit_perm: true,
|
||||
dash_save_perm: true,
|
||||
userId: '1',
|
||||
metadata: {},
|
||||
common: {
|
||||
conf: {},
|
||||
},
|
||||
},
|
||||
dashboardTitle: 'Title',
|
||||
editMode: false,
|
||||
expandedSlices: {},
|
||||
forceRefreshAllCharts: jest.fn(),
|
||||
hasUnsavedChanges: false,
|
||||
isLoading: false,
|
||||
layout: {},
|
||||
dataMask: {},
|
||||
onChange: jest.fn(),
|
||||
onSave: jest.fn(),
|
||||
refreshFrequency: 0,
|
||||
setRefreshFrequency: jest.fn(),
|
||||
shouldPersistRefreshFrequency: false,
|
||||
showPropertiesModal: jest.fn(),
|
||||
startPeriodicRender: jest.fn(),
|
||||
updateCss: jest.fn(),
|
||||
userCanEdit: false,
|
||||
userCanSave: false,
|
||||
userCanShare: false,
|
||||
lastModifiedTime: 0,
|
||||
});
|
||||
|
||||
const editModeOnProps = {
|
||||
...createProps(),
|
||||
editMode: true,
|
||||
};
|
||||
|
||||
const setup = (overrides?: any) => (
|
||||
<div className="dashboard-header">
|
||||
<HeaderActionsDropdown {...editModeOnProps} {...overrides} />
|
||||
</div>
|
||||
);
|
||||
|
||||
fetchMock.get('glob:*/csstemplateasyncmodelview/api/read', {});
|
||||
|
||||
const openRefreshIntervalModal = async () => {
|
||||
const headerActionsButton = screen.getByRole('img', { name: 'more-horiz' });
|
||||
userEvent.click(headerActionsButton);
|
||||
|
||||
const autoRefreshOption = screen.getByText('Set auto-refresh interval');
|
||||
userEvent.click(autoRefreshOption);
|
||||
};
|
||||
|
||||
const displayOptions = async () => {
|
||||
// Click default refresh interval option to display other options
|
||||
userEvent.click(screen.getByText(/don't refresh/i));
|
||||
};
|
||||
|
||||
const defaultRefreshIntervalModalProps = {
|
||||
triggerNode: <i className="fa fa-edit" />,
|
||||
refreshFrequency: 0,
|
||||
onChange: jest.fn(),
|
||||
editMode: true,
|
||||
};
|
||||
|
||||
describe('RefreshIntervalModal - RTL', () => {
|
||||
it('is valid', () => {
|
||||
expect(
|
||||
React.isValidElement(
|
||||
<RefreshIntervalModal {...defaultRefreshIntervalModalProps} />,
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
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();
|
||||
|
||||
screen.logTestingPlaygroundURL();
|
||||
// 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,
|
||||
);
|
||||
});
|
||||
|
||||
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();
|
||||
});
|
||||
});
|
||||
@@ -1,170 +0,0 @@
|
||||
/**
|
||||
* 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 { shallow } from 'enzyme';
|
||||
import sinon from 'sinon';
|
||||
|
||||
import { List } from 'react-virtualized';
|
||||
|
||||
import SliceAdder from 'src/dashboard/components/SliceAdder';
|
||||
import { sliceEntitiesForDashboard as mockSliceEntities } from 'spec/fixtures/mockSliceEntities';
|
||||
|
||||
describe('SliceAdder', () => {
|
||||
const mockEvent = {
|
||||
key: 'Enter',
|
||||
target: {
|
||||
value: 'mock event target',
|
||||
},
|
||||
preventDefault: () => {},
|
||||
};
|
||||
const props = {
|
||||
...mockSliceEntities,
|
||||
fetchAllSlices: () => {},
|
||||
selectedSliceIds: [127, 128],
|
||||
userId: '1',
|
||||
height: 100,
|
||||
};
|
||||
const errorProps = {
|
||||
...props,
|
||||
errorMessage: 'this is error',
|
||||
};
|
||||
|
||||
describe('SliceAdder.sortByComparator', () => {
|
||||
it('should sort by timestamp descending', () => {
|
||||
const sortedTimestamps = Object.values(props.slices)
|
||||
.sort(SliceAdder.sortByComparator('changed_on'))
|
||||
.map(slice => slice.changed_on);
|
||||
expect(
|
||||
sortedTimestamps.every((currentTimestamp, index) => {
|
||||
if (index === 0) {
|
||||
return true;
|
||||
}
|
||||
return currentTimestamp < sortedTimestamps[index - 1];
|
||||
}),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('should sort by slice_name', () => {
|
||||
const sortedNames = Object.values(props.slices)
|
||||
.sort(SliceAdder.sortByComparator('slice_name'))
|
||||
.map(slice => slice.slice_name);
|
||||
const expectedNames = Object.values(props.slices)
|
||||
.map(slice => slice.slice_name)
|
||||
.sort();
|
||||
expect(sortedNames).toEqual(expectedNames);
|
||||
});
|
||||
});
|
||||
|
||||
it('render List', () => {
|
||||
const wrapper = shallow(<SliceAdder {...props} />);
|
||||
wrapper.setState({ filteredSlices: Object.values(props.slices) });
|
||||
expect(wrapper.find(List)).toExist();
|
||||
});
|
||||
|
||||
it('render error', () => {
|
||||
const wrapper = shallow(<SliceAdder {...errorProps} />);
|
||||
wrapper.setState({ filteredSlices: Object.values(props.slices) });
|
||||
expect(wrapper.text()).toContain(errorProps.errorMessage);
|
||||
});
|
||||
|
||||
it('componentDidMount', () => {
|
||||
sinon.spy(SliceAdder.prototype, 'componentDidMount');
|
||||
sinon.spy(props, 'fetchAllSlices');
|
||||
|
||||
shallow(<SliceAdder {...props} />, {
|
||||
lifecycleExperimental: true,
|
||||
});
|
||||
expect(SliceAdder.prototype.componentDidMount.calledOnce).toBe(true);
|
||||
expect(props.fetchAllSlices.calledOnce).toBe(true);
|
||||
|
||||
SliceAdder.prototype.componentDidMount.restore();
|
||||
props.fetchAllSlices.restore();
|
||||
});
|
||||
|
||||
describe('UNSAFE_componentWillReceiveProps', () => {
|
||||
let wrapper;
|
||||
beforeEach(() => {
|
||||
wrapper = shallow(<SliceAdder {...props} />);
|
||||
wrapper.setState({ filteredSlices: Object.values(props.slices) });
|
||||
sinon.spy(wrapper.instance(), 'setState');
|
||||
});
|
||||
afterEach(() => {
|
||||
wrapper.instance().setState.restore();
|
||||
});
|
||||
|
||||
it('fetch slices should update state', () => {
|
||||
wrapper.instance().UNSAFE_componentWillReceiveProps({
|
||||
...props,
|
||||
lastUpdated: new Date().getTime(),
|
||||
});
|
||||
expect(wrapper.instance().setState.calledOnce).toBe(true);
|
||||
|
||||
const stateKeys = Object.keys(
|
||||
wrapper.instance().setState.lastCall.args[0],
|
||||
);
|
||||
expect(stateKeys).toContain('filteredSlices');
|
||||
});
|
||||
|
||||
it('select slices should update state', () => {
|
||||
wrapper.instance().UNSAFE_componentWillReceiveProps({
|
||||
...props,
|
||||
selectedSliceIds: [127],
|
||||
});
|
||||
expect(wrapper.instance().setState.calledOnce).toBe(true);
|
||||
|
||||
const stateKeys = Object.keys(
|
||||
wrapper.instance().setState.lastCall.args[0],
|
||||
);
|
||||
expect(stateKeys).toContain('selectedSliceIdsSet');
|
||||
});
|
||||
});
|
||||
|
||||
describe('should rerun filter and sort', () => {
|
||||
let wrapper;
|
||||
let spy;
|
||||
beforeEach(() => {
|
||||
wrapper = shallow(<SliceAdder {...props} />);
|
||||
wrapper.setState({ filteredSlices: Object.values(props.slices) });
|
||||
spy = sinon.spy(wrapper.instance(), 'getFilteredSortedSlices');
|
||||
});
|
||||
afterEach(() => {
|
||||
spy.restore();
|
||||
});
|
||||
|
||||
it('searchUpdated', () => {
|
||||
const newSearchTerm = 'new search term';
|
||||
wrapper.instance().searchUpdated(newSearchTerm);
|
||||
expect(spy.calledOnce).toBe(true);
|
||||
expect(spy.lastCall.args[0]).toBe(newSearchTerm);
|
||||
});
|
||||
|
||||
it('handleSelect', () => {
|
||||
const newSortBy = 'viz_type';
|
||||
wrapper.instance().handleSelect(newSortBy);
|
||||
expect(spy.calledOnce).toBe(true);
|
||||
expect(spy.lastCall.args[1]).toBe(newSortBy);
|
||||
});
|
||||
|
||||
it('handleKeyPress', () => {
|
||||
wrapper.instance().handleKeyPress(mockEvent);
|
||||
expect(spy.calledOnce).toBe(true);
|
||||
expect(spy.lastCall.args[0]).toBe(mockEvent.target.value);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,29 +0,0 @@
|
||||
/**
|
||||
* 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 { shallow } from 'enzyme';
|
||||
|
||||
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();
|
||||
});
|
||||
});
|
||||
@@ -1,87 +0,0 @@
|
||||
/**
|
||||
* 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 { shallow } from 'enzyme';
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
function setup(overrideProps) {
|
||||
const wrapper = shallow(<WithPopoverMenu {...props} {...overrideProps} />);
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
@@ -1,140 +0,0 @@
|
||||
/**
|
||||
* 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 { 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 { Dropdown, Menu } from 'src/common/components';
|
||||
import Alert from 'src/components/Alert';
|
||||
import { FiltersConfigModal } from 'src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigModal';
|
||||
|
||||
Object.defineProperty(window, 'matchMedia', {
|
||||
writable: true,
|
||||
value: jest.fn().mockImplementation(query => ({
|
||||
matches: false,
|
||||
media: query,
|
||||
onchange: null,
|
||||
addListener: jest.fn(), // deprecated
|
||||
removeListener: jest.fn(), // deprecated
|
||||
addEventListener: jest.fn(),
|
||||
removeEventListener: jest.fn(),
|
||||
dispatchEvent: jest.fn(),
|
||||
})),
|
||||
});
|
||||
|
||||
jest.mock('@superset-ui/core', () => ({
|
||||
// @ts-ignore
|
||||
...jest.requireActual('@superset-ui/core'),
|
||||
getChartMetadataRegistry: () => ({
|
||||
items: {
|
||||
filter_select: {
|
||||
value: {
|
||||
datasourceCount: 1,
|
||||
behaviors: ['NATIVE_FILTER'],
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
}));
|
||||
|
||||
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>,
|
||||
);
|
||||
}
|
||||
|
||||
it('should be a valid react element', () => {
|
||||
expect(React.isValidElement(<FiltersConfigModal {...mockedProps} />)).toBe(
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
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(Dropdown).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.',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,62 +0,0 @@
|
||||
/**
|
||||
* 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 { render } from 'spec/helpers/testing-library';
|
||||
|
||||
import ResizableContainer from 'src/dashboard/components/resizable/ResizableContainer';
|
||||
|
||||
interface ResizableContainerProps {
|
||||
id: string;
|
||||
children?: object;
|
||||
adjustableWidth?: boolean;
|
||||
adjustableHeight?: boolean;
|
||||
gutterWidth?: number;
|
||||
widthStep?: number;
|
||||
heightStep?: number;
|
||||
widthMultiple?: number;
|
||||
heightMultiple?: number;
|
||||
minWidthMultiple?: number;
|
||||
maxWidthMultiple?: number;
|
||||
minHeightMultiple?: number;
|
||||
maxHeightMultiple?: number;
|
||||
staticHeight?: number;
|
||||
staticHeightMultiple?: number;
|
||||
staticWidth?: number;
|
||||
staticWidthMultiple?: number;
|
||||
onResizeStop?: () => {};
|
||||
onResize?: () => {};
|
||||
onResizeStart?: () => {};
|
||||
editMode: boolean;
|
||||
}
|
||||
|
||||
describe('ResizableContainer', () => {
|
||||
const props = { editMode: false, id: 'id' };
|
||||
|
||||
const setup = (overrides?: ResizableContainerProps) => (
|
||||
<ResizableContainer {...props} {...overrides} />
|
||||
);
|
||||
|
||||
it('should render a Resizable container', () => {
|
||||
const rendered = render(setup());
|
||||
const resizableContainer = rendered.container.querySelector(
|
||||
'.resizable-container',
|
||||
);
|
||||
expect(resizableContainer).toBeVisible();
|
||||
});
|
||||
});
|
||||
@@ -1,49 +0,0 @@
|
||||
/**
|
||||
* 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 { render } from 'spec/helpers/testing-library';
|
||||
|
||||
import ResizableHandle from 'src/dashboard/components/resizable/ResizableHandle';
|
||||
|
||||
/* eslint-disable react/jsx-pascal-case */
|
||||
describe('ResizableHandle', () => {
|
||||
it('should render a right resize handle', () => {
|
||||
const rendered = render(<ResizableHandle.right />);
|
||||
expect(
|
||||
rendered.container.querySelector('.resize-handle.resize-handle--right'),
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
it('should render a bottom resize handle', () => {
|
||||
const rendered = render(<ResizableHandle.bottom />);
|
||||
expect(
|
||||
rendered.container.querySelector('.resize-handle.resize-handle--bottom'),
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
it('should render a bottomRight resize handle', () => {
|
||||
const rendered = render(<ResizableHandle.bottomRight />);
|
||||
expect(
|
||||
rendered.container.querySelector(
|
||||
'.resize-handle.resize-handle--bottom-right',
|
||||
),
|
||||
).toBeVisible();
|
||||
});
|
||||
});
|
||||
/* eslint-enable react/jsx-pascal-case */
|
||||
@@ -1,74 +0,0 @@
|
||||
/**
|
||||
* 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 { NativeFilterType } from 'src/dashboard/components/nativeFilters/types';
|
||||
import { NativeFiltersState } from 'src/dashboard/reducers/types';
|
||||
import { DataMaskStateWithId } from 'src/dataMask/types';
|
||||
|
||||
export const mockDataMaskInfo: DataMaskStateWithId = {
|
||||
DefaultsID: {
|
||||
id: 'DefaultId',
|
||||
ownState: {},
|
||||
filterState: {
|
||||
value: [],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const nativeFiltersInfo: NativeFiltersState = {
|
||||
filterSets: {
|
||||
'1': {
|
||||
id: 1,
|
||||
name: 'Set name',
|
||||
nativeFilters: {},
|
||||
dataMask: mockDataMaskInfo,
|
||||
},
|
||||
},
|
||||
filters: {
|
||||
DefaultsID: {
|
||||
cascadeParentIds: [],
|
||||
id: 'DefaultsID',
|
||||
name: 'test',
|
||||
filterType: 'filter_select',
|
||||
targets: [
|
||||
{
|
||||
datasetId: 0,
|
||||
column: {
|
||||
name: 'test column',
|
||||
displayName: 'test column',
|
||||
},
|
||||
},
|
||||
],
|
||||
defaultDataMask: {
|
||||
filterState: {
|
||||
value: null,
|
||||
},
|
||||
},
|
||||
scope: {
|
||||
rootPath: [],
|
||||
excluded: [],
|
||||
},
|
||||
controlValues: {
|
||||
allowsMultipleValues: true,
|
||||
isRequired: false,
|
||||
},
|
||||
type: NativeFilterType.NATIVE_FILTER,
|
||||
description: 'test description',
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -1,179 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
/* eslint-disable camelcase */
|
||||
import {
|
||||
ADD_FILTER,
|
||||
REMOVE_FILTER,
|
||||
CHANGE_FILTER,
|
||||
UPDATE_DASHBOARD_FILTERS_SCOPE,
|
||||
} from 'src/dashboard/actions/dashboardFilters';
|
||||
import dashboardFiltersReducer, {
|
||||
DASHBOARD_FILTER_SCOPE_GLOBAL,
|
||||
} from 'src/dashboard/reducers/dashboardFilters';
|
||||
import * as activeDashboardFilters from 'src/dashboard/util/activeDashboardFilters';
|
||||
import {
|
||||
emptyFilters,
|
||||
dashboardFilters,
|
||||
} from 'spec/fixtures/mockDashboardFilters';
|
||||
import {
|
||||
sliceEntitiesForDashboard,
|
||||
filterId,
|
||||
column,
|
||||
} from 'spec/fixtures/mockSliceEntities';
|
||||
import { filterComponent } from 'spec/fixtures/mockDashboardLayout';
|
||||
|
||||
describe('dashboardFilters reducer', () => {
|
||||
const { form_data } = sliceEntitiesForDashboard.slices[filterId];
|
||||
const component = filterComponent;
|
||||
const directPathToFilter = (component.parents || []).slice();
|
||||
directPathToFilter.push(component.id);
|
||||
|
||||
it('should add a new filter if it does not exist', () => {
|
||||
expect(
|
||||
dashboardFiltersReducer(emptyFilters, {
|
||||
type: ADD_FILTER,
|
||||
chartId: filterId,
|
||||
component,
|
||||
form_data,
|
||||
}),
|
||||
).toEqual({
|
||||
[filterId]: {
|
||||
chartId: filterId,
|
||||
componentId: component.id,
|
||||
directPathToFilter,
|
||||
filterName: component.meta.sliceName,
|
||||
isDateFilter: false,
|
||||
isInstantFilter: !!form_data.instant_filtering,
|
||||
columns: {
|
||||
[column]: undefined,
|
||||
},
|
||||
labels: {
|
||||
[column]: column,
|
||||
},
|
||||
scopes: {
|
||||
[column]: DASHBOARD_FILTER_SCOPE_GLOBAL,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should overwrite a filter if merge is false', () => {
|
||||
expect(
|
||||
dashboardFiltersReducer(dashboardFilters, {
|
||||
type: CHANGE_FILTER,
|
||||
chartId: filterId,
|
||||
newSelectedValues: {
|
||||
region: ['c'],
|
||||
gender: ['body', 'girl'],
|
||||
},
|
||||
merge: false,
|
||||
}),
|
||||
).toEqual({
|
||||
[filterId]: {
|
||||
chartId: filterId,
|
||||
componentId: component.id,
|
||||
directPathToFilter,
|
||||
isDateFilter: false,
|
||||
isInstantFilter: !!form_data.instant_filtering,
|
||||
columns: {
|
||||
region: ['c'],
|
||||
gender: ['body', 'girl'],
|
||||
},
|
||||
labels: {
|
||||
[column]: column,
|
||||
},
|
||||
scopes: {
|
||||
[column]: DASHBOARD_FILTER_SCOPE_GLOBAL,
|
||||
gender: DASHBOARD_FILTER_SCOPE_GLOBAL,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should merge a filter if merge is true', () => {
|
||||
expect(
|
||||
dashboardFiltersReducer(dashboardFilters, {
|
||||
type: CHANGE_FILTER,
|
||||
chartId: filterId,
|
||||
newSelectedValues: {
|
||||
region: ['c'],
|
||||
gender: ['body', 'girl'],
|
||||
},
|
||||
merge: true,
|
||||
}),
|
||||
).toEqual({
|
||||
[filterId]: {
|
||||
chartId: filterId,
|
||||
componentId: component.id,
|
||||
directPathToFilter,
|
||||
isDateFilter: false,
|
||||
isInstantFilter: !!form_data.instant_filtering,
|
||||
columns: {
|
||||
region: ['a', 'b', 'c'],
|
||||
gender: ['body', 'girl'],
|
||||
},
|
||||
labels: {
|
||||
[column]: column,
|
||||
},
|
||||
scopes: {
|
||||
region: DASHBOARD_FILTER_SCOPE_GLOBAL,
|
||||
gender: DASHBOARD_FILTER_SCOPE_GLOBAL,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should remove the filter if values are empty', () => {
|
||||
expect(
|
||||
dashboardFiltersReducer(dashboardFilters, {
|
||||
type: REMOVE_FILTER,
|
||||
chartId: filterId,
|
||||
}),
|
||||
).toEqual({});
|
||||
});
|
||||
|
||||
it('should buildActiveFilters on UPDATE_DASHBOARD_FILTERS_SCOPE', () => {
|
||||
const regionScope = {
|
||||
scope: ['TAB-1'],
|
||||
immune: [],
|
||||
};
|
||||
const genderScope = {
|
||||
scope: ['ROOT_ID'],
|
||||
immune: [1],
|
||||
};
|
||||
const scopes = {
|
||||
[`${filterId}_region`]: regionScope,
|
||||
[`${filterId}_gender`]: genderScope,
|
||||
};
|
||||
activeDashboardFilters.buildActiveFilters = jest.fn();
|
||||
expect(
|
||||
dashboardFiltersReducer(dashboardFilters, {
|
||||
type: UPDATE_DASHBOARD_FILTERS_SCOPE,
|
||||
scopes,
|
||||
})[filterId].scopes,
|
||||
).toEqual({
|
||||
region: regionScope,
|
||||
gender: genderScope,
|
||||
});
|
||||
|
||||
// when UPDATE_DASHBOARD_FILTERS_SCOPE is changed, applicable filters to a chart
|
||||
// might be changed.
|
||||
expect(activeDashboardFilters.buildActiveFilters).toBeCalled();
|
||||
});
|
||||
});
|
||||
@@ -1,458 +0,0 @@
|
||||
/**
|
||||
* 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 layoutReducer from 'src/dashboard/reducers/dashboardLayout';
|
||||
|
||||
import {
|
||||
UPDATE_COMPONENTS,
|
||||
DELETE_COMPONENT,
|
||||
CREATE_COMPONENT,
|
||||
MOVE_COMPONENT,
|
||||
CREATE_TOP_LEVEL_TABS,
|
||||
DELETE_TOP_LEVEL_TABS,
|
||||
} from 'src/dashboard/actions/dashboardLayout';
|
||||
|
||||
import {
|
||||
CHART_TYPE,
|
||||
DASHBOARD_GRID_TYPE,
|
||||
DASHBOARD_ROOT_TYPE,
|
||||
ROW_TYPE,
|
||||
TAB_TYPE,
|
||||
TABS_TYPE,
|
||||
} from 'src/dashboard/util/componentTypes';
|
||||
|
||||
import {
|
||||
DASHBOARD_ROOT_ID,
|
||||
DASHBOARD_GRID_ID,
|
||||
NEW_COMPONENTS_SOURCE_ID,
|
||||
NEW_TABS_ID,
|
||||
NEW_ROW_ID,
|
||||
} from 'src/dashboard/util/constants';
|
||||
|
||||
describe('dashboardLayout reducer', () => {
|
||||
it('should return initial state for unrecognized actions', () => {
|
||||
expect(layoutReducer(undefined, {})).toEqual({});
|
||||
});
|
||||
|
||||
it('should delete a component, remove its reference in its parent, and recursively all of its children', () => {
|
||||
expect(
|
||||
layoutReducer(
|
||||
{
|
||||
toDelete: {
|
||||
id: 'toDelete',
|
||||
children: ['child1'],
|
||||
},
|
||||
child1: {
|
||||
id: 'child1',
|
||||
children: ['child2'],
|
||||
},
|
||||
child2: {
|
||||
id: 'child2',
|
||||
children: [],
|
||||
},
|
||||
parentId: {
|
||||
id: 'parentId',
|
||||
type: ROW_TYPE,
|
||||
children: ['toDelete', 'anotherId'],
|
||||
},
|
||||
},
|
||||
{
|
||||
type: DELETE_COMPONENT,
|
||||
payload: { id: 'toDelete', parentId: 'parentId' },
|
||||
},
|
||||
),
|
||||
).toEqual({
|
||||
parentId: {
|
||||
id: 'parentId',
|
||||
children: ['anotherId'],
|
||||
type: ROW_TYPE,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should delete a parent if the parent was a row and no longer has children', () => {
|
||||
expect(
|
||||
layoutReducer(
|
||||
{
|
||||
grandparentId: {
|
||||
id: 'grandparentId',
|
||||
children: ['parentId'],
|
||||
},
|
||||
parentId: {
|
||||
id: 'parentId',
|
||||
type: ROW_TYPE,
|
||||
children: ['toDelete'],
|
||||
},
|
||||
toDelete: {
|
||||
id: 'toDelete',
|
||||
children: ['child1'],
|
||||
},
|
||||
child1: {
|
||||
id: 'child1',
|
||||
children: [],
|
||||
},
|
||||
},
|
||||
{
|
||||
type: DELETE_COMPONENT,
|
||||
payload: { id: 'toDelete', parentId: 'parentId' },
|
||||
},
|
||||
),
|
||||
).toEqual({
|
||||
grandparentId: {
|
||||
id: 'grandparentId',
|
||||
children: [],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should update components', () => {
|
||||
expect(
|
||||
layoutReducer(
|
||||
{
|
||||
update: {
|
||||
id: 'update',
|
||||
children: [],
|
||||
},
|
||||
update2: {
|
||||
id: 'update2',
|
||||
children: [],
|
||||
},
|
||||
dontUpdate: {
|
||||
id: 'dontUpdate',
|
||||
something: 'something',
|
||||
children: ['abcd'],
|
||||
},
|
||||
},
|
||||
{
|
||||
type: UPDATE_COMPONENTS,
|
||||
payload: {
|
||||
nextComponents: {
|
||||
update: {
|
||||
id: 'update',
|
||||
newField: 'newField',
|
||||
},
|
||||
update2: {
|
||||
id: 'update2',
|
||||
newField: 'newField',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
),
|
||||
).toEqual({
|
||||
update: {
|
||||
id: 'update',
|
||||
newField: 'newField',
|
||||
},
|
||||
update2: {
|
||||
id: 'update2',
|
||||
newField: 'newField',
|
||||
},
|
||||
dontUpdate: {
|
||||
id: 'dontUpdate',
|
||||
something: 'something',
|
||||
children: ['abcd'],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should move a component', () => {
|
||||
const layout = {
|
||||
source: {
|
||||
id: 'source',
|
||||
type: ROW_TYPE,
|
||||
children: ['dontMove', 'toMove'],
|
||||
},
|
||||
destination: {
|
||||
id: 'destination',
|
||||
type: ROW_TYPE,
|
||||
children: ['anotherChild'],
|
||||
},
|
||||
toMove: {
|
||||
id: 'toMove',
|
||||
type: CHART_TYPE,
|
||||
children: [],
|
||||
},
|
||||
};
|
||||
|
||||
const dropResult = {
|
||||
source: { id: 'source', type: ROW_TYPE, index: 1 },
|
||||
destination: { id: 'destination', type: ROW_TYPE, index: 0 },
|
||||
dragging: { id: 'toMove', type: CHART_TYPE },
|
||||
};
|
||||
|
||||
expect(
|
||||
layoutReducer(layout, {
|
||||
type: MOVE_COMPONENT,
|
||||
payload: { dropResult },
|
||||
}),
|
||||
).toEqual({
|
||||
source: {
|
||||
id: 'source',
|
||||
type: ROW_TYPE,
|
||||
children: ['dontMove'],
|
||||
},
|
||||
destination: {
|
||||
id: 'destination',
|
||||
type: ROW_TYPE,
|
||||
children: ['toMove', 'anotherChild'],
|
||||
},
|
||||
toMove: {
|
||||
id: 'toMove',
|
||||
type: CHART_TYPE,
|
||||
children: [],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should wrap a moved component in a row if need be', () => {
|
||||
const layout = {
|
||||
source: {
|
||||
id: 'source',
|
||||
type: ROW_TYPE,
|
||||
children: ['dontMove', 'toMove'],
|
||||
},
|
||||
destination: {
|
||||
id: 'destination',
|
||||
type: DASHBOARD_GRID_TYPE,
|
||||
children: [],
|
||||
},
|
||||
toMove: {
|
||||
id: 'toMove',
|
||||
type: CHART_TYPE,
|
||||
children: [],
|
||||
},
|
||||
};
|
||||
|
||||
const dropResult = {
|
||||
source: { id: 'source', type: ROW_TYPE, index: 1 },
|
||||
destination: { id: 'destination', type: DASHBOARD_GRID_TYPE, index: 0 },
|
||||
dragging: { id: 'toMove', type: CHART_TYPE },
|
||||
};
|
||||
|
||||
const result = layoutReducer(layout, {
|
||||
type: MOVE_COMPONENT,
|
||||
payload: { dropResult },
|
||||
});
|
||||
|
||||
const newRow = Object.values(result).find(
|
||||
component =>
|
||||
['source', 'destination', 'toMove'].indexOf(component.id) === -1,
|
||||
);
|
||||
|
||||
expect(newRow.children[0]).toBe('toMove');
|
||||
expect(result.destination.children[0]).toBe(newRow.id);
|
||||
expect(Object.keys(result)).toHaveLength(4);
|
||||
});
|
||||
|
||||
it('should add top-level tabs from a new tabs component, moving grid children to new tab', () => {
|
||||
const layout = {
|
||||
[DASHBOARD_ROOT_ID]: {
|
||||
id: DASHBOARD_ROOT_ID,
|
||||
children: [DASHBOARD_GRID_ID],
|
||||
},
|
||||
[DASHBOARD_GRID_ID]: {
|
||||
id: DASHBOARD_GRID_ID,
|
||||
children: ['child'],
|
||||
},
|
||||
child: {
|
||||
id: 'child',
|
||||
children: [],
|
||||
},
|
||||
};
|
||||
|
||||
const dropResult = {
|
||||
source: { id: NEW_COMPONENTS_SOURCE_ID, type: '' },
|
||||
destination: {
|
||||
id: DASHBOARD_ROOT_ID,
|
||||
type: DASHBOARD_ROOT_TYPE,
|
||||
index: 0,
|
||||
},
|
||||
dragging: { id: NEW_TABS_ID, type: TABS_TYPE },
|
||||
};
|
||||
|
||||
const result = layoutReducer(layout, {
|
||||
type: CREATE_TOP_LEVEL_TABS,
|
||||
payload: { dropResult },
|
||||
});
|
||||
|
||||
const tabComponent = Object.values(result).find(
|
||||
component => component.type === TAB_TYPE,
|
||||
);
|
||||
|
||||
const tabsComponent = Object.values(result).find(
|
||||
component => component.type === TABS_TYPE,
|
||||
);
|
||||
|
||||
expect(Object.keys(result)).toHaveLength(5); // initial + Tabs + Tab
|
||||
expect(result[DASHBOARD_ROOT_ID].children[0]).toBe(tabsComponent.id);
|
||||
expect(result[tabsComponent.id].children[0]).toBe(tabComponent.id);
|
||||
expect(result[tabComponent.id].children[0]).toBe('child');
|
||||
expect(result[DASHBOARD_GRID_ID].children).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('should add top-level tabs from an existing tabs component, moving grid children to new tab', () => {
|
||||
const layout = {
|
||||
[DASHBOARD_ROOT_ID]: {
|
||||
id: DASHBOARD_ROOT_ID,
|
||||
children: [DASHBOARD_GRID_ID],
|
||||
},
|
||||
[DASHBOARD_GRID_ID]: {
|
||||
id: DASHBOARD_GRID_ID,
|
||||
children: ['child', 'tabs', 'child2'],
|
||||
},
|
||||
child: {
|
||||
id: 'child',
|
||||
children: [],
|
||||
},
|
||||
child2: {
|
||||
id: 'child2',
|
||||
children: [],
|
||||
},
|
||||
tabs: {
|
||||
id: 'tabs',
|
||||
type: TABS_TYPE,
|
||||
children: ['tab'],
|
||||
},
|
||||
tab: {
|
||||
id: 'tab',
|
||||
type: TAB_TYPE,
|
||||
children: [],
|
||||
},
|
||||
};
|
||||
|
||||
const dropResult = {
|
||||
source: { id: DASHBOARD_GRID_ID, type: DASHBOARD_GRID_TYPE, index: 1 },
|
||||
destination: {
|
||||
id: DASHBOARD_ROOT_ID,
|
||||
type: DASHBOARD_ROOT_TYPE,
|
||||
index: 0,
|
||||
},
|
||||
dragging: { id: 'tabs', type: TABS_TYPE },
|
||||
};
|
||||
|
||||
const result = layoutReducer(layout, {
|
||||
type: CREATE_TOP_LEVEL_TABS,
|
||||
payload: { dropResult },
|
||||
});
|
||||
|
||||
expect(Object.keys(result)).toHaveLength(Object.keys(layout).length);
|
||||
expect(result[DASHBOARD_ROOT_ID].children[0]).toBe('tabs');
|
||||
expect(result.tabs.children[0]).toBe('tab');
|
||||
expect(result.tab.children).toEqual(['child', 'child2']);
|
||||
expect(result[DASHBOARD_GRID_ID].children).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('should remove top-level tabs, moving children to the grid', () => {
|
||||
const layout = {
|
||||
[DASHBOARD_ROOT_ID]: {
|
||||
id: DASHBOARD_ROOT_ID,
|
||||
children: ['tabs'],
|
||||
},
|
||||
[DASHBOARD_GRID_ID]: {
|
||||
id: DASHBOARD_GRID_ID,
|
||||
children: [],
|
||||
},
|
||||
child: {
|
||||
id: 'child',
|
||||
children: [],
|
||||
},
|
||||
child2: {
|
||||
id: 'child2',
|
||||
children: [],
|
||||
},
|
||||
tabs: {
|
||||
id: 'tabs',
|
||||
type: TABS_TYPE,
|
||||
children: ['tab'],
|
||||
},
|
||||
tab: {
|
||||
id: 'tab',
|
||||
type: TAB_TYPE,
|
||||
children: ['child', 'child2'],
|
||||
},
|
||||
};
|
||||
|
||||
const dropResult = {
|
||||
source: { id: DASHBOARD_GRID_ID, type: DASHBOARD_GRID_TYPE, index: 1 },
|
||||
destination: {
|
||||
id: DASHBOARD_ROOT_ID,
|
||||
type: DASHBOARD_ROOT_TYPE,
|
||||
index: 0,
|
||||
},
|
||||
dragging: { id: 'tabs', type: TABS_TYPE },
|
||||
};
|
||||
|
||||
const result = layoutReducer(layout, {
|
||||
type: DELETE_TOP_LEVEL_TABS,
|
||||
payload: { dropResult },
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
[DASHBOARD_ROOT_ID]: {
|
||||
id: DASHBOARD_ROOT_ID,
|
||||
children: [DASHBOARD_GRID_ID],
|
||||
},
|
||||
[DASHBOARD_GRID_ID]: {
|
||||
id: DASHBOARD_GRID_ID,
|
||||
children: ['child', 'child2'],
|
||||
},
|
||||
child: {
|
||||
id: 'child',
|
||||
children: [],
|
||||
},
|
||||
child2: {
|
||||
id: 'child2',
|
||||
children: [],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should create a component', () => {
|
||||
const layout = {
|
||||
[DASHBOARD_ROOT_ID]: {
|
||||
id: DASHBOARD_ROOT_ID,
|
||||
children: [DASHBOARD_GRID_ID],
|
||||
},
|
||||
[DASHBOARD_GRID_ID]: {
|
||||
id: DASHBOARD_GRID_ID,
|
||||
children: ['child'],
|
||||
},
|
||||
child: { id: 'child' },
|
||||
};
|
||||
|
||||
const dropResult = {
|
||||
source: { id: NEW_COMPONENTS_SOURCE_ID, type: '' },
|
||||
destination: {
|
||||
id: DASHBOARD_GRID_ID,
|
||||
type: DASHBOARD_GRID_TYPE,
|
||||
index: 1,
|
||||
},
|
||||
dragging: { id: NEW_ROW_ID, type: ROW_TYPE },
|
||||
};
|
||||
|
||||
const result = layoutReducer(layout, {
|
||||
type: CREATE_COMPONENT,
|
||||
payload: { dropResult },
|
||||
});
|
||||
|
||||
const newId = result[DASHBOARD_GRID_ID].children[1];
|
||||
expect(result[DASHBOARD_GRID_ID].children).toHaveLength(2);
|
||||
expect(result[newId].type).toBe(ROW_TYPE);
|
||||
});
|
||||
});
|
||||
@@ -1,200 +0,0 @@
|
||||
/**
|
||||
* 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 {
|
||||
ADD_SLICE,
|
||||
ON_CHANGE,
|
||||
ON_SAVE,
|
||||
REMOVE_SLICE,
|
||||
SET_EDIT_MODE,
|
||||
SET_FOCUSED_FILTER_FIELD,
|
||||
SET_MAX_UNDO_HISTORY_EXCEEDED,
|
||||
SET_UNSAVED_CHANGES,
|
||||
TOGGLE_EXPAND_SLICE,
|
||||
TOGGLE_FAVE_STAR,
|
||||
UNSET_FOCUSED_FILTER_FIELD,
|
||||
} from 'src/dashboard/actions/dashboardState';
|
||||
|
||||
import dashboardStateReducer from 'src/dashboard/reducers/dashboardState';
|
||||
|
||||
describe('dashboardState reducer', () => {
|
||||
it('should return initial state', () => {
|
||||
expect(dashboardStateReducer(undefined, {})).toEqual({});
|
||||
});
|
||||
|
||||
it('should add a slice', () => {
|
||||
expect(
|
||||
dashboardStateReducer(
|
||||
{ sliceIds: [1] },
|
||||
{ type: ADD_SLICE, slice: { slice_id: 2 } },
|
||||
),
|
||||
).toEqual({ sliceIds: [1, 2] });
|
||||
});
|
||||
|
||||
it('should remove a slice', () => {
|
||||
expect(
|
||||
dashboardStateReducer(
|
||||
{ sliceIds: [1, 2], filters: {} },
|
||||
{ type: REMOVE_SLICE, sliceId: 2 },
|
||||
),
|
||||
).toEqual({ sliceIds: [1], filters: {} });
|
||||
});
|
||||
|
||||
it('should toggle fav star', () => {
|
||||
expect(
|
||||
dashboardStateReducer(
|
||||
{ isStarred: false },
|
||||
{ type: TOGGLE_FAVE_STAR, isStarred: true },
|
||||
),
|
||||
).toEqual({ isStarred: true });
|
||||
});
|
||||
|
||||
it('should toggle edit mode', () => {
|
||||
expect(
|
||||
dashboardStateReducer(
|
||||
{ editMode: false },
|
||||
{ type: SET_EDIT_MODE, editMode: true },
|
||||
),
|
||||
).toEqual({
|
||||
editMode: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('should toggle expanded slices', () => {
|
||||
expect(
|
||||
dashboardStateReducer(
|
||||
{ expandedSlices: { 1: true, 2: false } },
|
||||
{ type: TOGGLE_EXPAND_SLICE, sliceId: 1 },
|
||||
),
|
||||
).toEqual({ expandedSlices: { 2: false } });
|
||||
|
||||
expect(
|
||||
dashboardStateReducer(
|
||||
{ expandedSlices: { 1: true, 2: false } },
|
||||
{ type: TOGGLE_EXPAND_SLICE, sliceId: 2 },
|
||||
),
|
||||
).toEqual({ expandedSlices: { 1: true, 2: true } });
|
||||
});
|
||||
|
||||
it('should set hasUnsavedChanges', () => {
|
||||
expect(dashboardStateReducer({}, { type: ON_CHANGE })).toEqual({
|
||||
hasUnsavedChanges: true,
|
||||
});
|
||||
|
||||
expect(
|
||||
dashboardStateReducer(
|
||||
{},
|
||||
{ type: SET_UNSAVED_CHANGES, payload: { hasUnsavedChanges: false } },
|
||||
),
|
||||
).toEqual({
|
||||
hasUnsavedChanges: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('should set maxUndoHistoryExceeded', () => {
|
||||
expect(
|
||||
dashboardStateReducer(
|
||||
{},
|
||||
{
|
||||
type: SET_MAX_UNDO_HISTORY_EXCEEDED,
|
||||
payload: { maxUndoHistoryExceeded: true },
|
||||
},
|
||||
),
|
||||
).toEqual({
|
||||
maxUndoHistoryExceeded: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('should set unsaved changes, max undo history, and editMode to false on save', () => {
|
||||
const result = dashboardStateReducer(
|
||||
{ hasUnsavedChanges: true },
|
||||
{ type: ON_SAVE },
|
||||
);
|
||||
expect(result.hasUnsavedChanges).toBe(false);
|
||||
expect(result.maxUndoHistoryExceeded).toBe(false);
|
||||
expect(result.editMode).toBe(false);
|
||||
expect(result.updatedColorScheme).toBe(false);
|
||||
});
|
||||
|
||||
it('should reset lastModifiedTime on save', () => {
|
||||
const initTime = new Date().getTime() / 1000;
|
||||
dashboardStateReducer(
|
||||
{
|
||||
lastModifiedTime: initTime,
|
||||
},
|
||||
{},
|
||||
);
|
||||
|
||||
const lastModifiedTime = new Date().getTime() / 1000;
|
||||
expect(
|
||||
dashboardStateReducer(
|
||||
{ hasUnsavedChanges: true },
|
||||
{ type: ON_SAVE, lastModifiedTime },
|
||||
).lastModifiedTime,
|
||||
).toBeGreaterThanOrEqual(initTime);
|
||||
});
|
||||
|
||||
it('should clear the focused filter field', () => {
|
||||
const initState = {
|
||||
focusedFilterField: {
|
||||
chartId: 1,
|
||||
column: 'column_1',
|
||||
},
|
||||
};
|
||||
|
||||
const cleared = dashboardStateReducer(initState, {
|
||||
type: UNSET_FOCUSED_FILTER_FIELD,
|
||||
chartId: 1,
|
||||
column: 'column_1',
|
||||
});
|
||||
|
||||
expect(cleared.focusedFilterField).toBeNull();
|
||||
});
|
||||
|
||||
it('should only clear focused filter when the fields match', () => {
|
||||
// dashboard only has 1 focused filter field at a time,
|
||||
// but when user switch different filter boxes,
|
||||
// browser didn't always fire onBlur and onFocus events in order.
|
||||
|
||||
// init state: has 1 focus field
|
||||
const initState = {
|
||||
focusedFilterField: {
|
||||
chartId: 1,
|
||||
column: 'column_1',
|
||||
},
|
||||
};
|
||||
// when user switching filter,
|
||||
// browser focus on new filter first,
|
||||
// then blur current filter
|
||||
const step1 = dashboardStateReducer(initState, {
|
||||
type: SET_FOCUSED_FILTER_FIELD,
|
||||
chartId: 2,
|
||||
column: 'column_2',
|
||||
});
|
||||
const step2 = dashboardStateReducer(step1, {
|
||||
type: UNSET_FOCUSED_FILTER_FIELD,
|
||||
chartId: 1,
|
||||
column: 'column_1',
|
||||
});
|
||||
|
||||
expect(step2.focusedFilterField).toEqual({
|
||||
chartId: 2,
|
||||
column: 'column_2',
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,66 +0,0 @@
|
||||
/**
|
||||
* 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 {
|
||||
FETCH_ALL_SLICES_FAILED,
|
||||
FETCH_ALL_SLICES_STARTED,
|
||||
SET_ALL_SLICES,
|
||||
} from 'src/dashboard/actions/sliceEntities';
|
||||
|
||||
import sliceEntitiesReducer from 'src/dashboard/reducers/sliceEntities';
|
||||
|
||||
describe('sliceEntities reducer', () => {
|
||||
it('should return initial state', () => {
|
||||
expect(sliceEntitiesReducer({}, {})).toEqual({});
|
||||
});
|
||||
|
||||
it('should set loading when fetching slices', () => {
|
||||
expect(
|
||||
sliceEntitiesReducer(
|
||||
{ isLoading: false },
|
||||
{ type: FETCH_ALL_SLICES_STARTED },
|
||||
).isLoading,
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('should set slices', () => {
|
||||
const result = sliceEntitiesReducer(
|
||||
{ slices: { a: {} } },
|
||||
{ type: SET_ALL_SLICES, payload: { slices: { 1: {}, 2: {} } } },
|
||||
);
|
||||
|
||||
expect(result.slices).toEqual({
|
||||
1: {},
|
||||
2: {},
|
||||
a: {},
|
||||
});
|
||||
expect(result.isLoading).toBe(false);
|
||||
});
|
||||
|
||||
it('should set an error on error', () => {
|
||||
const result = sliceEntitiesReducer(
|
||||
{},
|
||||
{
|
||||
type: FETCH_ALL_SLICES_FAILED,
|
||||
payload: { error: 'failed' },
|
||||
},
|
||||
);
|
||||
expect(result.isLoading).toBe(false);
|
||||
expect(result.errorMessage.indexOf('failed')).toBeGreaterThan(-1);
|
||||
});
|
||||
});
|
||||
@@ -1,57 +0,0 @@
|
||||
/**
|
||||
* 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 componentIsResizable from 'src/dashboard/util/componentIsResizable';
|
||||
import {
|
||||
CHART_TYPE,
|
||||
COLUMN_TYPE,
|
||||
DASHBOARD_GRID_TYPE,
|
||||
DASHBOARD_ROOT_TYPE,
|
||||
DIVIDER_TYPE,
|
||||
HEADER_TYPE,
|
||||
MARKDOWN_TYPE,
|
||||
ROW_TYPE,
|
||||
TABS_TYPE,
|
||||
TAB_TYPE,
|
||||
} from 'src/dashboard/util/componentTypes';
|
||||
|
||||
const notResizable = [
|
||||
DASHBOARD_GRID_TYPE,
|
||||
DASHBOARD_ROOT_TYPE,
|
||||
DIVIDER_TYPE,
|
||||
HEADER_TYPE,
|
||||
ROW_TYPE,
|
||||
TABS_TYPE,
|
||||
TAB_TYPE,
|
||||
];
|
||||
|
||||
const resizable = [COLUMN_TYPE, CHART_TYPE, MARKDOWN_TYPE];
|
||||
|
||||
describe('componentIsResizable', () => {
|
||||
resizable.forEach(type => {
|
||||
it(`should return true for ${type}`, () => {
|
||||
expect(componentIsResizable({ type })).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
notResizable.forEach(type => {
|
||||
it(`should return false for ${type}`, () => {
|
||||
expect(componentIsResizable({ type })).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,77 +0,0 @@
|
||||
/**
|
||||
* 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 reorderItem from 'src/dashboard/util/dnd-reorder';
|
||||
|
||||
describe('dnd-reorderItem', () => {
|
||||
it('should remove the item from its source entity and add it to its destination entity', () => {
|
||||
const result = reorderItem({
|
||||
entitiesMap: {
|
||||
a: {
|
||||
id: 'a',
|
||||
children: ['x', 'y', 'z'],
|
||||
},
|
||||
b: {
|
||||
id: 'b',
|
||||
children: ['banana'],
|
||||
},
|
||||
},
|
||||
source: { id: 'a', index: 2 },
|
||||
destination: { id: 'b', index: 1 },
|
||||
});
|
||||
|
||||
expect(result.a.children).toEqual(['x', 'y']);
|
||||
expect(result.b.children).toEqual(['banana', 'z']);
|
||||
});
|
||||
|
||||
it('should correctly move elements within the same list', () => {
|
||||
const result = reorderItem({
|
||||
entitiesMap: {
|
||||
a: {
|
||||
id: 'a',
|
||||
children: ['x', 'y', 'z'],
|
||||
},
|
||||
},
|
||||
source: { id: 'a', index: 2 },
|
||||
destination: { id: 'a', index: 0 },
|
||||
});
|
||||
|
||||
expect(result.a.children).toEqual(['z', 'x', 'y']);
|
||||
});
|
||||
|
||||
it('should copy items that do not move into the result', () => {
|
||||
const extraEntity = {};
|
||||
const result = reorderItem({
|
||||
entitiesMap: {
|
||||
a: {
|
||||
id: 'a',
|
||||
children: ['x', 'y', 'z'],
|
||||
},
|
||||
b: {
|
||||
id: 'b',
|
||||
children: ['banana'],
|
||||
},
|
||||
iAmExtra: extraEntity,
|
||||
},
|
||||
source: { id: 'a', index: 2 },
|
||||
destination: { id: 'b', index: 1 },
|
||||
});
|
||||
|
||||
expect(result.iAmExtra === extraEntity).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -1,242 +0,0 @@
|
||||
/**
|
||||
* 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 dropOverflowsParent from 'src/dashboard/util/dropOverflowsParent';
|
||||
import { NEW_COMPONENTS_SOURCE_ID } from 'src/dashboard/util/constants';
|
||||
import {
|
||||
CHART_TYPE,
|
||||
COLUMN_TYPE,
|
||||
ROW_TYPE,
|
||||
HEADER_TYPE,
|
||||
TAB_TYPE,
|
||||
} from 'src/dashboard/util/componentTypes';
|
||||
|
||||
describe('dropOverflowsParent', () => {
|
||||
it('returns true if a parent does NOT have adequate width for child', () => {
|
||||
const dropResult = {
|
||||
source: { id: '_' },
|
||||
destination: { id: 'a' },
|
||||
dragging: { id: 'z' },
|
||||
};
|
||||
|
||||
const layout = {
|
||||
a: {
|
||||
id: 'a',
|
||||
type: ROW_TYPE,
|
||||
children: ['b', 'b', 'b', 'b'], // width = 4x bs = 12
|
||||
},
|
||||
b: {
|
||||
id: 'b',
|
||||
type: CHART_TYPE,
|
||||
meta: {
|
||||
width: 3,
|
||||
},
|
||||
},
|
||||
z: {
|
||||
id: 'z',
|
||||
type: CHART_TYPE,
|
||||
meta: {
|
||||
width: 2,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
expect(dropOverflowsParent(dropResult, layout)).toBe(true);
|
||||
});
|
||||
|
||||
it('returns false if a parent DOES have adequate width for child', () => {
|
||||
const dropResult = {
|
||||
source: { id: '_' },
|
||||
destination: { id: 'a' },
|
||||
dragging: { id: 'z' },
|
||||
};
|
||||
|
||||
const layout = {
|
||||
a: {
|
||||
id: 'a',
|
||||
type: ROW_TYPE,
|
||||
children: ['b', 'b'],
|
||||
},
|
||||
b: {
|
||||
id: 'b',
|
||||
type: CHART_TYPE,
|
||||
meta: {
|
||||
width: 3,
|
||||
},
|
||||
},
|
||||
z: {
|
||||
id: 'z',
|
||||
type: CHART_TYPE,
|
||||
meta: {
|
||||
width: 2,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
expect(dropOverflowsParent(dropResult, layout)).toBe(false);
|
||||
});
|
||||
|
||||
it('returns false if a child CAN shrink to available parent space', () => {
|
||||
const dropResult = {
|
||||
source: { id: '_' },
|
||||
destination: { id: 'a' },
|
||||
dragging: { id: 'z' },
|
||||
};
|
||||
|
||||
const layout = {
|
||||
a: {
|
||||
id: 'a',
|
||||
type: ROW_TYPE,
|
||||
children: ['b', 'b'], // 2x b = 10
|
||||
},
|
||||
b: {
|
||||
id: 'b',
|
||||
type: CHART_TYPE,
|
||||
meta: {
|
||||
width: 5,
|
||||
},
|
||||
},
|
||||
z: {
|
||||
id: 'z',
|
||||
type: CHART_TYPE,
|
||||
meta: {
|
||||
width: 10,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
expect(dropOverflowsParent(dropResult, layout)).toBe(false);
|
||||
});
|
||||
|
||||
it('returns true if a child CANNOT shrink to available parent space', () => {
|
||||
const dropResult = {
|
||||
source: { id: '_' },
|
||||
destination: { id: 'a' },
|
||||
dragging: { id: 'b' },
|
||||
};
|
||||
|
||||
const layout = {
|
||||
a: {
|
||||
id: 'a',
|
||||
type: COLUMN_TYPE,
|
||||
meta: {
|
||||
width: 6,
|
||||
},
|
||||
},
|
||||
// rows with children cannot shrink
|
||||
b: {
|
||||
id: 'b',
|
||||
type: ROW_TYPE,
|
||||
children: ['bChild', 'bChild', 'bChild'],
|
||||
},
|
||||
bChild: {
|
||||
id: 'bChild',
|
||||
type: CHART_TYPE,
|
||||
meta: {
|
||||
width: 3,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
expect(dropOverflowsParent(dropResult, layout)).toBe(true);
|
||||
});
|
||||
|
||||
it('returns true if a column has children that CANNOT shrink to available parent space', () => {
|
||||
const dropResult = {
|
||||
source: { id: '_' },
|
||||
destination: { id: 'destination' },
|
||||
dragging: { id: 'dragging' },
|
||||
};
|
||||
|
||||
const layout = {
|
||||
destination: {
|
||||
id: 'destination',
|
||||
type: ROW_TYPE,
|
||||
children: ['b', 'b'], // 2x b = 10, 2 available
|
||||
},
|
||||
b: {
|
||||
id: 'b',
|
||||
type: CHART_TYPE,
|
||||
meta: {
|
||||
width: 5,
|
||||
},
|
||||
},
|
||||
dragging: {
|
||||
id: 'dragging',
|
||||
type: COLUMN_TYPE,
|
||||
meta: {
|
||||
width: 10,
|
||||
},
|
||||
children: ['rowWithChildren'], // 2x b = width 10
|
||||
},
|
||||
rowWithChildren: {
|
||||
id: 'rowWithChildren',
|
||||
type: ROW_TYPE,
|
||||
children: ['b', 'b'],
|
||||
},
|
||||
};
|
||||
|
||||
expect(dropOverflowsParent(dropResult, layout)).toBe(true);
|
||||
// remove children
|
||||
expect(
|
||||
dropOverflowsParent(dropResult, {
|
||||
...layout,
|
||||
dragging: { ...layout.dragging, children: [] },
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('should work with new components that are not in the layout', () => {
|
||||
const dropResult = {
|
||||
source: { id: NEW_COMPONENTS_SOURCE_ID },
|
||||
destination: { id: 'a' },
|
||||
dragging: { type: CHART_TYPE },
|
||||
};
|
||||
|
||||
const layout = {
|
||||
a: {
|
||||
id: 'a',
|
||||
type: ROW_TYPE,
|
||||
children: [],
|
||||
},
|
||||
};
|
||||
|
||||
expect(dropOverflowsParent(dropResult, layout)).toBe(false);
|
||||
});
|
||||
|
||||
it('source/destination without widths should not overflow parent', () => {
|
||||
const dropResult = {
|
||||
source: { id: '_' },
|
||||
destination: { id: 'tab' },
|
||||
dragging: { id: 'header' },
|
||||
};
|
||||
|
||||
const layout = {
|
||||
tab: {
|
||||
id: 'tab',
|
||||
type: TAB_TYPE,
|
||||
},
|
||||
header: {
|
||||
id: 'header',
|
||||
type: HEADER_TYPE,
|
||||
},
|
||||
};
|
||||
|
||||
expect(dropOverflowsParent(dropResult, layout)).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -1,126 +0,0 @@
|
||||
/**
|
||||
* 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 findFirstParentContainerId from 'src/dashboard/util/findFirstParentContainer';
|
||||
import {
|
||||
DASHBOARD_GRID_ID,
|
||||
DASHBOARD_ROOT_ID,
|
||||
} from 'src/dashboard/util/constants';
|
||||
|
||||
describe('findFirstParentContainer', () => {
|
||||
const mockGridLayout = {
|
||||
DASHBOARD_VERSION_KEY: 'v2',
|
||||
ROOT_ID: {
|
||||
type: 'ROOT',
|
||||
id: 'ROOT_ID',
|
||||
children: ['GRID_ID'],
|
||||
},
|
||||
GRID_ID: {
|
||||
type: 'GRID',
|
||||
id: 'GRID_ID',
|
||||
children: ['ROW-Bk45URrlQ'],
|
||||
},
|
||||
'ROW-Bk45URrlQ': {
|
||||
type: 'ROW',
|
||||
id: 'ROW-Bk45URrlQ',
|
||||
children: ['CHART-ryxVc8RHlX'],
|
||||
},
|
||||
'CHART-ryxVc8RHlX': {
|
||||
type: 'CHART',
|
||||
id: 'CHART-ryxVc8RHlX',
|
||||
children: [],
|
||||
},
|
||||
HEADER_ID: {
|
||||
id: 'HEADER_ID',
|
||||
type: 'HEADER',
|
||||
},
|
||||
};
|
||||
const mockTabsLayout = {
|
||||
'CHART-S1gilYABe7': {
|
||||
children: [],
|
||||
id: 'CHART-S1gilYABe7',
|
||||
type: 'CHART',
|
||||
},
|
||||
'CHART-SJli5K0HlQ': {
|
||||
children: [],
|
||||
id: 'CHART-SJli5K0HlQ',
|
||||
type: 'CHART',
|
||||
},
|
||||
GRID_ID: {
|
||||
children: [],
|
||||
id: 'GRID_ID',
|
||||
type: 'GRID',
|
||||
},
|
||||
HEADER_ID: {
|
||||
id: 'HEADER_ID',
|
||||
type: 'HEADER',
|
||||
},
|
||||
ROOT_ID: {
|
||||
children: ['TABS-SkgJ5t0Bem'],
|
||||
id: 'ROOT_ID',
|
||||
type: 'ROOT',
|
||||
},
|
||||
'ROW-S1B8-JLgX': {
|
||||
children: ['CHART-SJli5K0HlQ'],
|
||||
id: 'ROW-S1B8-JLgX',
|
||||
type: 'ROW',
|
||||
},
|
||||
'ROW-S1bUb1Ilm': {
|
||||
children: ['CHART-S1gilYABe7'],
|
||||
id: 'ROW-S1bUb1Ilm',
|
||||
type: 'ROW',
|
||||
},
|
||||
'TABS-ByeLSWyLe7': {
|
||||
children: ['TAB-BJbLSZ1UeQ'],
|
||||
id: 'TABS-ByeLSWyLe7',
|
||||
type: 'TABS',
|
||||
},
|
||||
'TABS-SkgJ5t0Bem': {
|
||||
children: ['TAB-HkWJcFCHxQ', 'TAB-ByDBbkLlQ'],
|
||||
id: 'TABS-SkgJ5t0Bem',
|
||||
meta: {},
|
||||
type: 'TABS',
|
||||
},
|
||||
'TAB-BJbLSZ1UeQ': {
|
||||
children: ['ROW-S1bUb1Ilm'],
|
||||
id: 'TAB-BJbLSZ1UeQ',
|
||||
type: 'TAB',
|
||||
},
|
||||
'TAB-ByDBbkLlQ': {
|
||||
children: ['ROW-S1B8-JLgX'],
|
||||
id: 'TAB-ByDBbkLlQ',
|
||||
type: 'TAB',
|
||||
},
|
||||
'TAB-HkWJcFCHxQ': {
|
||||
children: ['TABS-ByeLSWyLe7'],
|
||||
id: 'TAB-HkWJcFCHxQ',
|
||||
type: 'TAB',
|
||||
},
|
||||
DASHBOARD_VERSION_KEY: 'v2',
|
||||
};
|
||||
|
||||
it('should return grid root', () => {
|
||||
expect(findFirstParentContainerId(mockGridLayout)).toBe(DASHBOARD_GRID_ID);
|
||||
});
|
||||
|
||||
it('should return first tab', () => {
|
||||
const tabsId = mockTabsLayout[DASHBOARD_ROOT_ID].children[0];
|
||||
const firstTabId = mockTabsLayout[tabsId].children[0];
|
||||
expect(findFirstParentContainerId(mockTabsLayout)).toBe(firstTabId);
|
||||
});
|
||||
});
|
||||
@@ -1,44 +0,0 @@
|
||||
/**
|
||||
* 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 findParentId from 'src/dashboard/util/findParentId';
|
||||
|
||||
describe('findParentId', () => {
|
||||
const layout = {
|
||||
a: {
|
||||
id: 'a',
|
||||
children: ['b', 'r', 'k'],
|
||||
},
|
||||
b: {
|
||||
id: 'b',
|
||||
children: ['x', 'y', 'z'],
|
||||
},
|
||||
z: {
|
||||
id: 'z',
|
||||
children: [],
|
||||
},
|
||||
};
|
||||
it('should return the correct parentId', () => {
|
||||
expect(findParentId({ childId: 'b', layout })).toBe('a');
|
||||
expect(findParentId({ childId: 'z', layout })).toBe('b');
|
||||
});
|
||||
|
||||
it('should return null if the parent cannot be found', () => {
|
||||
expect(findParentId({ childId: 'a', layout })).toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -1,85 +0,0 @@
|
||||
/**
|
||||
* 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 findTabIndexByComponentId from 'src/dashboard/util/findTabIndexByComponentId';
|
||||
|
||||
describe('findTabIndexByComponentId', () => {
|
||||
const topLevelTabsComponent = {
|
||||
children: ['TAB-0g-5l347I2', 'TAB-qrwN_9VB5'],
|
||||
id: 'TABS-MNQQSW-kyd',
|
||||
meta: {},
|
||||
parents: ['ROOT_ID'],
|
||||
type: 'TABS',
|
||||
};
|
||||
const rowLevelTabsComponent = {
|
||||
children: [
|
||||
'TAB-TwyUUGp2Bg',
|
||||
'TAB-Zl1BQAUvN',
|
||||
'TAB-P0DllxzTU',
|
||||
'TAB---e53RNei',
|
||||
],
|
||||
id: 'TABS-Oduxop1L7I',
|
||||
meta: {},
|
||||
parents: ['ROOT_ID', 'TABS-MNQQSW-kyd', 'TAB-qrwN_9VB5'],
|
||||
type: 'TABS',
|
||||
};
|
||||
const goodPathToChild = [
|
||||
'ROOT_ID',
|
||||
'TABS-MNQQSW-kyd',
|
||||
'TAB-qrwN_9VB5',
|
||||
'TABS-Oduxop1L7I',
|
||||
'TAB-P0DllxzTU',
|
||||
'ROW-JXhrFnVP8',
|
||||
'CHART-dUIVg-ENq6',
|
||||
];
|
||||
const badPath = ['ROOT_ID', 'TABS-MNQQSW-kyd', 'TAB-ABC', 'TABS-Oduxop1L7I'];
|
||||
|
||||
it('should return -1 if no directPathToChild', () => {
|
||||
expect(
|
||||
findTabIndexByComponentId({
|
||||
currentComponent: topLevelTabsComponent,
|
||||
directPathToChild: [],
|
||||
}),
|
||||
).toBe(-1);
|
||||
});
|
||||
|
||||
it('should return -1 if not found tab id', () => {
|
||||
expect(
|
||||
findTabIndexByComponentId({
|
||||
currentComponent: topLevelTabsComponent,
|
||||
directPathToChild: badPath,
|
||||
}),
|
||||
).toBe(-1);
|
||||
});
|
||||
|
||||
it('should return children index if matched an id in the path', () => {
|
||||
expect(
|
||||
findTabIndexByComponentId({
|
||||
currentComponent: topLevelTabsComponent,
|
||||
directPathToChild: goodPathToChild,
|
||||
}),
|
||||
).toBe(1);
|
||||
|
||||
expect(
|
||||
findTabIndexByComponentId({
|
||||
currentComponent: rowLevelTabsComponent,
|
||||
directPathToChild: goodPathToChild,
|
||||
}),
|
||||
).toBe(2);
|
||||
});
|
||||
});
|
||||
@@ -1,38 +0,0 @@
|
||||
/**
|
||||
* 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 getChartAndLabelComponentIdFromPath from 'src/dashboard/util/getChartAndLabelComponentIdFromPath';
|
||||
|
||||
describe('getChartAndLabelComponentIdFromPath', () => {
|
||||
it('should return label and component id', () => {
|
||||
const directPathToChild = [
|
||||
'ROOT_ID',
|
||||
'TABS-aX1uNK-ryo',
|
||||
'TAB-ZRgxfD2ktj',
|
||||
'ROW-46632bc2',
|
||||
'COLUMN-XjlxaS-flc',
|
||||
'CHART-x-RMdAtlDb',
|
||||
'LABEL-region',
|
||||
];
|
||||
|
||||
expect(getChartAndLabelComponentIdFromPath(directPathToChild)).toEqual({
|
||||
label: 'LABEL-region',
|
||||
chart: 'CHART-x-RMdAtlDb',
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,53 +0,0 @@
|
||||
/**
|
||||
* 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 getChartIdsFromLayout from 'src/dashboard/util/getChartIdsFromLayout';
|
||||
import { ROW_TYPE, CHART_TYPE } from 'src/dashboard/util/componentTypes';
|
||||
|
||||
describe('getChartIdsFromLayout', () => {
|
||||
const mockLayout = {
|
||||
a: {
|
||||
id: 'a',
|
||||
type: CHART_TYPE,
|
||||
meta: { chartId: 'A' },
|
||||
},
|
||||
b: {
|
||||
id: 'b',
|
||||
type: CHART_TYPE,
|
||||
meta: { chartId: 'B' },
|
||||
},
|
||||
c: {
|
||||
id: 'c',
|
||||
type: ROW_TYPE,
|
||||
meta: { chartId: 'C' },
|
||||
},
|
||||
};
|
||||
|
||||
it('should return an array of chartIds', () => {
|
||||
const result = getChartIdsFromLayout(mockLayout);
|
||||
expect(Array.isArray(result)).toBe(true);
|
||||
expect(result.includes('A')).toBe(true);
|
||||
expect(result.includes('B')).toBe(true);
|
||||
});
|
||||
|
||||
it('should return ids only from CHART_TYPE components', () => {
|
||||
const result = getChartIdsFromLayout(mockLayout);
|
||||
expect(result).toHaveLength(2);
|
||||
expect(result.includes('C')).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -1,93 +0,0 @@
|
||||
/**
|
||||
* 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 getDashboardUrl from 'src/dashboard/util/getDashboardUrl';
|
||||
import { DASHBOARD_FILTER_SCOPE_GLOBAL } from 'src/dashboard/reducers/dashboardFilters';
|
||||
import { DashboardStandaloneMode } from '../../../../src/dashboard/util/constants';
|
||||
|
||||
describe('getChartIdsFromLayout', () => {
|
||||
const filters = {
|
||||
'35_key': {
|
||||
values: ['value'],
|
||||
scope: DASHBOARD_FILTER_SCOPE_GLOBAL,
|
||||
},
|
||||
};
|
||||
|
||||
const globalLocation = window.location;
|
||||
afterEach(() => {
|
||||
window.location = globalLocation;
|
||||
});
|
||||
|
||||
it('should encode filters', () => {
|
||||
const url = getDashboardUrl({ pathname: 'path', filters });
|
||||
expect(url).toBe(
|
||||
'path?preselect_filters=%7B%2235%22%3A%7B%22key%22%3A%5B%22value%22%5D%7D%7D',
|
||||
);
|
||||
});
|
||||
|
||||
it('should encode filters with hash', () => {
|
||||
const urlWithHash = getDashboardUrl({
|
||||
pathname: 'path',
|
||||
filters,
|
||||
hash: 'iamhashtag',
|
||||
});
|
||||
expect(urlWithHash).toBe(
|
||||
'path?preselect_filters=%7B%2235%22%3A%7B%22key%22%3A%5B%22value%22%5D%7D%7D#iamhashtag',
|
||||
);
|
||||
});
|
||||
|
||||
it('should encode filters with standalone', () => {
|
||||
const urlWithStandalone = getDashboardUrl({
|
||||
pathname: 'path',
|
||||
filters,
|
||||
standalone: DashboardStandaloneMode.HIDE_NAV,
|
||||
});
|
||||
expect(urlWithStandalone).toBe(
|
||||
`path?preselect_filters=%7B%2235%22%3A%7B%22key%22%3A%5B%22value%22%5D%7D%7D&standalone=${DashboardStandaloneMode.HIDE_NAV}`,
|
||||
);
|
||||
});
|
||||
|
||||
it('should encode filters with missing standalone', () => {
|
||||
const urlWithStandalone = getDashboardUrl({
|
||||
pathname: 'path',
|
||||
filters,
|
||||
standalone: null,
|
||||
});
|
||||
expect(urlWithStandalone).toBe(
|
||||
'path?preselect_filters=%7B%2235%22%3A%7B%22key%22%3A%5B%22value%22%5D%7D%7D',
|
||||
);
|
||||
});
|
||||
|
||||
it('should process native filters key', () => {
|
||||
const windowSpy = jest.spyOn(window, 'window', 'get');
|
||||
windowSpy.mockImplementation(() => ({
|
||||
location: {
|
||||
origin: 'https://localhost',
|
||||
search:
|
||||
'?preselect_filters=%7B%7D&native_filters_key=024380498jdkjf-2094838',
|
||||
},
|
||||
}));
|
||||
|
||||
const urlWithNativeFilters = getDashboardUrl({
|
||||
pathname: 'path',
|
||||
});
|
||||
expect(urlWithNativeFilters).toBe(
|
||||
'path?preselect_filters=%7B%7D&native_filters_key=024380498jdkjf-2094838',
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -1,240 +0,0 @@
|
||||
/**
|
||||
* 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 getDetailedComponentWidth from 'src/dashboard/util/getDetailedComponentWidth';
|
||||
import * as types from 'src/dashboard/util/componentTypes';
|
||||
import {
|
||||
GRID_COLUMN_COUNT,
|
||||
GRID_MIN_COLUMN_COUNT,
|
||||
} from 'src/dashboard/util/constants';
|
||||
|
||||
describe('getDetailedComponentWidth', () => {
|
||||
it('should return an object with width, minimumWidth, and occupiedWidth', () => {
|
||||
expect(
|
||||
Object.keys(getDetailedComponentWidth({ id: '_', components: {} })),
|
||||
).toEqual(
|
||||
expect.arrayContaining(['minimumWidth', 'occupiedWidth', 'width']),
|
||||
);
|
||||
});
|
||||
|
||||
describe('width', () => {
|
||||
it('should be undefined if the component is not resizable and has no defined width', () => {
|
||||
const empty = {
|
||||
width: undefined,
|
||||
occupiedWidth: undefined,
|
||||
minimumWidth: undefined,
|
||||
};
|
||||
|
||||
expect(
|
||||
getDetailedComponentWidth({
|
||||
component: { id: '', type: types.HEADER_TYPE },
|
||||
}),
|
||||
).toEqual(empty);
|
||||
|
||||
expect(
|
||||
getDetailedComponentWidth({
|
||||
component: { id: '', type: types.DIVIDER_TYPE },
|
||||
}),
|
||||
).toEqual(empty);
|
||||
|
||||
expect(
|
||||
getDetailedComponentWidth({
|
||||
component: { id: '', type: types.TAB_TYPE },
|
||||
}),
|
||||
).toEqual(empty);
|
||||
});
|
||||
|
||||
it('should match component meta width for resizeable components', () => {
|
||||
expect(
|
||||
getDetailedComponentWidth({
|
||||
component: { id: '', type: types.CHART_TYPE, meta: { width: 1 } },
|
||||
}),
|
||||
).toEqual({ width: 1, occupiedWidth: 1, minimumWidth: 1 });
|
||||
|
||||
expect(
|
||||
getDetailedComponentWidth({
|
||||
component: { id: '', type: types.MARKDOWN_TYPE, meta: { width: 2 } },
|
||||
}),
|
||||
).toEqual({ width: 2, occupiedWidth: 2, minimumWidth: 1 });
|
||||
|
||||
expect(
|
||||
getDetailedComponentWidth({
|
||||
component: { id: '', type: types.COLUMN_TYPE, meta: { width: 3 } },
|
||||
}),
|
||||
// note: occupiedWidth is zero for colunns/see test below
|
||||
).toEqual({ width: 3, occupiedWidth: 0, minimumWidth: 1 });
|
||||
});
|
||||
|
||||
it('should be GRID_COLUMN_COUNT for row components WITHOUT parents', () => {
|
||||
expect(
|
||||
getDetailedComponentWidth({
|
||||
id: 'row',
|
||||
components: { row: { id: 'row', type: types.ROW_TYPE } },
|
||||
}),
|
||||
).toEqual({
|
||||
width: GRID_COLUMN_COUNT,
|
||||
occupiedWidth: 0,
|
||||
minimumWidth: GRID_MIN_COLUMN_COUNT,
|
||||
});
|
||||
});
|
||||
|
||||
it('should match parent width for row components WITH parents', () => {
|
||||
expect(
|
||||
getDetailedComponentWidth({
|
||||
id: 'row',
|
||||
components: {
|
||||
row: { id: 'row', type: types.ROW_TYPE },
|
||||
parent: {
|
||||
id: 'parent',
|
||||
type: types.COLUMN_TYPE,
|
||||
children: ['row'],
|
||||
meta: { width: 7 },
|
||||
},
|
||||
},
|
||||
}),
|
||||
).toEqual({
|
||||
width: 7,
|
||||
occupiedWidth: 0,
|
||||
minimumWidth: GRID_MIN_COLUMN_COUNT,
|
||||
});
|
||||
});
|
||||
|
||||
it('should use either id or component (to support new components)', () => {
|
||||
expect(
|
||||
getDetailedComponentWidth({
|
||||
id: 'id',
|
||||
components: {
|
||||
id: { id: 'id', type: types.CHART_TYPE, meta: { width: 6 } },
|
||||
},
|
||||
}).width,
|
||||
).toBe(6);
|
||||
|
||||
expect(
|
||||
getDetailedComponentWidth({
|
||||
component: { id: 'id', type: types.CHART_TYPE, meta: { width: 6 } },
|
||||
}).width,
|
||||
).toBe(6);
|
||||
});
|
||||
});
|
||||
|
||||
describe('occupiedWidth', () => {
|
||||
it('should reflect the sum of child widths for row components', () => {
|
||||
expect(
|
||||
getDetailedComponentWidth({
|
||||
id: 'row',
|
||||
components: {
|
||||
row: {
|
||||
id: 'row',
|
||||
type: types.ROW_TYPE,
|
||||
children: ['child', 'child'],
|
||||
},
|
||||
child: { id: 'child', meta: { width: 3.5 } },
|
||||
},
|
||||
}),
|
||||
).toEqual({ width: 12, occupiedWidth: 7, minimumWidth: 7 });
|
||||
});
|
||||
|
||||
it('should always be zero for column components', () => {
|
||||
expect(
|
||||
getDetailedComponentWidth({
|
||||
component: { id: '', type: types.COLUMN_TYPE, meta: { width: 2 } },
|
||||
}),
|
||||
).toEqual({ width: 2, occupiedWidth: 0, minimumWidth: 1 });
|
||||
});
|
||||
});
|
||||
|
||||
describe('minimumWidth', () => {
|
||||
it('should equal GRID_MIN_COLUMN_COUNT for resizable components', () => {
|
||||
expect(
|
||||
getDetailedComponentWidth({
|
||||
component: { id: '', type: types.CHART_TYPE, meta: { width: 1 } },
|
||||
}),
|
||||
).toEqual({
|
||||
width: 1,
|
||||
minimumWidth: GRID_MIN_COLUMN_COUNT,
|
||||
occupiedWidth: 1,
|
||||
});
|
||||
|
||||
expect(
|
||||
getDetailedComponentWidth({
|
||||
component: { id: '', type: types.MARKDOWN_TYPE, meta: { width: 2 } },
|
||||
}),
|
||||
).toEqual({
|
||||
width: 2,
|
||||
minimumWidth: GRID_MIN_COLUMN_COUNT,
|
||||
occupiedWidth: 2,
|
||||
});
|
||||
|
||||
expect(
|
||||
getDetailedComponentWidth({
|
||||
component: { id: '', type: types.COLUMN_TYPE, meta: { width: 3 } },
|
||||
}),
|
||||
).toEqual({
|
||||
width: 3,
|
||||
minimumWidth: GRID_MIN_COLUMN_COUNT,
|
||||
occupiedWidth: 0,
|
||||
});
|
||||
});
|
||||
|
||||
it('should equal the width of row children for column components with row children', () => {
|
||||
expect(
|
||||
getDetailedComponentWidth({
|
||||
id: 'column',
|
||||
components: {
|
||||
column: {
|
||||
id: 'column',
|
||||
type: types.COLUMN_TYPE,
|
||||
children: ['rowChild', 'ignoredChartChild'],
|
||||
meta: { width: 12 },
|
||||
},
|
||||
rowChild: {
|
||||
id: 'rowChild',
|
||||
type: types.ROW_TYPE,
|
||||
children: ['rowChildChild', 'rowChildChild'],
|
||||
},
|
||||
rowChildChild: {
|
||||
id: 'rowChildChild',
|
||||
meta: { width: 3.5 },
|
||||
},
|
||||
ignoredChartChild: {
|
||||
id: 'ignoredChartChild',
|
||||
meta: { width: 100 },
|
||||
},
|
||||
},
|
||||
}),
|
||||
// occupiedWidth is zero for colunns/see test below
|
||||
).toEqual({ width: 12, occupiedWidth: 0, minimumWidth: 7 });
|
||||
});
|
||||
|
||||
it('should equal occupiedWidth for row components', () => {
|
||||
expect(
|
||||
getDetailedComponentWidth({
|
||||
id: 'row',
|
||||
components: {
|
||||
row: {
|
||||
id: 'row',
|
||||
type: types.ROW_TYPE,
|
||||
children: ['child', 'child'],
|
||||
},
|
||||
child: { id: 'child', meta: { width: 3.5 } },
|
||||
},
|
||||
}),
|
||||
).toEqual({ width: 12, occupiedWidth: 7, minimumWidth: 7 });
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,437 +0,0 @@
|
||||
/**
|
||||
* 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 getDropPosition, {
|
||||
DROP_TOP,
|
||||
DROP_RIGHT,
|
||||
DROP_BOTTOM,
|
||||
DROP_LEFT,
|
||||
} from 'src/dashboard/util/getDropPosition';
|
||||
|
||||
import {
|
||||
CHART_TYPE,
|
||||
DASHBOARD_GRID_TYPE,
|
||||
DASHBOARD_ROOT_TYPE,
|
||||
HEADER_TYPE,
|
||||
ROW_TYPE,
|
||||
TAB_TYPE,
|
||||
} from 'src/dashboard/util/componentTypes';
|
||||
|
||||
describe('getDropPosition', () => {
|
||||
// helper to easily configure test
|
||||
function getMocks({
|
||||
parentType,
|
||||
componentType,
|
||||
draggingType,
|
||||
depth = 1,
|
||||
hasChildren = false,
|
||||
orientation = 'row',
|
||||
clientOffset = { x: 0, y: 0 },
|
||||
boundingClientRect = {
|
||||
top: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
},
|
||||
isDraggingOverShallow = true,
|
||||
}) {
|
||||
const monitorMock = {
|
||||
getItem: () => ({
|
||||
id: 'id',
|
||||
type: draggingType,
|
||||
}),
|
||||
getClientOffset: () => clientOffset,
|
||||
};
|
||||
|
||||
const ComponentMock = {
|
||||
props: {
|
||||
depth,
|
||||
parentComponent: {
|
||||
type: parentType,
|
||||
},
|
||||
component: {
|
||||
type: componentType,
|
||||
children: hasChildren ? [''] : [],
|
||||
},
|
||||
orientation,
|
||||
isDraggingOverShallow,
|
||||
},
|
||||
ref: {
|
||||
getBoundingClientRect: () => boundingClientRect,
|
||||
},
|
||||
};
|
||||
|
||||
return [monitorMock, ComponentMock];
|
||||
}
|
||||
|
||||
describe('invalid child + invalid sibling', () => {
|
||||
it('should return null', () => {
|
||||
const result = getDropPosition(
|
||||
// TAB is an invalid child + sibling of GRID > ROW
|
||||
...getMocks({
|
||||
parentType: DASHBOARD_GRID_TYPE,
|
||||
componentType: ROW_TYPE,
|
||||
draggingType: TAB_TYPE,
|
||||
}),
|
||||
);
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('valid child + invalid sibling', () => {
|
||||
it('should return DROP_LEFT if component has NO children, and orientation is "row"', () => {
|
||||
// HEADER is a valid child + invalid sibling of ROOT > GRID
|
||||
const result = getDropPosition(
|
||||
...getMocks({
|
||||
parentType: DASHBOARD_ROOT_TYPE,
|
||||
componentType: DASHBOARD_GRID_TYPE,
|
||||
draggingType: HEADER_TYPE,
|
||||
}),
|
||||
);
|
||||
expect(result).toBe(DROP_LEFT);
|
||||
});
|
||||
|
||||
it('should return DROP_RIGHT if component HAS children, and orientation is "row"', () => {
|
||||
const result = getDropPosition(
|
||||
...getMocks({
|
||||
parentType: DASHBOARD_ROOT_TYPE,
|
||||
componentType: DASHBOARD_GRID_TYPE,
|
||||
draggingType: HEADER_TYPE,
|
||||
hasChildren: true,
|
||||
}),
|
||||
);
|
||||
expect(result).toBe(DROP_RIGHT);
|
||||
});
|
||||
|
||||
it('should return DROP_TOP if component has NO children, and orientation is "column"', () => {
|
||||
const result = getDropPosition(
|
||||
...getMocks({
|
||||
parentType: DASHBOARD_ROOT_TYPE,
|
||||
componentType: DASHBOARD_GRID_TYPE,
|
||||
draggingType: HEADER_TYPE,
|
||||
orientation: 'column',
|
||||
}),
|
||||
);
|
||||
expect(result).toBe(DROP_TOP);
|
||||
});
|
||||
|
||||
it('should return DROP_BOTTOM if component HAS children, and orientation is "column"', () => {
|
||||
const result = getDropPosition(
|
||||
...getMocks({
|
||||
parentType: DASHBOARD_ROOT_TYPE,
|
||||
componentType: DASHBOARD_GRID_TYPE,
|
||||
draggingType: HEADER_TYPE,
|
||||
orientation: 'column',
|
||||
hasChildren: true,
|
||||
}),
|
||||
);
|
||||
expect(result).toBe(DROP_BOTTOM);
|
||||
});
|
||||
});
|
||||
|
||||
describe('invalid child + valid sibling', () => {
|
||||
it('should return DROP_TOP if orientation="row" and clientOffset is closer to component top than bottom', () => {
|
||||
const result = getDropPosition(
|
||||
// HEADER is an invalid child but valid sibling of GRID > ROW
|
||||
...getMocks({
|
||||
parentType: DASHBOARD_GRID_TYPE,
|
||||
componentType: ROW_TYPE,
|
||||
draggingType: HEADER_TYPE,
|
||||
clientOffset: { y: 10 },
|
||||
boundingClientRect: {
|
||||
top: 0,
|
||||
bottom: 100,
|
||||
},
|
||||
}),
|
||||
);
|
||||
expect(result).toBe(DROP_TOP);
|
||||
});
|
||||
|
||||
it('should return DROP_BOTTOM if orientation="row" and clientOffset is closer to component bottom than top', () => {
|
||||
const result = getDropPosition(
|
||||
...getMocks({
|
||||
parentType: DASHBOARD_GRID_TYPE,
|
||||
componentType: ROW_TYPE,
|
||||
draggingType: HEADER_TYPE,
|
||||
clientOffset: { y: 55 },
|
||||
boundingClientRect: {
|
||||
top: 0,
|
||||
bottom: 100,
|
||||
},
|
||||
}),
|
||||
);
|
||||
expect(result).toBe(DROP_BOTTOM);
|
||||
});
|
||||
|
||||
it('should return DROP_LEFT if orientation="column" and clientOffset is closer to component left than right', () => {
|
||||
const result = getDropPosition(
|
||||
...getMocks({
|
||||
parentType: DASHBOARD_GRID_TYPE,
|
||||
componentType: ROW_TYPE,
|
||||
draggingType: HEADER_TYPE,
|
||||
orientation: 'column',
|
||||
clientOffset: { x: 45 },
|
||||
boundingClientRect: {
|
||||
left: 0,
|
||||
right: 100,
|
||||
},
|
||||
}),
|
||||
);
|
||||
expect(result).toBe(DROP_LEFT);
|
||||
});
|
||||
|
||||
it('should return DROP_RIGHT if orientation="column" and clientOffset is closer to component right than left', () => {
|
||||
const result = getDropPosition(
|
||||
...getMocks({
|
||||
parentType: DASHBOARD_GRID_TYPE,
|
||||
componentType: ROW_TYPE,
|
||||
draggingType: HEADER_TYPE,
|
||||
orientation: 'column',
|
||||
clientOffset: { x: 55 },
|
||||
boundingClientRect: {
|
||||
left: 0,
|
||||
right: 100,
|
||||
},
|
||||
}),
|
||||
);
|
||||
expect(result).toBe(DROP_RIGHT);
|
||||
});
|
||||
});
|
||||
|
||||
describe('child + valid sibling (row orientation)', () => {
|
||||
it('should return DROP_LEFT if component has NO children, and clientOffset is NOT near top/bottom sibling boundary', () => {
|
||||
const result = getDropPosition(
|
||||
// CHART is a valid child + sibling of GRID > ROW
|
||||
...getMocks({
|
||||
parentType: DASHBOARD_GRID_TYPE,
|
||||
componentType: ROW_TYPE,
|
||||
draggingType: CHART_TYPE,
|
||||
clientOffset: { x: 10, y: 50 },
|
||||
boundingClientRect: {
|
||||
left: 0,
|
||||
right: 100,
|
||||
top: 0,
|
||||
bottom: 100,
|
||||
},
|
||||
}),
|
||||
);
|
||||
expect(result).toBe(DROP_LEFT);
|
||||
});
|
||||
|
||||
it('should return DROP_RIGHT if component HAS children, and clientOffset is NOT near top/bottom sibling boundary', () => {
|
||||
const result = getDropPosition(
|
||||
...getMocks({
|
||||
parentType: DASHBOARD_GRID_TYPE,
|
||||
componentType: ROW_TYPE,
|
||||
draggingType: CHART_TYPE,
|
||||
hasChildren: true,
|
||||
clientOffset: { x: 10, y: 50 },
|
||||
boundingClientRect: {
|
||||
left: 0,
|
||||
right: 100,
|
||||
top: 0,
|
||||
bottom: 100,
|
||||
},
|
||||
}),
|
||||
);
|
||||
expect(result).toBe(DROP_RIGHT);
|
||||
});
|
||||
|
||||
it('should return DROP_TOP regardless of component children if clientOffset IS near top sibling boundary', () => {
|
||||
const noChildren = getDropPosition(
|
||||
...getMocks({
|
||||
parentType: DASHBOARD_GRID_TYPE,
|
||||
componentType: ROW_TYPE,
|
||||
draggingType: CHART_TYPE,
|
||||
clientOffset: { x: 10, y: 2 },
|
||||
boundingClientRect: {
|
||||
left: 0,
|
||||
right: 100,
|
||||
top: 0,
|
||||
bottom: 100,
|
||||
},
|
||||
}),
|
||||
);
|
||||
const withChildren = getDropPosition(
|
||||
...getMocks({
|
||||
parentType: DASHBOARD_GRID_TYPE,
|
||||
componentType: ROW_TYPE,
|
||||
draggingType: CHART_TYPE,
|
||||
hasChildren: true,
|
||||
clientOffset: { x: 10, y: 2 },
|
||||
boundingClientRect: {
|
||||
left: 0,
|
||||
right: 100,
|
||||
top: 0,
|
||||
bottom: 100,
|
||||
},
|
||||
}),
|
||||
);
|
||||
expect(noChildren).toBe(DROP_TOP);
|
||||
expect(withChildren).toBe(DROP_TOP);
|
||||
});
|
||||
|
||||
it('should return DROP_BOTTOM regardless of component children if clientOffset IS near bottom sibling boundary', () => {
|
||||
const noChildren = getDropPosition(
|
||||
...getMocks({
|
||||
parentType: DASHBOARD_GRID_TYPE,
|
||||
componentType: ROW_TYPE,
|
||||
draggingType: CHART_TYPE,
|
||||
clientOffset: { x: 10, y: 95 },
|
||||
boundingClientRect: {
|
||||
left: 0,
|
||||
right: 100,
|
||||
top: 0,
|
||||
bottom: 100,
|
||||
},
|
||||
}),
|
||||
);
|
||||
const withChildren = getDropPosition(
|
||||
...getMocks({
|
||||
parentType: DASHBOARD_GRID_TYPE,
|
||||
componentType: ROW_TYPE,
|
||||
draggingType: CHART_TYPE,
|
||||
hasChildren: true,
|
||||
clientOffset: { x: 10, y: 95 },
|
||||
boundingClientRect: {
|
||||
left: 0,
|
||||
right: 100,
|
||||
top: 0,
|
||||
bottom: 100,
|
||||
},
|
||||
}),
|
||||
);
|
||||
expect(noChildren).toBe(DROP_BOTTOM);
|
||||
expect(withChildren).toBe(DROP_BOTTOM);
|
||||
});
|
||||
});
|
||||
|
||||
describe('child + valid sibling (column orientation)', () => {
|
||||
it('should return DROP_TOP if component has NO children, and clientOffset is NOT near left/right sibling boundary', () => {
|
||||
const result = getDropPosition(
|
||||
// CHART is a valid child + sibling of GRID > ROW
|
||||
...getMocks({
|
||||
parentType: DASHBOARD_GRID_TYPE,
|
||||
componentType: ROW_TYPE,
|
||||
draggingType: CHART_TYPE,
|
||||
orientation: 'column',
|
||||
clientOffset: { x: 50, y: 0 },
|
||||
boundingClientRect: {
|
||||
left: 0,
|
||||
right: 100,
|
||||
top: 0,
|
||||
bottom: 100,
|
||||
},
|
||||
}),
|
||||
);
|
||||
expect(result).toBe(DROP_TOP);
|
||||
});
|
||||
|
||||
it('should return DROP_BOTTOM if component HAS children, and clientOffset is NOT near left/right sibling boundary', () => {
|
||||
const result = getDropPosition(
|
||||
...getMocks({
|
||||
parentType: DASHBOARD_GRID_TYPE,
|
||||
componentType: ROW_TYPE,
|
||||
draggingType: CHART_TYPE,
|
||||
orientation: 'column',
|
||||
hasChildren: true,
|
||||
clientOffset: { x: 50, y: 0 },
|
||||
boundingClientRect: {
|
||||
left: 0,
|
||||
right: 100,
|
||||
top: 0,
|
||||
bottom: 100,
|
||||
},
|
||||
}),
|
||||
);
|
||||
expect(result).toBe(DROP_BOTTOM);
|
||||
});
|
||||
|
||||
it('should return DROP_LEFT regardless of component children if clientOffset IS near left sibling boundary', () => {
|
||||
const noChildren = getDropPosition(
|
||||
...getMocks({
|
||||
parentType: DASHBOARD_GRID_TYPE,
|
||||
componentType: ROW_TYPE,
|
||||
draggingType: CHART_TYPE,
|
||||
orientation: 'column',
|
||||
clientOffset: { x: 10, y: 2 },
|
||||
boundingClientRect: {
|
||||
left: 0,
|
||||
right: 100,
|
||||
top: 0,
|
||||
bottom: 100,
|
||||
},
|
||||
}),
|
||||
);
|
||||
const withChildren = getDropPosition(
|
||||
...getMocks({
|
||||
parentType: DASHBOARD_GRID_TYPE,
|
||||
componentType: ROW_TYPE,
|
||||
draggingType: CHART_TYPE,
|
||||
orientation: 'column',
|
||||
hasChildren: true,
|
||||
clientOffset: { x: 10, y: 2 },
|
||||
boundingClientRect: {
|
||||
left: 0,
|
||||
right: 100,
|
||||
top: 0,
|
||||
bottom: 100,
|
||||
},
|
||||
}),
|
||||
);
|
||||
expect(noChildren).toBe(DROP_LEFT);
|
||||
expect(withChildren).toBe(DROP_LEFT);
|
||||
});
|
||||
|
||||
it('should return DROP_RIGHT regardless of component children if clientOffset IS near right sibling boundary', () => {
|
||||
const noChildren = getDropPosition(
|
||||
...getMocks({
|
||||
parentType: DASHBOARD_GRID_TYPE,
|
||||
componentType: ROW_TYPE,
|
||||
draggingType: CHART_TYPE,
|
||||
orientation: 'column',
|
||||
clientOffset: { x: 90, y: 95 },
|
||||
boundingClientRect: {
|
||||
left: 0,
|
||||
right: 100,
|
||||
top: 0,
|
||||
bottom: 100,
|
||||
},
|
||||
}),
|
||||
);
|
||||
const withChildren = getDropPosition(
|
||||
...getMocks({
|
||||
parentType: DASHBOARD_GRID_TYPE,
|
||||
componentType: ROW_TYPE,
|
||||
draggingType: CHART_TYPE,
|
||||
orientation: 'column',
|
||||
hasChildren: true,
|
||||
clientOffset: { x: 90, y: 95 },
|
||||
boundingClientRect: {
|
||||
left: 0,
|
||||
right: 100,
|
||||
top: 0,
|
||||
bottom: 100,
|
||||
},
|
||||
}),
|
||||
);
|
||||
expect(noChildren).toBe(DROP_RIGHT);
|
||||
expect(withChildren).toBe(DROP_RIGHT);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,41 +0,0 @@
|
||||
/**
|
||||
* 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 getEffectiveExtraFilters from 'src/dashboard/util/charts/getEffectiveExtraFilters';
|
||||
|
||||
describe('getEffectiveExtraFilters', () => {
|
||||
it('should create valid filters', () => {
|
||||
const result = getEffectiveExtraFilters({
|
||||
gender: ['girl'],
|
||||
name: null,
|
||||
__time_range: ' : 2020-07-17T00:00:00',
|
||||
});
|
||||
expect(result).toMatchObject([
|
||||
{
|
||||
col: 'gender',
|
||||
op: 'IN',
|
||||
val: ['girl'],
|
||||
},
|
||||
{
|
||||
col: '__time_range',
|
||||
op: '==',
|
||||
val: ' : 2020-07-17T00:00:00',
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -1,99 +0,0 @@
|
||||
/**
|
||||
* 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 getFilterConfigsFromFormdata from 'src/dashboard/util/getFilterConfigsFromFormdata';
|
||||
|
||||
describe('getFilterConfigsFromFormdata', () => {
|
||||
const testFormdata = {
|
||||
filter_configs: [
|
||||
{
|
||||
asc: true,
|
||||
clearable: true,
|
||||
column: 'state',
|
||||
defaultValue: 'CA',
|
||||
key: 'fvwncPjUf',
|
||||
multiple: true,
|
||||
},
|
||||
],
|
||||
date_filter: true,
|
||||
granularity_sqla: '__time',
|
||||
time_grain_sqla: 'P1M',
|
||||
time_range: '2018-12-30T00:00:00+:+last+saturday',
|
||||
};
|
||||
|
||||
it('should add time grain', () => {
|
||||
const result = getFilterConfigsFromFormdata({
|
||||
...testFormdata,
|
||||
show_sqla_time_granularity: true,
|
||||
});
|
||||
expect(result.columns).toMatchObject({
|
||||
__time_grain: testFormdata.time_grain_sqla,
|
||||
});
|
||||
});
|
||||
|
||||
it('should add time column', () => {
|
||||
const result = getFilterConfigsFromFormdata({
|
||||
...testFormdata,
|
||||
show_sqla_time_column: true,
|
||||
});
|
||||
expect(result.columns).toMatchObject({
|
||||
__time_col: testFormdata.granularity_sqla,
|
||||
});
|
||||
});
|
||||
|
||||
it('should use default value and treat empty defaults as null', () => {
|
||||
const result = getFilterConfigsFromFormdata({
|
||||
...testFormdata,
|
||||
show_sqla_time_column: true,
|
||||
filter_configs: [
|
||||
...testFormdata.filter_configs,
|
||||
{
|
||||
asc: false,
|
||||
clearable: true,
|
||||
column: 'country',
|
||||
defaultValue: '',
|
||||
key: 'foo',
|
||||
multiple: true,
|
||||
},
|
||||
],
|
||||
});
|
||||
expect(result.columns).toMatchObject({
|
||||
state: ['CA'],
|
||||
country: null,
|
||||
});
|
||||
});
|
||||
|
||||
it('should read multi values from form_data', () => {
|
||||
const result = getFilterConfigsFromFormdata({
|
||||
...testFormdata,
|
||||
filter_configs: [
|
||||
{
|
||||
asc: true,
|
||||
clearable: true,
|
||||
column: 'state',
|
||||
defaultValue: 'CA;NY',
|
||||
key: 'fvwncPjUf',
|
||||
multiple: true,
|
||||
},
|
||||
],
|
||||
});
|
||||
expect(result.columns).toMatchObject({
|
||||
state: ['CA', 'NY'],
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,476 +0,0 @@
|
||||
/**
|
||||
* 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 getFilterScopeFromNodesTree from 'src/dashboard/util/getFilterScopeFromNodesTree';
|
||||
|
||||
describe('getFilterScopeFromNodesTree', () => {
|
||||
it('should return empty scope', () => {
|
||||
const nodes = [];
|
||||
expect(
|
||||
getFilterScopeFromNodesTree({
|
||||
filterKey: '107_region',
|
||||
nodes,
|
||||
checkedChartIds: [],
|
||||
}),
|
||||
).toEqual({});
|
||||
});
|
||||
|
||||
it('should return scope for simple grid', () => {
|
||||
const nodes = [
|
||||
{
|
||||
label: 'All dashboard',
|
||||
type: 'ROOT',
|
||||
value: 'ROOT_ID',
|
||||
children: [
|
||||
{
|
||||
value: 104,
|
||||
label: 'Life Expectancy VS Rural %',
|
||||
type: 'CHART',
|
||||
},
|
||||
{ value: 105, label: 'Rural Breakdown', type: 'CHART' },
|
||||
{
|
||||
value: 106,
|
||||
label: "World's Pop Growth",
|
||||
type: 'CHART',
|
||||
},
|
||||
{
|
||||
label: 'Time Filter',
|
||||
showCheckbox: false,
|
||||
type: 'CHART',
|
||||
value: 108,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
const checkedChartIds = [104, 106];
|
||||
expect(
|
||||
getFilterScopeFromNodesTree({
|
||||
filterKey: '108___time_range',
|
||||
nodes,
|
||||
checkedChartIds,
|
||||
}),
|
||||
).toEqual({
|
||||
scope: ['ROOT_ID'],
|
||||
immune: [105],
|
||||
});
|
||||
});
|
||||
|
||||
describe('should return scope for tabbed dashboard', () => {
|
||||
// this is a commonly used layout for dashboard:
|
||||
// - Tab 1
|
||||
// - filter_107
|
||||
// - chart_106
|
||||
// - Tab 2
|
||||
// - filter_108
|
||||
// - chart_104
|
||||
// - Row Tab
|
||||
// - chart_105
|
||||
// - chart_103
|
||||
// - New Tab
|
||||
// - chart_101
|
||||
// - chart_102
|
||||
const nodes = [
|
||||
{
|
||||
label: 'All dashboard',
|
||||
type: 'ROOT',
|
||||
value: 'ROOT_ID',
|
||||
children: [
|
||||
{
|
||||
label: 'Tab 1',
|
||||
type: 'TAB',
|
||||
value: 'TAB-Rb5aaqKWgG',
|
||||
children: [
|
||||
{
|
||||
label: 'Geo Filters',
|
||||
showCheckbox: false,
|
||||
type: 'CHART',
|
||||
value: 107,
|
||||
},
|
||||
{
|
||||
label: "World's Pop Growth",
|
||||
showCheckbox: true,
|
||||
type: 'CHART',
|
||||
value: 106,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Tab 2',
|
||||
type: 'TAB',
|
||||
value: 'TAB-w5Fp904Rs',
|
||||
children: [
|
||||
{
|
||||
label: 'Time Filter',
|
||||
showCheckbox: true,
|
||||
type: 'CHART',
|
||||
value: 108,
|
||||
},
|
||||
{
|
||||
label: 'Life Expectancy VS Rural %',
|
||||
showCheckbox: true,
|
||||
type: 'CHART',
|
||||
value: 104,
|
||||
},
|
||||
{
|
||||
label: 'Row Tab 1',
|
||||
type: 'TAB',
|
||||
value: 'TAB-E4mJaZ-uQM',
|
||||
children: [
|
||||
{
|
||||
value: 105,
|
||||
label: 'Rural Breakdown',
|
||||
type: 'CHART',
|
||||
showCheckbox: true,
|
||||
},
|
||||
{
|
||||
value: 103,
|
||||
label: '% Rural',
|
||||
type: 'CHART',
|
||||
showCheckbox: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'TAB-rLYu-Cryu',
|
||||
label: 'New Tab',
|
||||
type: 'TAB',
|
||||
children: [
|
||||
{
|
||||
value: 102,
|
||||
label: 'Most Populated Countries',
|
||||
type: 'CHART',
|
||||
showCheckbox: true,
|
||||
},
|
||||
{
|
||||
value: 101,
|
||||
label: "World's Population",
|
||||
type: 'CHART',
|
||||
showCheckbox: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
// this is another commonly used layout for dashboard:
|
||||
// - filter_109
|
||||
// - Tab 1
|
||||
// - Row Tab 1
|
||||
// - filter_107
|
||||
// - chart_106
|
||||
// - Tab 2
|
||||
// - filter_108
|
||||
// - chart_104
|
||||
// - Row Tab
|
||||
// - chart_105
|
||||
// - chart_103
|
||||
// - New Tab
|
||||
// - chart_101
|
||||
// - chart_102
|
||||
const nodes2 = [
|
||||
{
|
||||
label: 'All dashboard',
|
||||
type: 'ROOT',
|
||||
value: 'ROOT_ID',
|
||||
children: [
|
||||
{
|
||||
label: 'Time Filter',
|
||||
showCheckbox: true,
|
||||
type: 'CHART',
|
||||
value: 109,
|
||||
},
|
||||
{
|
||||
label: 'Tab 1',
|
||||
type: 'TAB',
|
||||
value: 'TAB-Rb5aaqKWgG',
|
||||
children: [
|
||||
{
|
||||
label: 'Row Tab 1',
|
||||
type: 'TAB',
|
||||
value: 'TAB-row-tab1',
|
||||
children: [
|
||||
{
|
||||
label: 'Geo Filters',
|
||||
showCheckbox: false,
|
||||
type: 'CHART',
|
||||
value: 107,
|
||||
},
|
||||
{
|
||||
label: "World's Pop Growth",
|
||||
showCheckbox: true,
|
||||
type: 'CHART',
|
||||
value: 106,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Tab 2',
|
||||
type: 'TAB',
|
||||
value: 'TAB-w5Fp904Rs',
|
||||
children: [
|
||||
{
|
||||
label: 'Time Filter',
|
||||
showCheckbox: true,
|
||||
type: 'CHART',
|
||||
value: 108,
|
||||
},
|
||||
{
|
||||
label: 'Life Expectancy VS Rural %',
|
||||
showCheckbox: true,
|
||||
type: 'CHART',
|
||||
value: 104,
|
||||
},
|
||||
{
|
||||
label: 'Row Tab 1',
|
||||
type: 'TAB',
|
||||
value: 'TAB-E4mJaZ-uQM',
|
||||
children: [
|
||||
{
|
||||
value: 105,
|
||||
label: 'Rural Breakdown',
|
||||
type: 'CHART',
|
||||
showCheckbox: true,
|
||||
},
|
||||
{
|
||||
value: 103,
|
||||
label: '% Rural',
|
||||
type: 'CHART',
|
||||
showCheckbox: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'TAB-rLYu-Cryu',
|
||||
label: 'New Tab',
|
||||
type: 'TAB',
|
||||
children: [
|
||||
{
|
||||
value: 102,
|
||||
label: 'Most Populated Countries',
|
||||
type: 'CHART',
|
||||
showCheckbox: true,
|
||||
},
|
||||
{
|
||||
value: 101,
|
||||
label: "World's Population",
|
||||
type: 'CHART',
|
||||
showCheckbox: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
it('root level tab scope', () => {
|
||||
const checkedChartIds = [106];
|
||||
expect(
|
||||
getFilterScopeFromNodesTree({
|
||||
filterKey: '107_region',
|
||||
nodes,
|
||||
checkedChartIds,
|
||||
}),
|
||||
).toEqual({
|
||||
scope: ['TAB-Rb5aaqKWgG'],
|
||||
immune: [],
|
||||
});
|
||||
});
|
||||
|
||||
it('global scope', () => {
|
||||
const checkedChartIds = [106, 104, 101, 102, 103, 105];
|
||||
expect(
|
||||
getFilterScopeFromNodesTree({
|
||||
filterKey: '107_country_name',
|
||||
nodes,
|
||||
checkedChartIds,
|
||||
}),
|
||||
).toEqual({
|
||||
scope: ['ROOT_ID'],
|
||||
immune: [108],
|
||||
});
|
||||
});
|
||||
|
||||
it('row level tab scope', () => {
|
||||
const checkedChartIds = [103, 105];
|
||||
expect(
|
||||
getFilterScopeFromNodesTree({
|
||||
filterKey: '108___time_range',
|
||||
nodes,
|
||||
checkedChartIds,
|
||||
}),
|
||||
).toEqual({
|
||||
scope: ['TAB-E4mJaZ-uQM'],
|
||||
immune: [],
|
||||
});
|
||||
});
|
||||
|
||||
it('mixed row level and root level scope', () => {
|
||||
const checkedChartIds = [103, 105, 106];
|
||||
expect(
|
||||
getFilterScopeFromNodesTree({
|
||||
filterKey: '107_region',
|
||||
nodes,
|
||||
checkedChartIds,
|
||||
}),
|
||||
).toEqual({
|
||||
scope: ['TAB-Rb5aaqKWgG', 'TAB-E4mJaZ-uQM'],
|
||||
immune: [],
|
||||
});
|
||||
});
|
||||
|
||||
it('mixed row level tab and chart scope', () => {
|
||||
const checkedChartIds = [103, 105, 102];
|
||||
expect(
|
||||
getFilterScopeFromNodesTree({
|
||||
filterKey: '107_region',
|
||||
nodes,
|
||||
checkedChartIds,
|
||||
}),
|
||||
).toEqual({
|
||||
scope: ['TAB-E4mJaZ-uQM', 'TAB-rLYu-Cryu'],
|
||||
immune: [101],
|
||||
});
|
||||
});
|
||||
|
||||
it('exclude sub-tab', () => {
|
||||
const checkedChartIds = [103, 104, 105];
|
||||
expect(
|
||||
getFilterScopeFromNodesTree({
|
||||
filterKey: '108___time_range',
|
||||
nodes,
|
||||
checkedChartIds,
|
||||
}),
|
||||
).toEqual({
|
||||
scope: ['TAB-w5Fp904Rs'],
|
||||
immune: [102, 101],
|
||||
});
|
||||
});
|
||||
|
||||
it('exclude top-tab', () => {
|
||||
const checkedChartIds = [106, 109];
|
||||
expect(
|
||||
getFilterScopeFromNodesTree({
|
||||
filterKey: '107_region',
|
||||
nodes: nodes2,
|
||||
checkedChartIds,
|
||||
}),
|
||||
).toEqual({
|
||||
scope: ['ROOT_ID'],
|
||||
immune: [105, 103, 102, 101, 108, 104],
|
||||
});
|
||||
});
|
||||
|
||||
it('exclude nested sub-tab', () => {
|
||||
// another layout for test:
|
||||
// - filter_109
|
||||
// - chart_106
|
||||
// - Tab 1
|
||||
// - Nested_Tab1
|
||||
// - chart_101
|
||||
// - chart_102
|
||||
// - Nested_Tab2
|
||||
// - chart_103
|
||||
// - chart_104
|
||||
const nodes3 = [
|
||||
{
|
||||
label: 'All dashboard',
|
||||
type: 'ROOT',
|
||||
value: 'ROOT_ID',
|
||||
children: [
|
||||
{
|
||||
label: 'Time Filter',
|
||||
showCheckbox: true,
|
||||
type: 'CHART',
|
||||
value: 109,
|
||||
},
|
||||
{
|
||||
label: "World's Pop Growth",
|
||||
showCheckbox: true,
|
||||
type: 'CHART',
|
||||
value: 106,
|
||||
},
|
||||
{
|
||||
label: 'Row Tab 1',
|
||||
type: 'TAB',
|
||||
value: 'TAB-w5Fp904Rs',
|
||||
children: [
|
||||
{
|
||||
label: 'Nested Tab 1',
|
||||
type: 'TAB',
|
||||
value: 'TAB-E4mJaZ-uQM',
|
||||
children: [
|
||||
{
|
||||
value: 104,
|
||||
label: 'Rural Breakdown',
|
||||
type: 'CHART',
|
||||
showCheckbox: true,
|
||||
},
|
||||
{
|
||||
value: 103,
|
||||
label: '% Rural',
|
||||
type: 'CHART',
|
||||
showCheckbox: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'TAB-rLYu-Cryu',
|
||||
label: 'Nested Tab 2',
|
||||
type: 'TAB',
|
||||
children: [
|
||||
{
|
||||
value: 102,
|
||||
label: 'Most Populated Countries',
|
||||
type: 'CHART',
|
||||
showCheckbox: true,
|
||||
},
|
||||
{
|
||||
value: 101,
|
||||
label: "World's Population",
|
||||
type: 'CHART',
|
||||
showCheckbox: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const checkedChartIds = [103, 104, 106];
|
||||
expect(
|
||||
getFilterScopeFromNodesTree({
|
||||
filterKey: '109___time_range',
|
||||
nodes: nodes3,
|
||||
checkedChartIds,
|
||||
}),
|
||||
).toEqual({
|
||||
scope: ['ROOT_ID'],
|
||||
immune: [102, 101],
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,104 +0,0 @@
|
||||
/**
|
||||
* 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 getFormDataWithExtraFilters, {
|
||||
GetFormDataWithExtraFiltersArguments,
|
||||
} from 'src/dashboard/util/charts/getFormDataWithExtraFilters';
|
||||
import { DASHBOARD_ROOT_ID } from 'src/dashboard/util/constants';
|
||||
import { Filter } from 'src/dashboard/components/nativeFilters/types';
|
||||
import { LayoutItem } from 'src/dashboard/types';
|
||||
import { dashboardLayout } from '../../../fixtures/mockDashboardLayout';
|
||||
import { sliceId as chartId } from '../../../fixtures/mockChartQueries';
|
||||
|
||||
describe('getFormDataWithExtraFilters', () => {
|
||||
const filterId = 'native-filter-1';
|
||||
const mockChart = {
|
||||
id: chartId,
|
||||
chartAlert: null,
|
||||
chartStatus: null,
|
||||
chartUpdateEndTime: null,
|
||||
chartUpdateStartTime: 1,
|
||||
lastRendered: 1,
|
||||
latestQueryFormData: {},
|
||||
sliceFormData: null,
|
||||
queryController: null,
|
||||
queriesResponse: null,
|
||||
triggerQuery: false,
|
||||
formData: {
|
||||
viz_type: 'filter_select',
|
||||
filters: [
|
||||
{
|
||||
col: 'country_name',
|
||||
op: 'IN',
|
||||
val: ['United States'],
|
||||
},
|
||||
],
|
||||
datasource: '123',
|
||||
},
|
||||
};
|
||||
const mockArgs: GetFormDataWithExtraFiltersArguments = {
|
||||
chartConfiguration: {},
|
||||
charts: {
|
||||
[chartId as number]: mockChart,
|
||||
},
|
||||
chart: mockChart,
|
||||
filters: {
|
||||
region: ['Spain'],
|
||||
color: ['pink', 'purple'],
|
||||
},
|
||||
sliceId: chartId,
|
||||
nativeFilters: {
|
||||
filterSets: {},
|
||||
filters: {
|
||||
[filterId]: {
|
||||
id: filterId,
|
||||
scope: {
|
||||
rootPath: [DASHBOARD_ROOT_ID],
|
||||
excluded: [],
|
||||
},
|
||||
} as unknown as Filter,
|
||||
},
|
||||
},
|
||||
dataMask: {
|
||||
[filterId]: {
|
||||
id: filterId,
|
||||
extraFormData: {},
|
||||
filterState: {},
|
||||
ownState: {},
|
||||
},
|
||||
},
|
||||
layout: dashboardLayout.present as unknown as {
|
||||
[key: string]: LayoutItem;
|
||||
},
|
||||
};
|
||||
|
||||
it('should include filters from the passed filters', () => {
|
||||
const result = getFormDataWithExtraFilters(mockArgs);
|
||||
expect(result.extra_filters).toHaveLength(2);
|
||||
expect(result.extra_filters[0]).toEqual({
|
||||
col: 'region',
|
||||
op: 'IN',
|
||||
val: ['Spain'],
|
||||
});
|
||||
expect(result.extra_filters[1]).toEqual({
|
||||
col: 'color',
|
||||
op: 'IN',
|
||||
val: ['pink', 'purple'],
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,36 +0,0 @@
|
||||
/**
|
||||
* 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 getLeafComponentIdFromPath from 'src/dashboard/util/getLeafComponentIdFromPath';
|
||||
import { filterId } from 'spec/fixtures/mockSliceEntities';
|
||||
import { dashboardFilters } from 'spec/fixtures/mockDashboardFilters';
|
||||
|
||||
describe('getLeafComponentIdFromPath', () => {
|
||||
const path = dashboardFilters[filterId].directPathToFilter;
|
||||
const leaf = path.slice().pop();
|
||||
|
||||
it('should return component id', () => {
|
||||
expect(getLeafComponentIdFromPath(path)).toBe(leaf);
|
||||
});
|
||||
|
||||
it('should not return label component', () => {
|
||||
const updatedPath =
|
||||
dashboardFilters[filterId].directPathToFilter.concat('LABEL-test123');
|
||||
expect(getLeafComponentIdFromPath(updatedPath)).toBe(leaf);
|
||||
});
|
||||
});
|
||||
@@ -1,42 +0,0 @@
|
||||
/**
|
||||
* 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 isDashboardEmpty from 'src/dashboard/util/isDashboardEmpty';
|
||||
import getEmptyLayout from 'src/dashboard/util/getEmptyLayout';
|
||||
|
||||
describe('isDashboardEmpty', () => {
|
||||
const emptyLayout: object = getEmptyLayout();
|
||||
const testLayout: object = {
|
||||
...emptyLayout,
|
||||
'MARKDOWN-IhTGLhyiTd': {
|
||||
children: [],
|
||||
id: 'MARKDOWN-IhTGLhyiTd',
|
||||
meta: { code: 'test me', height: 50, width: 4 },
|
||||
parents: ['ROOT_ID', 'GRID_ID', 'ROW-uPjcKNYJQy'],
|
||||
type: 'MARKDOWN',
|
||||
},
|
||||
};
|
||||
|
||||
it('should return true for empty dashboard', () => {
|
||||
expect(isDashboardEmpty(emptyLayout)).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false for non-empty dashboard', () => {
|
||||
expect(isDashboardEmpty(testLayout)).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -1,164 +0,0 @@
|
||||
/**
|
||||
* 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 isValidChild from 'src/dashboard/util/isValidChild';
|
||||
|
||||
import {
|
||||
CHART_TYPE as CHART,
|
||||
COLUMN_TYPE as COLUMN,
|
||||
DASHBOARD_GRID_TYPE as GRID,
|
||||
DASHBOARD_ROOT_TYPE as ROOT,
|
||||
DIVIDER_TYPE as DIVIDER,
|
||||
HEADER_TYPE as HEADER,
|
||||
MARKDOWN_TYPE as MARKDOWN,
|
||||
ROW_TYPE as ROW,
|
||||
TABS_TYPE as TABS,
|
||||
TAB_TYPE as TAB,
|
||||
} from 'src/dashboard/util/componentTypes';
|
||||
|
||||
const getIndentation = (depth: number) =>
|
||||
Array(depth * 3)
|
||||
.fill('')
|
||||
.join('-');
|
||||
|
||||
describe('isValidChild', () => {
|
||||
describe('valid calls', () => {
|
||||
// these are representations of nested structures for easy testing
|
||||
// [ROOT (depth 0) > GRID (depth 1) > HEADER (depth 2)]
|
||||
// every unique parent > child relationship is tested, but because this
|
||||
// test representation WILL result in duplicates, we hash each test
|
||||
// to keep track of which we've run
|
||||
const didTest = {};
|
||||
const validExamples = [
|
||||
[ROOT, GRID, CHART], // chart is valid because it is wrapped in a row
|
||||
[ROOT, GRID, MARKDOWN], // markdown is valid because it is wrapped in a row
|
||||
[ROOT, GRID, COLUMN], // column is valid because it is wrapped in a row
|
||||
[ROOT, GRID, HEADER],
|
||||
[ROOT, GRID, ROW, MARKDOWN],
|
||||
[ROOT, GRID, ROW, CHART],
|
||||
|
||||
[ROOT, GRID, ROW, COLUMN, HEADER],
|
||||
[ROOT, GRID, ROW, COLUMN, DIVIDER],
|
||||
[ROOT, GRID, ROW, COLUMN, CHART],
|
||||
[ROOT, GRID, ROW, COLUMN, MARKDOWN],
|
||||
|
||||
[ROOT, GRID, ROW, COLUMN, ROW, CHART],
|
||||
[ROOT, GRID, ROW, COLUMN, ROW, MARKDOWN],
|
||||
|
||||
[ROOT, GRID, ROW, COLUMN, ROW, COLUMN, CHART],
|
||||
[ROOT, GRID, ROW, COLUMN, ROW, COLUMN, MARKDOWN],
|
||||
[ROOT, GRID, TABS, TAB, ROW, COLUMN, ROW, COLUMN, MARKDOWN],
|
||||
|
||||
// tab equivalents
|
||||
[ROOT, TABS, TAB, CHART],
|
||||
[ROOT, TABS, TAB, MARKDOWN],
|
||||
[ROOT, TABS, TAB, COLUMN],
|
||||
[ROOT, TABS, TAB, HEADER],
|
||||
[ROOT, TABS, TAB, ROW, MARKDOWN],
|
||||
[ROOT, TABS, TAB, ROW, CHART],
|
||||
|
||||
[ROOT, TABS, TAB, ROW, COLUMN, HEADER],
|
||||
[ROOT, TABS, TAB, ROW, COLUMN, DIVIDER],
|
||||
[ROOT, TABS, TAB, ROW, COLUMN, CHART],
|
||||
[ROOT, TABS, TAB, ROW, COLUMN, MARKDOWN],
|
||||
|
||||
[ROOT, TABS, TAB, ROW, COLUMN, ROW, CHART],
|
||||
[ROOT, TABS, TAB, ROW, COLUMN, ROW, MARKDOWN],
|
||||
|
||||
[ROOT, TABS, TAB, ROW, COLUMN, ROW, COLUMN, CHART],
|
||||
[ROOT, TABS, TAB, ROW, COLUMN, ROW, COLUMN, MARKDOWN],
|
||||
[ROOT, TABS, TAB, TABS, TAB, ROW, COLUMN, ROW, COLUMN, MARKDOWN],
|
||||
[ROOT, GRID, ROW, COLUMN, TABS],
|
||||
];
|
||||
|
||||
validExamples.forEach((example, exampleIdx) => {
|
||||
let childDepth = 0;
|
||||
example.forEach((childType, i) => {
|
||||
const parentDepth = childDepth - 1;
|
||||
const parentType = example[i - 1];
|
||||
const testKey = `${parentType}-${childType}-${parentDepth}`;
|
||||
|
||||
if (i > 0 && !didTest[testKey]) {
|
||||
didTest[testKey] = true;
|
||||
|
||||
it(`(${exampleIdx})${getIndentation(
|
||||
childDepth,
|
||||
)}${parentType} (depth ${parentDepth}) > ${childType} ✅`, () => {
|
||||
expect(
|
||||
isValidChild({
|
||||
parentDepth,
|
||||
parentType,
|
||||
childType,
|
||||
}),
|
||||
).toBe(true);
|
||||
});
|
||||
}
|
||||
// see isValidChild.js for why tabs do not increment the depth of their children
|
||||
childDepth += childType !== TABS && childType !== TAB ? 1 : 0;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('invalid calls', () => {
|
||||
// In order to assert that a parent > child hierarchy at a given depth is invalid
|
||||
// we also define some valid hierarchies in doing so. we indicate which
|
||||
// parent > [child] relationships should be asserted as invalid using a nested array
|
||||
const invalidExamples = [
|
||||
[ROOT, [DIVIDER]],
|
||||
[ROOT, [CHART]],
|
||||
[ROOT, [MARKDOWN]],
|
||||
[ROOT, GRID, [TAB]],
|
||||
[ROOT, GRID, TABS, [ROW]],
|
||||
// [ROOT, GRID, TABS, TAB, [TABS]], // @TODO this needs to be fixed
|
||||
[ROOT, GRID, ROW, [TABS]],
|
||||
[ROOT, GRID, ROW, [TAB]],
|
||||
[ROOT, GRID, ROW, [DIVIDER]],
|
||||
[ROOT, GRID, ROW, COLUMN, [TAB]],
|
||||
[ROOT, GRID, ROW, COLUMN, ROW, [DIVIDER]],
|
||||
[ROOT, GRID, ROW, COLUMN, ROW, COLUMN, [ROW]], // too nested
|
||||
];
|
||||
|
||||
invalidExamples.forEach((example, exampleIdx) => {
|
||||
let childDepth = 0;
|
||||
example.forEach((childType, i) => {
|
||||
// should test child
|
||||
if (i > 0 && Array.isArray(childType)) {
|
||||
const parentDepth = childDepth - 1;
|
||||
const parentType = example[i - 1];
|
||||
|
||||
if (typeof parentType !== 'string')
|
||||
throw TypeError('parent must be string');
|
||||
|
||||
it(`(${exampleIdx})${getIndentation(
|
||||
childDepth,
|
||||
)}${parentType} (depth ${parentDepth}) > ${childType} ❌`, () => {
|
||||
expect(
|
||||
isValidChild({
|
||||
parentDepth,
|
||||
parentType,
|
||||
childType: childType[0],
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
}
|
||||
// see isValidChild.js for why tabs do not increment the depth of their children
|
||||
childDepth += childType !== TABS && childType !== TAB ? 1 : 0;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,66 +0,0 @@
|
||||
/**
|
||||
* 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 newComponentFactory from 'src/dashboard/util/newComponentFactory';
|
||||
|
||||
import {
|
||||
CHART_TYPE,
|
||||
COLUMN_TYPE,
|
||||
DASHBOARD_GRID_TYPE,
|
||||
DASHBOARD_ROOT_TYPE,
|
||||
DIVIDER_TYPE,
|
||||
HEADER_TYPE,
|
||||
MARKDOWN_TYPE,
|
||||
NEW_COMPONENT_SOURCE_TYPE,
|
||||
ROW_TYPE,
|
||||
TABS_TYPE,
|
||||
TAB_TYPE,
|
||||
} from 'src/dashboard/util/componentTypes';
|
||||
|
||||
const types = [
|
||||
CHART_TYPE,
|
||||
COLUMN_TYPE,
|
||||
DASHBOARD_GRID_TYPE,
|
||||
DASHBOARD_ROOT_TYPE,
|
||||
DIVIDER_TYPE,
|
||||
HEADER_TYPE,
|
||||
MARKDOWN_TYPE,
|
||||
NEW_COMPONENT_SOURCE_TYPE,
|
||||
ROW_TYPE,
|
||||
TABS_TYPE,
|
||||
TAB_TYPE,
|
||||
];
|
||||
|
||||
describe('newEntityFactory', () => {
|
||||
types.forEach(type => {
|
||||
it(`returns a new ${type}`, () => {
|
||||
const result = newComponentFactory(type);
|
||||
|
||||
expect(result.type).toBe(type);
|
||||
expect(typeof result.id).toBe('string');
|
||||
expect(typeof result.meta).toBe('object');
|
||||
expect(Array.isArray(result.children)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('adds passed meta data to the entity', () => {
|
||||
const banana = 'banana';
|
||||
const result = newComponentFactory(CHART_TYPE, { banana });
|
||||
expect(result.meta.banana).toBe(banana);
|
||||
});
|
||||
});
|
||||
@@ -1,104 +0,0 @@
|
||||
/**
|
||||
* 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 newEntitiesFromDrop from 'src/dashboard/util/newEntitiesFromDrop';
|
||||
import {
|
||||
CHART_TYPE,
|
||||
DASHBOARD_GRID_TYPE,
|
||||
ROW_TYPE,
|
||||
TABS_TYPE,
|
||||
TAB_TYPE,
|
||||
} from 'src/dashboard/util/componentTypes';
|
||||
|
||||
describe('newEntitiesFromDrop', () => {
|
||||
it('should return a new Entity of appropriate type, and add it to the drop target children', () => {
|
||||
const result = newEntitiesFromDrop({
|
||||
dropResult: {
|
||||
destination: { id: 'a', index: 0 },
|
||||
dragging: { type: CHART_TYPE },
|
||||
source: { id: 'b', index: 0 },
|
||||
},
|
||||
layout: {
|
||||
a: {
|
||||
id: 'a',
|
||||
type: ROW_TYPE,
|
||||
children: [],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const newId = result.a.children[0];
|
||||
expect(result.a.children).toHaveLength(1);
|
||||
expect(Object.keys(result)).toHaveLength(2);
|
||||
expect(result[newId].type).toBe(CHART_TYPE);
|
||||
});
|
||||
|
||||
it('should create Tab AND Tabs components if the drag entity is Tabs', () => {
|
||||
const result = newEntitiesFromDrop({
|
||||
dropResult: {
|
||||
destination: { id: 'a', index: 0 },
|
||||
dragging: { type: TABS_TYPE },
|
||||
source: { id: 'b', index: 0 },
|
||||
},
|
||||
layout: {
|
||||
a: {
|
||||
id: 'a',
|
||||
type: DASHBOARD_GRID_TYPE,
|
||||
children: [],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const newTabsId = result.a.children[0];
|
||||
const newTabId = result[newTabsId].children[0];
|
||||
|
||||
expect(result.a.children).toHaveLength(1);
|
||||
expect(Object.keys(result)).toHaveLength(3);
|
||||
expect(result[newTabsId].type).toBe(TABS_TYPE);
|
||||
expect(result[newTabId].type).toBe(TAB_TYPE);
|
||||
});
|
||||
|
||||
it('should create a Row if the drag entity should be wrapped in a row', () => {
|
||||
const result = newEntitiesFromDrop({
|
||||
dropResult: {
|
||||
destination: { id: 'a', index: 0 },
|
||||
dragging: { type: CHART_TYPE },
|
||||
source: { id: 'b', index: 0 },
|
||||
},
|
||||
layout: {
|
||||
a: {
|
||||
id: 'a',
|
||||
type: DASHBOARD_GRID_TYPE,
|
||||
children: [],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const newRowId = result.a.children[0];
|
||||
const newChartId = result[newRowId].children[0];
|
||||
|
||||
expect(result.a.children).toHaveLength(1);
|
||||
expect(Object.keys(result)).toHaveLength(3);
|
||||
const newRow = result[newRowId];
|
||||
expect(newRow.type).toBe(ROW_TYPE);
|
||||
expect(newRow.parents).toEqual(['a']);
|
||||
const newChart = result[newChartId];
|
||||
expect(newChart.type).toBe(CHART_TYPE);
|
||||
expect(newChart.parents).toEqual(['a', newRowId]);
|
||||
});
|
||||
});
|
||||
@@ -1,97 +0,0 @@
|
||||
/**
|
||||
* 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 updateComponentParentsList from 'src/dashboard/util/updateComponentParentsList';
|
||||
import { DASHBOARD_ROOT_ID } from 'src/dashboard/util/constants';
|
||||
import {
|
||||
dashboardLayout,
|
||||
dashboardLayoutWithTabs,
|
||||
} from 'spec/fixtures/mockDashboardLayout';
|
||||
|
||||
describe('updateComponentParentsList', () => {
|
||||
const emptyLayout = {
|
||||
DASHBOARD_VERSION_KEY: 'v2',
|
||||
GRID_ID: {
|
||||
children: [],
|
||||
id: 'GRID_ID',
|
||||
type: 'GRID',
|
||||
},
|
||||
ROOT_ID: {
|
||||
children: ['GRID_ID'],
|
||||
id: 'ROOT_ID',
|
||||
type: 'ROOT',
|
||||
},
|
||||
};
|
||||
const gridLayout = {
|
||||
...dashboardLayout.present,
|
||||
};
|
||||
const tabsLayout = {
|
||||
...dashboardLayoutWithTabs.present,
|
||||
};
|
||||
|
||||
it('should handle empty layout', () => {
|
||||
const nextState = {
|
||||
...emptyLayout,
|
||||
};
|
||||
|
||||
updateComponentParentsList({
|
||||
currentComponent: nextState[DASHBOARD_ROOT_ID],
|
||||
layout: nextState,
|
||||
});
|
||||
|
||||
expect(nextState.GRID_ID.parents).toEqual(['ROOT_ID']);
|
||||
});
|
||||
|
||||
it('should handle grid layout', () => {
|
||||
const nextState = {
|
||||
...gridLayout,
|
||||
};
|
||||
|
||||
updateComponentParentsList({
|
||||
currentComponent: nextState[DASHBOARD_ROOT_ID],
|
||||
layout: nextState,
|
||||
});
|
||||
|
||||
expect(nextState.GRID_ID.parents).toEqual(['ROOT_ID']);
|
||||
expect(nextState.CHART_ID.parents).toEqual([
|
||||
'ROOT_ID',
|
||||
'GRID_ID',
|
||||
'ROW_ID',
|
||||
'COLUMN_ID',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should handle root level tabs', () => {
|
||||
const nextState = {
|
||||
...tabsLayout,
|
||||
};
|
||||
|
||||
updateComponentParentsList({
|
||||
currentComponent: nextState[DASHBOARD_ROOT_ID],
|
||||
layout: nextState,
|
||||
});
|
||||
|
||||
expect(nextState.GRID_ID.parents).toEqual(['ROOT_ID']);
|
||||
expect(nextState.CHART_ID2.parents).toEqual([
|
||||
'ROOT_ID',
|
||||
'TABS_ID',
|
||||
'TAB_ID2',
|
||||
'ROW_ID2',
|
||||
]);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user