diff --git a/superset/assets/package.json b/superset/assets/package.json
index 6c3eb44df0c32..c148f42839f43 100644
--- a/superset/assets/package.json
+++ b/superset/assets/package.json
@@ -8,9 +8,9 @@
"test": "spec"
},
"scripts": {
- "test": "mocha --require ignore-styles --compilers js:babel-core/register --require spec/helpers/browser.js 'spec/**/*_spec.*'",
- "test:one": "mocha --require ignore-styles --compilers js:babel-core/register --require spec/helpers/browser.js",
- "cover": "babel-node node_modules/.bin/babel-istanbul cover _mocha -- --compilers babel-core/register --require spec/helpers/browser.js --require ignore-styles 'spec/**/*_spec.*'",
+ "test": "mocha --require ignore-styles --compilers js:babel-core/register --require spec/helpers/shim.js 'spec/**/*_spec.*'",
+ "test:one": "mocha --require ignore-styles --compilers js:babel-core/register --require spec/helpers/shim.js",
+ "cover": "babel-node node_modules/.bin/babel-istanbul cover _mocha -- --compilers babel-core/register --require spec/helpers/shim.js --require ignore-styles 'spec/**/*_spec.*'",
"dev": "webpack --mode=development --colors --progress --debug --watch",
"prod": "node --max_old_space_size=4096 webpack --mode=production --colors --progress",
"build": "webpack --mode=production --colors --progress",
@@ -80,7 +80,7 @@
"nvd3": "1.8.6",
"prop-types": "^15.6.0",
"re-resizable": "^4.3.1",
- "react": "^15.6.2",
+ "react": "^16.4.1",
"react-ace": "^5.10.0",
"react-addons-css-transition-group": "^15.6.0",
"react-addons-shallow-compare": "^15.4.2",
@@ -92,7 +92,7 @@
"react-datetime": "^2.14.0",
"react-dnd": "^2.5.4",
"react-dnd-html5-backend": "^2.5.4",
- "react-dom": "^15.6.2",
+ "react-dom": "^16.4.1",
"react-gravatar": "^2.6.1",
"react-map-gl": "^3.0.4",
"react-markdown": "^3.3.0",
@@ -107,7 +107,7 @@
"react-syntax-highlighter": "^7.0.4",
"react-tag-autocomplete": "^5.5.1",
"react-virtualized": "9.19.1",
- "react-virtualized-select": "^2.4.0",
+ "react-virtualized-select": "^3.1.3",
"reactable": "1.0.2",
"redux": "^3.5.2",
"redux-localstorage": "^0.4.1",
@@ -133,10 +133,13 @@
"babel-plugin-syntax-dynamic-import": "^6.18.0",
"babel-polyfill": "^6.23.0",
"babel-preset-airbnb": "^2.1.1",
+ "babel-preset-env": "^1.7.0",
"chai": "^4.0.2",
"clean-webpack-plugin": "^0.1.19",
"css-loader": "^0.28.0",
- "enzyme": "^2.0.0",
+ "cypress": "^3.0.3",
+ "enzyme": "^3.3.0",
+ "enzyme-adapter-react-16": "^1.1.1",
"eslint": "^4.19.0",
"eslint-config-airbnb": "^15.0.1",
"eslint-config-prettier": "^2.9.0",
@@ -146,7 +149,7 @@
"eslint-plugin-react": "^7.0.1",
"exports-loader": "^0.7.0",
"file-loader": "^1.1.11",
- "github-changes": "^1.0.4",
+ "gl": "^4.0.4",
"ignore-styles": "^5.0.1",
"imports-loader": "^0.7.1",
"istanbul": "^1.0.0-alpha",
@@ -156,7 +159,8 @@
"less-loader": "^4.1.0",
"mini-css-extract-plugin": "^0.4.0",
"mocha": "^3.5.3",
- "npm-check-updates": "^2.14.0",
+ "npm-check-updates": "^2.14.2",
+ "optimize-css-assets-webpack-plugin": "^5.0.1",
"po2json": "^0.4.5",
"prettier": "^1.12.1",
"react-addons-test-utils": "^15.6.2",
@@ -167,7 +171,7 @@
"transform-loader": "^0.2.3",
"uglifyjs-webpack-plugin": "^1.1.0",
"url-loader": "^1.0.1",
- "webpack": "^4.6.0",
+ "webpack": "^4.18.0",
"webpack-assets-manifest": "^3.0.1",
"webpack-cli": "^2.0.10",
"webpack-sources": "^1.1.0"
diff --git a/superset/assets/spec/helpers/browser.js b/superset/assets/spec/helpers/shim.js
similarity index 92%
rename from superset/assets/spec/helpers/browser.js
rename to superset/assets/spec/helpers/shim.js
index 80ce31539feae..3acf9b395a4c2 100644
--- a/superset/assets/spec/helpers/browser.js
+++ b/superset/assets/spec/helpers/shim.js
@@ -2,6 +2,10 @@
import 'babel-polyfill';
import chai from 'chai';
import jsdom from 'jsdom';
+import { configure } from 'enzyme';
+import Adapter from 'enzyme-adapter-react-16';
+
+configure({ adapter: new Adapter() });
require('babel-register')({
// NOTE: If `dynamic-import-node` is in .babelrc alongside
@@ -47,3 +51,4 @@ global.window.XMLHttpRequest = global.XMLHttpRequest;
global.window.location = { href: 'about:blank' };
global.window.performance = { now: () => new Date().getTime() };
global.$ = require('jquery')(global.window);
+
diff --git a/superset/assets/spec/javascripts/chart/Chart_spec.jsx b/superset/assets/spec/javascripts/chart/Chart_spec.jsx
index 30f24feb6574e..d1e3f373c7af3 100644
--- a/superset/assets/spec/javascripts/chart/Chart_spec.jsx
+++ b/superset/assets/spec/javascripts/chart/Chart_spec.jsx
@@ -66,12 +66,10 @@ describe('Chart', () => {
});
it('should call after resize', () => {
- const prevProp = wrapper.props();
wrapper.setProps({
chartStatus: 'rendered',
height: 100,
});
- wrapper.instance().componentDidUpdate(prevProp);
expect(stub.callCount).to.equals(1);
});
});
diff --git a/superset/assets/spec/javascripts/components/AsyncSelect_spec.jsx b/superset/assets/spec/javascripts/components/AsyncSelect_spec.jsx
index bde4610258b41..98d8b517c0ed9 100644
--- a/superset/assets/spec/javascripts/components/AsyncSelect_spec.jsx
+++ b/superset/assets/spec/javascripts/components/AsyncSelect_spec.jsx
@@ -63,7 +63,6 @@ describe('AsyncSelect', () => {
,
);
const spy = sinon.spy(wrapper.instance(), 'onChange');
- wrapper.instance().fetchOptions();
server.respond();
expect(spy.callCount).to.equal(1);
diff --git a/superset/assets/spec/javascripts/dashboard/components/Dashboard_spec.jsx b/superset/assets/spec/javascripts/dashboard/components/Dashboard_spec.jsx
index aa82f01705e91..aa6446315bd8a 100644
--- a/superset/assets/spec/javascripts/dashboard/components/Dashboard_spec.jsx
+++ b/superset/assets/spec/javascripts/dashboard/components/Dashboard_spec.jsx
@@ -152,7 +152,6 @@ describe('Dashboard', () => {
it('should call refresh if a filter is added', () => {
const wrapper = setup({ dashboardState: overrideDashboardState });
const refreshExceptSpy = sinon.spy(wrapper.instance(), 'refreshExcept');
- const prevProps = wrapper.instance().props;
wrapper.setProps({
dashboardState: {
...overrideDashboardState,
@@ -162,7 +161,6 @@ describe('Dashboard', () => {
},
},
});
- wrapper.instance().componentDidUpdate(prevProps);
refreshExceptSpy.restore();
expect(refreshExceptSpy.callCount).to.equal(1);
});
@@ -170,14 +168,12 @@ describe('Dashboard', () => {
it('should call refresh if a filter is removed', () => {
const wrapper = setup({ dashboardState: overrideDashboardState });
const refreshExceptSpy = sinon.spy(wrapper.instance(), 'refreshExcept');
- const prevProps = wrapper.instance().props;
wrapper.setProps({
dashboardState: {
...overrideDashboardState,
filters: {},
},
});
- wrapper.instance().componentDidUpdate(prevProps);
refreshExceptSpy.restore();
expect(refreshExceptSpy.callCount).to.equal(1);
});
@@ -185,7 +181,6 @@ describe('Dashboard', () => {
it('should call refresh if a filter is changed', () => {
const wrapper = setup({ dashboardState: overrideDashboardState });
const refreshExceptSpy = sinon.spy(wrapper.instance(), 'refreshExcept');
- const prevProps = wrapper.instance().props;
wrapper.setProps({
dashboardState: {
...overrideDashboardState,
@@ -195,7 +190,6 @@ describe('Dashboard', () => {
},
},
});
- wrapper.instance().componentDidUpdate(prevProps);
refreshExceptSpy.restore();
expect(refreshExceptSpy.callCount).to.equal(1);
});
@@ -203,7 +197,6 @@ describe('Dashboard', () => {
it('should not call refresh if filters change and refresh is false', () => {
const wrapper = setup({ dashboardState: overrideDashboardState });
const refreshExceptSpy = sinon.spy(wrapper.instance(), 'refreshExcept');
- const prevProps = wrapper.instance().props;
wrapper.setProps({
dashboardState: {
...overrideDashboardState,
@@ -214,7 +207,6 @@ describe('Dashboard', () => {
refresh: false,
},
});
- wrapper.instance().componentDidUpdate(prevProps);
refreshExceptSpy.restore();
expect(refreshExceptSpy.callCount).to.equal(0);
});
diff --git a/superset/assets/spec/javascripts/dashboard/components/gridComponents/Markdown_spec.jsx b/superset/assets/spec/javascripts/dashboard/components/gridComponents/Markdown_spec.jsx
index ca71045de7f42..9046089ee3c0f 100644
--- a/superset/assets/spec/javascripts/dashboard/components/gridComponents/Markdown_spec.jsx
+++ b/superset/assets/spec/javascripts/dashboard/components/gridComponents/Markdown_spec.jsx
@@ -113,9 +113,10 @@ describe('Markdown', () => {
// the mode dropdown onchange instead
const dropdown = wrapper.find(MarkdownModeDropdown);
dropdown.prop('onChange')('preview');
+ wrapper.update();
- expect(wrapper.find(AceEditor)).to.have.length(0);
expect(wrapper.find(ReactMarkdown)).to.have.length(1);
+ expect(wrapper.find(AceEditor)).to.have.length(0);
});
it('should call updateComponents when editMode changes from edit => preview, and there are markdownSource changes', () => {
diff --git a/superset/assets/spec/javascripts/explore/components/AdhocFilterEditPopover_spec.jsx b/superset/assets/spec/javascripts/explore/components/AdhocFilterEditPopover_spec.jsx
index 3b062ed272855..b6395813b9258 100644
--- a/superset/assets/spec/javascripts/explore/components/AdhocFilterEditPopover_spec.jsx
+++ b/superset/assets/spec/javascripts/explore/components/AdhocFilterEditPopover_spec.jsx
@@ -106,7 +106,7 @@ describe('AdhocFilterEditPopover', () => {
expect(wrapper.find('i.glyphicon-resize-full')).to.have.lengthOf(1);
expect(wrapper.instance().onDragDown.calledOnce).to.be.false;
- wrapper.find('i.glyphicon-resize-full').simulate('mouseDown');
+ wrapper.find('i.glyphicon-resize-full').simulate('mouseDown', {});
expect(wrapper.instance().onDragDown.calledOnce).to.be.true;
});
});
diff --git a/superset/assets/spec/javascripts/explore/components/AdhocMetricEditPopoverTitle_spec.jsx b/superset/assets/spec/javascripts/explore/components/AdhocMetricEditPopoverTitle_spec.jsx
index 4acb24e279dd3..015999a4d7f10 100644
--- a/superset/assets/spec/javascripts/explore/components/AdhocMetricEditPopoverTitle_spec.jsx
+++ b/superset/assets/spec/javascripts/explore/components/AdhocMetricEditPopoverTitle_spec.jsx
@@ -36,7 +36,7 @@ describe('AdhocMetricEditPopoverTitle', () => {
it('renders an OverlayTrigger wrapper with the title', () => {
const { wrapper } = setup();
expect(wrapper.find(OverlayTrigger)).to.have.lengthOf(1);
- expect(wrapper.find(OverlayTrigger).dive().text()).to.equal('My Metric\xa0');
+ expect(wrapper.find(OverlayTrigger).find('span').text()).to.equal('My Metric\xa0');
});
it('transfers to edit mode when clicked', () => {
diff --git a/superset/assets/spec/javascripts/explore/components/DateFilterControl_spec.jsx b/superset/assets/spec/javascripts/explore/components/DateFilterControl_spec.jsx
index 0892d05abbb23..9c380c4202909 100644
--- a/superset/assets/spec/javascripts/explore/components/DateFilterControl_spec.jsx
+++ b/superset/assets/spec/javascripts/explore/components/DateFilterControl_spec.jsx
@@ -4,7 +4,7 @@ import sinon from 'sinon';
import { expect } from 'chai';
import { describe, it, beforeEach } from 'mocha';
import { shallow } from 'enzyme';
-import { Button } from 'react-bootstrap';
+import { Button, Label } from 'react-bootstrap';
import DateFilterControl from '../../../../src/explore/components/controls/DateFilterControl';
import ControlHeader from '../../../../src/explore/components/ControlHeader';
@@ -29,36 +29,28 @@ describe('DateFilterControl', () => {
expect(controlHeader).to.have.lengthOf(1);
});
it('renders 3 Buttons', () => {
- const label = wrapper.find('.label').first();
+ const label = wrapper.find(Label).first();
label.simulate('click');
setTimeout(() => {
expect(wrapper.find(Button)).to.have.length(3);
}, 10);
});
it('loads the right state', () => {
- const label = wrapper.find('.label').first();
+ const label = wrapper.find(Label).first();
label.simulate('click');
setTimeout(() => {
expect(wrapper.state().num).to.equal('90');
}, 10);
});
- it('sets now and closes', () => {
- const label = wrapper.find('.now').first();
- label.simulate('click');
- setTimeout(() => {
- expect(wrapper.state().free).to.equal('now');
- expect(wrapper.find('.popover')).to.have.length(0);
- }, 10);
- });
it('renders 2 dimmed sections', () => {
- const label = wrapper.find('.label').first();
+ const label = wrapper.find(Label).first();
label.simulate('click');
setTimeout(() => {
expect(wrapper.find(Button)).to.have.length(3);
}, 10);
});
it('opens and closes', () => {
- const label = wrapper.find('.label').first();
+ const label = wrapper.find(Label).first();
label.simulate('click');
setTimeout(() => {
expect(wrapper.find('.popover')).to.have.length(1);
diff --git a/superset/assets/spec/javascripts/explore/components/DisplayQueryButton_spec.jsx b/superset/assets/spec/javascripts/explore/components/DisplayQueryButton_spec.jsx
index 49c41d37037aa..ab43ddf8643c7 100644
--- a/superset/assets/spec/javascripts/explore/components/DisplayQueryButton_spec.jsx
+++ b/superset/assets/spec/javascripts/explore/components/DisplayQueryButton_spec.jsx
@@ -2,7 +2,6 @@ import React from 'react';
import { expect } from 'chai';
import { describe, it } from 'mocha';
import { mount } from 'enzyme';
-import { Modal } from 'react-bootstrap';
import ModalTrigger from './../../../../src/components/ModalTrigger';
import DisplayQueryButton from '../../../../src/explore/components/DisplayQueryButton';
@@ -27,6 +26,5 @@ describe('DisplayQueryButton', () => {
it('renders a dropdown', () => {
const wrapper = mount();
expect(wrapper.find(ModalTrigger)).to.have.lengthOf(2);
- expect(wrapper.find(Modal)).to.have.lengthOf(2);
});
});
diff --git a/superset/assets/spec/javascripts/sqllab/TableElement_spec.jsx b/superset/assets/spec/javascripts/sqllab/TableElement_spec.jsx
index ff3087158599f..9909bd6af0a63 100644
--- a/superset/assets/spec/javascripts/sqllab/TableElement_spec.jsx
+++ b/superset/assets/spec/javascripts/sqllab/TableElement_spec.jsx
@@ -36,7 +36,7 @@ describe('TableElement', () => {
mount();
});
it('sorts columns', () => {
- const wrapper = mount();
+ const wrapper = shallow();
expect(wrapper.state().sortColumns).to.equal(false);
expect(wrapper.find(ColumnElement).first().props().column.name).to.equal('id');
wrapper.find('.sort-cols').simulate('click');
@@ -50,7 +50,7 @@ describe('TableElement', () => {
expect(mockedActions.collapseTable.called).to.equal(true);
});
it('removes the table', () => {
- const wrapper = mount();
+ const wrapper = shallow();
expect(wrapper.state().expanded).to.equal(true);
wrapper.find('.table-remove').simulate('click');
expect(wrapper.state().expanded).to.equal(false);
diff --git a/superset/assets/spec/javascripts/visualizations/table_spec.jsx b/superset/assets/spec/javascripts/visualizations/table_spec.jsx
index e34472b84b898..db3e8938e33fd 100644
--- a/superset/assets/spec/javascripts/visualizations/table_spec.jsx
+++ b/superset/assets/spec/javascripts/visualizations/table_spec.jsx
@@ -2,9 +2,9 @@ import { describe, it } from 'mocha';
import { expect } from 'chai';
import $ from 'jquery';
-import '../../helpers/browser';
import { d3format } from '../../../src/modules/utils';
+import '../../helpers/shim';
import tableVis from '../../../src/visualizations/table';
describe('table viz', () => {
diff --git a/superset/assets/src/SqlLab/actions.js b/superset/assets/src/SqlLab/actions.js
index dae8ebf7a5954..efd4b97dd8620 100644
--- a/superset/assets/src/SqlLab/actions.js
+++ b/superset/assets/src/SqlLab/actions.js
@@ -9,7 +9,7 @@ import {
addDangerToast as addDangerToastAction,
addInfoToast as addInfoToastAction,
} from '../messageToasts/actions';
-import { COMMON_ERR_MESSAGES } from '../common';
+import { COMMON_ERR_MESSAGES } from '../utils/common';
export const RESET_STATE = 'RESET_STATE';
export const ADD_QUERY_EDITOR = 'ADD_QUERY_EDITOR';
@@ -441,7 +441,6 @@ export function popDatasourceQuery(datasourceKey, sql) {
});
};
}
-
export function createDatasourceStarted() {
return { type: CREATE_DATASOURCE_STARTED };
}
diff --git a/superset/assets/src/SqlLab/components/SqlEditor.jsx b/superset/assets/src/SqlLab/components/SqlEditor.jsx
index 4fd3fda580148..d3ed2bb6eb44e 100644
--- a/superset/assets/src/SqlLab/components/SqlEditor.jsx
+++ b/superset/assets/src/SqlLab/components/SqlEditor.jsx
@@ -89,7 +89,7 @@ class SqlEditor extends React.PureComponent {
height,
});
- if (this.refs.ace.clientHeight) {
+ if (this.refs.ace && this.refs.ace.clientHeight) {
this.props.actions.persistEditorHeight(this.props.queryEditor, this.refs.ace.clientHeight);
}
}
diff --git a/superset/assets/src/chart/chartAction.js b/superset/assets/src/chart/chartAction.js
index 58ad1028520ea..05e76e4f39ae7 100644
--- a/superset/assets/src/chart/chartAction.js
+++ b/superset/assets/src/chart/chartAction.js
@@ -2,7 +2,7 @@ import URI from 'urijs';
import { getExploreUrlAndPayload, getAnnotationJsonUrl } from '../explore/exploreUtils';
import { requiresQuery, ANNOTATION_SOURCE_TYPES } from '../modules/AnnotationTypes';
import { Logger, LOG_ACTIONS_LOAD_CHART } from '../logger';
-import { COMMON_ERR_MESSAGES } from '../common';
+import { COMMON_ERR_MESSAGES } from '../utils/common';
import { t } from '../locales';
const $ = (window.$ = require('jquery'));
diff --git a/superset/assets/src/common.js b/superset/assets/src/common.js
index cc509eb892e8b..67ce4982d2041 100644
--- a/superset/assets/src/common.js
+++ b/superset/assets/src/common.js
@@ -1,6 +1,7 @@
/* eslint-disable global-require */
import $ from 'jquery';
-import { t } from './locales';
+// Everything imported in this file ends up in the common entry file
+// be mindful of double-imports
const utils = require('./modules/utils');
@@ -31,8 +32,3 @@ export function appSetup() {
window.jQuery = $;
require('bootstrap');
}
-
-// Error messages used in many places across applications
-export const COMMON_ERR_MESSAGES = {
- SESSION_TIMED_OUT: t('Your session timed out, please refresh your page and try again.'),
-};
diff --git a/superset/assets/src/explore/components/AdhocMetricEditPopover.jsx b/superset/assets/src/explore/components/AdhocMetricEditPopover.jsx
index 439e16bed66fc..77926a836cca2 100644
--- a/superset/assets/src/explore/components/AdhocMetricEditPopover.jsx
+++ b/superset/assets/src/explore/components/AdhocMetricEditPopover.jsx
@@ -86,10 +86,12 @@ export default class AdhocMetricEditPopover extends React.Component {
}
onColumnChange(column) {
- this.setState({ adhocMetric: this.state.adhocMetric.duplicateWith({
- column,
- expressionType: EXPRESSION_TYPES.SIMPLE,
- }) });
+ this.setState({
+ adhocMetric: this.state.adhocMetric.duplicateWith({
+ column,
+ expressionType: EXPRESSION_TYPES.SIMPLE,
+ }),
+ });
}
onAggregateChange(aggregate) {
diff --git a/superset/assets/src/explore/components/controls/SpatialControl.jsx b/superset/assets/src/explore/components/controls/SpatialControl.jsx
index 97e6b0c3e4f12..dac5cb9ca8b96 100644
--- a/superset/assets/src/explore/components/controls/SpatialControl.jsx
+++ b/superset/assets/src/explore/components/controls/SpatialControl.jsx
@@ -3,7 +3,6 @@ import PropTypes from 'prop-types';
import {
Row, Col, Button, Label, OverlayTrigger, Popover,
} from 'react-bootstrap';
-import 'react-datetime/css/react-datetime.css';
import ControlHeader from '../ControlHeader';
import SelectControl from './SelectControl';
diff --git a/superset/assets/src/utils/common.js b/superset/assets/src/utils/common.js
index c14631b2e29e4..0f2248775b6dd 100644
--- a/superset/assets/src/utils/common.js
+++ b/superset/assets/src/utils/common.js
@@ -1,6 +1,7 @@
/* eslint global-require: 0 */
import $ from 'jquery';
import URI from 'urijs';
+import { t } from '../locales';
const d3 = require('d3');
@@ -131,3 +132,8 @@ export function optionFromValue(opt) {
// From a list of options, handles special values & labels
return { value: optionValue(opt), label: optionLabel(opt) };
}
+
+// Error messages used in many places across applications
+export const COMMON_ERR_MESSAGES = {
+ SESSION_TIMED_OUT: t('Your session timed out, please refresh your page and try again.'),
+};