[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,42 @@
import { describe, it } from 'mocha';
import { expect } from 'chai';
import componentIsResizable from '../../../../src/dashboard/util/componentIsResizable';
import {
CHART_TYPE,
COLUMN_TYPE,
DASHBOARD_GRID_TYPE,
DASHBOARD_ROOT_TYPE,
DIVIDER_TYPE,
HEADER_TYPE,
MARKDOWN_TYPE,
ROW_TYPE,
TABS_TYPE,
TAB_TYPE,
} from '../../../../src/dashboard/util/componentTypes';
const notResizable = [
DASHBOARD_GRID_TYPE,
DASHBOARD_ROOT_TYPE,
DIVIDER_TYPE,
HEADER_TYPE,
ROW_TYPE,
TABS_TYPE,
TAB_TYPE,
];
const resizable = [COLUMN_TYPE, CHART_TYPE, MARKDOWN_TYPE];
describe('componentIsResizable', () => {
resizable.forEach(type => {
it(`should return true for ${type}`, () => {
expect(componentIsResizable({ type })).to.equal(true);
});
});
notResizable.forEach(type => {
it(`should return false for ${type}`, () => {
expect(componentIsResizable({ type })).to.equal(false);
});
});
});

View File

@@ -0,0 +1,62 @@
import { describe, it } from 'mocha';
import { expect } from 'chai';
import reorderItem from '../../../../src/dashboard/util/dnd-reorder';
describe('dnd-reorderItem', () => {
it('should remove the item from its source entity and add it to its destination entity', () => {
const result = reorderItem({
entitiesMap: {
a: {
id: 'a',
children: ['x', 'y', 'z'],
},
b: {
id: 'b',
children: ['banana'],
},
},
source: { id: 'a', index: 2 },
destination: { id: 'b', index: 1 },
});
expect(result.a.children).to.deep.equal(['x', 'y']);
expect(result.b.children).to.deep.equal(['banana', 'z']);
});
it('should correctly move elements within the same list', () => {
const result = reorderItem({
entitiesMap: {
a: {
id: 'a',
children: ['x', 'y', 'z'],
},
},
source: { id: 'a', index: 2 },
destination: { id: 'a', index: 0 },
});
expect(result.a.children).to.deep.equal(['z', 'x', 'y']);
});
it('should copy items that do not move into the result', () => {
const extraEntity = {};
const result = reorderItem({
entitiesMap: {
a: {
id: 'a',
children: ['x', 'y', 'z'],
},
b: {
id: 'b',
children: ['banana'],
},
iAmExtra: extraEntity,
},
source: { id: 'a', index: 2 },
destination: { id: 'b', index: 1 },
});
expect(result.iAmExtra === extraEntity).to.equal(true);
});
});

View File

@@ -0,0 +1,227 @@
import { describe, it } from 'mocha';
import { expect } from 'chai';
import dropOverflowsParent from '../../../../src/dashboard/util/dropOverflowsParent';
import { NEW_COMPONENTS_SOURCE_ID } from '../../../../src/dashboard/util/constants';
import {
CHART_TYPE,
COLUMN_TYPE,
ROW_TYPE,
HEADER_TYPE,
TAB_TYPE,
} from '../../../../src/dashboard/util/componentTypes';
describe('dropOverflowsParent', () => {
it('returns true if a parent does NOT have adequate width for child', () => {
const dropResult = {
source: { id: '_' },
destination: { id: 'a' },
dragging: { id: 'z' },
};
const layout = {
a: {
id: 'a',
type: ROW_TYPE,
children: ['b', 'b', 'b', 'b'], // width = 4x bs = 12
},
b: {
id: 'b',
type: CHART_TYPE,
meta: {
width: 3,
},
},
z: {
id: 'z',
type: CHART_TYPE,
meta: {
width: 2,
},
},
};
expect(dropOverflowsParent(dropResult, layout)).to.equal(true);
});
it('returns false if a parent DOES have adequate width for child', () => {
const dropResult = {
source: { id: '_' },
destination: { id: 'a' },
dragging: { id: 'z' },
};
const layout = {
a: {
id: 'a',
type: ROW_TYPE,
children: ['b', 'b'],
},
b: {
id: 'b',
type: CHART_TYPE,
meta: {
width: 3,
},
},
z: {
id: 'z',
type: CHART_TYPE,
meta: {
width: 2,
},
},
};
expect(dropOverflowsParent(dropResult, layout)).to.equal(false);
});
it('returns false if a child CAN shrink to available parent space', () => {
const dropResult = {
source: { id: '_' },
destination: { id: 'a' },
dragging: { id: 'z' },
};
const layout = {
a: {
id: 'a',
type: ROW_TYPE,
children: ['b', 'b'], // 2x b = 10
},
b: {
id: 'b',
type: CHART_TYPE,
meta: {
width: 5,
},
},
z: {
id: 'z',
type: CHART_TYPE,
meta: {
width: 10,
},
},
};
expect(dropOverflowsParent(dropResult, layout)).to.equal(false);
});
it('returns true if a child CANNOT shrink to available parent space', () => {
const dropResult = {
source: { id: '_' },
destination: { id: 'a' },
dragging: { id: 'b' },
};
const layout = {
a: {
id: 'a',
type: COLUMN_TYPE,
meta: {
width: 6,
},
},
// rows with children cannot shrink
b: {
id: 'b',
type: ROW_TYPE,
children: ['bChild', 'bChild', 'bChild'],
},
bChild: {
id: 'bChild',
type: CHART_TYPE,
meta: {
width: 3,
},
},
};
expect(dropOverflowsParent(dropResult, layout)).to.equal(true);
});
it('returns true if a column has children that CANNOT shrink to available parent space', () => {
const dropResult = {
source: { id: '_' },
destination: { id: 'destination' },
dragging: { id: 'dragging' },
};
const layout = {
destination: {
id: 'destination',
type: ROW_TYPE,
children: ['b', 'b'], // 2x b = 10, 2 available
},
b: {
id: 'b',
type: CHART_TYPE,
meta: {
width: 5,
},
},
dragging: {
id: 'dragging',
type: COLUMN_TYPE,
meta: {
width: 10,
},
children: ['rowWithChildren'], // 2x b = width 10
},
rowWithChildren: {
id: 'rowWithChildren',
type: ROW_TYPE,
children: ['b', 'b'],
},
};
expect(dropOverflowsParent(dropResult, layout)).to.equal(true);
// remove children
expect(
dropOverflowsParent(dropResult, {
...layout,
dragging: { ...layout.dragging, children: [] },
}),
).to.equal(false);
});
it('should work with new components that are not in the layout', () => {
const dropResult = {
source: { id: NEW_COMPONENTS_SOURCE_ID },
destination: { id: 'a' },
dragging: { type: CHART_TYPE },
};
const layout = {
a: {
id: 'a',
type: ROW_TYPE,
children: [],
},
};
expect(dropOverflowsParent(dropResult, layout)).to.equal(false);
});
it('source/destination without widths should not overflow parent', () => {
const dropResult = {
source: { id: '_' },
destination: { id: 'tab' },
dragging: { id: 'header' },
};
const layout = {
tab: {
id: 'tab',
type: TAB_TYPE,
},
header: {
id: 'header',
type: HEADER_TYPE,
},
};
expect(dropOverflowsParent(dropResult, layout)).to.equal(false);
});
});

View File

@@ -0,0 +1,116 @@
import { describe, it } from 'mocha';
import { expect } from 'chai';
import findFirstParentContainerId from '../../../../src/dashboard/util/findFirstParentContainer';
import {
DASHBOARD_GRID_ID,
DASHBOARD_ROOT_ID,
} from '../../../../src/dashboard/util/constants';
describe('findFirstParentContainer', () => {
const mockGridLayout = {
DASHBOARD_VERSION_KEY: 'v2',
DASHBOARD_ROOT_ID: {
type: 'DASHBOARD_ROOT_TYPE',
id: 'DASHBOARD_ROOT_ID',
children: ['DASHBOARD_GRID_ID'],
},
DASHBOARD_GRID_ID: {
type: 'DASHBOARD_GRID_TYPE',
id: 'DASHBOARD_GRID_ID',
children: ['DASHBOARD_ROW_TYPE-Bk45URrlQ'],
},
'DASHBOARD_ROW_TYPE-Bk45URrlQ': {
type: 'DASHBOARD_ROW_TYPE',
id: 'DASHBOARD_ROW_TYPE-Bk45URrlQ',
children: ['DASHBOARD_CHART_TYPE-ryxVc8RHlX'],
},
'DASHBOARD_CHART_TYPE-ryxVc8RHlX': {
type: 'DASHBOARD_CHART_TYPE',
id: 'DASHBOARD_CHART_TYPE-ryxVc8RHlX',
children: [],
},
DASHBOARD_HEADER_ID: {
id: 'DASHBOARD_HEADER_ID',
type: 'DASHBOARD_HEADER_TYPE',
},
};
const mockTabsLayout = {
'DASHBOARD_CHART_TYPE-S1gilYABe7': {
children: [],
id: 'DASHBOARD_CHART_TYPE-S1gilYABe7',
type: 'DASHBOARD_CHART_TYPE',
},
'DASHBOARD_CHART_TYPE-SJli5K0HlQ': {
children: [],
id: 'DASHBOARD_CHART_TYPE-SJli5K0HlQ',
type: 'DASHBOARD_CHART_TYPE',
},
DASHBOARD_GRID_ID: {
children: [],
id: 'DASHBOARD_GRID_ID',
type: 'DASHBOARD_GRID_TYPE',
},
DASHBOARD_HEADER_ID: {
id: 'DASHBOARD_HEADER_ID',
type: 'DASHBOARD_HEADER_TYPE',
},
DASHBOARD_ROOT_ID: {
children: ['DASHBOARD_TABS_TYPE-SkgJ5t0Bem'],
id: 'DASHBOARD_ROOT_ID',
type: 'DASHBOARD_ROOT_TYPE',
},
'DASHBOARD_ROW_TYPE-S1B8-JLgX': {
children: ['DASHBOARD_CHART_TYPE-SJli5K0HlQ'],
id: 'DASHBOARD_ROW_TYPE-S1B8-JLgX',
type: 'DASHBOARD_ROW_TYPE',
},
'DASHBOARD_ROW_TYPE-S1bUb1Ilm': {
children: ['DASHBOARD_CHART_TYPE-S1gilYABe7'],
id: 'DASHBOARD_ROW_TYPE-S1bUb1Ilm',
type: 'DASHBOARD_ROW_TYPE',
},
'DASHBOARD_TABS_TYPE-ByeLSWyLe7': {
children: ['DASHBOARD_TAB_TYPE-BJbLSZ1UeQ'],
id: 'DASHBOARD_TABS_TYPE-ByeLSWyLe7',
type: 'DASHBOARD_TABS_TYPE',
},
'DASHBOARD_TABS_TYPE-SkgJ5t0Bem': {
children: [
'DASHBOARD_TAB_TYPE-HkWJcFCHxQ',
'DASHBOARD_TAB_TYPE-ByDBbkLlQ',
],
id: 'DASHBOARD_TABS_TYPE-SkgJ5t0Bem',
meta: {},
type: 'DASHBOARD_TABS_TYPE',
},
'DASHBOARD_TAB_TYPE-BJbLSZ1UeQ': {
children: ['DASHBOARD_ROW_TYPE-S1bUb1Ilm'],
id: 'DASHBOARD_TAB_TYPE-BJbLSZ1UeQ',
type: 'DASHBOARD_TAB_TYPE',
},
'DASHBOARD_TAB_TYPE-ByDBbkLlQ': {
children: ['DASHBOARD_ROW_TYPE-S1B8-JLgX'],
id: 'DASHBOARD_TAB_TYPE-ByDBbkLlQ',
type: 'DASHBOARD_TAB_TYPE',
},
'DASHBOARD_TAB_TYPE-HkWJcFCHxQ': {
children: ['DASHBOARD_TABS_TYPE-ByeLSWyLe7'],
id: 'DASHBOARD_TAB_TYPE-HkWJcFCHxQ',
type: 'DASHBOARD_TAB_TYPE',
},
DASHBOARD_VERSION_KEY: 'v2',
};
it('should return grid root', () => {
expect(findFirstParentContainerId(mockGridLayout)).to.equal(
DASHBOARD_GRID_ID,
);
});
it('should return first tab', () => {
const tabsId = mockTabsLayout[DASHBOARD_ROOT_ID].children[0];
const firstTabId = mockTabsLayout[tabsId].children[0];
expect(findFirstParentContainerId(mockTabsLayout)).to.equal(firstTabId);
});
});

View File

@@ -0,0 +1,29 @@
import { describe, it } from 'mocha';
import { expect } from 'chai';
import findParentId from '../../../../src/dashboard/util/findParentId';
describe('findParentId', () => {
const layout = {
a: {
id: 'a',
children: ['b', 'r', 'k'],
},
b: {
id: 'b',
children: ['x', 'y', 'z'],
},
z: {
id: 'z',
children: [],
},
};
it('should return the correct parentId', () => {
expect(findParentId({ childId: 'b', layout })).to.equal('a');
expect(findParentId({ childId: 'z', layout })).to.equal('b');
});
it('should return null if the parent cannot be found', () => {
expect(findParentId({ childId: 'a', layout })).to.equal(null);
});
});

View File

@@ -0,0 +1,41 @@
import { describe, it } from 'mocha';
import { expect } from 'chai';
import getChartIdsFromLayout from '../../../../src/dashboard/util/getChartIdsFromLayout';
import {
ROW_TYPE,
CHART_TYPE,
} from '../../../../src/dashboard/util/componentTypes';
describe('getChartIdsFromLayout', () => {
const mockLayout = {
a: {
id: 'a',
type: CHART_TYPE,
meta: { chartId: 'A' },
},
b: {
id: 'b',
type: CHART_TYPE,
meta: { chartId: 'B' },
},
c: {
id: 'c',
type: ROW_TYPE,
meta: { chartId: 'C' },
},
};
it('should return an array of chartIds', () => {
const result = getChartIdsFromLayout(mockLayout);
expect(Array.isArray(result)).to.equal(true);
expect(result.includes('A')).to.equal(true);
expect(result.includes('B')).to.equal(true);
});
it('should return ids only from CHART_TYPE components', () => {
const result = getChartIdsFromLayout(mockLayout);
expect(result.length).to.equal(2);
expect(result.includes('C')).to.equal(false);
});
});

View File

@@ -0,0 +1,223 @@
import { describe, it } from 'mocha';
import { expect } from 'chai';
import getDetailedComponentWidth from '../../../../src/dashboard/util/getDetailedComponentWidth';
import * as types from '../../../../src/dashboard/util/componentTypes';
import {
GRID_COLUMN_COUNT,
GRID_MIN_COLUMN_COUNT,
} from '../../../../src/dashboard/util/constants';
describe('getDetailedComponentWidth', () => {
it('should return an object with width, minimumWidth, and occupiedWidth', () => {
expect(
getDetailedComponentWidth({ id: '_', components: {} }),
).to.have.all.keys(['minimumWidth', 'occupiedWidth', 'width']);
});
describe('width', () => {
it('should be undefined if the component is not resizable and has no defined width', () => {
const empty = {
width: undefined,
occupiedWidth: undefined,
minimumWidth: undefined,
};
expect(
getDetailedComponentWidth({
component: { id: '', type: types.HEADER_TYPE },
}),
).to.deep.equal(empty);
expect(
getDetailedComponentWidth({
component: { id: '', type: types.DIVIDER_TYPE },
}),
).to.deep.equal(empty);
expect(
getDetailedComponentWidth({
component: { id: '', type: types.TAB_TYPE },
}),
).to.deep.equal(empty);
});
it('should match component meta width for resizeable components', () => {
expect(
getDetailedComponentWidth({
component: { id: '', type: types.CHART_TYPE, meta: { width: 1 } },
}),
).to.deep.equal({ width: 1, occupiedWidth: 1, minimumWidth: 1 });
expect(
getDetailedComponentWidth({
component: { id: '', type: types.MARKDOWN_TYPE, meta: { width: 2 } },
}),
).to.deep.equal({ width: 2, occupiedWidth: 2, minimumWidth: 1 });
expect(
getDetailedComponentWidth({
component: { id: '', type: types.COLUMN_TYPE, meta: { width: 3 } },
}),
// note: occupiedWidth is zero for colunns/see test below
).to.deep.equal({ width: 3, occupiedWidth: 0, minimumWidth: 1 });
});
it('should be GRID_COLUMN_COUNT for row components WITHOUT parents', () => {
expect(
getDetailedComponentWidth({
id: 'row',
components: { row: { id: 'row', type: types.ROW_TYPE } },
}),
).to.deep.equal({
width: GRID_COLUMN_COUNT,
occupiedWidth: 0,
minimumWidth: GRID_MIN_COLUMN_COUNT,
});
});
it('should match parent width for row components WITH parents', () => {
expect(
getDetailedComponentWidth({
id: 'row',
components: {
row: { id: 'row', type: types.ROW_TYPE },
parent: {
id: 'parent',
type: types.COLUMN_TYPE,
children: ['row'],
meta: { width: 7 },
},
},
}),
).to.deep.equal({
width: 7,
occupiedWidth: 0,
minimumWidth: GRID_MIN_COLUMN_COUNT,
});
});
it('should use either id or component (to support new components)', () => {
expect(
getDetailedComponentWidth({
id: 'id',
components: {
id: { id: 'id', type: types.CHART_TYPE, meta: { width: 6 } },
},
}).width,
).to.equal(6);
expect(
getDetailedComponentWidth({
component: { id: 'id', type: types.CHART_TYPE, meta: { width: 6 } },
}).width,
).to.equal(6);
});
});
describe('occupiedWidth', () => {
it('should reflect the sum of child widths for row components', () => {
expect(
getDetailedComponentWidth({
id: 'row',
components: {
row: {
id: 'row',
type: types.ROW_TYPE,
children: ['child', 'child'],
},
child: { id: 'child', meta: { width: 3.5 } },
},
}),
).to.deep.equal({ width: 12, occupiedWidth: 7, minimumWidth: 7 });
});
it('should always be zero for column components', () => {
expect(
getDetailedComponentWidth({
component: { id: '', type: types.COLUMN_TYPE, meta: { width: 2 } },
}),
).to.deep.equal({ width: 2, occupiedWidth: 0, minimumWidth: 1 });
});
});
describe('minimumWidth', () => {
it('should equal GRID_MIN_COLUMN_COUNT for resizable components', () => {
expect(
getDetailedComponentWidth({
component: { id: '', type: types.CHART_TYPE, meta: { width: 1 } },
}),
).to.deep.equal({
width: 1,
minimumWidth: GRID_MIN_COLUMN_COUNT,
occupiedWidth: 1,
});
expect(
getDetailedComponentWidth({
component: { id: '', type: types.MARKDOWN_TYPE, meta: { width: 2 } },
}),
).to.deep.equal({
width: 2,
minimumWidth: GRID_MIN_COLUMN_COUNT,
occupiedWidth: 2,
});
expect(
getDetailedComponentWidth({
component: { id: '', type: types.COLUMN_TYPE, meta: { width: 3 } },
}),
).to.deep.equal({
width: 3,
minimumWidth: GRID_MIN_COLUMN_COUNT,
occupiedWidth: 0,
});
});
it('should equal the width of row children for column components with row children', () => {
expect(
getDetailedComponentWidth({
id: 'column',
components: {
column: {
id: 'column',
type: types.COLUMN_TYPE,
children: ['rowChild', 'ignoredChartChild'],
meta: { width: 12 },
},
rowChild: {
id: 'rowChild',
type: types.ROW_TYPE,
children: ['rowChildChild', 'rowChildChild'],
},
rowChildChild: {
id: 'rowChildChild',
meta: { width: 3.5 },
},
ignoredChartChild: {
id: 'ignoredChartChild',
meta: { width: 100 },
},
},
}),
// occupiedWidth is zero for colunns/see test below
).to.deep.equal({ width: 12, occupiedWidth: 0, minimumWidth: 7 });
});
it('should equal occupiedWidth for row components', () => {
expect(
getDetailedComponentWidth({
id: 'row',
components: {
row: {
id: 'row',
type: types.ROW_TYPE,
children: ['child', 'child'],
},
child: { id: 'child', meta: { width: 3.5 } },
},
}),
).to.deep.equal({ width: 12, occupiedWidth: 7, minimumWidth: 7 });
});
});
});

View File

@@ -0,0 +1,422 @@
import { describe, it } from 'mocha';
import { expect } from 'chai';
import getDropPosition, {
DROP_TOP,
DROP_RIGHT,
DROP_BOTTOM,
DROP_LEFT,
} from '../../../../src/dashboard/util/getDropPosition';
import {
CHART_TYPE,
DASHBOARD_GRID_TYPE,
DASHBOARD_ROOT_TYPE,
HEADER_TYPE,
ROW_TYPE,
TAB_TYPE,
} from '../../../../src/dashboard/util/componentTypes';
describe('getDropPosition', () => {
// helper to easily configure test
function getMocks({
parentType,
componentType,
draggingType,
depth = 1,
hasChildren = false,
orientation = 'row',
clientOffset = { x: 0, y: 0 },
boundingClientRect = {
top: 0,
right: 0,
bottom: 0,
left: 0,
},
isDraggingOverShallow = true,
}) {
const monitorMock = {
getItem: () => ({
id: 'id',
type: draggingType,
}),
getClientOffset: () => clientOffset,
};
const ComponentMock = {
props: {
depth,
parentComponent: {
type: parentType,
},
component: {
type: componentType,
children: hasChildren ? [''] : [],
},
orientation,
isDraggingOverShallow,
},
ref: {
getBoundingClientRect: () => boundingClientRect,
},
};
return [monitorMock, ComponentMock];
}
describe('invalid child + invalid sibling', () => {
it('should return null', () => {
const result = getDropPosition(
// TAB is an invalid child + sibling of GRID > ROW
...getMocks({
parentType: DASHBOARD_GRID_TYPE,
componentType: ROW_TYPE,
draggingType: TAB_TYPE,
}),
);
expect(result).to.equal(null);
});
});
describe('valid child + invalid sibling', () => {
it('should return DROP_LEFT if component has NO children, and orientation is "row"', () => {
// HEADER is a valid child + invalid sibling of ROOT > GRID
const result = getDropPosition(
...getMocks({
parentType: DASHBOARD_ROOT_TYPE,
componentType: DASHBOARD_GRID_TYPE,
draggingType: HEADER_TYPE,
}),
);
expect(result).to.equal(DROP_LEFT);
});
it('should return DROP_RIGHT if component HAS children, and orientation is "row"', () => {
const result = getDropPosition(
...getMocks({
parentType: DASHBOARD_ROOT_TYPE,
componentType: DASHBOARD_GRID_TYPE,
draggingType: HEADER_TYPE,
hasChildren: true,
}),
);
expect(result).to.equal(DROP_RIGHT);
});
it('should return DROP_TOP if component has NO children, and orientation is "column"', () => {
const result = getDropPosition(
...getMocks({
parentType: DASHBOARD_ROOT_TYPE,
componentType: DASHBOARD_GRID_TYPE,
draggingType: HEADER_TYPE,
orientation: 'column',
}),
);
expect(result).to.equal(DROP_TOP);
});
it('should return DROP_BOTTOM if component HAS children, and orientation is "column"', () => {
const result = getDropPosition(
...getMocks({
parentType: DASHBOARD_ROOT_TYPE,
componentType: DASHBOARD_GRID_TYPE,
draggingType: HEADER_TYPE,
orientation: 'column',
hasChildren: true,
}),
);
expect(result).to.equal(DROP_BOTTOM);
});
});
describe('invalid child + valid sibling', () => {
it('should return DROP_TOP if orientation="row" and clientOffset is closer to component top than bottom', () => {
const result = getDropPosition(
// HEADER is an invalid child but valid sibling of GRID > ROW
...getMocks({
parentType: DASHBOARD_GRID_TYPE,
componentType: ROW_TYPE,
draggingType: HEADER_TYPE,
clientOffset: { y: 10 },
boundingClientRect: {
top: 0,
bottom: 100,
},
}),
);
expect(result).to.equal(DROP_TOP);
});
it('should return DROP_BOTTOM if orientation="row" and clientOffset is closer to component bottom than top', () => {
const result = getDropPosition(
...getMocks({
parentType: DASHBOARD_GRID_TYPE,
componentType: ROW_TYPE,
draggingType: HEADER_TYPE,
clientOffset: { y: 55 },
boundingClientRect: {
top: 0,
bottom: 100,
},
}),
);
expect(result).to.equal(DROP_BOTTOM);
});
it('should return DROP_LEFT if orientation="column" and clientOffset is closer to component left than right', () => {
const result = getDropPosition(
...getMocks({
parentType: DASHBOARD_GRID_TYPE,
componentType: ROW_TYPE,
draggingType: HEADER_TYPE,
orientation: 'column',
clientOffset: { x: 45 },
boundingClientRect: {
left: 0,
right: 100,
},
}),
);
expect(result).to.equal(DROP_LEFT);
});
it('should return DROP_RIGHT if orientation="column" and clientOffset is closer to component right than left', () => {
const result = getDropPosition(
...getMocks({
parentType: DASHBOARD_GRID_TYPE,
componentType: ROW_TYPE,
draggingType: HEADER_TYPE,
orientation: 'column',
clientOffset: { x: 55 },
boundingClientRect: {
left: 0,
right: 100,
},
}),
);
expect(result).to.equal(DROP_RIGHT);
});
});
describe('child + valid sibling (row orientation)', () => {
it('should return DROP_LEFT if component has NO children, and clientOffset is NOT near top/bottom sibling boundary', () => {
const result = getDropPosition(
// CHART is a valid child + sibling of GRID > ROW
...getMocks({
parentType: DASHBOARD_GRID_TYPE,
componentType: ROW_TYPE,
draggingType: CHART_TYPE,
clientOffset: { x: 10, y: 50 },
boundingClientRect: {
left: 0,
right: 100,
top: 0,
bottom: 100,
},
}),
);
expect(result).to.equal(DROP_LEFT);
});
it('should return DROP_RIGHT if component HAS children, and clientOffset is NOT near top/bottom sibling boundary', () => {
const result = getDropPosition(
...getMocks({
parentType: DASHBOARD_GRID_TYPE,
componentType: ROW_TYPE,
draggingType: CHART_TYPE,
hasChildren: true,
clientOffset: { x: 10, y: 50 },
boundingClientRect: {
left: 0,
right: 100,
top: 0,
bottom: 100,
},
}),
);
expect(result).to.equal(DROP_RIGHT);
});
it('should return DROP_TOP regardless of component children if clientOffset IS near top sibling boundary', () => {
const noChildren = getDropPosition(
...getMocks({
parentType: DASHBOARD_GRID_TYPE,
componentType: ROW_TYPE,
draggingType: CHART_TYPE,
clientOffset: { x: 10, y: 2 },
boundingClientRect: {
left: 0,
right: 100,
top: 0,
bottom: 100,
},
}),
);
const withChildren = getDropPosition(
...getMocks({
parentType: DASHBOARD_GRID_TYPE,
componentType: ROW_TYPE,
draggingType: CHART_TYPE,
hasChildren: true,
clientOffset: { x: 10, y: 2 },
boundingClientRect: {
left: 0,
right: 100,
top: 0,
bottom: 100,
},
}),
);
expect(noChildren).to.equal(DROP_TOP);
expect(withChildren).to.equal(DROP_TOP);
});
it('should return DROP_BOTTOM regardless of component children if clientOffset IS near bottom sibling boundary', () => {
const noChildren = getDropPosition(
...getMocks({
parentType: DASHBOARD_GRID_TYPE,
componentType: ROW_TYPE,
draggingType: CHART_TYPE,
clientOffset: { x: 10, y: 95 },
boundingClientRect: {
left: 0,
right: 100,
top: 0,
bottom: 100,
},
}),
);
const withChildren = getDropPosition(
...getMocks({
parentType: DASHBOARD_GRID_TYPE,
componentType: ROW_TYPE,
draggingType: CHART_TYPE,
hasChildren: true,
clientOffset: { x: 10, y: 95 },
boundingClientRect: {
left: 0,
right: 100,
top: 0,
bottom: 100,
},
}),
);
expect(noChildren).to.equal(DROP_BOTTOM);
expect(withChildren).to.equal(DROP_BOTTOM);
});
});
describe('child + valid sibling (column orientation)', () => {
it('should return DROP_TOP if component has NO children, and clientOffset is NOT near left/right sibling boundary', () => {
const result = getDropPosition(
// CHART is a valid child + sibling of GRID > ROW
...getMocks({
parentType: DASHBOARD_GRID_TYPE,
componentType: ROW_TYPE,
draggingType: CHART_TYPE,
orientation: 'column',
clientOffset: { x: 50, y: 0 },
boundingClientRect: {
left: 0,
right: 100,
top: 0,
bottom: 100,
},
}),
);
expect(result).to.equal(DROP_TOP);
});
it('should return DROP_BOTTOM if component HAS children, and clientOffset is NOT near left/right sibling boundary', () => {
const result = getDropPosition(
...getMocks({
parentType: DASHBOARD_GRID_TYPE,
componentType: ROW_TYPE,
draggingType: CHART_TYPE,
orientation: 'column',
hasChildren: true,
clientOffset: { x: 50, y: 0 },
boundingClientRect: {
left: 0,
right: 100,
top: 0,
bottom: 100,
},
}),
);
expect(result).to.equal(DROP_BOTTOM);
});
it('should return DROP_LEFT regardless of component children if clientOffset IS near left sibling boundary', () => {
const noChildren = getDropPosition(
...getMocks({
parentType: DASHBOARD_GRID_TYPE,
componentType: ROW_TYPE,
draggingType: CHART_TYPE,
orientation: 'column',
clientOffset: { x: 10, y: 2 },
boundingClientRect: {
left: 0,
right: 100,
top: 0,
bottom: 100,
},
}),
);
const withChildren = getDropPosition(
...getMocks({
parentType: DASHBOARD_GRID_TYPE,
componentType: ROW_TYPE,
draggingType: CHART_TYPE,
orientation: 'column',
hasChildren: true,
clientOffset: { x: 10, y: 2 },
boundingClientRect: {
left: 0,
right: 100,
top: 0,
bottom: 100,
},
}),
);
expect(noChildren).to.equal(DROP_LEFT);
expect(withChildren).to.equal(DROP_LEFT);
});
it('should return DROP_RIGHT regardless of component children if clientOffset IS near right sibling boundary', () => {
const noChildren = getDropPosition(
...getMocks({
parentType: DASHBOARD_GRID_TYPE,
componentType: ROW_TYPE,
draggingType: CHART_TYPE,
orientation: 'column',
clientOffset: { x: 90, y: 95 },
boundingClientRect: {
left: 0,
right: 100,
top: 0,
bottom: 100,
},
}),
);
const withChildren = getDropPosition(
...getMocks({
parentType: DASHBOARD_GRID_TYPE,
componentType: ROW_TYPE,
draggingType: CHART_TYPE,
orientation: 'column',
hasChildren: true,
clientOffset: { x: 90, y: 95 },
boundingClientRect: {
left: 0,
right: 100,
top: 0,
bottom: 100,
},
}),
);
expect(noChildren).to.equal(DROP_RIGHT);
expect(withChildren).to.equal(DROP_RIGHT);
});
});
});

View File

@@ -0,0 +1,70 @@
import { describe, it } from 'mocha';
import { expect } from 'chai';
import getFormDataWithExtraFilters from '../../../../src/dashboard/util/charts/getFormDataWithExtraFilters';
describe('getFormDataWithExtraFilters', () => {
const chartId = 'chartId';
const mockArgs = {
chart: {
id: chartId,
formData: {
filters: [
{
col: 'country_name',
op: 'in',
val: ['United States'],
},
],
},
},
dashboardMetadata: {
filter_immune_slices: [],
filter_immune_slice_fields: {},
},
filters: {
filterId: {
region: ['Spain'],
color: ['pink', 'purple'],
},
},
sliceId: chartId,
};
it('should include filters from the passed filters', () => {
const result = getFormDataWithExtraFilters(mockArgs);
expect(result.extra_filters).to.have.length(2);
expect(result.extra_filters[0]).to.deep.equal({
col: 'region',
op: 'in',
val: ['Spain'],
});
expect(result.extra_filters[1]).to.deep.equal({
col: 'color',
op: 'in',
val: ['pink', 'purple'],
});
});
it('should not add additional filters if the slice is immune to them', () => {
const result = getFormDataWithExtraFilters({
...mockArgs,
dashboardMetadata: {
filter_immune_slices: [chartId],
},
});
expect(result.extra_filters).to.have.length(0);
});
it('should not add additional filters for fields to which the slice is immune', () => {
const result = getFormDataWithExtraFilters({
...mockArgs,
dashboardMetadata: {
filter_immune_slice_fields: {
[chartId]: ['region'],
},
},
});
expect(result.extra_filters).to.have.length(1);
});
});

View File

@@ -0,0 +1,147 @@
import { describe, it } from 'mocha';
import { expect } from 'chai';
import isValidChild from '../../../../src/dashboard/util/isValidChild';
import {
CHART_TYPE as CHART,
COLUMN_TYPE as COLUMN,
DASHBOARD_GRID_TYPE as GRID,
DASHBOARD_ROOT_TYPE as ROOT,
DIVIDER_TYPE as DIVIDER,
HEADER_TYPE as HEADER,
MARKDOWN_TYPE as MARKDOWN,
ROW_TYPE as ROW,
TABS_TYPE as TABS,
TAB_TYPE as TAB,
} from '../../../../src/dashboard/util/componentTypes';
const getIndentation = depth =>
Array(depth * 3)
.fill('')
.join('-');
describe('isValidChild', () => {
describe('valid calls', () => {
// these are representations of nested structures for easy testing
// [ROOT (depth 0) > GRID (depth 1) > HEADER (depth 2)]
// every unique parent > child relationship is tested, but because this
// test representation WILL result in duplicates, we hash each test
// to keep track of which we've run
const didTest = {};
const validExamples = [
[ROOT, GRID, CHART], // chart is valid because it is wrapped in a row
[ROOT, GRID, MARKDOWN], // markdown is valid because it is wrapped in a row
[ROOT, GRID, COLUMN], // column is valid because it is wrapped in a row
[ROOT, GRID, HEADER],
[ROOT, GRID, ROW, MARKDOWN],
[ROOT, GRID, ROW, CHART],
[ROOT, GRID, ROW, COLUMN, HEADER],
[ROOT, GRID, ROW, COLUMN, DIVIDER],
[ROOT, GRID, ROW, COLUMN, CHART],
[ROOT, GRID, ROW, COLUMN, MARKDOWN],
[ROOT, GRID, ROW, COLUMN, ROW, CHART],
[ROOT, GRID, ROW, COLUMN, ROW, MARKDOWN],
[ROOT, GRID, ROW, COLUMN, ROW, COLUMN, CHART],
[ROOT, GRID, ROW, COLUMN, ROW, COLUMN, MARKDOWN],
[ROOT, GRID, TABS, TAB, ROW, COLUMN, ROW, COLUMN, MARKDOWN],
// tab equivalents
[ROOT, TABS, TAB, CHART],
[ROOT, TABS, TAB, MARKDOWN],
[ROOT, TABS, TAB, COLUMN],
[ROOT, TABS, TAB, HEADER],
[ROOT, TABS, TAB, ROW, MARKDOWN],
[ROOT, TABS, TAB, ROW, CHART],
[ROOT, TABS, TAB, ROW, COLUMN, HEADER],
[ROOT, TABS, TAB, ROW, COLUMN, DIVIDER],
[ROOT, TABS, TAB, ROW, COLUMN, CHART],
[ROOT, TABS, TAB, ROW, COLUMN, MARKDOWN],
[ROOT, TABS, TAB, ROW, COLUMN, ROW, CHART],
[ROOT, TABS, TAB, ROW, COLUMN, ROW, MARKDOWN],
[ROOT, TABS, TAB, ROW, COLUMN, ROW, COLUMN, CHART],
[ROOT, TABS, TAB, ROW, COLUMN, ROW, COLUMN, MARKDOWN],
[ROOT, TABS, TAB, TABS, TAB, ROW, COLUMN, ROW, COLUMN, MARKDOWN],
];
validExamples.forEach((example, exampleIdx) => {
let childDepth = 0;
example.forEach((childType, i) => {
const parentDepth = childDepth - 1;
const parentType = example[i - 1];
const testKey = `${parentType}-${childType}-${parentDepth}`;
if (i > 0 && !didTest[testKey]) {
didTest[testKey] = true;
it(`(${exampleIdx})${getIndentation(
childDepth,
)}${parentType} (depth ${parentDepth}) > ${childType}`, () => {
expect(
isValidChild({
parentDepth,
parentType,
childType,
}),
).to.equal(true);
});
}
// see isValidChild.js for why tabs do not increment the depth of their children
childDepth += childType !== TABS && childType !== TAB ? 1 : 0;
});
});
});
describe('invalid calls', () => {
// In order to assert that a parent > child hierarchy at a given depth is invalid
// we also define some valid hierarchies in doing so. we indicate which
// parent > [child] relationships should be asserted as invalid using a nested array
const invalidExamples = [
[ROOT, [DIVIDER]],
[ROOT, [CHART]],
[ROOT, [MARKDOWN]],
[ROOT, GRID, [TAB]],
[ROOT, GRID, TABS, [ROW]],
// [ROOT, GRID, TABS, TAB, [TABS]], // @TODO this needs to be fixed
[ROOT, GRID, ROW, [TABS]],
[ROOT, GRID, ROW, [TAB]],
[ROOT, GRID, ROW, [DIVIDER]],
[ROOT, GRID, ROW, COLUMN, [TABS]],
[ROOT, GRID, ROW, COLUMN, [TAB]],
[ROOT, GRID, ROW, COLUMN, ROW, [DIVIDER]],
[ROOT, GRID, ROW, COLUMN, ROW, COLUMN, [ROW]], // too nested
];
invalidExamples.forEach((example, exampleIdx) => {
let childDepth = 0;
example.forEach((childType, i) => {
const shouldTestChild = Array.isArray(childType);
if (i > 0 && shouldTestChild) {
const parentDepth = childDepth - 1;
const parentType = example[i - 1];
it(`(${exampleIdx})${getIndentation(
childDepth,
)}${parentType} (depth ${parentDepth}) > ${childType}`, () => {
expect(
isValidChild({
parentDepth,
parentType,
childType,
}),
).to.equal(false);
});
}
// see isValidChild.js for why tabs do not increment the depth of their children
childDepth += childType !== TABS && childType !== TAB ? 1 : 0;
});
});
});
});

View File

@@ -0,0 +1,51 @@
import { describe, it } from 'mocha';
import { expect } from 'chai';
import newComponentFactory from '../../../../src/dashboard/util/newComponentFactory';
import {
CHART_TYPE,
COLUMN_TYPE,
DASHBOARD_GRID_TYPE,
DASHBOARD_ROOT_TYPE,
DIVIDER_TYPE,
HEADER_TYPE,
MARKDOWN_TYPE,
NEW_COMPONENT_SOURCE_TYPE,
ROW_TYPE,
TABS_TYPE,
TAB_TYPE,
} from '../../../../src/dashboard/util/componentTypes';
const types = [
CHART_TYPE,
COLUMN_TYPE,
DASHBOARD_GRID_TYPE,
DASHBOARD_ROOT_TYPE,
DIVIDER_TYPE,
HEADER_TYPE,
MARKDOWN_TYPE,
NEW_COMPONENT_SOURCE_TYPE,
ROW_TYPE,
TABS_TYPE,
TAB_TYPE,
];
describe('newEntityFactory', () => {
types.forEach(type => {
it(`returns a new ${type}`, () => {
const result = newComponentFactory(type);
expect(result.type).to.equal(type);
expect(typeof result.id).to.equal('string');
expect(typeof result.meta).to.equal('object');
expect(Array.isArray(result.children)).to.equal(true);
});
});
it('adds passed meta data to the entity', () => {
const banana = 'banana';
const result = newComponentFactory(CHART_TYPE, { banana });
expect(result.meta.banana).to.equal(banana);
});
});

View File

@@ -0,0 +1,85 @@
import { describe, it } from 'mocha';
import { expect } from 'chai';
import newEntitiesFromDrop from '../../../../src/dashboard/util/newEntitiesFromDrop';
import {
CHART_TYPE,
DASHBOARD_GRID_TYPE,
ROW_TYPE,
TABS_TYPE,
TAB_TYPE,
} from '../../../../src/dashboard/util/componentTypes';
describe('newEntitiesFromDrop', () => {
it('should return a new Entity of appropriate type, and add it to the drop target children', () => {
const result = newEntitiesFromDrop({
dropResult: {
destination: { id: 'a', index: 0 },
dragging: { type: CHART_TYPE },
source: { id: 'b', index: 0 },
},
layout: {
a: {
id: 'a',
type: ROW_TYPE,
children: [],
},
},
});
const newId = result.a.children[0];
expect(result.a.children.length).to.equal(1);
expect(Object.keys(result).length).to.equal(2);
expect(result[newId].type).to.equal(CHART_TYPE);
});
it('should create Tab AND Tabs components if the drag entity is Tabs', () => {
const result = newEntitiesFromDrop({
dropResult: {
destination: { id: 'a', index: 0 },
dragging: { type: TABS_TYPE },
source: { id: 'b', index: 0 },
},
layout: {
a: {
id: 'a',
type: DASHBOARD_GRID_TYPE,
children: [],
},
},
});
const newTabsId = result.a.children[0];
const newTabId = result[newTabsId].children[0];
expect(result.a.children.length).to.equal(1);
expect(Object.keys(result).length).to.equal(3);
expect(result[newTabsId].type).to.equal(TABS_TYPE);
expect(result[newTabId].type).to.equal(TAB_TYPE);
});
it('should create a Row if the drag entity should be wrapped in a row', () => {
const result = newEntitiesFromDrop({
dropResult: {
destination: { id: 'a', index: 0 },
dragging: { type: CHART_TYPE },
source: { id: 'b', index: 0 },
},
layout: {
a: {
id: 'a',
type: DASHBOARD_GRID_TYPE,
children: [],
},
},
});
const newRowId = result.a.children[0];
const newChartId = result[newRowId].children[0];
expect(result.a.children.length).to.equal(1);
expect(Object.keys(result).length).to.equal(3);
expect(result[newRowId].type).to.equal(ROW_TYPE);
expect(result[newChartId].type).to.equal(CHART_TYPE);
});
});