[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
This commit is contained in:
Chris Williams
2018-06-25 09:17:22 -07:00
committed by GitHub
parent ca5cc672ca
commit c065319508
257 changed files with 20271 additions and 5191 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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