mirror of
https://github.com/apache/superset.git
synced 2026-05-11 10:55:43 +00:00
[wip] dashboard builder v2 (#4528)
* [dashboard builder] Add dir structure for dashboard/v2, simplified Header, split pane, Draggable side panel
[grid] add <DashboardGrid />, <ResizableContainer />, and initial grid components.
[grid] gridComponents/ directory, add fixtures/ directory and test layout, add <Column />
[grid] working grid with gutters
[grid] design tweaks and polish, add <Tabs />
[header] add gradient header logo and favicon
[dnd] begin adding dnd functionality
[dnd] add util/isValidChild.js
[react-beautiful-dnd] iterate on dnd until blocked
[dnd] refactor to use react-dnd
[react-dnd] refactor to use composable <DashboardComponent /> structure
[dnd] factor out DashboardComponent, let components render dropInidcator and set draggableRef, add draggable tabs
[dnd] refactor to use redux, add DashboardComponent and DashboardGrid containers
[dragdroppable] rename horizontal/vertical => row/column
[builder] refactor into HoverMenu, add WithPopoverMenu
[builder] add editable header and disableDragDrop prop for Dragdroppable's
[builder] make tabs editable
[builder] add generic popover dropdown and header row style editability
[builder] add hover rowStyle dropdown, make row styles editable
[builder] add some new component icons, add popover with delete to charts
[builder] add preview icons, add popover menu to rows.
[builder] add IconButton and RowStyleDropdown
[resizable] use ResizableContainer instead of DimensionProvider, fix resize and delete bugs
[builder] fix bug with spacer
[builder] clean up, header.size => header.headerSize
[builder] support more drag/drop combinations by wrapping some components in rows upon drop. fix within list drop index. refactor some utils.
[builder][tabs] fix broken add tab button
[dashboard builder] don't pass dashboard layout to all dashboard components, improve drop indicator logic, fix delete component pure component bug
[dnd] refactor drop position logic
* fix rebase error, clean up css organization and use @less vars
* [dashboard-builder] add top-level tabs + undo-redo (#4626)
* [top-level-tabs] initial working version of top-level tabs
* [top-level-tabs] simplify redux and disable ability to displace top-level tabs with other tabs
* [top-level-tabs] improve tab drag and drop css
* [undo-redo] add redux undo redo
* [dnd] clean up dropResult shape, add new component source id + type, use css for drop indicator instead of styles and fix tab indicators.
* [top-level-tabs] add 'Collapse tab content' to delete tabs button
* [dnd] add depth validation to drag and drop logic
* [dashboard-builder] add resize action, enforce minimum width of columns, column children inherit column size when necessary, meta.rowStyle => meta.background, add background to columns
* [dashboard-builder] make sure getChildWidth returns a number
* [dashboard builder] static layout + toasts (#4763)
* [dashboard-builder] remove spacer component
* [dashboard-builder] better transparent indicator, better grid gutter logic, no dragging top-level tabs, headers are multiples of grid unit, fix row height granularity, update redux state key dashboard => dashboardLayout
* [dashboard-builder] don't blast column child dimensions on resize
* [dashboard-builder] ResizableContainer min size can't be smaller than size, fix row style, role=none on WithPopoverMenu container
* [edit mode] add edit mode to redux and propogate to all <DashboardComponent />s
* [toasts] add Toast component, ToastPresenter container and component, and toast redux actions + reducers
* [dashboard-builder] add info toast when dropResult overflows parent
* [dashboard builder] git mv to src/ post-rebase
* Dashboard builder rebased + linted (#4849)
* define dashboard redux state
* update dashboard state reducer
* dashboard layout converter + grid render
* builder pane + slice adder
* Dashboard header + slice header controls
* fix linting
* 2nd code review comments
* [dashboard builder] improve perf (#4855)
* address major perf + css issues
[dashboard builder] fix dashboard filters and some css
[dashboard builder] use VIZ_TYPES, move stricter .eslintrc to dashboard/, more css fixes
[builder] delete GridCell and GridLayout, remove some unused css. fix broken tabs.
* [builder] fix errors post-rebase
* [builder] add support for custom DragDroppable drag layer and add AddSliceDragPreview
* [AddSliceDragPreview] fix type check
* [dashboard builder] add prettier and update all files
* [dashboard builder] merge v2/ directory int dashboard/
* [dashboard builder] move component/*Container => containers/*
* add sticky tabs + sidepane, better tabs perf, better container hierarchy, better chart header (#4893)
* dashboard header, slice header UI improvement
* add slider and sticky
* dashboard header, slice header UI improvement
* make builder pane floating
* [dashboard builder] add sticky top-level tabs, refactor for performant tabs
* [dashboard builder] visually distinct containers, icons for undo-redo, fix some isValidChild bugs
* [dashboard builder] better undo redo <> save changes state, notify upon reaching undo limit
* [dashboard builder] hook up edit + create component actions to saved-state pop.
* [dashboard builder] visual refinement, refactor Dashboard header content and updates into layout for undo-redo, refactor save dashboard modal to use toasts instead of notify.
* [dashboard builder] refactor chart name update logic to use layout for undo redo, save slice name changes on dashboard save
* add slider and sticky
* [dashboard builder] fix layout converter slice_id + chartId type casting, don't change grid size upon edit (perf)
* [dashboard builder] don't set version key in getInitialState
* [dashboard builder] make top level tabs addition/removal undoable, fix double sticky tabs + side panel.
* [dashboard builder] fix sticky tabs offset bug
* [dashboard builder] fix drag preview width, css polish, fix rebase issue
* [dashboard builder] fix side pane labels and hove z-index
* Markdown for dashboard (#4962)
* fix dashboard server-side unit tests (#5009)
* Dashboard save button (#4979)
* save button
* fix slices list height
* save custom css
* merge save-dash changes from dashboard v1
https://github.com/apache/incubator-superset/pull/4900
https://github.com/apache/incubator-superset/pull/5051
* [dashboard v2] check for default_filters before json_loads-ing them (#5064)
[dashboard v2] check for default_filters before json-loads-ing them
* [dashboard v2] fix bugs from rebase
* [dashboard v2] tests! (#5066)
* [dashboard v2][tests] add tests for newComponentFactory, isValidChild, dropOverflowsParent, and dnd-reorder
* [dashboard v2][tests] add tests for componentIsResizable, findParentId, getChartIdsFromLayout, newEntitiesFromDrop, and getDropPosition
* [dashboard v2][tests] add mockStore, mockState, and tests for DragDroppable, DashboardBuilder, DashboardGrid, ToastPresenter, and Toast
* [dashboard builder][tests] separate files for state tree fixtures, add ChartHolder, Chart, Divider, Header, Row tests and WithDragDropContext helper
* [dashboard v2][tests] fix dragdrop context with util/getDragDropManager, add test for menu/* and resizable/*, and new components
* [dashboard v2][tests] fix and re-write Dashboard tests, add getFormDataWithExtraFilters_spec
* [dashboard v2][tests] add reducer tests, fix lint error
* [dashboard-v2][tests] add actions/dashboardLayout_spec
* [dashboard v2] fix some prop bugs, open side pane on edit, fix slice name bug
* [dashboard v2] fix slice name save bug
* [dashboard v2] fix lint errors
* [dashboard v2] fix filters bug and add test
* [dashboard v2] fix getFormDataWithExtraFilters_spec
* [dashboard v2] logging updates (#5087)
* [dashboard v2] initial logging refactor
* [dashboard v2] clean up logger
* [logger] update explore with new log events, add refresh dashboard + refresh dashboard chart actions
* [logging] add logger_spec.js, fix reducers/dashboardState_spec + gridComponents/Chart_spec
* [dashboard v2][logging] refactor for bulk logging in python
* [logging] tweak python, fix and remove dup start_offset entries
* [dashboard v2][logging] add dashboard_first_load event
* [dashboard v2][logging] add slice_ids to dashboard pane load event
* [tests] fix npm test script
* Fix: update slices list when add/remove multiple slices (#5138)
* [dashboard v2] add v1 switch (#5126)
* [dashboard] copy all dashboard v1 into working v1 switch
* [dashboard] add functional v1 <> v2 switch with messaging
* [dashboard] add v2 logging to v1 dashboard, add read-v2-changes link, add client logging to track v1 <> v2 switches
* [dashboard] Remove default values for feedback url + v2 auto convert date
* [dashboard v2] fix misc UI/UX issues
* [dashboard v2] fix Markdown persistance issues and css, fix copy dash title, don't enforce shallow hovering with drop indicator
* [dashboard v2] improve non-shallow drop target UX, fix Markdown drop indicator, clarify slice adder filter/sort
* [dashboard v2] delete empty rows on drag or delete events that leave them without children, add test
* [dashboard v2] improve v1<>v2 switch modals, add convert to v2 badge in v1, fix unsaved changes issue in preview mode, don't auto convert column child widths for now
* [dashboard v2][dnd] add drop position cache to fix non-shallow drops
* [dashboard] fix test script with glob instead of recurse, fix tests, add temp fix for tab nesting, ignore v1 lint errors
* [dashboard] v2 badge style tweaks, add back v1 _set_dash_metadata for v1 editing
* [dashboard] fix python linting and tests
* [dashboard] lint tests
* add slice from explore view (#5141)
* Fix dashboard position row data (#5131)
* add slice_name to markdown
(cherry picked from commit 14b01f1)
* set min grid width be 1 column
* remove empty column
* check total columns count <= 12
* scan position data and fix rows
* fix dashboard url with default_filters
* [dashboard v2] better grid drop ux, fix tab bugs 🐛 (#5151)
* [dashboard v2] add empty droptarget to dashboard grid for better ux and update test
* [dashboard] reset tab index upon top-level tab deletion, fix findparentid bug
* [dashboard] update v1<>v2 modal link for tracking
* Fix: Should pass slice_can_edit flag down (#5159)
* [dash builder fix] combine markdown and slice name, slice picker height (#5165)
* combine markdown code and markdown slice name
* allow dynamic height for slice picker cell
* add word break for long datasource name
* [fix] new dashboard state (#5213)
* [dashboard v2] ui + ux fixes (#5208)
* [dashboard v2] use <Loading /> throughout, small loading gif, improve row/column visual hierarchy, add cached data pop
* [dashboard v2] lots of polish
* [dashboard v2] remove markdown padding on edit, more opaque slice drag preview, unsavedChanges=true upon moving a component, fix initial load logging.
* [dashboard v2] gray loading.gif, sticky header, undo/redo keyboard shortcuts, fix move component saved changes update, v0 double scrollbar fix
* [dashboard v2] move UndoRedoKeylisteners into Header, render only in edit mode, show visual feedback for keyboard shortcut, hide hover menu in top-level tabs
* [dashboard v2] fix grid + sidepane height issues
* [dashboard v2] add auto-resize functionality, update tests. cache findParentId results.
* [dashboard v2][tests] add getDetailedComponentWidth_spec.js
* [dashboard v2] fix lint
* [fix] layout converter fix (#5218)
* [fix] layout converter fix
* add changed_on into initial sliceEntity data
* add unit tests for SliceAdder component
* remove old fixtures file
* [dashboard v2] remove webpack-cli, fresh yarn.lock post-rebase
* [dashboard v2] lint javascript
* [dashboard v2] fix python tests
* [Fix] import/export dash in V2 (#5273)
* [dashboard v2] add markdown tests (#5275)
* [dashboard v2] add Markdown tests
* [dashboard v2][mocks] fix markdown mock
(cherry picked from commit c065319508)
This commit is contained in:
committed by
Maxime Beauchemin
parent
b25c14da09
commit
10357ba58b
@@ -0,0 +1,19 @@
|
||||
import React from 'react';
|
||||
import { mount } from 'enzyme';
|
||||
import { describe, it } from 'mocha';
|
||||
import { expect } from 'chai';
|
||||
|
||||
import CodeModal from '../../../../src/dashboard/components/CodeModal';
|
||||
|
||||
describe('CodeModal', () => {
|
||||
const mockedProps = {
|
||||
triggerNode: <i className="fa fa-edit" />,
|
||||
};
|
||||
it('is valid', () => {
|
||||
expect(React.isValidElement(<CodeModal {...mockedProps} />)).to.equal(true);
|
||||
});
|
||||
it('renders the trigger node', () => {
|
||||
const wrapper = mount(<CodeModal {...mockedProps} />);
|
||||
expect(wrapper.find('.fa-edit')).to.have.length(1);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,19 @@
|
||||
import React from 'react';
|
||||
import { mount } from 'enzyme';
|
||||
import { describe, it } from 'mocha';
|
||||
import { expect } from 'chai';
|
||||
|
||||
import CssEditor from '../../../../src/dashboard/components/CssEditor';
|
||||
|
||||
describe('CssEditor', () => {
|
||||
const mockedProps = {
|
||||
triggerNode: <i className="fa fa-edit" />,
|
||||
};
|
||||
it('is valid', () => {
|
||||
expect(React.isValidElement(<CssEditor {...mockedProps} />)).to.equal(true);
|
||||
});
|
||||
it('renders the trigger node', () => {
|
||||
const wrapper = mount(<CssEditor {...mockedProps} />);
|
||||
expect(wrapper.find('.fa-edit')).to.have.length(1);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,138 @@
|
||||
import { Provider } from 'react-redux';
|
||||
import React from 'react';
|
||||
import { shallow, mount } from 'enzyme';
|
||||
import { describe, it } from 'mocha';
|
||||
import { expect } from 'chai';
|
||||
|
||||
import ParentSize from '@vx/responsive/build/components/ParentSize';
|
||||
import { Sticky, StickyContainer } from 'react-sticky';
|
||||
import { TabContainer, TabContent, TabPane } from 'react-bootstrap';
|
||||
|
||||
import BuilderComponentPane from '../../../../src/dashboard/components/BuilderComponentPane';
|
||||
import DashboardBuilder from '../../../../src/dashboard/components/DashboardBuilder';
|
||||
import DashboardComponent from '../../../../src/dashboard/containers/DashboardComponent';
|
||||
import DashboardHeader from '../../../../src/dashboard/containers/DashboardHeader';
|
||||
import DashboardGrid from '../../../../src/dashboard/containers/DashboardGrid';
|
||||
import WithDragDropContext from '../helpers/WithDragDropContext';
|
||||
import {
|
||||
dashboardLayout as undoableDashboardLayout,
|
||||
dashboardLayoutWithTabs as undoableDashboardLayoutWithTabs,
|
||||
} from '../fixtures/mockDashboardLayout';
|
||||
|
||||
import { mockStore, mockStoreWithTabs } from '../fixtures/mockStore';
|
||||
|
||||
const dashboardLayout = undoableDashboardLayout.present;
|
||||
const layoutWithTabs = undoableDashboardLayoutWithTabs.present;
|
||||
|
||||
describe('DashboardBuilder', () => {
|
||||
const props = {
|
||||
dashboardLayout,
|
||||
deleteTopLevelTabs() {},
|
||||
editMode: false,
|
||||
showBuilderPane: false,
|
||||
handleComponentDrop() {},
|
||||
toggleBuilderPane() {},
|
||||
};
|
||||
|
||||
function setup(overrideProps, useProvider = false, store = mockStore) {
|
||||
const builder = <DashboardBuilder {...props} {...overrideProps} />;
|
||||
return useProvider
|
||||
? mount(
|
||||
<Provider store={store}>
|
||||
<WithDragDropContext>{builder}</WithDragDropContext>
|
||||
</Provider>,
|
||||
)
|
||||
: shallow(builder);
|
||||
}
|
||||
|
||||
it('should render a StickyContainer with class "dashboard"', () => {
|
||||
const wrapper = setup();
|
||||
const stickyContainer = wrapper.find(StickyContainer);
|
||||
expect(stickyContainer).to.have.length(1);
|
||||
expect(stickyContainer.prop('className')).to.equal('dashboard');
|
||||
});
|
||||
|
||||
it('should add the "dashboard--editing" class if editMode=true', () => {
|
||||
const wrapper = setup({ editMode: true });
|
||||
const stickyContainer = wrapper.find(StickyContainer);
|
||||
expect(stickyContainer.prop('className')).to.equal(
|
||||
'dashboard dashboard--editing',
|
||||
);
|
||||
});
|
||||
|
||||
it('should render a DragDroppable DashboardHeader', () => {
|
||||
const wrapper = setup(null, true);
|
||||
expect(wrapper.find(DashboardHeader)).to.have.length(1);
|
||||
});
|
||||
|
||||
it('should render a Sticky top-level Tabs if the dashboard has tabs', () => {
|
||||
const wrapper = setup(
|
||||
{ dashboardLayout: layoutWithTabs },
|
||||
true,
|
||||
mockStoreWithTabs,
|
||||
);
|
||||
const sticky = wrapper.find(Sticky);
|
||||
const dashboardComponent = sticky.find(DashboardComponent);
|
||||
|
||||
const tabChildren = layoutWithTabs.TABS_ID.children;
|
||||
expect(sticky).to.have.length(1);
|
||||
expect(dashboardComponent).to.have.length(1 + tabChildren.length); // tab + tabs
|
||||
expect(dashboardComponent.at(0).prop('id')).to.equal('TABS_ID');
|
||||
tabChildren.forEach((tabId, i) => {
|
||||
expect(dashboardComponent.at(i + 1).prop('id')).to.equal(tabId);
|
||||
});
|
||||
});
|
||||
|
||||
it('should render a TabContainer and TabContent', () => {
|
||||
const wrapper = setup({ dashboardLayout: layoutWithTabs });
|
||||
const parentSize = wrapper.find(ParentSize).dive();
|
||||
expect(parentSize.find(TabContainer)).to.have.length(1);
|
||||
expect(parentSize.find(TabContent)).to.have.length(1);
|
||||
});
|
||||
|
||||
it('should set animation=true, mountOnEnter=true, and unmounOnExit=false on TabContainer for perf', () => {
|
||||
const wrapper = setup({ dashboardLayout: layoutWithTabs });
|
||||
const tabProps = wrapper
|
||||
.find(ParentSize)
|
||||
.dive()
|
||||
.find(TabContainer)
|
||||
.props();
|
||||
expect(tabProps.animation).to.equal(true);
|
||||
expect(tabProps.mountOnEnter).to.equal(true);
|
||||
expect(tabProps.unmountOnExit).to.equal(false);
|
||||
});
|
||||
|
||||
it('should render a TabPane and DashboardGrid for each Tab', () => {
|
||||
const wrapper = setup({ dashboardLayout: layoutWithTabs });
|
||||
const parentSize = wrapper.find(ParentSize).dive();
|
||||
|
||||
const expectedCount = layoutWithTabs.TABS_ID.children.length;
|
||||
expect(parentSize.find(TabPane)).to.have.length(expectedCount);
|
||||
expect(parentSize.find(DashboardGrid)).to.have.length(expectedCount);
|
||||
});
|
||||
|
||||
it('should render a BuilderComponentPane if editMode=showBuilderPane=true', () => {
|
||||
const wrapper = setup();
|
||||
expect(wrapper.find(BuilderComponentPane)).to.have.length(0);
|
||||
|
||||
wrapper.setProps({ ...props, editMode: true, showBuilderPane: true });
|
||||
expect(wrapper.find(BuilderComponentPane)).to.have.length(1);
|
||||
});
|
||||
|
||||
it('should change tabs if a top-level Tab is clicked', () => {
|
||||
const wrapper = setup(
|
||||
{ dashboardLayout: layoutWithTabs },
|
||||
true,
|
||||
mockStoreWithTabs,
|
||||
);
|
||||
|
||||
expect(wrapper.find(TabContainer).prop('activeKey')).to.equal(0);
|
||||
|
||||
wrapper
|
||||
.find('.dashboard-component-tabs .nav-tabs a')
|
||||
.at(1)
|
||||
.simulate('click');
|
||||
|
||||
expect(wrapper.find(TabContainer).prop('activeKey')).to.equal(1);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,84 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import { describe, it } from 'mocha';
|
||||
import { expect } from 'chai';
|
||||
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,
|
||||
};
|
||||
|
||||
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')).to.have.length(1);
|
||||
});
|
||||
|
||||
it('should render one DashboardComponent for each gridComponent child', () => {
|
||||
const wrapper = setup({
|
||||
gridComponent: { ...props.gridComponent, children: ['a', 'b'] },
|
||||
});
|
||||
expect(wrapper.find(DashboardComponent)).to.have.length(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)).to.have.length(0);
|
||||
expect(editMode.find(DragDroppable)).to.have.length(2);
|
||||
});
|
||||
|
||||
it('should render grid column guides when resizing', () => {
|
||||
const wrapper = setup({ editMode: true });
|
||||
expect(wrapper.find('.grid-column-guide')).to.have.length(0);
|
||||
|
||||
wrapper.setState({ isResizing: true });
|
||||
|
||||
expect(wrapper.find('.grid-column-guide')).to.have.length(
|
||||
GRID_COLUMN_COUNT,
|
||||
);
|
||||
});
|
||||
|
||||
it('should render a grid row guide when resizing', () => {
|
||||
const wrapper = setup();
|
||||
expect(wrapper.find('.grid-row-guide')).to.have.length(0);
|
||||
wrapper.setState({ isResizing: true, rowGuideTop: 10 });
|
||||
expect(wrapper.find('.grid-row-guide')).to.have.length(1);
|
||||
});
|
||||
|
||||
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).to.equal(1);
|
||||
expect(resizeComponent.getCall(0).args[0]).to.deep.equal({
|
||||
id: 'id',
|
||||
width: 1,
|
||||
height: 3,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,250 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import { describe, it } from 'mocha';
|
||||
import { expect } from 'chai';
|
||||
import sinon from 'sinon';
|
||||
|
||||
import Dashboard from '../../../../src/dashboard/components/Dashboard';
|
||||
import DashboardBuilder from '../../../../src/dashboard/containers/DashboardBuilder';
|
||||
|
||||
// mock data
|
||||
import chartQueries, { sliceId as chartId } from '../fixtures/mockChartQueries';
|
||||
import datasources from '../fixtures/mockDatasource';
|
||||
import dashboardInfo from '../fixtures/mockDashboardInfo';
|
||||
import { dashboardLayout } from '../fixtures/mockDashboardLayout';
|
||||
import dashboardState from '../fixtures/mockDashboardState';
|
||||
import { sliceEntitiesForChart as sliceEntities } from '../fixtures/mockSliceEntities';
|
||||
|
||||
import { CHART_TYPE } from '../../../../src/dashboard/util/componentTypes';
|
||||
import newComponentFactory from '../../../../src/dashboard/util/newComponentFactory';
|
||||
|
||||
describe('Dashboard', () => {
|
||||
const props = {
|
||||
actions: {
|
||||
addSliceToDashboard() {},
|
||||
removeSliceFromDashboard() {},
|
||||
runQuery() {},
|
||||
},
|
||||
initMessages: [],
|
||||
dashboardState,
|
||||
dashboardInfo,
|
||||
charts: chartQueries,
|
||||
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;
|
||||
}
|
||||
|
||||
it('should render a DashboardBuilder', () => {
|
||||
const wrapper = setup();
|
||||
expect(wrapper.find(DashboardBuilder)).to.have.length(1);
|
||||
});
|
||||
|
||||
describe('refreshExcept', () => {
|
||||
const overrideCharts = {
|
||||
...chartQueries,
|
||||
1001: {
|
||||
...chartQueries[chartId],
|
||||
id: 1001,
|
||||
},
|
||||
};
|
||||
|
||||
const overrideSlices = {
|
||||
...props.slices,
|
||||
1001: {
|
||||
...props.slices[chartId],
|
||||
slice_id: 1001,
|
||||
},
|
||||
};
|
||||
|
||||
it('should call runQuery for all non-exempt slices', () => {
|
||||
const wrapper = setup({ charts: overrideCharts, slices: overrideSlices });
|
||||
const spy = sinon.spy(props.actions, 'runQuery');
|
||||
wrapper.instance().refreshExcept('1001');
|
||||
spy.restore();
|
||||
expect(spy.callCount).to.equal(Object.keys(overrideCharts).length - 1);
|
||||
});
|
||||
|
||||
it('should not call runQuery for filter_immune_slices', () => {
|
||||
const wrapper = setup({
|
||||
charts: overrideCharts,
|
||||
dashboardInfo: {
|
||||
...dashboardInfo,
|
||||
metadata: {
|
||||
...dashboardInfo.metadata,
|
||||
filter_immune_slices: Object.keys(overrideCharts).map(id =>
|
||||
Number(id),
|
||||
),
|
||||
},
|
||||
},
|
||||
});
|
||||
const spy = sinon.spy(props.actions, 'runQuery');
|
||||
wrapper.instance().refreshExcept();
|
||||
spy.restore();
|
||||
expect(spy.callCount).to.equal(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('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().componentWillReceiveProps({
|
||||
...props,
|
||||
layout: layoutWithExtraChart,
|
||||
});
|
||||
spy.restore();
|
||||
expect(spy.callCount).to.equal(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().componentWillReceiveProps({
|
||||
...props,
|
||||
layout: nextLayout,
|
||||
});
|
||||
spy.restore();
|
||||
expect(spy.callCount).to.equal(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('componentDidUpdate', () => {
|
||||
const overrideDashboardState = {
|
||||
...dashboardState,
|
||||
filters: {
|
||||
1: { region: [] },
|
||||
2: { country_name: ['USA'] },
|
||||
},
|
||||
refresh: true,
|
||||
};
|
||||
|
||||
it('should not call refresh when there is no change', () => {
|
||||
const wrapper = setup({ dashboardState: overrideDashboardState });
|
||||
const refreshExceptSpy = sinon.spy(wrapper.instance(), 'refreshExcept');
|
||||
const prevProps = wrapper.instance().props;
|
||||
wrapper.setProps({
|
||||
dashboardState: {
|
||||
...overrideDashboardState,
|
||||
},
|
||||
});
|
||||
wrapper.instance().componentDidUpdate(prevProps);
|
||||
refreshExceptSpy.restore();
|
||||
expect(refreshExceptSpy.callCount).to.equal(0);
|
||||
});
|
||||
|
||||
it('should call refresh if a filter is added', () => {
|
||||
const wrapper = setup({ dashboardState: overrideDashboardState });
|
||||
const refreshExceptSpy = sinon.spy(wrapper.instance(), 'refreshExcept');
|
||||
const prevProps = wrapper.instance().props;
|
||||
wrapper.setProps({
|
||||
dashboardState: {
|
||||
...overrideDashboardState,
|
||||
filters: {
|
||||
...overrideDashboardState.filters,
|
||||
3: { another_filter: ['please'] },
|
||||
},
|
||||
},
|
||||
});
|
||||
wrapper.instance().componentDidUpdate(prevProps);
|
||||
refreshExceptSpy.restore();
|
||||
expect(refreshExceptSpy.callCount).to.equal(1);
|
||||
});
|
||||
|
||||
it('should call refresh if a filter is removed', () => {
|
||||
const wrapper = setup({ dashboardState: overrideDashboardState });
|
||||
const refreshExceptSpy = sinon.spy(wrapper.instance(), 'refreshExcept');
|
||||
const prevProps = wrapper.instance().props;
|
||||
wrapper.setProps({
|
||||
dashboardState: {
|
||||
...overrideDashboardState,
|
||||
filters: {},
|
||||
},
|
||||
});
|
||||
wrapper.instance().componentDidUpdate(prevProps);
|
||||
refreshExceptSpy.restore();
|
||||
expect(refreshExceptSpy.callCount).to.equal(1);
|
||||
});
|
||||
|
||||
it('should call refresh if a filter is changed', () => {
|
||||
const wrapper = setup({ dashboardState: overrideDashboardState });
|
||||
const refreshExceptSpy = sinon.spy(wrapper.instance(), 'refreshExcept');
|
||||
const prevProps = wrapper.instance().props;
|
||||
wrapper.setProps({
|
||||
dashboardState: {
|
||||
...overrideDashboardState,
|
||||
filters: {
|
||||
...overrideDashboardState.filters,
|
||||
2: { country_name: ['Canada'] },
|
||||
},
|
||||
},
|
||||
});
|
||||
wrapper.instance().componentDidUpdate(prevProps);
|
||||
refreshExceptSpy.restore();
|
||||
expect(refreshExceptSpy.callCount).to.equal(1);
|
||||
});
|
||||
|
||||
it('should not call refresh if filters change and refresh is false', () => {
|
||||
const wrapper = setup({ dashboardState: overrideDashboardState });
|
||||
const refreshExceptSpy = sinon.spy(wrapper.instance(), 'refreshExcept');
|
||||
const prevProps = wrapper.instance().props;
|
||||
wrapper.setProps({
|
||||
dashboardState: {
|
||||
...overrideDashboardState,
|
||||
filters: {
|
||||
...overrideDashboardState.filters,
|
||||
2: { country_name: ['Canada'] },
|
||||
},
|
||||
refresh: false,
|
||||
},
|
||||
});
|
||||
wrapper.instance().componentDidUpdate(prevProps);
|
||||
refreshExceptSpy.restore();
|
||||
expect(refreshExceptSpy.callCount).to.equal(0);
|
||||
});
|
||||
|
||||
it('should not refresh filter_immune_slices', () => {
|
||||
const wrapper = setup({
|
||||
dashboardState: overrideDashboardState,
|
||||
dashboardInfo: {
|
||||
...dashboardInfo,
|
||||
metadata: {
|
||||
...dashboardInfo.metadata,
|
||||
filter_immune_slices: [chartId],
|
||||
},
|
||||
},
|
||||
});
|
||||
const refreshExceptSpy = sinon.spy(wrapper.instance(), 'refreshExcept');
|
||||
const prevProps = wrapper.instance().props;
|
||||
wrapper.setProps({
|
||||
dashboardState: {
|
||||
...overrideDashboardState,
|
||||
filters: {
|
||||
...overrideDashboardState.filters,
|
||||
2: { country_name: ['Canada'] },
|
||||
},
|
||||
refresh: false,
|
||||
},
|
||||
});
|
||||
wrapper.instance().componentDidUpdate(prevProps);
|
||||
refreshExceptSpy.restore();
|
||||
expect(refreshExceptSpy.callCount).to.equal(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,21 @@
|
||||
import React from 'react';
|
||||
import { mount } from 'enzyme';
|
||||
import { describe, it } from 'mocha';
|
||||
import { expect } from 'chai';
|
||||
|
||||
import RefreshIntervalModal from '../../../../src/dashboard/components/RefreshIntervalModal';
|
||||
|
||||
describe('RefreshIntervalModal', () => {
|
||||
const mockedProps = {
|
||||
triggerNode: <i className="fa fa-edit" />,
|
||||
};
|
||||
it('is valid', () => {
|
||||
expect(
|
||||
React.isValidElement(<RefreshIntervalModal {...mockedProps} />),
|
||||
).to.equal(true);
|
||||
});
|
||||
it('renders the trigger node', () => {
|
||||
const wrapper = mount(<RefreshIntervalModal {...mockedProps} />);
|
||||
expect(wrapper.find('.fa-edit')).to.have.length(1);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,154 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import { describe, it, beforeEach, afterEach } from 'mocha';
|
||||
import sinon from 'sinon';
|
||||
import { expect } from 'chai';
|
||||
|
||||
import { List } from 'react-virtualized';
|
||||
|
||||
import SliceAdder from '../../../../src/dashboard/components/SliceAdder';
|
||||
import { sliceEntitiesForDashboard as mockSliceEntities } from '../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];
|
||||
}),
|
||||
).to.equal(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).to.deep.equal(expectedNames);
|
||||
});
|
||||
});
|
||||
|
||||
it('render List', () => {
|
||||
const wrapper = shallow(<SliceAdder {...props} />);
|
||||
wrapper.setState({ filteredSlices: Object.values(props.slices) });
|
||||
expect(wrapper.find(List)).to.have.length(1);
|
||||
});
|
||||
|
||||
it('render error', () => {
|
||||
const wrapper = shallow(<SliceAdder {...errorProps} />);
|
||||
wrapper.setState({ filteredSlices: Object.values(props.slices) });
|
||||
expect(wrapper.text()).to.have.string(errorProps.errorMessage);
|
||||
});
|
||||
|
||||
it('componentDidMount', () => {
|
||||
sinon.spy(SliceAdder.prototype, 'componentDidMount');
|
||||
sinon.spy(props, 'fetchAllSlices');
|
||||
|
||||
shallow(<SliceAdder {...props} />, {
|
||||
lifecycleExperimental: true,
|
||||
});
|
||||
expect(SliceAdder.prototype.componentDidMount.calledOnce).to.equal(true);
|
||||
expect(props.fetchAllSlices.calledOnce).to.equal(true);
|
||||
|
||||
SliceAdder.prototype.componentDidMount.restore();
|
||||
props.fetchAllSlices.restore();
|
||||
});
|
||||
|
||||
describe('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().componentWillReceiveProps({
|
||||
...props,
|
||||
lastUpdated: new Date().getTime(),
|
||||
});
|
||||
expect(wrapper.instance().setState.calledOnce).to.equal(true);
|
||||
|
||||
const stateKeys = Object.keys(
|
||||
wrapper.instance().setState.lastCall.args[0],
|
||||
);
|
||||
expect(stateKeys).to.include('filteredSlices');
|
||||
});
|
||||
|
||||
it('select slices should update state', () => {
|
||||
wrapper.instance().componentWillReceiveProps({
|
||||
...props,
|
||||
selectedSliceIds: [127],
|
||||
});
|
||||
expect(wrapper.instance().setState.calledOnce).to.equal(true);
|
||||
|
||||
const stateKeys = Object.keys(
|
||||
wrapper.instance().setState.lastCall.args[0],
|
||||
);
|
||||
expect(stateKeys).to.include('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).to.equal(true);
|
||||
expect(spy.lastCall.args[0]).to.equal(newSearchTerm);
|
||||
});
|
||||
|
||||
it('handleSelect', () => {
|
||||
const newSortBy = 1;
|
||||
wrapper.instance().handleSelect(newSortBy);
|
||||
expect(spy.calledOnce).to.equal(true);
|
||||
expect(spy.lastCall.args[1]).to.equal(newSortBy);
|
||||
});
|
||||
|
||||
it('handleKeyPress', () => {
|
||||
wrapper.instance().handleKeyPress(mockEvent);
|
||||
expect(spy.calledOnce).to.equal(true);
|
||||
expect(spy.lastCall.args[0]).to.equal(mockEvent.target.value);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,41 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import { describe, it } from 'mocha';
|
||||
import { expect } from 'chai';
|
||||
|
||||
import mockMessageToasts from '../fixtures/mockMessageToasts';
|
||||
import Toast from '../../../../src/dashboard/components/Toast';
|
||||
import ToastPresenter from '../../../../src/dashboard/components/ToastPresenter';
|
||||
|
||||
describe('ToastPresenter', () => {
|
||||
const props = {
|
||||
toasts: mockMessageToasts,
|
||||
removeToast() {},
|
||||
};
|
||||
|
||||
function setup(overrideProps) {
|
||||
const wrapper = shallow(<ToastPresenter {...props} {...overrideProps} />);
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
it('should render a div with class toast-presenter', () => {
|
||||
const wrapper = setup();
|
||||
expect(wrapper.find('.toast-presenter')).to.have.length(1);
|
||||
});
|
||||
|
||||
it('should render a Toast for each toast object', () => {
|
||||
const wrapper = setup();
|
||||
expect(wrapper.find(Toast)).to.have.length(props.toasts.length);
|
||||
});
|
||||
|
||||
it('should pass removeToast to the Toast component', () => {
|
||||
const removeToast = () => {};
|
||||
const wrapper = setup({ removeToast });
|
||||
expect(
|
||||
wrapper
|
||||
.find(Toast)
|
||||
.first()
|
||||
.prop('onCloseToast'),
|
||||
).to.equal(removeToast);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,43 @@
|
||||
import { Alert } from 'react-bootstrap';
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import { describe, it } from 'mocha';
|
||||
import { expect } from 'chai';
|
||||
|
||||
import mockMessageToasts from '../fixtures/mockMessageToasts';
|
||||
import Toast from '../../../../src/dashboard/components/Toast';
|
||||
|
||||
describe('Toast', () => {
|
||||
const props = {
|
||||
toast: mockMessageToasts[0],
|
||||
onCloseToast() {},
|
||||
};
|
||||
|
||||
function setup(overrideProps) {
|
||||
const wrapper = shallow(<Toast {...props} {...overrideProps} />);
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
it('should render an Alert', () => {
|
||||
const wrapper = setup();
|
||||
expect(wrapper.find(Alert)).to.have.length(1);
|
||||
});
|
||||
|
||||
it('should render toastText within the alert', () => {
|
||||
const wrapper = setup();
|
||||
const alert = wrapper.find(Alert).dive();
|
||||
|
||||
expect(alert.childAt(1).text()).to.equal(props.toast.text);
|
||||
});
|
||||
|
||||
it('should call onCloseToast upon alert dismissal', done => {
|
||||
const onCloseToast = id => {
|
||||
expect(id).to.equal(props.toast.id);
|
||||
done();
|
||||
};
|
||||
const wrapper = setup({ onCloseToast });
|
||||
const handleClosePress = wrapper.instance().handleClosePress;
|
||||
expect(wrapper.find(Alert).prop('onDismiss')).to.equal(handleClosePress);
|
||||
handleClosePress(); // there is a timeout for onCloseToast to be called
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,90 @@
|
||||
import React from 'react';
|
||||
import { shallow, mount } from 'enzyme';
|
||||
import { describe, it } from 'mocha';
|
||||
import { expect } from 'chai';
|
||||
import sinon from 'sinon';
|
||||
|
||||
import newComponentFactory from '../../../../../src/dashboard/util/newComponentFactory';
|
||||
import {
|
||||
CHART_TYPE,
|
||||
ROW_TYPE,
|
||||
} from '../../../../../src/dashboard/util/componentTypes';
|
||||
import { UnwrappedDragDroppable as DragDroppable } from '../../../../../src/dashboard/components/dnd/DragDroppable';
|
||||
|
||||
describe('DragDroppable', () => {
|
||||
const props = {
|
||||
component: newComponentFactory(CHART_TYPE),
|
||||
parentComponent: newComponentFactory(ROW_TYPE),
|
||||
editMode: false,
|
||||
depth: 1,
|
||||
index: 0,
|
||||
isDragging: false,
|
||||
isDraggingOver: false,
|
||||
isDraggingOverShallow: false,
|
||||
droppableRef() {},
|
||||
dragSourceRef() {},
|
||||
dragPreviewRef() {},
|
||||
};
|
||||
|
||||
function setup(overrideProps, shouldMount = false) {
|
||||
const method = shouldMount ? mount : shallow;
|
||||
const wrapper = method(<DragDroppable {...props} {...overrideProps} />);
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
it('should render a div with class dragdroppable', () => {
|
||||
const wrapper = setup();
|
||||
expect(wrapper.find('.dragdroppable')).to.have.length(1);
|
||||
});
|
||||
|
||||
it('should add class dragdroppable--dragging when dragging', () => {
|
||||
const wrapper = setup({ isDragging: true });
|
||||
expect(wrapper.find('.dragdroppable')).to.have.length(1);
|
||||
});
|
||||
|
||||
it('should call its child function', () => {
|
||||
const childrenSpy = sinon.spy();
|
||||
setup({ children: childrenSpy });
|
||||
expect(childrenSpy.callCount).to.equal(1);
|
||||
});
|
||||
|
||||
it('should call its child function with "dragSourceRef" if editMode=true', () => {
|
||||
const children = sinon.spy();
|
||||
const dragSourceRef = () => {};
|
||||
setup({ children, editMode: false, dragSourceRef });
|
||||
setup({ children, editMode: true, dragSourceRef });
|
||||
|
||||
expect(children.getCall(0).args[0].dragSourceRef).to.equal(undefined);
|
||||
expect(children.getCall(1).args[0].dragSourceRef).to.equal(dragSourceRef);
|
||||
});
|
||||
|
||||
it('should call its child function with "dropIndicatorProps" dependent on editMode, isDraggingOver, state.dropIndicator is set', () => {
|
||||
const children = sinon.spy();
|
||||
const wrapper = setup({ children, editMode: false, isDraggingOver: false });
|
||||
wrapper.setState({ dropIndicator: 'nonsense' });
|
||||
wrapper.setProps({ ...props, editMode: true, isDraggingOver: true });
|
||||
|
||||
expect(children.callCount).to.equal(3); // initial + setState + setProps
|
||||
expect(children.getCall(0).args[0].dropIndicatorProps).to.equal(undefined);
|
||||
expect(children.getCall(2).args[0].dropIndicatorProps).to.deep.equal({
|
||||
className: 'drop-indicator',
|
||||
});
|
||||
});
|
||||
|
||||
it('should call props.dragPreviewRef and props.droppableRef on mount', () => {
|
||||
const dragPreviewRef = sinon.spy();
|
||||
const droppableRef = sinon.spy();
|
||||
|
||||
setup({ dragPreviewRef, droppableRef }, true);
|
||||
expect(dragPreviewRef.callCount).to.equal(1);
|
||||
expect(droppableRef.callCount).to.equal(1);
|
||||
});
|
||||
|
||||
it('should set this.mounted dependent on life cycle', () => {
|
||||
const wrapper = setup({}, true);
|
||||
const instance = wrapper.instance();
|
||||
expect(instance.mounted).to.equal(true);
|
||||
wrapper.unmount();
|
||||
expect(instance.mounted).to.equal(false);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,112 @@
|
||||
import { Provider } from 'react-redux';
|
||||
import React from 'react';
|
||||
import { mount } from 'enzyme';
|
||||
import { describe, it } from 'mocha';
|
||||
import { expect } from 'chai';
|
||||
import sinon from 'sinon';
|
||||
|
||||
import Chart from '../../../../../src/dashboard/containers/Chart';
|
||||
import ChartHolder from '../../../../../src/dashboard/components/gridComponents/ChartHolder';
|
||||
import DeleteComponentButton from '../../../../../src/dashboard/components/DeleteComponentButton';
|
||||
import DragDroppable from '../../../../../src/dashboard/components/dnd/DragDroppable';
|
||||
import HoverMenu from '../../../../../src/dashboard/components/menu/HoverMenu';
|
||||
import ResizableContainer from '../../../../../src/dashboard/components/resizable/ResizableContainer';
|
||||
|
||||
import { mockStore } from '../../fixtures/mockStore';
|
||||
import { sliceId } from '../../fixtures/mockSliceEntities';
|
||||
import { dashboardLayout as mockLayout } from '../../fixtures/mockDashboardLayout';
|
||||
import WithDragDropContext from '../../helpers/WithDragDropContext';
|
||||
|
||||
describe('ChartHolder', () => {
|
||||
const props = {
|
||||
id: String(sliceId),
|
||||
parentId: 'ROW_ID',
|
||||
component: mockLayout.present.CHART_ID,
|
||||
depth: 2,
|
||||
parentComponent: mockLayout.present.ROW_ID,
|
||||
index: 0,
|
||||
editMode: false,
|
||||
availableColumnCount: 12,
|
||||
columnWidth: 50,
|
||||
onResizeStart() {},
|
||||
onResize() {},
|
||||
onResizeStop() {},
|
||||
handleComponentDrop() {},
|
||||
updateComponents() {},
|
||||
deleteComponent() {},
|
||||
};
|
||||
|
||||
function setup(overrideProps) {
|
||||
// We have to wrap provide DragDropContext for the underlying DragDroppable
|
||||
// otherwise we cannot assert on DragDroppable children
|
||||
const wrapper = mount(
|
||||
<Provider store={mockStore}>
|
||||
<WithDragDropContext>
|
||||
<ChartHolder {...props} {...overrideProps} />
|
||||
</WithDragDropContext>
|
||||
</Provider>,
|
||||
);
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
it('should render a DragDroppable', () => {
|
||||
const wrapper = setup();
|
||||
expect(wrapper.find(DragDroppable)).to.have.length(1);
|
||||
});
|
||||
|
||||
it('should render a ResizableContainer', () => {
|
||||
const wrapper = setup();
|
||||
expect(wrapper.find(ResizableContainer)).to.have.length(1);
|
||||
});
|
||||
|
||||
it('should only have an adjustableWidth if its parent is a Row', () => {
|
||||
let wrapper = setup();
|
||||
expect(wrapper.find(ResizableContainer).prop('adjustableWidth')).to.equal(
|
||||
true,
|
||||
);
|
||||
|
||||
wrapper = setup({ ...props, parentComponent: mockLayout.present.CHART_ID });
|
||||
expect(wrapper.find(ResizableContainer).prop('adjustableWidth')).to.equal(
|
||||
false,
|
||||
);
|
||||
});
|
||||
|
||||
it('should pass correct props to ResizableContainer', () => {
|
||||
const wrapper = setup();
|
||||
const resizableProps = wrapper.find(ResizableContainer).props();
|
||||
expect(resizableProps.widthStep).to.equal(props.columnWidth);
|
||||
expect(resizableProps.widthMultiple).to.equal(props.component.meta.width);
|
||||
expect(resizableProps.heightMultiple).to.equal(props.component.meta.height);
|
||||
expect(resizableProps.maxWidthMultiple).to.equal(
|
||||
props.component.meta.width + props.availableColumnCount,
|
||||
);
|
||||
});
|
||||
|
||||
it('should render a div with class "dashboard-component-chart-holder"', () => {
|
||||
const wrapper = setup();
|
||||
expect(wrapper.find('.dashboard-component-chart-holder')).to.have.length(1);
|
||||
});
|
||||
|
||||
it('should render a Chart', () => {
|
||||
const wrapper = setup();
|
||||
expect(wrapper.find(Chart)).to.have.length(1);
|
||||
});
|
||||
|
||||
it('should render a HoverMenu with DeleteComponentButton in editMode', () => {
|
||||
let wrapper = setup();
|
||||
expect(wrapper.find(HoverMenu)).to.have.length(0);
|
||||
expect(wrapper.find(DeleteComponentButton)).to.have.length(0);
|
||||
|
||||
// we cannot set props on the Divider because of the WithDragDropContext wrapper
|
||||
wrapper = setup({ editMode: true });
|
||||
expect(wrapper.find(HoverMenu)).to.have.length(1);
|
||||
expect(wrapper.find(DeleteComponentButton)).to.have.length(1);
|
||||
});
|
||||
|
||||
it('should call deleteComponent when deleted', () => {
|
||||
const deleteComponent = sinon.spy();
|
||||
const wrapper = setup({ editMode: true, deleteComponent });
|
||||
wrapper.find(DeleteComponentButton).simulate('click');
|
||||
expect(deleteComponent.callCount).to.equal(1);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,89 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import { describe, it } from 'mocha';
|
||||
import { expect } from 'chai';
|
||||
import sinon from 'sinon';
|
||||
|
||||
import Chart from '../../../../../src/dashboard/components/gridComponents/Chart';
|
||||
import SliceHeader from '../../../../../src/dashboard/components/SliceHeader';
|
||||
import ChartContainer from '../../../../../src/chart/ChartContainer';
|
||||
|
||||
import mockDatasource from '../../fixtures/mockDatasource';
|
||||
import {
|
||||
sliceEntitiesForChart as sliceEntities,
|
||||
sliceId,
|
||||
} from '../../fixtures/mockSliceEntities';
|
||||
import chartQueries, {
|
||||
sliceId as queryId,
|
||||
} from '../../fixtures/mockChartQueries';
|
||||
|
||||
describe('Chart', () => {
|
||||
const props = {
|
||||
id: sliceId,
|
||||
width: 100,
|
||||
height: 100,
|
||||
updateSliceName() {},
|
||||
|
||||
// from redux
|
||||
chart: chartQueries[queryId],
|
||||
formData: chartQueries[queryId].formData,
|
||||
datasource: mockDatasource[sliceEntities.slices[sliceId].datasource],
|
||||
slice: {
|
||||
...sliceEntities.slices[sliceId],
|
||||
description_markeddown: 'markdown',
|
||||
},
|
||||
sliceName: sliceEntities.slices[sliceId].slice_name,
|
||||
timeout: 60,
|
||||
filters: {},
|
||||
refreshChart() {},
|
||||
toggleExpandSlice() {},
|
||||
addFilter() {},
|
||||
editMode: false,
|
||||
isExpanded: false,
|
||||
supersetCanExplore: false,
|
||||
sliceCanEdit: false,
|
||||
};
|
||||
|
||||
function setup(overrideProps) {
|
||||
const wrapper = shallow(<Chart {...props} {...overrideProps} />);
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
it('should render a SliceHeader', () => {
|
||||
const wrapper = setup();
|
||||
expect(wrapper.find(SliceHeader)).to.have.length(1);
|
||||
});
|
||||
|
||||
it('should render a ChartContainer', () => {
|
||||
const wrapper = setup();
|
||||
expect(wrapper.find(ChartContainer)).to.have.length(1);
|
||||
});
|
||||
|
||||
it('should render a description if it has one and isExpanded=true', () => {
|
||||
const wrapper = setup();
|
||||
expect(wrapper.find('.slice_description')).to.have.length(0);
|
||||
|
||||
wrapper.setProps({ ...props, isExpanded: true });
|
||||
expect(wrapper.find('.slice_description')).to.have.length(1);
|
||||
});
|
||||
|
||||
it('should call refreshChart when SliceHeader calls forceRefresh', () => {
|
||||
const refreshChart = sinon.spy();
|
||||
const wrapper = setup({ refreshChart });
|
||||
wrapper.instance().forceRefresh();
|
||||
expect(refreshChart.callCount).to.equal(1);
|
||||
});
|
||||
|
||||
it('should call addFilter when ChartContainer calls addFilter', () => {
|
||||
const addFilter = sinon.spy();
|
||||
const wrapper = setup({ addFilter });
|
||||
wrapper.instance().addFilter();
|
||||
expect(addFilter.callCount).to.equal(1);
|
||||
});
|
||||
|
||||
it('should return props.filters when its getFilters method is called', () => {
|
||||
const filters = { column: ['value'] };
|
||||
const wrapper = setup({ filters });
|
||||
expect(wrapper.instance().getFilters()).to.equal(filters);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,144 @@
|
||||
import { Provider } from 'react-redux';
|
||||
import React from 'react';
|
||||
import { mount } from 'enzyme';
|
||||
import { describe, it } from 'mocha';
|
||||
import { expect } from 'chai';
|
||||
import sinon from 'sinon';
|
||||
|
||||
import BackgroundStyleDropdown from '../../../../../src/dashboard/components/menu/BackgroundStyleDropdown';
|
||||
import Column from '../../../../../src/dashboard/components/gridComponents/Column';
|
||||
import DashboardComponent from '../../../../../src/dashboard/containers/DashboardComponent';
|
||||
import DeleteComponentButton from '../../../../../src/dashboard/components/DeleteComponentButton';
|
||||
import DragDroppable from '../../../../../src/dashboard/components/dnd/DragDroppable';
|
||||
import HoverMenu from '../../../../../src/dashboard/components/menu/HoverMenu';
|
||||
import IconButton from '../../../../../src/dashboard/components/IconButton';
|
||||
import ResizableContainer from '../../../../../src/dashboard/components/resizable/ResizableContainer';
|
||||
import WithPopoverMenu from '../../../../../src/dashboard/components/menu/WithPopoverMenu';
|
||||
|
||||
import { mockStore } from '../../fixtures/mockStore';
|
||||
import { dashboardLayout as mockLayout } from '../../fixtures/mockDashboardLayout';
|
||||
import WithDragDropContext from '../../helpers/WithDragDropContext';
|
||||
|
||||
describe('Column', () => {
|
||||
const columnWithoutChildren = {
|
||||
...mockLayout.present.COLUMN_ID,
|
||||
children: [],
|
||||
};
|
||||
const props = {
|
||||
id: 'COLUMN_ID',
|
||||
parentId: 'ROW_ID',
|
||||
component: mockLayout.present.COLUMN_ID,
|
||||
parentComponent: mockLayout.present.ROW_ID,
|
||||
index: 0,
|
||||
depth: 2,
|
||||
editMode: false,
|
||||
availableColumnCount: 12,
|
||||
minColumnWidth: 2,
|
||||
columnWidth: 50,
|
||||
occupiedColumnCount: 6,
|
||||
onResizeStart() {},
|
||||
onResize() {},
|
||||
onResizeStop() {},
|
||||
handleComponentDrop() {},
|
||||
deleteComponent() {},
|
||||
updateComponents() {},
|
||||
};
|
||||
|
||||
function setup(overrideProps) {
|
||||
// We have to wrap provide DragDropContext for the underlying DragDroppable
|
||||
// otherwise we cannot assert on DragDroppable children
|
||||
const wrapper = mount(
|
||||
<Provider store={mockStore}>
|
||||
<WithDragDropContext>
|
||||
<Column {...props} {...overrideProps} />
|
||||
</WithDragDropContext>
|
||||
</Provider>,
|
||||
);
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
it('should render a DragDroppable', () => {
|
||||
// don't count child DragDroppables
|
||||
const wrapper = setup({ component: columnWithoutChildren });
|
||||
expect(wrapper.find(DragDroppable)).to.have.length(1);
|
||||
});
|
||||
|
||||
it('should render a WithPopoverMenu', () => {
|
||||
// don't count child DragDroppables
|
||||
const wrapper = setup({ component: columnWithoutChildren });
|
||||
expect(wrapper.find(WithPopoverMenu)).to.have.length(1);
|
||||
});
|
||||
|
||||
it('should render a ResizableContainer', () => {
|
||||
// don't count child DragDroppables
|
||||
const wrapper = setup({ component: columnWithoutChildren });
|
||||
expect(wrapper.find(ResizableContainer)).to.have.length(1);
|
||||
});
|
||||
|
||||
it('should render a HoverMenu in editMode', () => {
|
||||
let wrapper = setup({ component: columnWithoutChildren });
|
||||
expect(wrapper.find(HoverMenu)).to.have.length(0);
|
||||
|
||||
// we cannot set props on the Row because of the WithDragDropContext wrapper
|
||||
wrapper = setup({ component: columnWithoutChildren, editMode: true });
|
||||
expect(wrapper.find(HoverMenu)).to.have.length(1);
|
||||
});
|
||||
|
||||
it('should render a DeleteComponentButton in editMode', () => {
|
||||
let wrapper = setup({ component: columnWithoutChildren });
|
||||
expect(wrapper.find(DeleteComponentButton)).to.have.length(0);
|
||||
|
||||
// we cannot set props on the Row because of the WithDragDropContext wrapper
|
||||
wrapper = setup({ component: columnWithoutChildren, editMode: true });
|
||||
expect(wrapper.find(DeleteComponentButton)).to.have.length(1);
|
||||
});
|
||||
|
||||
it('should render a BackgroundStyleDropdown when focused', () => {
|
||||
let wrapper = setup({ component: columnWithoutChildren });
|
||||
expect(wrapper.find(BackgroundStyleDropdown)).to.have.length(0);
|
||||
|
||||
// we cannot set props on the Row because of the WithDragDropContext wrapper
|
||||
wrapper = setup({ component: columnWithoutChildren, editMode: true });
|
||||
wrapper
|
||||
.find(IconButton)
|
||||
.at(1) // first one is delete button
|
||||
.simulate('click');
|
||||
|
||||
expect(wrapper.find(BackgroundStyleDropdown)).to.have.length(1);
|
||||
});
|
||||
|
||||
it('should call deleteComponent when deleted', () => {
|
||||
const deleteComponent = sinon.spy();
|
||||
const wrapper = setup({ editMode: true, deleteComponent });
|
||||
wrapper.find(DeleteComponentButton).simulate('click');
|
||||
expect(deleteComponent.callCount).to.equal(1);
|
||||
});
|
||||
|
||||
it('should pass its own width as availableColumnCount to children', () => {
|
||||
const wrapper = setup();
|
||||
const dashboardComponent = wrapper.find(DashboardComponent).first();
|
||||
expect(dashboardComponent.props().availableColumnCount).to.equal(
|
||||
props.component.meta.width,
|
||||
);
|
||||
});
|
||||
|
||||
it('should pass appropriate dimensions to ResizableContainer', () => {
|
||||
const wrapper = setup({ component: columnWithoutChildren });
|
||||
const columnWidth = columnWithoutChildren.meta.width;
|
||||
const resizableProps = wrapper.find(ResizableContainer).props();
|
||||
expect(resizableProps.adjustableWidth).to.equal(true);
|
||||
expect(resizableProps.adjustableHeight).to.equal(false);
|
||||
expect(resizableProps.widthStep).to.equal(props.columnWidth);
|
||||
expect(resizableProps.widthMultiple).to.equal(columnWidth);
|
||||
expect(resizableProps.minWidthMultiple).to.equal(props.minColumnWidth);
|
||||
expect(resizableProps.maxWidthMultiple).to.equal(
|
||||
props.availableColumnCount + columnWidth,
|
||||
);
|
||||
});
|
||||
|
||||
it('should increment the depth of its children', () => {
|
||||
const wrapper = setup();
|
||||
const dashboardComponent = wrapper.find(DashboardComponent);
|
||||
expect(dashboardComponent.props().depth).to.equal(props.depth + 1);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,70 @@
|
||||
import React from 'react';
|
||||
import { mount } from 'enzyme';
|
||||
import { describe, it } from 'mocha';
|
||||
import { expect } from 'chai';
|
||||
import sinon from 'sinon';
|
||||
|
||||
import DeleteComponentButton from '../../../../../src/dashboard/components/DeleteComponentButton';
|
||||
import HoverMenu from '../../../../../src/dashboard/components/menu/HoverMenu';
|
||||
import DragDroppable from '../../../../../src/dashboard/components/dnd/DragDroppable';
|
||||
import Divider from '../../../../../src/dashboard/components/gridComponents/Divider';
|
||||
import newComponentFactory from '../../../../../src/dashboard/util/newComponentFactory';
|
||||
import {
|
||||
DIVIDER_TYPE,
|
||||
DASHBOARD_GRID_TYPE,
|
||||
} from '../../../../../src/dashboard/util/componentTypes';
|
||||
|
||||
import WithDragDropContext from '../../helpers/WithDragDropContext';
|
||||
|
||||
describe('Divider', () => {
|
||||
const props = {
|
||||
id: 'id',
|
||||
parentId: 'parentId',
|
||||
component: newComponentFactory(DIVIDER_TYPE),
|
||||
depth: 1,
|
||||
parentComponent: newComponentFactory(DASHBOARD_GRID_TYPE),
|
||||
index: 0,
|
||||
editMode: false,
|
||||
handleComponentDrop() {},
|
||||
deleteComponent() {},
|
||||
};
|
||||
|
||||
function setup(overrideProps) {
|
||||
// We have to wrap provide DragDropContext for the underlying DragDroppable
|
||||
// otherwise we cannot assert on DragDroppable children
|
||||
const wrapper = mount(
|
||||
<WithDragDropContext>
|
||||
<Divider {...props} {...overrideProps} />
|
||||
</WithDragDropContext>,
|
||||
);
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
it('should render a DragDroppable', () => {
|
||||
const wrapper = setup();
|
||||
expect(wrapper.find(DragDroppable)).to.have.length(1);
|
||||
});
|
||||
|
||||
it('should render a div with class "dashboard-component-divider"', () => {
|
||||
const wrapper = setup();
|
||||
expect(wrapper.find('.dashboard-component-divider')).to.have.length(1);
|
||||
});
|
||||
|
||||
it('should render a HoverMenu with DeleteComponentButton in editMode', () => {
|
||||
let wrapper = setup();
|
||||
expect(wrapper.find(HoverMenu)).to.have.length(0);
|
||||
expect(wrapper.find(DeleteComponentButton)).to.have.length(0);
|
||||
|
||||
// we cannot set props on the Divider because of the WithDragDropContext wrapper
|
||||
wrapper = setup({ editMode: true });
|
||||
expect(wrapper.find(HoverMenu)).to.have.length(1);
|
||||
expect(wrapper.find(DeleteComponentButton)).to.have.length(1);
|
||||
});
|
||||
|
||||
it('should call deleteComponent when deleted', () => {
|
||||
const deleteComponent = sinon.spy();
|
||||
const wrapper = setup({ editMode: true, deleteComponent });
|
||||
wrapper.find(DeleteComponentButton).simulate('click');
|
||||
expect(deleteComponent.callCount).to.equal(1);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,100 @@
|
||||
import React from 'react';
|
||||
import { mount } from 'enzyme';
|
||||
import { describe, it } from 'mocha';
|
||||
import { expect } from 'chai';
|
||||
import sinon from 'sinon';
|
||||
|
||||
import DeleteComponentButton from '../../../../../src/dashboard/components/DeleteComponentButton';
|
||||
import EditableTitle from '../../../../../src/components/EditableTitle';
|
||||
import HoverMenu from '../../../../../src/dashboard/components/menu/HoverMenu';
|
||||
import WithPopoverMenu from '../../../../../src/dashboard/components/menu/WithPopoverMenu';
|
||||
import DragDroppable from '../../../../../src/dashboard/components/dnd/DragDroppable';
|
||||
import Header from '../../../../../src/dashboard/components/gridComponents/Header';
|
||||
import newComponentFactory from '../../../../../src/dashboard/util/newComponentFactory';
|
||||
import {
|
||||
HEADER_TYPE,
|
||||
DASHBOARD_GRID_TYPE,
|
||||
} from '../../../../../src/dashboard/util/componentTypes';
|
||||
|
||||
import WithDragDropContext from '../../helpers/WithDragDropContext';
|
||||
|
||||
describe('Header', () => {
|
||||
const props = {
|
||||
id: 'id',
|
||||
parentId: 'parentId',
|
||||
component: newComponentFactory(HEADER_TYPE),
|
||||
depth: 1,
|
||||
parentComponent: newComponentFactory(DASHBOARD_GRID_TYPE),
|
||||
index: 0,
|
||||
editMode: false,
|
||||
handleComponentDrop() {},
|
||||
deleteComponent() {},
|
||||
updateComponents() {},
|
||||
};
|
||||
|
||||
function setup(overrideProps) {
|
||||
// We have to wrap provide DragDropContext for the underlying DragDroppable
|
||||
// otherwise we cannot assert on DragDroppable children
|
||||
const wrapper = mount(
|
||||
<WithDragDropContext>
|
||||
<Header {...props} {...overrideProps} />
|
||||
</WithDragDropContext>,
|
||||
);
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
it('should render a DragDroppable', () => {
|
||||
const wrapper = setup();
|
||||
expect(wrapper.find(DragDroppable)).to.have.length(1);
|
||||
});
|
||||
|
||||
it('should render a WithPopoverMenu', () => {
|
||||
const wrapper = setup();
|
||||
expect(wrapper.find(WithPopoverMenu)).to.have.length(1);
|
||||
});
|
||||
|
||||
it('should render a HoverMenu in editMode', () => {
|
||||
let wrapper = setup();
|
||||
expect(wrapper.find(HoverMenu)).to.have.length(0);
|
||||
|
||||
// we cannot set props on the Header because of the WithDragDropContext wrapper
|
||||
wrapper = setup({ editMode: true });
|
||||
expect(wrapper.find(HoverMenu)).to.have.length(1);
|
||||
});
|
||||
|
||||
it('should render an EditableTitle with meta.text', () => {
|
||||
const wrapper = setup();
|
||||
expect(wrapper.find(EditableTitle)).to.have.length(1);
|
||||
expect(wrapper.find('input').prop('value')).to.equal(
|
||||
props.component.meta.text,
|
||||
);
|
||||
});
|
||||
|
||||
it('should call updateComponents when EditableTitle changes', () => {
|
||||
const updateComponents = sinon.spy();
|
||||
const wrapper = setup({ editMode: true, updateComponents });
|
||||
wrapper.find(EditableTitle).prop('onSaveTitle')('New title');
|
||||
|
||||
const headerId = props.component.id;
|
||||
expect(updateComponents.callCount).to.equal(1);
|
||||
expect(updateComponents.getCall(0).args[0][headerId].meta.text).to.equal(
|
||||
'New title',
|
||||
);
|
||||
});
|
||||
|
||||
it('should render a DeleteComponentButton when focused in editMode', () => {
|
||||
const wrapper = setup({ editMode: true });
|
||||
wrapper.find(WithPopoverMenu).simulate('click'); // focus
|
||||
|
||||
expect(wrapper.find(DeleteComponentButton)).to.have.length(1);
|
||||
});
|
||||
|
||||
it('should call deleteComponent when deleted', () => {
|
||||
const deleteComponent = sinon.spy();
|
||||
const wrapper = setup({ editMode: true, deleteComponent });
|
||||
wrapper.find(WithPopoverMenu).simulate('click'); // focus
|
||||
wrapper.find(DeleteComponentButton).simulate('click');
|
||||
|
||||
expect(deleteComponent.callCount).to.equal(1);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,156 @@
|
||||
import { Provider } from 'react-redux';
|
||||
import React from 'react';
|
||||
import { mount } from 'enzyme';
|
||||
import { describe, it } from 'mocha';
|
||||
import { expect } from 'chai';
|
||||
import sinon from 'sinon';
|
||||
import AceEditor from 'react-ace';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
|
||||
import Markdown from '../../../../../src/dashboard/components/gridComponents/Markdown';
|
||||
import MarkdownModeDropdown from '../../../../../src/dashboard/components/menu/MarkdownModeDropdown';
|
||||
import DeleteComponentButton from '../../../../../src/dashboard/components/DeleteComponentButton';
|
||||
import DragDroppable from '../../../../../src/dashboard/components/dnd/DragDroppable';
|
||||
import WithPopoverMenu from '../../../../../src/dashboard/components/menu/WithPopoverMenu';
|
||||
import ResizableContainer from '../../../../../src/dashboard/components/resizable/ResizableContainer';
|
||||
|
||||
import { mockStore } from '../../fixtures/mockStore';
|
||||
import { dashboardLayout as mockLayout } from '../../fixtures/mockDashboardLayout';
|
||||
import WithDragDropContext from '../../helpers/WithDragDropContext';
|
||||
|
||||
describe('Markdown', () => {
|
||||
const props = {
|
||||
id: 'id',
|
||||
parentId: 'parentId',
|
||||
component: mockLayout.present.MARKDOWN_ID,
|
||||
depth: 2,
|
||||
parentComponent: mockLayout.present.ROW_ID,
|
||||
index: 0,
|
||||
editMode: false,
|
||||
availableColumnCount: 12,
|
||||
columnWidth: 50,
|
||||
onResizeStart() {},
|
||||
onResize() {},
|
||||
onResizeStop() {},
|
||||
handleComponentDrop() {},
|
||||
updateComponents() {},
|
||||
deleteComponent() {},
|
||||
};
|
||||
|
||||
function setup(overrideProps) {
|
||||
// We have to wrap provide DragDropContext for the underlying DragDroppable
|
||||
// otherwise we cannot assert on DragDroppable children
|
||||
const wrapper = mount(
|
||||
<Provider store={mockStore}>
|
||||
<WithDragDropContext>
|
||||
<Markdown {...props} {...overrideProps} />
|
||||
</WithDragDropContext>
|
||||
</Provider>,
|
||||
);
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
it('should render a DragDroppable', () => {
|
||||
const wrapper = setup();
|
||||
expect(wrapper.find(DragDroppable)).to.have.length(1);
|
||||
});
|
||||
|
||||
it('should render a WithPopoverMenu', () => {
|
||||
const wrapper = setup();
|
||||
expect(wrapper.find(WithPopoverMenu)).to.have.length(1);
|
||||
});
|
||||
|
||||
it('should render a ResizableContainer', () => {
|
||||
const wrapper = setup();
|
||||
expect(wrapper.find(ResizableContainer)).to.have.length(1);
|
||||
});
|
||||
|
||||
it('should only have an adjustableWidth if its parent is a Row', () => {
|
||||
let wrapper = setup();
|
||||
expect(wrapper.find(ResizableContainer).prop('adjustableWidth')).to.equal(
|
||||
true,
|
||||
);
|
||||
|
||||
wrapper = setup({ ...props, parentComponent: mockLayout.present.CHART_ID });
|
||||
expect(wrapper.find(ResizableContainer).prop('adjustableWidth')).to.equal(
|
||||
false,
|
||||
);
|
||||
});
|
||||
|
||||
it('should pass correct props to ResizableContainer', () => {
|
||||
const wrapper = setup();
|
||||
const resizableProps = wrapper.find(ResizableContainer).props();
|
||||
expect(resizableProps.widthStep).to.equal(props.columnWidth);
|
||||
expect(resizableProps.widthMultiple).to.equal(props.component.meta.width);
|
||||
expect(resizableProps.heightMultiple).to.equal(props.component.meta.height);
|
||||
expect(resizableProps.maxWidthMultiple).to.equal(
|
||||
props.component.meta.width + props.availableColumnCount,
|
||||
);
|
||||
});
|
||||
|
||||
it('should render an Markdown when NOT focused', () => {
|
||||
const wrapper = setup();
|
||||
expect(wrapper.find(AceEditor)).to.have.length(0);
|
||||
expect(wrapper.find(ReactMarkdown)).to.have.length(1);
|
||||
});
|
||||
|
||||
it('should render an AceEditor when focused and editMode=true and editorMode=edit', () => {
|
||||
const wrapper = setup({ editMode: true });
|
||||
expect(wrapper.find(AceEditor)).to.have.length(0);
|
||||
expect(wrapper.find(ReactMarkdown)).to.have.length(1);
|
||||
wrapper.find(WithPopoverMenu).simulate('click'); // focus + edit
|
||||
expect(wrapper.find(AceEditor)).to.have.length(1);
|
||||
expect(wrapper.find(ReactMarkdown)).to.have.length(0);
|
||||
});
|
||||
|
||||
it('should render a ReactMarkdown when focused and editMode=true and editorMode=preview', () => {
|
||||
const wrapper = setup({ editMode: true });
|
||||
wrapper.find(WithPopoverMenu).simulate('click'); // focus + edit
|
||||
expect(wrapper.find(AceEditor)).to.have.length(1);
|
||||
expect(wrapper.find(ReactMarkdown)).to.have.length(0);
|
||||
|
||||
// we can't call setState on Markdown bc it's not the root component, so call
|
||||
// the mode dropdown onchange instead
|
||||
const dropdown = wrapper.find(MarkdownModeDropdown);
|
||||
dropdown.prop('onChange')('preview');
|
||||
|
||||
expect(wrapper.find(AceEditor)).to.have.length(0);
|
||||
expect(wrapper.find(ReactMarkdown)).to.have.length(1);
|
||||
});
|
||||
|
||||
it('should call updateComponents when editMode changes from edit => preview, and there are markdownSource changes', () => {
|
||||
const updateComponents = sinon.spy();
|
||||
const wrapper = setup({ editMode: true, updateComponents });
|
||||
wrapper.find(WithPopoverMenu).simulate('click'); // focus + edit
|
||||
|
||||
// we can't call setState on Markdown bc it's not the root component, so call
|
||||
// the mode dropdown onchange instead
|
||||
const dropdown = wrapper.find(MarkdownModeDropdown);
|
||||
dropdown.prop('onChange')('preview');
|
||||
expect(updateComponents.callCount).to.equal(0);
|
||||
|
||||
dropdown.prop('onChange')('edit');
|
||||
// because we can't call setState on Markdown, change it through the editor
|
||||
// then go back to preview mode to invoke updateComponents
|
||||
const editor = wrapper.find(AceEditor);
|
||||
editor.prop('onChange')('new markdown!');
|
||||
dropdown.prop('onChange')('preview');
|
||||
expect(updateComponents.callCount).to.equal(1);
|
||||
});
|
||||
|
||||
it('should render a DeleteComponentButton when focused in editMode', () => {
|
||||
const wrapper = setup({ editMode: true });
|
||||
wrapper.find(WithPopoverMenu).simulate('click'); // focus
|
||||
|
||||
expect(wrapper.find(DeleteComponentButton)).to.have.length(1);
|
||||
});
|
||||
|
||||
it('should call deleteComponent when deleted', () => {
|
||||
const deleteComponent = sinon.spy();
|
||||
const wrapper = setup({ editMode: true, deleteComponent });
|
||||
wrapper.find(WithPopoverMenu).simulate('click'); // focus
|
||||
wrapper.find(DeleteComponentButton).simulate('click');
|
||||
|
||||
expect(deleteComponent.callCount).to.equal(1);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,120 @@
|
||||
import { Provider } from 'react-redux';
|
||||
import React from 'react';
|
||||
import { mount } from 'enzyme';
|
||||
import { describe, it } from 'mocha';
|
||||
import { expect } from 'chai';
|
||||
import sinon from 'sinon';
|
||||
|
||||
import BackgroundStyleDropdown from '../../../../../src/dashboard/components/menu/BackgroundStyleDropdown';
|
||||
import DashboardComponent from '../../../../../src/dashboard/containers/DashboardComponent';
|
||||
import DeleteComponentButton from '../../../../../src/dashboard/components/DeleteComponentButton';
|
||||
import DragDroppable from '../../../../../src/dashboard/components/dnd/DragDroppable';
|
||||
import HoverMenu from '../../../../../src/dashboard/components/menu/HoverMenu';
|
||||
import IconButton from '../../../../../src/dashboard/components/IconButton';
|
||||
import Row from '../../../../../src/dashboard/components/gridComponents/Row';
|
||||
import WithPopoverMenu from '../../../../../src/dashboard/components/menu/WithPopoverMenu';
|
||||
|
||||
import { mockStore } from '../../fixtures/mockStore';
|
||||
import { DASHBOARD_GRID_ID } from '../../../../../src/dashboard/util/constants';
|
||||
import { dashboardLayout as mockLayout } from '../../fixtures/mockDashboardLayout';
|
||||
import WithDragDropContext from '../../helpers/WithDragDropContext';
|
||||
|
||||
describe('Row', () => {
|
||||
const rowWithoutChildren = { ...mockLayout.present.ROW_ID, children: [] };
|
||||
const props = {
|
||||
id: 'ROW_ID',
|
||||
parentId: DASHBOARD_GRID_ID,
|
||||
component: mockLayout.present.ROW_ID,
|
||||
parentComponent: mockLayout.present[DASHBOARD_GRID_ID],
|
||||
index: 0,
|
||||
depth: 2,
|
||||
editMode: false,
|
||||
availableColumnCount: 12,
|
||||
columnWidth: 50,
|
||||
occupiedColumnCount: 6,
|
||||
onResizeStart() {},
|
||||
onResize() {},
|
||||
onResizeStop() {},
|
||||
handleComponentDrop() {},
|
||||
deleteComponent() {},
|
||||
updateComponents() {},
|
||||
};
|
||||
|
||||
function setup(overrideProps) {
|
||||
// We have to wrap provide DragDropContext for the underlying DragDroppable
|
||||
// otherwise we cannot assert on DragDroppable children
|
||||
const wrapper = mount(
|
||||
<Provider store={mockStore}>
|
||||
<WithDragDropContext>
|
||||
<Row {...props} {...overrideProps} />
|
||||
</WithDragDropContext>
|
||||
</Provider>,
|
||||
);
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
it('should render a DragDroppable', () => {
|
||||
// don't count child DragDroppables
|
||||
const wrapper = setup({ component: rowWithoutChildren });
|
||||
expect(wrapper.find(DragDroppable)).to.have.length(1);
|
||||
});
|
||||
|
||||
it('should render a WithPopoverMenu', () => {
|
||||
// don't count child DragDroppables
|
||||
const wrapper = setup({ component: rowWithoutChildren });
|
||||
expect(wrapper.find(WithPopoverMenu)).to.have.length(1);
|
||||
});
|
||||
|
||||
it('should render a HoverMenu in editMode', () => {
|
||||
let wrapper = setup({ component: rowWithoutChildren });
|
||||
expect(wrapper.find(HoverMenu)).to.have.length(0);
|
||||
|
||||
// we cannot set props on the Row because of the WithDragDropContext wrapper
|
||||
wrapper = setup({ component: rowWithoutChildren, editMode: true });
|
||||
expect(wrapper.find(HoverMenu)).to.have.length(1);
|
||||
});
|
||||
|
||||
it('should render a DeleteComponentButton in editMode', () => {
|
||||
let wrapper = setup({ component: rowWithoutChildren });
|
||||
expect(wrapper.find(DeleteComponentButton)).to.have.length(0);
|
||||
|
||||
// we cannot set props on the Row because of the WithDragDropContext wrapper
|
||||
wrapper = setup({ component: rowWithoutChildren, editMode: true });
|
||||
expect(wrapper.find(DeleteComponentButton)).to.have.length(1);
|
||||
});
|
||||
|
||||
it('should render a BackgroundStyleDropdown when focused', () => {
|
||||
let wrapper = setup({ component: rowWithoutChildren });
|
||||
expect(wrapper.find(BackgroundStyleDropdown)).to.have.length(0);
|
||||
|
||||
// we cannot set props on the Row because of the WithDragDropContext wrapper
|
||||
wrapper = setup({ component: rowWithoutChildren, editMode: true });
|
||||
wrapper
|
||||
.find(IconButton)
|
||||
.at(1) // first one is delete button
|
||||
.simulate('click');
|
||||
|
||||
expect(wrapper.find(BackgroundStyleDropdown)).to.have.length(1);
|
||||
});
|
||||
|
||||
it('should call deleteComponent when deleted', () => {
|
||||
const deleteComponent = sinon.spy();
|
||||
const wrapper = setup({ editMode: true, deleteComponent });
|
||||
wrapper.find(DeleteComponentButton).simulate('click');
|
||||
expect(deleteComponent.callCount).to.equal(1);
|
||||
});
|
||||
|
||||
it('should pass appropriate availableColumnCount to children', () => {
|
||||
const wrapper = setup();
|
||||
const dashboardComponent = wrapper.find(DashboardComponent).first();
|
||||
expect(dashboardComponent.props().availableColumnCount).to.equal(
|
||||
props.availableColumnCount - props.occupiedColumnCount,
|
||||
);
|
||||
});
|
||||
|
||||
it('should increment the depth of its children', () => {
|
||||
const wrapper = setup();
|
||||
const dashboardComponent = wrapper.find(DashboardComponent).first();
|
||||
expect(dashboardComponent.props().depth).to.equal(props.depth + 1);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,126 @@
|
||||
import { Provider } from 'react-redux';
|
||||
import React from 'react';
|
||||
import { mount } from 'enzyme';
|
||||
import { describe, it } from 'mocha';
|
||||
import { expect } from 'chai';
|
||||
import sinon from 'sinon';
|
||||
|
||||
import DashboardComponent from '../../../../../src/dashboard/containers/DashboardComponent';
|
||||
import DeleteComponentButton from '../../../../../src/dashboard/components/DeleteComponentButton';
|
||||
import DragDroppable from '../../../../../src/dashboard/components/dnd/DragDroppable';
|
||||
import EditableTitle from '../../../../../src/components/EditableTitle';
|
||||
import WithPopoverMenu from '../../../../../src/dashboard/components/menu/WithPopoverMenu';
|
||||
import Tab, {
|
||||
RENDER_TAB,
|
||||
RENDER_TAB_CONTENT,
|
||||
} from '../../../../../src/dashboard/components/gridComponents/Tab';
|
||||
import WithDragDropContext from '../../helpers/WithDragDropContext';
|
||||
import { dashboardLayoutWithTabs } from '../../fixtures/mockDashboardLayout';
|
||||
import { mockStoreWithTabs } from '../../fixtures/mockStore';
|
||||
|
||||
describe('Tabs', () => {
|
||||
const props = {
|
||||
id: 'TAB_ID',
|
||||
parentId: 'TABS_ID',
|
||||
component: dashboardLayoutWithTabs.present.TAB_ID,
|
||||
parentComponent: dashboardLayoutWithTabs.present.TABS_ID,
|
||||
index: 0,
|
||||
depth: 1,
|
||||
editMode: false,
|
||||
renderType: RENDER_TAB,
|
||||
onDropOnTab() {},
|
||||
onDeleteTab() {},
|
||||
availableColumnCount: 12,
|
||||
columnWidth: 50,
|
||||
onResizeStart() {},
|
||||
onResize() {},
|
||||
onResizeStop() {},
|
||||
createComponent() {},
|
||||
handleComponentDrop() {},
|
||||
onChangeTab() {},
|
||||
deleteComponent() {},
|
||||
updateComponents() {},
|
||||
};
|
||||
|
||||
function setup(overrideProps) {
|
||||
// We have to wrap provide DragDropContext for the underlying DragDroppable
|
||||
// otherwise we cannot assert on DragDroppable children
|
||||
const wrapper = mount(
|
||||
<Provider store={mockStoreWithTabs}>
|
||||
<WithDragDropContext>
|
||||
<Tab {...props} {...overrideProps} />
|
||||
</WithDragDropContext>
|
||||
</Provider>,
|
||||
);
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
describe('renderType=RENDER_TAB', () => {
|
||||
it('should render a DragDroppable', () => {
|
||||
const wrapper = setup();
|
||||
expect(wrapper.find(DragDroppable)).to.have.length(1);
|
||||
});
|
||||
|
||||
it('should render an EditableTitle with meta.text', () => {
|
||||
const wrapper = setup();
|
||||
const title = wrapper.find(EditableTitle);
|
||||
expect(title).to.have.length(1);
|
||||
expect(title.find('input').prop('value')).to.equal(
|
||||
props.component.meta.text,
|
||||
);
|
||||
});
|
||||
|
||||
it('should call updateComponents when EditableTitle changes', () => {
|
||||
const updateComponents = sinon.spy();
|
||||
const wrapper = setup({ editMode: true, updateComponents });
|
||||
wrapper.find(EditableTitle).prop('onSaveTitle')('New title');
|
||||
|
||||
expect(updateComponents.callCount).to.equal(1);
|
||||
expect(updateComponents.getCall(0).args[0].TAB_ID.meta.text).to.equal(
|
||||
'New title',
|
||||
);
|
||||
});
|
||||
|
||||
it('should render a WithPopoverMenu', () => {
|
||||
const wrapper = setup();
|
||||
expect(wrapper.find(WithPopoverMenu)).to.have.length(1);
|
||||
});
|
||||
|
||||
it('should render a DeleteComponentButton when focused if its not the only tab', () => {
|
||||
let wrapper = setup();
|
||||
wrapper.find(WithPopoverMenu).simulate('click'); // focus
|
||||
expect(wrapper.find(DeleteComponentButton)).to.have.length(0);
|
||||
|
||||
wrapper = setup({ editMode: true });
|
||||
wrapper.find(WithPopoverMenu).simulate('click');
|
||||
expect(wrapper.find(DeleteComponentButton)).to.have.length(1);
|
||||
|
||||
wrapper = setup({
|
||||
editMode: true,
|
||||
parentComponent: {
|
||||
...props.parentComponent,
|
||||
children: props.parentComponent.children.slice(0, 1),
|
||||
},
|
||||
});
|
||||
wrapper.find(WithPopoverMenu).simulate('click');
|
||||
expect(wrapper.find(DeleteComponentButton)).to.have.length(0);
|
||||
});
|
||||
|
||||
it('should call deleteComponent when deleted', () => {
|
||||
const deleteComponent = sinon.spy();
|
||||
const wrapper = setup({ editMode: true, deleteComponent });
|
||||
wrapper.find(WithPopoverMenu).simulate('click'); // focus
|
||||
wrapper.find(DeleteComponentButton).simulate('click');
|
||||
|
||||
expect(deleteComponent.callCount).to.equal(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('renderType=RENDER_TAB_CONTENT', () => {
|
||||
it('should render a DashboardComponent', () => {
|
||||
const wrapper = setup({ renderType: RENDER_TAB_CONTENT });
|
||||
// We expect 2 because this Tab has a Row child and the row has a Chart
|
||||
expect(wrapper.find(DashboardComponent)).to.have.length(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,140 @@
|
||||
import { Provider } from 'react-redux';
|
||||
import React from 'react';
|
||||
import { mount } from 'enzyme';
|
||||
import { describe, it } from 'mocha';
|
||||
import { expect } from 'chai';
|
||||
import sinon from 'sinon';
|
||||
import { Tabs as BootstrapTabs, Tab as BootstrapTab } from 'react-bootstrap';
|
||||
|
||||
import DashboardComponent from '../../../../../src/dashboard/containers/DashboardComponent';
|
||||
import DeleteComponentButton from '../../../../../src/dashboard/components/DeleteComponentButton';
|
||||
import HoverMenu from '../../../../../src/dashboard/components/menu/HoverMenu';
|
||||
import DragDroppable from '../../../../../src/dashboard/components/dnd/DragDroppable';
|
||||
import Tabs from '../../../../../src/dashboard/components/gridComponents/Tabs';
|
||||
import WithDragDropContext from '../../helpers/WithDragDropContext';
|
||||
import { dashboardLayoutWithTabs } from '../../fixtures/mockDashboardLayout';
|
||||
import { mockStoreWithTabs } from '../../fixtures/mockStore';
|
||||
import { DASHBOARD_ROOT_ID } from '../../../../../src/dashboard/util/constants';
|
||||
|
||||
describe('Tabs', () => {
|
||||
const props = {
|
||||
id: 'TABS_ID',
|
||||
parentId: DASHBOARD_ROOT_ID,
|
||||
component: dashboardLayoutWithTabs.present.TABS_ID,
|
||||
parentComponent: dashboardLayoutWithTabs.present[DASHBOARD_ROOT_ID],
|
||||
index: 0,
|
||||
depth: 1,
|
||||
renderTabContent: true,
|
||||
editMode: false,
|
||||
availableColumnCount: 12,
|
||||
columnWidth: 50,
|
||||
onResizeStart() {},
|
||||
onResize() {},
|
||||
onResizeStop() {},
|
||||
createComponent() {},
|
||||
handleComponentDrop() {},
|
||||
onChangeTab() {},
|
||||
deleteComponent() {},
|
||||
updateComponents() {},
|
||||
};
|
||||
|
||||
function setup(overrideProps) {
|
||||
// We have to wrap provide DragDropContext for the underlying DragDroppable
|
||||
// otherwise we cannot assert on DragDroppable children
|
||||
const wrapper = mount(
|
||||
<Provider store={mockStoreWithTabs}>
|
||||
<WithDragDropContext>
|
||||
<Tabs {...props} {...overrideProps} />
|
||||
</WithDragDropContext>
|
||||
</Provider>,
|
||||
);
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
it('should render a DragDroppable', () => {
|
||||
// test just Tabs with no children DragDroppables
|
||||
const wrapper = setup({ component: { ...props.component, children: [] } });
|
||||
expect(wrapper.find(DragDroppable)).to.have.length(1);
|
||||
});
|
||||
|
||||
it('should render BootstrapTabs', () => {
|
||||
const wrapper = setup();
|
||||
expect(wrapper.find(BootstrapTabs)).to.have.length(1);
|
||||
});
|
||||
|
||||
it('should set animation=true, mountOnEnter=true, and unmounOnExit=false on BootstrapTabs for perf', () => {
|
||||
const wrapper = setup();
|
||||
const tabProps = wrapper.find(BootstrapTabs).props();
|
||||
expect(tabProps.animation).to.equal(true);
|
||||
expect(tabProps.mountOnEnter).to.equal(true);
|
||||
expect(tabProps.unmountOnExit).to.equal(false);
|
||||
});
|
||||
|
||||
it('should render a BootstrapTab for each child', () => {
|
||||
const wrapper = setup();
|
||||
expect(wrapper.find(BootstrapTab)).to.have.length(
|
||||
props.component.children.length,
|
||||
);
|
||||
});
|
||||
|
||||
it('should render an extra (+) BootstrapTab in editMode', () => {
|
||||
const wrapper = setup({ editMode: true });
|
||||
expect(wrapper.find(BootstrapTab)).to.have.length(
|
||||
props.component.children.length + 1,
|
||||
);
|
||||
});
|
||||
|
||||
it('should render a DashboardComponent for each child', () => {
|
||||
// note: this does not test Tab content
|
||||
const wrapper = setup({ renderTabContent: false });
|
||||
expect(wrapper.find(DashboardComponent)).to.have.length(
|
||||
props.component.children.length,
|
||||
);
|
||||
});
|
||||
|
||||
it('should call createComponent if the (+) tab is clicked', () => {
|
||||
const createComponent = sinon.spy();
|
||||
const wrapper = setup({ editMode: true, createComponent });
|
||||
wrapper
|
||||
.find('.dashboard-component-tabs .nav-tabs a')
|
||||
.last()
|
||||
.simulate('click');
|
||||
|
||||
expect(createComponent.callCount).to.equal(1);
|
||||
});
|
||||
|
||||
it('should call onChangeTab when a tab is clicked', () => {
|
||||
const onChangeTab = sinon.spy();
|
||||
const wrapper = setup({ editMode: true, onChangeTab });
|
||||
wrapper
|
||||
.find('.dashboard-component-tabs .nav-tabs a')
|
||||
.at(1) // will not call if it is already selected
|
||||
.simulate('click');
|
||||
|
||||
expect(onChangeTab.callCount).to.equal(1);
|
||||
});
|
||||
|
||||
it('should render a HoverMenu in editMode', () => {
|
||||
let wrapper = setup();
|
||||
expect(wrapper.find(HoverMenu)).to.have.length(0);
|
||||
|
||||
wrapper = setup({ editMode: true });
|
||||
expect(wrapper.find(HoverMenu)).to.have.length(1);
|
||||
});
|
||||
|
||||
it('should render a DeleteComponentButton in editMode', () => {
|
||||
let wrapper = setup();
|
||||
expect(wrapper.find(DeleteComponentButton)).to.have.length(0);
|
||||
|
||||
wrapper = setup({ editMode: true });
|
||||
expect(wrapper.find(DeleteComponentButton)).to.have.length(1);
|
||||
});
|
||||
|
||||
it('should call deleteComponent when deleted', () => {
|
||||
const deleteComponent = sinon.spy();
|
||||
const wrapper = setup({ editMode: true, deleteComponent });
|
||||
wrapper.find(DeleteComponentButton).simulate('click');
|
||||
|
||||
expect(deleteComponent.callCount).to.equal(1);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,68 @@
|
||||
import React from 'react';
|
||||
import { mount } from 'enzyme';
|
||||
import { describe, it } from 'mocha';
|
||||
import { expect } from 'chai';
|
||||
|
||||
import DragDroppable from '../../../../../../src/dashboard/components/dnd/DragDroppable';
|
||||
import DraggableNewComponent from '../../../../../../src/dashboard/components/gridComponents/new/DraggableNewComponent';
|
||||
import WithDragDropContext from '../../../helpers/WithDragDropContext';
|
||||
|
||||
import { NEW_COMPONENTS_SOURCE_ID } from '../../../../../../src/dashboard/util/constants';
|
||||
import {
|
||||
NEW_COMPONENT_SOURCE_TYPE,
|
||||
CHART_TYPE,
|
||||
} from '../../../../../../src/dashboard/util/componentTypes';
|
||||
|
||||
describe('DraggableNewComponent', () => {
|
||||
const props = {
|
||||
id: 'id',
|
||||
type: CHART_TYPE,
|
||||
label: 'label!',
|
||||
className: 'a_class',
|
||||
};
|
||||
|
||||
function setup(overrideProps) {
|
||||
// We have to wrap provide DragDropContext for the underlying DragDroppable
|
||||
// otherwise we cannot assert on DragDroppable children
|
||||
const wrapper = mount(
|
||||
<WithDragDropContext>
|
||||
<DraggableNewComponent {...props} {...overrideProps} />
|
||||
</WithDragDropContext>,
|
||||
);
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
it('should render a DragDroppable', () => {
|
||||
const wrapper = setup();
|
||||
expect(wrapper.find(DragDroppable)).to.have.length(1);
|
||||
});
|
||||
|
||||
it('should pass component={ type, id } to DragDroppable', () => {
|
||||
const wrapper = setup();
|
||||
const dragdroppable = wrapper.find(DragDroppable);
|
||||
expect(dragdroppable.prop('component')).to.deep.equal({
|
||||
id: props.id,
|
||||
type: props.type,
|
||||
});
|
||||
});
|
||||
|
||||
it('should pass appropriate parent source and id to DragDroppable', () => {
|
||||
const wrapper = setup();
|
||||
const dragdroppable = wrapper.find(DragDroppable);
|
||||
expect(dragdroppable.prop('parentComponent')).to.deep.equal({
|
||||
id: NEW_COMPONENTS_SOURCE_ID,
|
||||
type: NEW_COMPONENT_SOURCE_TYPE,
|
||||
});
|
||||
});
|
||||
|
||||
it('should render the passed label', () => {
|
||||
const wrapper = setup();
|
||||
expect(wrapper.find('.new-component').text()).to.equal(props.label);
|
||||
});
|
||||
|
||||
it('should add the passed className', () => {
|
||||
const wrapper = setup();
|
||||
const className = `.new-component-placeholder.${props.className}`;
|
||||
expect(wrapper.find(className)).to.have.length(1);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,29 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import { describe, it } from 'mocha';
|
||||
import { expect } from 'chai';
|
||||
|
||||
import DraggableNewComponent from '../../../../../../src/dashboard/components/gridComponents/new/DraggableNewComponent';
|
||||
import NewColumn from '../../../../../../src/dashboard/components/gridComponents/new/NewColumn';
|
||||
|
||||
import { NEW_COLUMN_ID } from '../../../../../../src/dashboard/util/constants';
|
||||
import { COLUMN_TYPE } from '../../../../../../src/dashboard/util/componentTypes';
|
||||
|
||||
describe('NewColumn', () => {
|
||||
function setup() {
|
||||
return shallow(<NewColumn />);
|
||||
}
|
||||
|
||||
it('should render a DraggableNewComponent', () => {
|
||||
const wrapper = setup();
|
||||
expect(wrapper.find(DraggableNewComponent)).to.have.length(1);
|
||||
});
|
||||
|
||||
it('should set appropriate type and id', () => {
|
||||
const wrapper = setup();
|
||||
expect(wrapper.find(DraggableNewComponent).props()).to.include({
|
||||
type: COLUMN_TYPE,
|
||||
id: NEW_COLUMN_ID,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,29 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import { describe, it } from 'mocha';
|
||||
import { expect } from 'chai';
|
||||
|
||||
import DraggableNewComponent from '../../../../../../src/dashboard/components/gridComponents/new/DraggableNewComponent';
|
||||
import NewDivider from '../../../../../../src/dashboard/components/gridComponents/new/NewDivider';
|
||||
|
||||
import { NEW_DIVIDER_ID } from '../../../../../../src/dashboard/util/constants';
|
||||
import { DIVIDER_TYPE } from '../../../../../../src/dashboard/util/componentTypes';
|
||||
|
||||
describe('NewDivider', () => {
|
||||
function setup() {
|
||||
return shallow(<NewDivider />);
|
||||
}
|
||||
|
||||
it('should render a DraggableNewComponent', () => {
|
||||
const wrapper = setup();
|
||||
expect(wrapper.find(DraggableNewComponent)).to.have.length(1);
|
||||
});
|
||||
|
||||
it('should set appropriate type and id', () => {
|
||||
const wrapper = setup();
|
||||
expect(wrapper.find(DraggableNewComponent).props()).to.include({
|
||||
type: DIVIDER_TYPE,
|
||||
id: NEW_DIVIDER_ID,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,29 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import { describe, it } from 'mocha';
|
||||
import { expect } from 'chai';
|
||||
|
||||
import DraggableNewComponent from '../../../../../../src/dashboard/components/gridComponents/new/DraggableNewComponent';
|
||||
import NewHeader from '../../../../../../src/dashboard/components/gridComponents/new/NewHeader';
|
||||
|
||||
import { NEW_HEADER_ID } from '../../../../../../src/dashboard/util/constants';
|
||||
import { HEADER_TYPE } from '../../../../../../src/dashboard/util/componentTypes';
|
||||
|
||||
describe('NewHeader', () => {
|
||||
function setup() {
|
||||
return shallow(<NewHeader />);
|
||||
}
|
||||
|
||||
it('should render a DraggableNewComponent', () => {
|
||||
const wrapper = setup();
|
||||
expect(wrapper.find(DraggableNewComponent)).to.have.length(1);
|
||||
});
|
||||
|
||||
it('should set appropriate type and id', () => {
|
||||
const wrapper = setup();
|
||||
expect(wrapper.find(DraggableNewComponent).props()).to.include({
|
||||
type: HEADER_TYPE,
|
||||
id: NEW_HEADER_ID,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,29 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import { describe, it } from 'mocha';
|
||||
import { expect } from 'chai';
|
||||
|
||||
import DraggableNewComponent from '../../../../../../src/dashboard/components/gridComponents/new/DraggableNewComponent';
|
||||
import NewRow from '../../../../../../src/dashboard/components/gridComponents/new/NewRow';
|
||||
|
||||
import { NEW_ROW_ID } from '../../../../../../src/dashboard/util/constants';
|
||||
import { ROW_TYPE } from '../../../../../../src/dashboard/util/componentTypes';
|
||||
|
||||
describe('NewRow', () => {
|
||||
function setup() {
|
||||
return shallow(<NewRow />);
|
||||
}
|
||||
|
||||
it('should render a DraggableNewComponent', () => {
|
||||
const wrapper = setup();
|
||||
expect(wrapper.find(DraggableNewComponent)).to.have.length(1);
|
||||
});
|
||||
|
||||
it('should set appropriate type and id', () => {
|
||||
const wrapper = setup();
|
||||
expect(wrapper.find(DraggableNewComponent).props()).to.include({
|
||||
type: ROW_TYPE,
|
||||
id: NEW_ROW_ID,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,29 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import { describe, it } from 'mocha';
|
||||
import { expect } from 'chai';
|
||||
|
||||
import DraggableNewComponent from '../../../../../../src/dashboard/components/gridComponents/new/DraggableNewComponent';
|
||||
import NewTabs from '../../../../../../src/dashboard/components/gridComponents/new/NewTabs';
|
||||
|
||||
import { NEW_TABS_ID } from '../../../../../../src/dashboard/util/constants';
|
||||
import { TABS_TYPE } from '../../../../../../src/dashboard/util/componentTypes';
|
||||
|
||||
describe('NewTabs', () => {
|
||||
function setup() {
|
||||
return shallow(<NewTabs />);
|
||||
}
|
||||
|
||||
it('should render a DraggableNewComponent', () => {
|
||||
const wrapper = setup();
|
||||
expect(wrapper.find(DraggableNewComponent)).to.have.length(1);
|
||||
});
|
||||
|
||||
it('should set appropriate type and id', () => {
|
||||
const wrapper = setup();
|
||||
expect(wrapper.find(DraggableNewComponent).props()).to.include({
|
||||
type: TABS_TYPE,
|
||||
id: NEW_TABS_ID,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,13 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import { describe, it } from 'mocha';
|
||||
import { expect } from 'chai';
|
||||
|
||||
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')).to.have.length(1);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,71 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import { describe, it } from 'mocha';
|
||||
import { expect } from 'chai';
|
||||
|
||||
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')).to.have.length(1);
|
||||
});
|
||||
|
||||
it('should render the passed children', () => {
|
||||
const wrapper = setup();
|
||||
expect(wrapper.find('#child')).to.have.length(1);
|
||||
});
|
||||
|
||||
it('should focus on click in editMode', () => {
|
||||
const wrapper = setup();
|
||||
expect(wrapper.state('isFocused')).to.equal(false);
|
||||
|
||||
wrapper.simulate('click');
|
||||
expect(wrapper.state('isFocused')).to.equal(false);
|
||||
|
||||
wrapper.setProps({ ...props, editMode: true });
|
||||
wrapper.simulate('click');
|
||||
expect(wrapper.state('isFocused')).to.equal(true);
|
||||
});
|
||||
|
||||
it('should render menuItems when focused', () => {
|
||||
const wrapper = setup({ editMode: true });
|
||||
expect(wrapper.find('#menu1')).to.have.length(0);
|
||||
expect(wrapper.find('#menu2')).to.have.length(0);
|
||||
|
||||
wrapper.simulate('click');
|
||||
expect(wrapper.find('#menu1')).to.have.length(1);
|
||||
expect(wrapper.find('#menu2')).to.have.length(1);
|
||||
});
|
||||
|
||||
it('should not focus when disableClick=true', () => {
|
||||
const wrapper = setup({ disableClick: true, editMode: true });
|
||||
expect(wrapper.state('isFocused')).to.equal(false);
|
||||
|
||||
wrapper.simulate('click');
|
||||
expect(wrapper.state('isFocused')).to.equal(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')).to.equal(false);
|
||||
|
||||
wrapper.simulate('click');
|
||||
expect(wrapper.state('isFocused')).to.equal(false);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,20 @@
|
||||
import React from 'react';
|
||||
import Resizable from 're-resizable';
|
||||
import { shallow } from 'enzyme';
|
||||
import { describe, it } from 'mocha';
|
||||
import { expect } from 'chai';
|
||||
|
||||
import ResizableContainer from '../../../../../src/dashboard/components/resizable/ResizableContainer';
|
||||
|
||||
describe('ResizableContainer', () => {
|
||||
const props = { editMode: false, id: 'id' };
|
||||
|
||||
function setup(propOverrides) {
|
||||
return shallow(<ResizableContainer {...props} {...propOverrides} />);
|
||||
}
|
||||
|
||||
it('should render a Resizable', () => {
|
||||
const wrapper = setup();
|
||||
expect(wrapper.find(Resizable)).to.have.length(1);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,29 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import { describe, it } from 'mocha';
|
||||
import { expect } from 'chai';
|
||||
|
||||
import ResizableHandle from '../../../../../src/dashboard/components/resizable/ResizableHandle';
|
||||
|
||||
describe('ResizableHandle', () => {
|
||||
it('should render a right resize handle', () => {
|
||||
const wrapper = shallow(<ResizableHandle.right />);
|
||||
expect(wrapper.find('.resize-handle.resize-handle--right')).to.have.length(
|
||||
1,
|
||||
);
|
||||
});
|
||||
|
||||
it('should render a bottom resize handle', () => {
|
||||
const wrapper = shallow(<ResizableHandle.bottom />);
|
||||
expect(wrapper.find('.resize-handle.resize-handle--bottom')).to.have.length(
|
||||
1,
|
||||
);
|
||||
});
|
||||
|
||||
it('should render a bottomRight resize handle', () => {
|
||||
const wrapper = shallow(<ResizableHandle.bottomRight />);
|
||||
expect(
|
||||
wrapper.find('.resize-handle.resize-handle--bottom-right'),
|
||||
).to.have.length(1);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user